text: import { extend, nextTick, deleteProps } from '../../shared/utils';
  import Framework7Class from '../../shared/class';
  import $ from '../../shared/dom7';
  import { getDevice } from '../../shared/get-device';
  import { getSupport } from '../../shared/get-support';
  /** @jsx $jsx */
  import $jsx from '../../shared/$jsx';
  
  class Calendar extends Framework7Class {
    constructor(app, params = {}) {
      super(params, [app]);
      const calendar = this;
  
      calendar.params = extend({}, app.params.calendar, params);
  
      let $containerEl;
      if (calendar.params.containerEl) {
        $containerEl = $(calendar.params.containerEl);
        if ($containerEl.length === 0) return calendar;
      }
  
      let $inputEl;
      if (calendar.params.inputEl) {
        $inputEl = $(calendar.params.inputEl);
      }
  
      const isHorizontal = calendar.params.direction === 'horizontal';
  
      let inverter = 1;
      if (isHorizontal) {
        inverter = app.rtl ? -1 : 1;
      }
  
      extend(calendar, {
        app,
        $containerEl,
        containerEl: $containerEl && $containerEl[0],
        inline: $containerEl && $containerEl.length > 0,
        $inputEl,
        inputEl: $inputEl && $inputEl[0],
        initialized: false,
        opened: false,
        url: calendar.params.url,
        isHorizontal,
        inverter,
        animating: false,
        allowTouchMove: true,
        hasTimePicker:
          calendar.params.timePicker && !calendar.params.rangePicker && !calendar.params.multiple,
      });
      calendar.dayFormatter = new Intl.DateTimeFormat(calendar.params.locale, { day: 'numeric' });
      calendar.monthFormatter = new Intl.DateTimeFormat(calendar.params.locale, { month: 'long' });
      calendar.yearFormatter = new Intl.DateTimeFormat(calendar.params.locale, { year: 'numeric' });
      calendar.timeSelectorFormatter = new Intl.DateTimeFormat(
        calendar.params.locale,
        calendar.params.timePickerFormat,
      );
      const timeFormatCheckDate = calendar.timeSelectorFormatter.format(new Date()).toLowerCase();
      calendar.is12HoursFormat =
        timeFormatCheckDate.indexOf('pm') >= 0 || timeFormatCheckDate.indexOf('am') >= 0;
  
      // Auto names
      let { monthNames, monthNamesShort, dayNames, dayNamesShort } = calendar.params;
      const { monthNamesIntl, monthNamesShortIntl, dayNamesIntl, dayNamesShortIntl } =
        calendar.getIntlNames();
      if (monthNames === 'auto') monthNames = monthNamesIntl;
      if (monthNamesShort === 'auto') monthNamesShort = monthNamesShortIntl;
      if (dayNames === 'auto') dayNames = dayNamesIntl;
      if (dayNamesShort === 'auto') dayNamesShort = dayNamesShortIntl;
  
      extend(calendar, {
        monthNames,
        monthNamesShort,
        dayNames,
        dayNamesShort,
      });
  
      function onInputClick() {
        calendar.open();
      }
      function onInputFocus(e) {
        e.preventDefault();
      }
      function onInputClear() {
        calendar.setValue([]);
        if (calendar.opened) {
          calendar.update();
        }
      }
      function onHtmlClick(e) {
        const $targetEl = $(e.target);
        if (calendar.destroyed || !calendar.params) return;
        if (calendar.isPopover()) return;
        if (!calendar.opened || calendar.closing) return;
        if ($targetEl.closest('[class*="backdrop"]').length) return;
        if ($inputEl && $inputEl.length > 0) {
          if (
            $targetEl[0] !== $inputEl[0] &&
            $targetEl.closest('.sheet-modal, .calendar-modal').length === 0
          ) {
            calendar.close();
          }
        } else if ($(e.target).closest('.sheet-modal, .calendar-modal').length === 0) {
          calendar.close();
        }
      }
  
      // Events
      extend(calendar, {
        attachInputEvents() {
          calendar.$inputEl.on('click', onInputClick);
          calendar.$inputEl.on('input:clear', onInputClear);
          if (calendar.params.inputReadOnly) {
            calendar.$inputEl.on('focus mousedown', onInputFocus);
            if (calendar.$inputEl[0]) {
              calendar.$inputEl[0].f7ValidateReadonly = true;
            }
          }
        },
        detachInputEvents() {
          calendar.$inputEl.off('click', onInputClick);
          calendar.$inputEl.off('input:clear', onInputClear);
          if (calendar.params.inputReadOnly) {
            calendar.$inputEl.off('focus mousedown', onInputFocus);
            if (calendar.$inputEl[0]) {
              delete calendar.$inputEl[0].f7ValidateReadonly;
            }
          }
        },
        attachHtmlEvents() {
          app.on('click', onHtmlClick);
        },
        detachHtmlEvents() {
          app.off('click', onHtmlClick);
        },
      });
      calendar.attachCalendarEvents = function attachCalendarEvents() {
        let allowItemClick = true;
        let isTouched;
        let isMoved;
        let touchStartX;
        let touchStartY;
        let touchCurrentX;
        let touchCurrentY;
        let touchStartTime;
        let touchEndTime;
        let currentTranslate;
        let wrapperWidth;
        let wrapperHeight;
        let percentage;
        let touchesDiff;
        let isScrolling;
  
        const { $el, $wrapperEl } = calendar;
  
        function handleTouchStart(e) {
          if (isMoved || isTouched) return;
          isTouched = true;
          touchStartX = e.type === 'touchstart' ? e.targetTouches[0].pageX : e.pageX;
          touchCurrentX = touchStartX;
          touchStartY = e.type === 'touchstart' ? e.targetTouches[0].pageY : e.pageY;
          touchCurrentY = touchStartY;
          touchStartTime = new Date().getTime();
          percentage = 0;
          allowItemClick = true;
          isScrolling = undefined;
          currentTranslate = calendar.monthsTranslate;
        }
        function handleTouchMove(e) {
          if (!isTouched) return;
          const { isHorizontal: isH } = calendar;
  
          touchCurrentX = e.type === 'touchmove' ? e.targetTouches[0].pageX : e.pageX;
          touchCurrentY = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.pageY;
          if (typeof isScrolling === 'undefined') {
            isScrolling = !!(
              isScrolling ||
              Math.abs(touchCurrentY - touchStartY) > Math.abs(touchCurrentX - touchStartX)
            );
          }
          if ((isH && isScrolling) || !calendar.allowTouchMove) {
            isTouched = false;
            return;
          }
          e.preventDefault();
          if (calendar.animating) {
            isTouched = false;
            return;
          }
          allowItemClick = false;
          if (!isMoved) {
            // First move
            isMoved = true;
            wrapperWidth = $wrapperEl[0].offsetWidth;
            wrapperHeight = $wrapperEl[0].offsetHeight;
            $wrapperEl.transition(0);
          }
  
          touchesDiff = isH ? touchCurrentX - touchStartX : touchCurrentY - touchStartY;
          percentage = touchesDiff / (isH ? wrapperWidth : wrapperHeight);
          currentTranslate = (calendar.monthsTranslate * calendar.inverter + percentage) * 100;
  
          // Transform wrapper
          $wrapperEl.transform(
            `translate3d(${isH ? currentTranslate : 0}%, ${isH ? 0 : currentTranslate}%, 0)`,
          );
        }
        function handleTouchEnd() {
          if (!isTouched || !isMoved) {
            isTouched = false;
            isMoved = false;
            return;
          }
          isTouched = false;
          isMoved = false;
  
          touchEndTime = new Date().getTime();
          if (touchEndTime - touchStartTime < 300) {
            if (Math.abs(touchesDiff) < 10) {
              calendar.resetMonth();
            } else if (touchesDiff >= 10) {
              if (app.rtl) calendar.nextMonth();
              else calendar.prevMonth();
            } else if (app.rtl) calendar.prevMonth();
            else calendar.nextMonth();
          } else if (percentage <= -0.5) {
            if (app.rtl) calendar.prevMonth();
            else calendar.nextMonth();
          } else if (percentage >= 0.5) {
            if (app.rtl) calendar.nextMonth();
            else calendar.prevMonth();
          } else {
            calendar.resetMonth();
          }
  
          // Allow click
          setTimeout(() => {
            allowItemClick = true;
          }, 100);
        }
  
        function handleDayClick(e) {
          if (!allowItemClick) return;
          let $dayEl = $(e.target).parents('.calendar-day');
          if ($dayEl.length === 0 && $(e.target).hasClass('calendar-day')) {
            $dayEl = $(e.target);
          }
          if ($dayEl.length === 0) return;
          if ($dayEl.hasClass('calendar-day-disabled')) return;
          if (!calendar.params.rangePicker) {
            if ($dayEl.hasClass('calendar-day-next')) calendar.nextMonth();
            if ($dayEl.hasClass('calendar-day-prev')) calendar.prevMonth();
          }
          const dateYear = parseInt($dayEl.attr('data-year'), 10);
          const dateMonth = parseInt($dayEl.attr('data-month'), 10);
          const dateDay = parseInt($dayEl.attr('data-day'), 10);
          calendar.emit(
            'local::dayClick calendarDayClick',
            calendar,
            $dayEl[0],
            dateYear,
            dateMonth,
            dateDay,
          );
          if (
            !$dayEl.hasClass('calendar-day-selected') ||
            calendar.params.multiple ||
            calendar.params.rangePicker
          ) {
            const valueToAdd = new Date(dateYear, dateMonth, dateDay, 0, 0, 0);
            if (calendar.hasTimePicker) {
              if (calendar.value && calendar.value[0]) {
                valueToAdd.setHours(calendar.value[0].getHours(), calendar.value[0].getMinutes());
              } else {
                valueToAdd.setHours(new Date().getHours(), new Date().getMinutes());
              }
            }
            calendar.addValue(valueToAdd);
          }
          if (calendar.params.closeOnSelect) {
            if (
              (calendar.params.rangePicker && calendar.value.length === 2) ||
              !calendar.params.rangePicker
            ) {
              calendar.close();
            }
          }
        }
  
        function onNextMonthClick() {
          calendar.nextMonth();
        }
  
        function onPrevMonthClick() {
          calendar.prevMonth();
        }
  
        function onNextYearClick() {
          calendar.nextYear();
        }
  
        function onPrevYearClick() {
          calendar.prevYear();
        }
  
        function onMonthSelectorClick() {
          $el.append(calendar.renderMonthPicker());
        }
        function onMonthSelectorItemClick() {
          const $clickedEl = $(this);
          if ($clickedEl.hasClass('calendar-month-picker-item-current')) {
            $el.find('.calendar-month-picker').remove();
            return;
          }
          $el
            .find('.calendar-month-picker-item-current')
            .add($clickedEl)
            .toggleClass('calendar-month-picker-item-current');
          const index = $clickedEl.index();
          const localeMonthIndex = parseInt(
            calendar.$el.find('.calendar-month-current').attr('data-locale-month'),
            10,
          );
          const monthIndex = calendar.currentMonth;
          const diff = localeMonthIndex - monthIndex;
          const diffIndex = index - diff;
          calendar.setYearMonth(calendar.currentYear, diffIndex, 0);
          setTimeout(() => {
            $el.find('.calendar-month-picker').remove();
          }, 200);
        }
  
        function onYearSelectorClick() {
          $el.append(calendar.renderYearPicker());
          const $currentEl = $el.find('.calendar-year-picker-item-current');
          const $yearPickerEl = $el.find('.calendar-year-picker');
          if (!$currentEl || !$currentEl.length) return;
          $yearPickerEl.scrollTop(
            $currentEl[0].offsetTop -
              $yearPickerEl[0].offsetHeight / 2 +
              $currentEl[0].offsetHeight / 2,
          );
        }
  
        function onYearSelectorItemClick() {
          const $clickedEl = $(this);
          if ($clickedEl.hasClass('calendar-year-picker-item-current')) {
            $el.find('.calendar-year-picker').remove();
            return;
          }
          $el
            .find('.calendar-year-picker-item-current')
            .add($clickedEl)
            .toggleClass('calendar-year-picker-item-current');
          const year = parseInt($clickedEl.attr('data-year'), 10);
          calendar.setYearMonth(year, undefined, 0);
          setTimeout(() => {
            $el.find('.calendar-year-picker').remove();
          }, 200);
        }
  
        function onTimeSelectorClick() {
          calendar.openTimePicker();
        }
        function onTimePickerCloseClick() {
          calendar.closeTimePicker();
        }
  
        const passiveListener =
          app.touchEvents.start === 'touchstart' && getSupport().passiveListener
            ? { passive: true, capture: false }
            : false;
        // Selectors clicks
        $el.find('.calendar-prev-month-button').on('click', onPrevMonthClick);
        $el.find('.calendar-next-month-button').on('click', onNextMonthClick);
        $el.find('.calendar-prev-year-button').on('click', onPrevYearClick);
        $el.find('.calendar-next-year-button').on('click', onNextYearClick);
        if (calendar.params.monthPicker) {
          $el.find('.current-month-value').on('click', onMonthSelectorClick);
          $el.on('click', '.calendar-month-picker-item', onMonthSelectorItemClick);
        }
        if (calendar.params.yearPicker) {
          $el.find('.current-year-value').on('click', onYearSelectorClick);
          $el.on('click', '.calendar-year-picker-item', onYearSelectorItemClick);
        }
        if (calendar.hasTimePicker) {
          $el.find('.calendar-time-selector a').on('click', onTimeSelectorClick);
          $el.on('click', '.calendar-time-picker-close', onTimePickerCloseClick);
        }
        // Day clicks
        $wrapperEl.on('click', handleDayClick);
        // Touch events
        if (calendar.params.touchMove) {
          $wrapperEl.on(app.touchEvents.start, handleTouchStart, passiveListener);
          app.on('touchmove:active', handleTouchMove);
          app.on('touchend:passive', handleTouchEnd);
        }
  
        calendar.detachCalendarEvents = function detachCalendarEvents() {
          $el.find('.calendar-prev-month-button').off('click', onPrevMonthClick);
          $el.find('.calendar-next-month-button').off('click', onNextMonthClick);
          $el.find('.calendar-prev-year-button').off('click', onPrevYearClick);
          $el.find('.calendar-next-year-button').off('click', onNextYearClick);
          if (calendar.params.monthPicker) {
            $el.find('.current-month-value').off('click', onMonthSelectorClick);
            $el.off('click', '.calendar-month-picker-item', onMonthSelectorItemClick);
          }
          if (calendar.params.yearPicker) {
            $el.find('.current-year-value').off('click', onYearSelectorClick);
            $el.off('click', '.calendar-year-picker-item', onYearSelectorItemClick);
          }
          if (calendar.hasTimePicker) {
            $el.find('.calendar-time-selector a').off('click', onTimeSelectorClick);
            $el.off('click', '.calendar-time-picker-close', onTimePickerCloseClick);
          }
          $wrapperEl.off('click', handleDayClick);
          if (calendar.params.touchMove) {
            $wrapperEl.off(app.touchEvents.start, handleTouchStart, passiveListener);
            app.off('touchmove:active', handleTouchMove);
            app.off('touchend:passive', handleTouchEnd);
          }
        };
      };
  
      calendar.init();
  
      return calendar;
    }
  
    get view() {
      const { $inputEl, app, params } = this;
      let view;
      if (params.view) {
        view = params.view;
      } else if ($inputEl) {
        view = $inputEl.parents('.view').length && $inputEl.parents('.view')[0].f7View;
      }
      if (!view) view = app.views.main;
      return view;
    }
  
    getIntlNames() {
      const calendar = this;
      const locale = calendar.params.locale;
  
      const monthNamesIntl = [];
      const monthNamesShortIntl = [];
      const dayNamesIntl = [];
      const dayNamesShortIntl = [];
      const formatterMonthNames = new Intl.DateTimeFormat(locale, { month: 'long' });
      const formatterMonthNamesShort = new Intl.DateTimeFormat(locale, { month: 'short' });
      const formatterDayNames = new Intl.DateTimeFormat(locale, { weekday: 'long' });
      const formatterDayNamesShort = new Intl.DateTimeFormat(locale, { weekday: 'short' });
      let year;
      let yearStarted;
      let yearEnded;
      for (let i = 0; i < 24; i += 1) {
        const date = new Date().setMonth(i, 1);
        const currentYear = calendar.yearFormatter.format(date);
  
        if (year && currentYear !== year) {
          if (yearStarted) yearEnded = true;
          yearStarted = true;
          year = currentYear;
        }
        if (!year) {
          year = currentYear;
        }
        if (yearStarted && year === currentYear && !yearEnded) {
          monthNamesIntl.push(formatterMonthNames.format(date));
          monthNamesShortIntl.push(formatterMonthNamesShort.format(date));
        }
      }
      const weekDay = new Date().getDay();
      for (let i = 0; i < 7; i += 1) {
        const date = new Date().getTime() + (i - weekDay) * 24 * 60 * 60 * 1000;
        dayNamesIntl.push(formatterDayNames.format(date));
        dayNamesShortIntl.push(formatterDayNamesShort.format(date));
      }
  
      return {
        monthNamesIntl,
        monthNamesShortIntl,
        dayNamesIntl,
        dayNamesShortIntl,
      };
    }
  
    normalizeDate(date) {
      const calendar = this;
      const d = new Date(date);
      if (calendar.hasTimePicker) {
        return new Date(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes());
      }
      return new Date(d.getFullYear(), d.getMonth(), d.getDate());
    }
  
    normalizeValues(values) {
      const calendar = this;
      let newValues = [];
      if (values && Array.isArray(values)) {
        newValues = values.map((val) => calendar.normalizeDate(val));
      }
      return newValues;
    }
  
    initInput() {
      const calendar = this;
      if (!calendar.$inputEl) return;
      if (calendar.params.inputReadOnly) calendar.$inputEl.prop('readOnly', true);
    }
  
    isPopover() {
      const calendar = this;
      const { app, modal, params } = calendar;
      const device = getDevice();
      if (params.openIn === 'sheet') return false;
      if (modal && modal.type !== 'popover') return false;
  
      if (!calendar.inline && calendar.inputEl) {
        if (params.openIn === 'popover') return true;
        if (device.ios) {
          return !!device.ipad;
        }
        if (app.width >= 768) {
          return true;
        }
        if (device.desktop && app.theme === 'aurora') {
          return true;
        }
      }
      return false;
    }
  
    formatDate(d) {
      const calendar = this;
      const date = new Date(d);
      const year = date.getFullYear();
      const month = date.getMonth();
      const month1 = month + 1;
      const day = date.getDate();
      const weekDay = date.getDay();
      const { monthNames, monthNamesShort, dayNames, dayNamesShort } = calendar;
      const { dateFormat, locale } = calendar.params;
  
      function twoDigits(number) {
        return number < 10 ? `0${number}` : number;
      }
      if (typeof dateFormat === 'string') {
        const tokens = {
          yyyy: year,
          yy: String(year).substring(2),
          mm: twoDigits(month1),
          m: month1,
          MM: monthNames[month],
          M: monthNamesShort[month],
          dd: twoDigits(day),
          d: day,
          DD: dayNames[weekDay],
          D: dayNamesShort[weekDay],
        };
        if (calendar.params.timePicker) {
          const hours = date.getHours();
          const minutes = date.getMinutes();
          const seconds = date.getSeconds();
          let hours12 = hours;
          if (hours > 12) hours12 = hours - 12;
          if (hours === 0) hours12 = 12;
          const a = hours >= 12 && hours !== 0 ? 'pm' : 'am';
  
          Object.assign(tokens, {
            HH: twoDigits(hours),
            H: hours,
            hh: twoDigits(hours12),
            h: hours12,
            ss: twoDigits(seconds),
            s: seconds,
            ':mm': twoDigits(minutes),
            ':m': minutes,
            a,
            A: a.toUpperCase(),
          });
        }
        const regexp = new RegExp(
          Object.keys(tokens)
            .map((t) => `(${t})`)
            .join('|'),
          'g',
        );
        return dateFormat.replace(regexp, (token) => {
          if (token in tokens) return tokens[token];
          return token;
        });
      }
      if (typeof dateFormat === 'function') {
        return dateFormat(date);
      }
      // Intl Object
      const formatter = new Intl.DateTimeFormat(locale, dateFormat);
      return formatter.format(date);
    }
  
    formatValue() {
      const calendar = this;
      const { value } = calendar;
      if (calendar.params.formatValue) {
        return calendar.params.formatValue.call(calendar, value);
      }
      return value
        .map((v) => calendar.formatDate(v))
        .join(calendar.params.rangePicker ? ' - ' : ', ');
    }
  
    addValue(newValue) {
      const calendar = this;
      const { multiple, rangePicker, rangePickerMinDays, rangePickerMaxDays } = calendar.params;
      if (multiple) {
        if (!calendar.value) calendar.value = [];
        let inValuesIndex;
        for (let i = 0; i < calendar.value.length; i += 1) {
          if (new Date(newValue).getTime() === new Date(calendar.value[i]).getTime()) {
            inValuesIndex = i;
          }
        }
        if (typeof inValuesIndex === 'undefined') {
          calendar.value.push(newValue);
        } else {
          calendar.value.splice(inValuesIndex, 1);
        }
        calendar.updateValue();
      } else if (rangePicker) {
        if (!calendar.value) calendar.value = [];
        if (calendar.value.length === 2 || calendar.value.length === 0) {
          calendar.value = [];
        }
  
        if (
          calendar.value.length === 0 ||
          (Math.abs(calendar.value[0].getTime() - newValue.getTime()) >=
            (rangePickerMinDays - 1) * 60 * 60 * 24 * 1000 &&
            (rangePickerMaxDays === 0 ||
              Math.abs(calendar.value[0].getTime() - newValue.getTime()) <=
                (rangePickerMaxDays - 1) * 60 * 60 * 24 * 1000))
        )
          calendar.value.push(newValue);
        else calendar.value = [];
  
        calendar.value.sort((a, b) => a - b);
        calendar.updateValue();
      } else {
        calendar.value = [newValue];
        calendar.updateValue();
      }
    }
  
    setValue(values) {
      const calendar = this;
      const currentValue = calendar.value;
      if (
        Array.isArray(currentValue) &&
        Array.isArray(values) &&
        currentValue.length === values.length
      ) {
        let equal = true;
        currentValue.forEach((v, index) => {
          if (v !== values[index]) equal = false;
        });
        if (equal) return;
      }
      calendar.value = values;
      calendar.updateValue();
    }
  
    getValue() {
      const calendar = this;
      return calendar.value;
    }
  
    updateValue(onlyHeader) {
      const calendar = this;
      const { $el, $wrapperEl, $inputEl, value, params } = calendar;
      let i;
      if ($el && $el.length > 0) {
        $wrapperEl
          .find('.calendar-day-selected')
          .removeClass(
            'calendar-day-selected calendar-day-selected-range calendar-day-selected-left calendar-day-selected-right',
          );
        let valueDate;
  
        if (params.rangePicker && value.length === 2) {
          const leftDate = new Date(value[0]).getTime();
          const rightDate = new Date(value[1]).getTime();
          for (i = leftDate; i <= rightDate; i += 24 * 60 * 60 * 1000) {
            valueDate = new Date(i);
            let addClass = 'calendar-day-selected';
            if (leftDate !== rightDate) {
              if (i !== leftDate && i !== rightDate) {
                addClass += ' calendar-day-selected-range';
              }
              if (i === leftDate) {
                addClass += ' calendar-day-selected-left';
              }
              if (i === rightDate) {
                addClass += ' calendar-day-selected-right';
              }
            }
            $wrapperEl
              .find(
                `.calendar-day[data-date="${valueDate.getFullYear()}-${valueDate.getMonth()}-${valueDate.getDate()}"]`,
              )
              .addClass(addClass);
          }
          valueDate = new Date(leftDate);
          $wrapperEl
            .find(
              `.calendar-day[data-date="${valueDate.getFullYear()}-${valueDate.getMonth()}-${valueDate.getDate()}"]`,
            )
            .removeClass('calendar-day-selected-range')
            .addClass('calendar-day-selected calendar-day-selected-left');
          valueDate = new Date(rightDate);
          $wrapperEl
            .find(
              `.calendar-day[data-date="${valueDate.getFullYear()}-${valueDate.getMonth()}-${valueDate.getDate()}"]`,
            )
            .removeClass('calendar-day-selected-range')
            .addClass('calendar-day-selected calendar-day-selected-right');
        } else {
          for (i = 0; i < calendar.value.length; i += 1) {
            valueDate = new Date(value[i]);
            $wrapperEl
              .find(
                `.calendar-day[data-date="${valueDate.getFullYear()}-${valueDate.getMonth()}-${valueDate.getDate()}"]`,
              )
              .addClass('calendar-day-selected');
          }
        }
      }
      if (!onlyHeader) {
        calendar.emit('local::change calendarChange', calendar, value);
      }
  
      if ($el && $el.length > 0 && calendar.hasTimePicker) {
        $el
          .find('.calendar-time-selector a')
          .text(
            value && value.length
              ? calendar.timeSelectorFormatter.format(value[0])
              : calendar.params.timePickerPlaceholder,
          );
      }
  
      if (($inputEl && $inputEl.length) || params.header) {
        const inputValue = calendar.formatValue(value);
        if (params.header && $el && $el.length) {
          $el.find('.calendar-selected-date').text(inputValue);
        }
        if ($inputEl && $inputEl.length && !onlyHeader) {
          $inputEl.val(inputValue);
          $inputEl.trigger('change');
        }
      }
    }
  
    updateCurrentMonthYear(dir) {
      const calendar = this;
      const { $months, $el, monthNames } = calendar;
      let currentLocaleMonth;
      let currentLocaleYear;
      if (typeof dir === 'undefined') {
        calendar.currentMonth = parseInt($months.eq(1).attr('data-month'), 10);
        calendar.currentYear = parseInt($months.eq(1).attr('data-year'), 10);
        currentLocaleMonth = $months.eq(1).attr('data-locale-month');
        currentLocaleYear = $months.eq(1).attr('data-locale-year');
      } else {
        calendar.currentMonth = parseInt(
          $months.eq(dir === 'next' ? $months.length - 1 : 0).attr('data-month'),
          10,
        );
        calendar.currentYear = parseInt(
          $months.eq(dir === 'next' ? $months.length - 1 : 0).attr('data-year'),
          10,
        );
        currentLocaleMonth = $months
          .eq(dir === 'next' ? $months.length - 1 : 0)
          .attr('data-locale-month');
        currentLocaleYear = $months
          .eq(dir === 'next' ? $months.length - 1 : 0)
          .attr('data-locale-year');
      }
      $el.find('.current-month-value').text(monthNames[currentLocaleMonth]);
      $el.find('.current-year-value').text(currentLocaleYear);
    }
  
    update() {
      const calendar = this;
      const { currentYear, currentMonth, $wrapperEl } = calendar;
      const currentDate = new Date(currentYear, currentMonth);
      const prevMonthHtml = calendar.renderMonth(currentDate, 'prev');
      const currentMonthHtml = calendar.renderMonth(currentDate);
      const nextMonthHtml = calendar.renderMonth(currentDate, 'next');
  
      $wrapperEl
        .transition(0)
        .html(`${prevMonthHtml}${currentMonthHtml}${nextMonthHtml}`)
        .transform('translate3d(0,0,0)');
      calendar.$months = $wrapperEl.find('.calendar-month');
      calendar.monthsTranslate = 0;
      calendar.setMonthsTranslate();
      calendar.$months.each((monthEl) => {
        calendar.emit('local::monthAdd calendarMonthAdd', monthEl);
      });
    }
  
    onMonthChangeStart(dir) {
      const calendar = this;
      const { $months, currentYear, currentMonth } = calendar;
      calendar.updateCurrentMonthYear(dir);
      $months.removeClass('calendar-month-current calendar-month-prev calendar-month-next');
      const currentIndex = dir === 'next' ? $months.length - 1 : 0;
  
      $months.eq(currentIndex).addClass('calendar-month-current');
      $months
        .eq(dir === 'next' ? currentIndex - 1 : currentIndex + 1)
        .addClass(dir === 'next' ? 'calendar-month-prev' : 'calendar-month-next');
  
      calendar.emit(
        'local::monthYearChangeStart calendarMonthYearChangeStart',
        calendar,
        currentYear,
        currentMonth,
      );
    }
  
    onMonthChangeEnd(dir, rebuildBoth) {
      const calendar = this;
      const { currentYear, currentMonth, $wrapperEl, monthsTranslate } = calendar;
      calendar.animating = false;
      let nextMonthHtml;
      let prevMonthHtml;
      let currentMonthHtml;
      $wrapperEl
        .find(
          '.calendar-month:not(.calendar-month-prev):not(.calendar-month-current):not(.calendar-month-next)',
        )
        .remove();
  
      if (typeof dir === 'undefined') {
        dir = 'next'; // eslint-disable-line
        rebuildBoth = true; // eslint-disable-line
      }
      if (!rebuildBoth) {
        currentMonthHtml = calendar.renderMonth(new Date(currentYear, currentMonth), dir);
      } else {
        $wrapperEl.find('.calendar-month-next, .calendar-month-prev').remove();
        prevMonthHtml = calendar.renderMonth(new Date(currentYear, currentMonth), 'prev');
        nextMonthHtml = calendar.renderMonth(new Date(currentYear, currentMonth), 'next');
      }
      if (dir === 'next' || rebuildBoth) {
        $wrapperEl.append(currentMonthHtml || nextMonthHtml);
      }
      if (dir === 'prev' || rebuildBoth) {
        $wrapperEl.prepend(currentMonthHtml || prevMonthHtml);
      }
      const $months = $wrapperEl.find('.calendar-month');
      calendar.$months = $months;
      calendar.setMonthsTranslate(monthsTranslate);
      calendar.emit(
        'local::monthAdd calendarMonthAdd',
        calendar,
        dir === 'next' ? $months.eq($months.length - 1)[0] : $months.eq(0)[0],
      );
      calendar.emit(
        'local::monthYearChangeEnd calendarMonthYearChangeEnd',
        calendar,
        currentYear,
        currentMonth,
      );
    }
  
    setMonthsTranslate(translate) {
      const calendar = this;
      const { $months, isHorizontal: isH, inverter } = calendar;
      // eslint-disable-next-line
      translate = translate || calendar.monthsTranslate || 0;
      if (typeof calendar.monthsTranslate === 'undefined') {
        calendar.monthsTranslate = translate;
      }
      $months.removeClass('calendar-month-current calendar-month-prev calendar-month-next');
      const prevMonthTranslate = -(translate + 1) * 100 * inverter;
      const currentMonthTranslate = -translate * 100 * inverter;
      const nextMonthTranslate = -(translate - 1) * 100 * inverter;
      $months
        .eq(0)
        .transform(
          `translate3d(${isH ? prevMonthTranslate : 0}%, ${isH ? 0 : prevMonthTranslate}%, 0)`,
        )
        .addClass('calendar-month-prev');
      $months
        .eq(1)
        .transform(
          `translate3d(${isH ? currentMonthTranslate : 0}%, ${isH ? 0 : currentMonthTranslate}%, 0)`,
        )
        .addClass('calendar-month-current');
      $months
        .eq(2)
        .transform(
          `translate3d(${isH ? nextMonthTranslate : 0}%, ${isH ? 0 : nextMonthTranslate}%, 0)`,
        )
        .addClass('calendar-month-next');
    }
  
    nextMonth(transition) {
      const calendar = this;
      const { params, $wrapperEl, inverter, isHorizontal: isH } = calendar;
      if (typeof transition === 'undefined' || typeof transition === 'object') {
        transition = ''; // eslint-disable-line
        if (!params.animate) transition = 0; // eslint-disable-line
      }
      const nextMonth = parseInt(
        calendar.$months.eq(calendar.$months.length - 1).attr('data-month'),
        10,
      );
      const nextYear = parseInt(
        calendar.$months.eq(calendar.$months.length - 1).attr('data-year'),
        10,
      );
      const nextDate = new Date(nextYear, nextMonth);
      const nextDateTime = nextDate.getTime();
      const transitionEndCallback = !calendar.animating;
      if (params.maxDate) {
        if (nextDateTime > new Date(params.maxDate).getTime()) {
          calendar.resetMonth();
          return;
        }
      }
      calendar.monthsTranslate -= 1;
      if (nextMonth === calendar.currentMonth) {
        const nextMonthTranslate = -calendar.monthsTranslate * 100 * inverter;
        const nextMonthHtml = $(calendar.renderMonth(nextDateTime, 'next'))
          .transform(
            `translate3d(${isH ? nextMonthTranslate : 0}%, ${isH ? 0 : nextMonthTranslate}%, 0)`,
          )
          .addClass('calendar-month-next');
        $wrapperEl.append(nextMonthHtml[0]);
        calendar.$months = $wrapperEl.find('.calendar-month');
        calendar.emit(
          'local::monthAdd calendarMonthAdd',
          calendar.$months.eq(calendar.$months.length - 1)[0],
        );
      }
      calendar.animating = true;
      calendar.onMonthChangeStart('next');
      const translate = calendar.monthsTranslate * 100 * inverter;
  
      $wrapperEl
        .transition(transition)
        .transform(`translate3d(${isH ? translate : 0}%, ${isH ? 0 : translate}%, 0)`);
      if (transitionEndCallback) {
        $wrapperEl.transitionEnd(() => {
          calendar.onMonthChangeEnd('next');
        });
      }
      if (!params.animate) {
        calendar.onMonthChangeEnd('next');
      }
    }
  
    prevMonth(transition) {
      const calendar = this;
      const { params, $wrapperEl, inverter, isHorizontal: isH } = calendar;
      if (typeof transition === 'undefined' || typeof transition === 'object') {
        transition = ''; // eslint-disable-line
        if (!params.animate) transition = 0; // eslint-disable-line
      }
      const prevMonth = parseInt(calendar.$months.eq(0).attr('data-month'), 10);
      const prevYear = parseInt(calendar.$months.eq(0).attr('data-year'), 10);
      const prevDate = new Date(prevYear, prevMonth + 1, -1);
      const prevDateTime = prevDate.getTime();
      const transitionEndCallback = !calendar.animating;
      if (params.minDate) {
        let minDate = new Date(params.minDate);
        minDate = new Date(minDate.getFullYear(), minDate.getMonth(), 1);
        if (prevDateTime < minDate.getTime()) {
          calendar.resetMonth();
          return;
        }
      }
      calendar.monthsTranslate += 1;
      if (prevMonth === calendar.currentMonth) {
        const prevMonthTranslate = -calendar.monthsTranslate * 100 * inverter;
        const prevMonthHtml = $(calendar.renderMonth(prevDateTime, 'prev'))
          .transform(
            `translate3d(${isH ? prevMonthTranslate : 0}%, ${isH ? 0 : prevMonthTranslate}%, 0)`,
          )
          .addClass('calendar-month-prev');
        $wrapperEl.prepend(prevMonthHtml[0]);
        calendar.$months = $wrapperEl.find('.calendar-month');
        calendar.emit('local::monthAdd calendarMonthAdd', calendar.$months.eq(0)[0]);
      }
      calendar.animating = true;
      calendar.onMonthChangeStart('prev');
      const translate = calendar.monthsTranslate * 100 * inverter;
      $wrapperEl
        .transition(transition)
        .transform(`translate3d(${isH ? translate : 0}%, ${isH ? 0 : translate}%, 0)`);
      if (transitionEndCallback) {
        $wrapperEl.transitionEnd(() => {
          calendar.onMonthChangeEnd('prev');
        });
      }
      if (!params.animate) {
        calendar.onMonthChangeEnd('prev');
      }
    }
  
    resetMonth(transition = '') {
      const calendar = this;
      const { $wrapperEl, inverter, isHorizontal: isH, monthsTranslate } = calendar;
      const translate = monthsTranslate * 100 * inverter;
      $wrapperEl
        .transition(transition)
        .transform(`translate3d(${isH ? translate : 0}%, ${isH ? 0 : translate}%, 0)`);
    }
    // eslint-disable-next-line
    setYearMonth(year, month, transition) {
      const calendar = this;
      const { params, isHorizontal: isH, $wrapperEl, inverter } = calendar;
      // eslint-disable-next-line
      if (typeof year === 'undefined') year = calendar.currentYear;
      // eslint-disable-next-line
      if (typeof month === 'undefined') month = calendar.currentMonth;
      if (typeof transition === 'undefined' || typeof transition === 'object') {
        // eslint-disable-next-line
        transition = '';
        // eslint-disable-next-line
        if (!params.animate) transition = 0;
      }
      let targetDate;
      if (year < calendar.currentYear) {
        targetDate = new Date(year, month + 1, -1).getTime();
      } else {
        targetDate = new Date(year, month).getTime();
      }
      if (params.maxDate && targetDate > new Date(params.maxDate).getTime()) {
        return false;
      }
      if (params.minDate) {
        let minDate = new Date(params.minDate);
        minDate = new Date(minDate.getFullYear(), minDate.getMonth(), 1);
        if (targetDate < minDate.getTime()) {
          return false;
        }
      }
      const currentDate = new Date(calendar.currentYear, calendar.currentMonth).getTime();
      const dir = targetDate > currentDate ? 'next' : 'prev';
      const newMonthHTML = calendar.renderMonth(new Date(year, month));
      calendar.monthsTranslate = calendar.monthsTranslate || 0;
      const prevTranslate = calendar.monthsTranslate;
      let monthTranslate;
      const transitionEndCallback = !calendar.animating && transition !== 0;
      if (targetDate > currentDate) {
        // To next
        calendar.monthsTranslate -= 1;
        if (!calendar.animating) calendar.$months.eq(calendar.$months.length - 1).remove();
        $wrapperEl.append(newMonthHTML);
        calendar.$months = $wrapperEl.find('.calendar-month');
        monthTranslate = -(prevTranslate - 1) * 100 * inverter;
        calendar.$months
          .eq(calendar.$months.length - 1)
          .transform(`translate3d(${isH ? monthTranslate : 0}%, ${isH ? 0 : monthTranslate}%, 0)`)
          .addClass('calendar-month-next');
      } else {
        // To prev
        calendar.monthsTranslate += 1;
        if (!calendar.animating) calendar.$months.eq(0).remove();
        $wrapperEl.prepend(newMonthHTML);
        calendar.$months = $wrapperEl.find('.calendar-month');
        monthTranslate = -(prevTranslate + 1) * 100 * inverter;
        calendar.$months
          .eq(0)
          .transform(`translate3d(${isH ? monthTranslate : 0}%, ${isH ? 0 : monthTranslate}%, 0)`)
          .addClass('calendar-month-prev');
      }
      calendar.emit(
        'local::monthAdd calendarMonthAdd',
        dir === 'next'
          ? calendar.$months.eq(calendar.$months.length - 1)[0]
          : calendar.$months.eq(0)[0],
      );
  
      calendar.animating = true;
      calendar.onMonthChangeStart(dir);
      const wrapperTranslate = calendar.monthsTranslate * 100 * inverter;
      $wrapperEl
        .transition(transition)
        .transform(`translate3d(${isH ? wrapperTranslate : 0}%, ${isH ? 0 : wrapperTranslate}%, 0)`);
      if (transitionEndCallback) {
        $wrapperEl.transitionEnd(() => {
          calendar.onMonthChangeEnd(dir, true);
        });
      }
      if (!params.animate || transition === 0) {
        calendar.onMonthChangeEnd(dir, true);
      }
    }
  
    nextYear() {
      const calendar = this;
      calendar.setYearMonth(calendar.currentYear + 1);
    }
  
    prevYear() {
      const calendar = this;
      calendar.setYearMonth(calendar.currentYear - 1);
    }
    // eslint-disable-next-line
    dateInRange(dayDate, range) {
      let match = false;
      let i;
      if (!range) return false;
      if (Array.isArray(range)) {
        for (i = 0; i < range.length; i += 1) {
          if (range[i].from || range[i].to) {
            if (range[i].from && range[i].to) {
              if (
                dayDate <= new Date(range[i].to).getTime() &&
                dayDate >= new Date(range[i].from).getTime()
              ) {
                match = true;
              }
            } else if (range[i].from) {
              if (dayDate >= new Date(range[i].from).getTime()) {
                match = true;
              }
            } else if (range[i].to) {
              if (dayDate <= new Date(range[i].to).getTime()) {
                match = true;
              }
            }
          } else if (range[i].date) {
            if (dayDate === new Date(range[i].date).getTime()) {
              match = true;
            }
          } else if (dayDate === new Date(range[i]).getTime()) {
            match = true;
          }
        }
      } else if (range.from || range.to) {
        if (range.from && range.to) {
          if (dayDate <= new Date(range.to).getTime() && dayDate >= new Date(range.from).getTime()) {
            match = true;
          }
        } else if (range.from) {
          if (dayDate >= new Date(range.from).getTime()) {
            match = true;
          }
        } else if (range.to) {
          if (dayDate <= new Date(range.to).getTime()) {
            match = true;
          }
        }
      } else if (range.date) {
        match = dayDate === new Date(range.date).getTime();
      } else if (typeof range === 'function') {
        match = range(new Date(dayDate));
      }
      return match;
    }
    // eslint-disable-next-line
    daysInMonth(date) {
      const d = new Date(date);
      return new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate();
    }
  
    renderMonths(date) {
      const calendar = this;
      if (calendar.params.renderMonths) {
        return calendar.params.renderMonths.call(calendar, date);
      }
      return (
        <div class="calendar-months-wrapper">
          {calendar.renderMonth(date, 'prev')}
          {calendar.renderMonth(date)}
          {calendar.renderMonth(date, 'next')}
        </div>
      );
    }
  
    renderMonth(d, offset) {
      const calendar = this;
      const { params, value } = calendar;
      if (params.renderMonth) {
        return params.renderMonth.call(calendar, d, offset);
      }
      let date = new Date(d);
      let year = date.getFullYear();
      let month = date.getMonth();
      let localeMonth = calendar.monthNames.indexOf(calendar.monthFormatter.format(date));
      if (localeMonth < 0) localeMonth = month;
      let localeYear = calendar.yearFormatter.format(date);
  
      if (offset === 'next') {
        if (month === 11) date = new Date(year + 1, 0);
        else date = new Date(year, month + 1, 1);
      }
      if (offset === 'prev') {
        if (month === 0) date = new Date(year - 1, 11);
        else date = new Date(year, month - 1, 1);
      }
      if (offset === 'next' || offset === 'prev') {
        month = date.getMonth();
        year = date.getFullYear();
        localeMonth = calendar.monthNames.indexOf(calendar.monthFormatter.format(date));
        if (localeMonth < 0) localeMonth = month;
        localeYear = calendar.yearFormatter.format(date);
      }
  
      const currentValues = [];
      const today = new Date().setHours(0, 0, 0, 0);
      const minDate = params.minDate ? new Date(params.minDate).getTime() : null;
      const maxDate = params.maxDate ? new Date(params.maxDate).getTime() : null;
      const rows = 6;
      const cols = 7;
      const daysInPrevMonth = calendar.daysInMonth(
        new Date(date.getFullYear(), date.getMonth()).getTime() - 10 * 24 * 60 * 60 * 1000,
      );
      const daysInMonth = calendar.daysInMonth(date);
      const minDayNumber = params.firstDay === 6 ? 0 : 1;
  
      let monthHtml = '';
      let dayIndex = 0 + (params.firstDay - 1);
      let disabled;
      let hasEvents;
      let firstDayOfMonthIndex = new Date(date.getFullYear(), date.getMonth()).getDay();
      if (firstDayOfMonthIndex === 0) firstDayOfMonthIndex = 7;
  
      if (value && value.length) {
        for (let i = 0; i < value.length; i += 1) {
          currentValues.push(new Date(value[i]).setHours(0, 0, 0, 0));
        }
      }
  
      for (let row = 1; row <= rows; row += 1) {
        let rowHtml = '';
        for (let col = 1; col <= cols; col += 1) {
          dayIndex += 1;
          let dayDate;
          let dayNumber = dayIndex - firstDayOfMonthIndex;
          let addClass = '';
          if (row === 1 && col === 1 && dayNumber > minDayNumber && params.firstDay !== 1) {
            dayIndex -= 7;
            dayNumber = dayIndex - firstDayOfMonthIndex;
          }
  
          const weekDayIndex =
            col - 1 + params.firstDay > 6 ? col - 1 - 7 + params.firstDay : col - 1 + params.firstDay;
  
          if (dayNumber < 0) {
            dayNumber = daysInPrevMonth + dayNumber + 1;
            addClass += ' calendar-day-prev';
            dayDate = new Date(
              month - 1 < 0 ? year - 1 : year,
              month - 1 < 0 ? 11 : month - 1,
              dayNumber,
            ).getTime();
          } else {
            dayNumber += 1;
            if (dayNumber > daysInMonth) {
              dayNumber -= daysInMonth;
              addClass += ' calendar-day-next';
              dayDate = new Date(
                month + 1 > 11 ? year + 1 : year,
                month + 1 > 11 ? 0 : month + 1,
                dayNumber,
              ).getTime();
            } else {
              dayDate = new Date(year, month, dayNumber).getTime();
            }
          }
          // Today
          if (dayDate === today) addClass += ' calendar-day-today';
  
          // Selected
          if (params.rangePicker && currentValues.length === 2) {
            if (dayDate >= currentValues[0] && dayDate <= currentValues[1]) {
              addClass += ' calendar-day-selected';
            }
            if (currentValues[0] !== currentValues[1]) {
              if (dayDate > currentValues[0] && dayDate < currentValues[1]) {
                addClass += ' calendar-day-selected-range';
              }
              if (dayDate === currentValues[0]) {
                addClass += ' calendar-day-selected-left';
              }
              if (dayDate === currentValues[1]) {
                addClass += ' calendar-day-selected-right';
              }
            }
          } else if (currentValues.indexOf(dayDate) >= 0) addClass += ' calendar-day-selected';
          // Weekend
          if (params.weekendDays.indexOf(weekDayIndex) >= 0) {
            addClass += ' calendar-day-weekend';
          }
          // Events
          let eventsHtml = '';
          hasEvents = false;
          if (params.events) {
            if (calendar.dateInRange(dayDate, params.events)) {
              hasEvents = true;
            }
          }
          if (hasEvents) {
            addClass += ' calendar-day-has-events';
            // prettier-ignore
            eventsHtml = `
              <span class="calendar-day-events">
                <span class="calendar-day-event"></span>
              </span>
            `;
            if (Array.isArray(params.events)) {
              const eventDots = [];
              params.events.forEach((ev) => {
                const color = ev.color || '';
                if (eventDots.indexOf(color) < 0 && calendar.dateInRange(dayDate, ev)) {
                  eventDots.push(color);
                }
              });
              // prettier-ignore
              eventsHtml = `
                <span class="calendar-day-events">
                  ${eventDots.map(color => `
                    <span class="calendar-day-event" style="${color ? `background-color: ${color}` : ''}"></span>
                  `.trim()).join('')}
                </span>
              `;
            }
          }
          // Custom Ranges
          if (params.rangesClasses) {
            for (let k = 0; k < params.rangesClasses.length; k += 1) {
              if (calendar.dateInRange(dayDate, params.rangesClasses[k].range)) {
                addClass += ` ${params.rangesClasses[k].cssClass}`;
              }
            }
          }
          // Disabled
          disabled = false;
          if ((minDate && dayDate < minDate) || (maxDate && dayDate > maxDate)) {
            disabled = true;
          }
          if (params.disabled) {
            if (calendar.dateInRange(dayDate, params.disabled)) {
              disabled = true;
            }
          }
          if (disabled) {
            addClass += ' calendar-day-disabled';
          }
  
          dayDate = new Date(dayDate);
          const dayYear = dayDate.getFullYear();
          const dayMonth = dayDate.getMonth();
          const dayNumberDisplay = calendar.dayFormatter.format(dayDate);
          // prettier-ignore
          rowHtml += `
            <div data-year="${dayYear}" data-month="${dayMonth}" data-day="${dayNumber}" class="calendar-day${addClass}" data-date="${dayYear}-${dayMonth}-${dayNumber}">
              <span class="calendar-day-number">${dayNumberDisplay}${eventsHtml}</span>
            </div>`.trim();
        }
        monthHtml += `<div class="calendar-row">${rowHtml}</div>`;
      }
      monthHtml = `<div class="calendar-month" data-year="${year}" data-month="${month}" data-locale-year="${localeYear}" data-locale-month="${localeMonth}">${monthHtml}</div>`;
      return monthHtml;
    }
  
    renderWeekHeader() {
      const calendar = this;
      if (calendar.params.renderWeekHeader) {
        return calendar.params.renderWeekHeader.call(calendar);
      }
      const { params } = calendar;
      let weekDaysHtml = '';
      for (let i = 0; i < 7; i += 1) {
        const dayIndex = i + params.firstDay > 6 ? i - 7 + params.firstDay : i + params.firstDay;
        const dayName = calendar.dayNamesShort[dayIndex];
        weekDaysHtml += `<div class="calendar-week-day">${dayName}</div>`;
      }
      return <div class="calendar-week-header">{weekDaysHtml}</div>;
    }
  
    renderMonthSelector() {
      const calendar = this;
      if (calendar.params.renderMonthSelector) {
        return calendar.params.renderMonthSelector.call(calendar);
      }
      return (
        <div class="calendar-month-selector">
          <a class="link icon-only calendar-prev-month-button">
            <i class="icon icon-prev"></i>
          </a>
          {calendar.params.monthPicker ? (
            <a class="current-month-value link"></a>
          ) : (
            <span class="current-month-value"></span>
          )}
          <a class="link icon-only calendar-next-month-button">
            <i class="icon icon-next"></i>
          </a>
        </div>
      );
    }
  
    renderMonthPicker() {
      const calendar = this;
      const localeMonth = parseInt(
        calendar.$el.find('.calendar-month-current').attr('data-locale-month'),
        10,
      );
      return (
        <div class="calendar-month-picker">
          {calendar.monthNames.map((m, index) => (
            <div
              class={`calendar-month-picker-item ${
                localeMonth === index ? 'calendar-month-picker-item-current' : ''
              }`}
            >
              <span>{m}</span>
            </div>
          ))}
        </div>
      );
    }
  
    renderYearSelector() {
      const calendar = this;
      if (calendar.params.renderYearSelector) {
        return calendar.params.renderYearSelector.call(calendar);
      }
      return (
        <div class="calendar-year-selector">
          <a class="link icon-only calendar-prev-year-button">
            <i class="icon icon-prev"></i>
          </a>
          {calendar.params.yearPicker ? (
            <a class="current-year-value link"></a>
          ) : (
            <span class="current-year-value"></span>
          )}
          <a class="link icon-only calendar-next-year-button">
            <i class="icon icon-next"></i>
          </a>
        </div>
      );
    }
  
    renderYearPicker() {
      const calendar = this;
      const currentYear = calendar.currentYear;
      let yearMin = calendar.params.yearPickerMin || new Date().getFullYear() - 100;
      if (calendar.params.minDate) {
        yearMin = Math.max(yearMin, new Date(calendar.params.minDate).getFullYear());
      }
      let yearMax = calendar.params.yearPickerMax || new Date().getFullYear() + 100;
      if (calendar.params.maxDate) {
        yearMax = Math.min(yearMax, new Date(calendar.params.maxDate).getFullYear());
      }
      const years = [];
      for (let i = yearMin; i <= yearMax; i += 1) {
        years.push(i);
      }
      return (
        <div class="calendar-year-picker">
          {years.map((year) => (
            <div
              data-year={year}
              class={`calendar-year-picker-item ${
                year === currentYear ? 'calendar-year-picker-item-current' : ''
              }`}
            >
              <span>{calendar.yearFormatter.format(new Date().setFullYear(year))}</span>
            </div>
          ))}
        </div>
      );
    }
  
    // eslint-disable-next-line
    renderTimeSelector() {
      const calendar = this;
      const value = calendar.value && calendar.value[0];
      let timeString;
      if (value) timeString = calendar.timeSelectorFormatter.format(value);
      return (
        <div class="calendar-time-selector">
          <a class="link">{timeString || calendar.params.timePickerPlaceholder}</a>
        </div>
      );
    }
  
    renderHeader() {
      const calendar = this;
      if (calendar.params.renderHeader) {
        return calendar.params.renderHeader.call(calendar);
      }
      return (
        <div class="calendar-header">
          <div class="calendar-selected-date">{calendar.params.headerPlaceholder}</div>
        </div>
      );
    }
  
    renderFooter() {
      const calendar = this;
      const app = calendar.app;
      if (calendar.params.renderFooter) {
        return calendar.params.renderFooter.call(calendar);
      }
      return (
        <div class="calendar-footer">
          <a
            class={`${
              app.theme === 'md' ? 'button' : 'link'
            } calendar-close sheet-close popover-close`}
          >
            {calendar.params.toolbarCloseText}
          </a>
        </div>
      );
    }
  
    renderToolbar() {
      const calendar = this;
      if (calendar.params.renderToolbar) {
        return calendar.params.renderToolbar.call(calendar, calendar);
      }
      // prettier-ignore
      return (
      <div class="toolbar toolbar-top no-shadow">
        <div class="toolbar-inner">
          {calendar.params.monthSelector ? calendar.renderMonthSelector() : ''}
          {calendar.params.yearSelector ? calendar.renderYearSelector() : ''}
        </div>
      </div>
      );
    }
    // eslint-disable-next-line
    renderInline() {
      const calendar = this;
      const { cssClass, toolbar, header, footer, rangePicker, weekHeader } = calendar.params;
      const { value, hasTimePicker } = calendar;
      const date = value && value.length ? value[0] : new Date().setHours(0, 0, 0);
      return (
        <div
          class={`calendar calendar-inline ${rangePicker ? 'calendar-range' : ''} ${cssClass || ''}`}
        >
          {header && calendar.renderHeader()}
          {toolbar && calendar.renderToolbar()}
          {weekHeader && calendar.renderWeekHeader()}
          <div class="calendar-months">{calendar.renderMonths(date)}</div>
          {hasTimePicker && calendar.renderTimeSelector()}
          {footer && calendar.renderFooter()}
        </div>
      );
    }
  
    renderCustomModal() {
      const calendar = this;
      const { cssClass, toolbar, header, footer, rangePicker, weekHeader } = calendar.params;
      const { value, hasTimePicker } = calendar;
      const date = value && value.length ? value[0] : new Date().setHours(0, 0, 0);
      return (
        <div
          class={`calendar calendar-modal ${rangePicker ? 'calendar-range' : ''} ${cssClass || ''}`}
        >
          {header && calendar.renderHeader()}
          {toolbar && calendar.renderToolbar()}
          {weekHeader && calendar.renderWeekHeader()}
          <div class="calendar-months">{calendar.renderMonths(date)}</div>
          {hasTimePicker && calendar.renderTimeSelector()}
          {footer && calendar.renderFooter()}
        </div>
      );
    }
  
    renderSheet() {
      const calendar = this;
      const { cssClass, toolbar, header, footer, rangePicker, weekHeader } = calendar.params;
      const { value, hasTimePicker } = calendar;
      const date = value && value.length ? value[0] : new Date().setHours(0, 0, 0);
      return (
        <div
          class={`sheet-modal calendar calendar-sheet ${rangePicker ? 'calendar-range' : ''} ${
            cssClass || ''
          }`}
        >
          {header && calendar.renderHeader()}
          {toolbar && calendar.renderToolbar()}
          {weekHeader && calendar.renderWeekHeader()}
          <div class="sheet-modal-inner calendar-months">{calendar.renderMonths(date)}</div>
          {hasTimePicker && calendar.renderTimeSelector()}
          {footer && calendar.renderFooter()}
        </div>
      );
    }
  
    renderPopover() {
      const calendar = this;
      const { cssClass, toolbar, header, footer, rangePicker, weekHeader } = calendar.params;
      const { value, hasTimePicker } = calendar;
      const date = value && value.length ? value[0] : new Date().setHours(0, 0, 0);
      return (
        <div class="popover calendar-popover">
          <div class="popover-inner">
            <div class={`calendar ${rangePicker ? 'calendar-range' : ''} ${cssClass || ''}`}>
              {header && calendar.renderHeader()}
              {toolbar && calendar.renderToolbar()}
              {weekHeader && calendar.renderWeekHeader()}
              <div class="calendar-months">{calendar.renderMonths(date)}</div>
              {hasTimePicker && calendar.renderTimeSelector()}
              {footer && calendar.renderFooter()}
            </div>
          </div>
        </div>
      );
    }
  
    render() {
      const calendar = this;
      const { params } = calendar;
      if (params.render) return params.render.call(calendar);
      if (!calendar.inline) {
        let modalType = params.openIn;
        if (modalType === 'auto') modalType = calendar.isPopover() ? 'popover' : 'sheet';
  
        if (modalType === 'popover') return calendar.renderPopover();
        if (modalType === 'sheet') return calendar.renderSheet();
        return calendar.renderCustomModal();
      }
      return calendar.renderInline();
    }
  
    openTimePicker() {
      const calendar = this;
      const { $el, app, is12HoursFormat } = calendar;
      if (!$el || !$el.length) return;
      $el.append('<div class="calendar-time-picker"></div>');
      const hoursArr = [];
      const minutesArr = [];
      const hoursMin = is12HoursFormat ? 1 : 0;
      const hoursMax = is12HoursFormat ? 12 : 23;
      for (let i = hoursMin; i <= hoursMax; i += 1) {
        hoursArr.push(i);
      }
      for (let i = 0; i <= 59; i += 1) {
        minutesArr.push(i);
      }
      let value;
      if (calendar.value && calendar.value.length) {
        value = [calendar.value[0].getHours(), calendar.value[0].getMinutes()];
      } else {
        value = [new Date().getHours(), new Date().getMinutes()];
      }
      if (is12HoursFormat) {
        value.push(value[0] < 12 ? 'AM' : 'PM');
        if (value[0] > 12) value[0] -= 12;
        if (value[0] === 0) value[0] = 12;
      }
      calendar.timePickerInstance = app.picker.create({
        containerEl: $el.find('.calendar-time-picker'),
        value,
        toolbar: true,
        rotateEffect: false,
        toolbarCloseText: calendar.params.toolbarCloseText,
        cols: [
          {
            values: hoursArr,
          },
          {
            divider: true,
            content: ':',
          },
          {
            values: minutesArr,
            displayValues: minutesArr.map((m) => (m < 10 ? `0${m}` : m)),
          },
          ...(is12HoursFormat
            ? [
                {
                  values: ['AM', 'PM'],
                },
              ]
            : []),
        ],
      });
      calendar.timePickerInstance.$el
        .find('.toolbar a')
        .removeClass('sheet-close popover-close')
        .addClass('calendar-time-picker-close');
    }
  
    closeTimePicker() {
      const calendar = this;
      const { is12HoursFormat } = calendar;
      if (calendar.timePickerInstance) {
        const timePickerValue = calendar.timePickerInstance.value;
        let hours = parseInt(timePickerValue[0], 10);
        const minutes = parseInt(timePickerValue[1], 10);
        const period = calendar.timePickerInstance.value[2];
        if (is12HoursFormat) {
          if (period === 'AM' && hours === 12) {
            hours = 0;
          } else if (period === 'PM' && hours !== 12) {
            hours += 12;
          }
        }
  
        let value = calendar.value && calendar.value.length && calendar.value[0];
        if (!value) {
          value = new Date();
          value.setHours(hours, minutes, 0, 0);
        } else {
          value = new Date(value);
          value.setHours(hours, minutes);
        }
        calendar.setValue([value]);
        calendar.timePickerInstance.close();
        calendar.timePickerInstance.destroy();
        delete calendar.timePickerInstance;
      }
      if (calendar.$el && calendar.$el.length) {
        calendar.$el.find('.calendar-time-picker').remove();
      }
    }
  
    onOpen() {
      const calendar = this;
      const { initialized, $el, app, $inputEl, inline, value, params } = calendar;
      calendar.closing = false;
      calendar.opened = true;
      calendar.opening = true;
  
      // Init main events
      calendar.attachCalendarEvents();
  
      const updateValue = !value && params.value;
  
      // Set value
      if (!initialized) {
        if (value) calendar.setValue(value, 0);
        else if (params.value) {
          calendar.setValue(calendar.normalizeValues(params.value), 0);
        }
      } else if (value) {
        calendar.setValue(value, 0);
      }
  
      // Update current month and year
      calendar.updateCurrentMonthYear();
  
      // Set initial translate
      calendar.monthsTranslate = 0;
      calendar.setMonthsTranslate();
  
      // Update input value
      if (updateValue) calendar.updateValue();
      else if (params.header && value) {
        calendar.updateValue(true);
      }
  
      // Extra focus
      if (!inline && $inputEl && $inputEl.length && app.theme === 'md') {
        $inputEl.trigger('focus');
      }
  
      calendar.initialized = true;
  
      calendar.$months.each((monthEl) => {
        calendar.emit('local::monthAdd calendarMonthAdd', monthEl);
      });
  
      // Trigger events
      if ($el) {
        $el.trigger('calendar:open');
      }
      if ($inputEl) {
        $inputEl.trigger('calendar:open');
      }
      calendar.emit('local::open calendarOpen', calendar);
    }
  
    onOpened() {
      const calendar = this;
      calendar.opening = false;
      if (calendar.$el) {
        calendar.$el.trigger('calendar:opened');
      }
      if (calendar.$inputEl) {
        calendar.$inputEl.trigger('calendar:opened');
      }
      calendar.emit('local::opened calendarOpened', calendar);
    }
  
    onClose() {
      const calendar = this;
      const app = calendar.app;
      calendar.opening = false;
      calendar.closing = true;
  
      if (calendar.$inputEl) {
        if (app.theme === 'md') {
          calendar.$inputEl.trigger('blur');
        } else {
          const validate = calendar.$inputEl.attr('validate');
          const required = calendar.$inputEl.attr('required');
          if (validate && required) {
            app.input.validate(calendar.$inputEl);
          }
        }
      }
      if (calendar.detachCalendarEvents) {
        calendar.detachCalendarEvents();
      }
  
      if (calendar.$el) {
        calendar.$el.trigger('calendar:close');
      }
      if (calendar.$inputEl) {
        calendar.$inputEl.trigger('calendar:close');
      }
      calendar.emit('local::close calendarClose', calendar);
    }
  
    onClosed() {
      const calendar = this;
      calendar.opened = false;
      calendar.closing = false;
  
      if (!calendar.inline) {
        nextTick(() => {
          if (calendar.modal && calendar.modal.el && calendar.modal.destroy) {
            if (!calendar.params.routableModals) {
              calendar.modal.destroy();
            }
          }
          delete calendar.modal;
        });
      }
      if (calendar.timePickerInstance) {
        if (calendar.timePickerInstance.destroy) calendar.timePickerInstance.destroy();
        delete calendar.timePickerInstance;
      }
      if (calendar.$el) {
        calendar.$el.trigger('calendar:closed');
      }
      if (calendar.$inputEl) {
        calendar.$inputEl.trigger('calendar:closed');
      }
      calendar.emit('local::closed calendarClosed', calendar);
    }
  
    open() {
      const calendar = this;
      const { app, opened, inline, $inputEl, params } = calendar;
      if (opened) return;
  
      if (inline) {
        calendar.$el = $(calendar.render());
        calendar.$el[0].f7Calendar = calendar;
        calendar.$wrapperEl = calendar.$el.find('.calendar-months-wrapper');
        calendar.$months = calendar.$wrapperEl.find('.calendar-month');
        calendar.$containerEl.append(calendar.$el);
        calendar.onOpen();
        calendar.onOpened();
        return;
      }
      let modalType = params.openIn;
      if (modalType === 'auto') {
        modalType = calendar.isPopover() ? 'popover' : 'sheet';
      }
      const modalContent = calendar.render();
  
      const modalParams = {
        targetEl: $inputEl,
        scrollToEl: params.scrollToInput ? $inputEl : undefined,
        content: modalContent,
        backdrop:
          params.backdrop === true ||
          (modalType === 'popover' &&
            app.params.popover.backdrop !== false &&
            params.backdrop !== false),
        closeByBackdropClick: params.closeByBackdropClick,
        on: {
          open() {
            const modal = this;
            calendar.modal = modal;
            calendar.$el = modalType === 'popover' ? modal.$el.find('.calendar') : modal.$el;
            calendar.$wrapperEl = calendar.$el.find('.calendar-months-wrapper');
            calendar.$months = calendar.$wrapperEl.find('.calendar-month');
            calendar.$el[0].f7Calendar = calendar;
            if (modalType === 'customModal') {
              $(calendar.$el)
                .find('.calendar-close')
                .once('click', () => {
                  calendar.close();
                });
            }
            calendar.onOpen();
          },
          opened() {
            calendar.onOpened();
          },
          close() {
            calendar.onClose();
          },
          closed() {
            calendar.onClosed();
          },
        },
      };
      if (modalType === 'sheet') {
        modalParams.push = params.sheetPush;
        modalParams.swipeToClose = params.sheetSwipeToClose;
      }
      if (params.routableModals && calendar.view) {
        calendar.view.router.navigate({
          url: calendar.url,
          route: {
            path: calendar.url,
            [modalType]: modalParams,
          },
        });
      } else {
        calendar.modal = app[modalType].create(modalParams);
        calendar.modal.open();
      }
    }
  
    close() {
      const calendar = this;
      const { opened, inline } = calendar;
      if (!opened) return;
      if (inline) {
        calendar.onClose();
        calendar.onClosed();
        return;
      }
      if (calendar.params.routableModals && calendar.view) {
        calendar.view.router.back();
      } else {
        calendar.modal.close();
      }
    }
  
    init() {
      const calendar = this;
  
      calendar.initInput();
  
      if (calendar.inline) {
        calendar.open();
        calendar.emit('local::init calendarInit', calendar);
        return;
      }
  
      if (!calendar.initialized && calendar.params.value) {
        calendar.setValue(calendar.normalizeValues(calendar.params.value));
      }
  
      // Attach input Events
      if (calendar.$inputEl) {
        calendar.attachInputEvents();
      }
      if (calendar.params.closeByOutsideClick) {
        calendar.attachHtmlEvents();
      }
      calendar.emit('local::init calendarInit', calendar);
    }
  
    destroy() {
      const calendar = this;
      if (calendar.destroyed) return;
      const { $el } = calendar;
      calendar.emit('local::beforeDestroy calendarBeforeDestroy', calendar);
      if ($el) $el.trigger('calendar:beforedestroy');
  
      calendar.close();
  
      // Detach Events
      if (calendar.$inputEl) {
        calendar.detachInputEvents();
      }
      if (calendar.params.closeByOutsideClick) {
        calendar.detachHtmlEvents();
      }
  
      if (calendar.timePickerInstance) {
        if (calendar.timePickerInstance.destroy) calendar.timePickerInstance.destroy();
        delete calendar.timePickerInstance;
      }
  
      if ($el && $el.length) delete calendar.$el[0].f7Calendar;
      deleteProps(calendar);
      calendar.destroyed = true;
    }
  }
  
  export default Calendar;
  