ngx-daterangepicker-material
Version:
Angular 9 and 10 date range picker (with material design theme)
1 lines • 159 kB
Source Map (JSON)
{"version":3,"file":"ngx-daterangepicker-material.mjs","sources":["../../src/daterangepicker/daterangepicker.config.ts","../../src/daterangepicker/locale.service.ts","../../src/daterangepicker/daterangepicker.component.ts","../../src/daterangepicker/daterangepicker.component.html","../../src/daterangepicker/daterangepicker.directive.ts","../../src/daterangepicker/daterangepicker.module.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\nimport dayjs from 'dayjs/esm';\nimport localeData from 'dayjs/esm/plugin/localeData';\ndayjs.extend(localeData);\n\nexport const LOCALE_CONFIG = new InjectionToken<LocaleConfig>('daterangepicker.config');\n/**\n * LocaleConfig Interface\n */\nexport interface LocaleConfig {\n direction?: string;\n separator?: string;\n weekLabel?: string;\n applyLabel?: string;\n cancelLabel?: string;\n clearLabel?: string;\n customRangeLabel?: string;\n daysOfWeek?: string[];\n monthNames?: string[];\n firstDay?: number;\n format?: string;\n displayFormat?: string;\n locale?: any;\n}\n/**\n * DefaultLocaleConfig\n */\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport const DefaultLocaleConfig: LocaleConfig = {\n direction: 'ltr',\n separator: ' - ',\n weekLabel: 'W',\n applyLabel: 'Apply',\n cancelLabel: 'Cancel',\n clearLabel: 'Clear',\n customRangeLabel: 'Custom range',\n daysOfWeek: dayjs.weekdaysMin(),\n monthNames: dayjs.monthsShort(),\n firstDay: dayjs.localeData().firstDayOfWeek()\n};\n","import { Injectable, Inject } from '@angular/core';\nimport { LOCALE_CONFIG, DefaultLocaleConfig, LocaleConfig } from './daterangepicker.config';\n\n@Injectable()\nexport class LocaleService {\n constructor(@Inject(LOCALE_CONFIG) private configHolder: LocaleConfig) {}\n\n get config() {\n if (!this.configHolder) {\n return DefaultLocaleConfig;\n }\n return { ...DefaultLocaleConfig, ...this.configHolder };\n }\n\n configWithLocale(locale) {\n if (!this.configHolder && !locale) {\n return DefaultLocaleConfig;\n }\n return {\n ...DefaultLocaleConfig,\n ...{ daysOfWeek: locale.weekdaysMin, monthNames: locale.monthsShort, firstDay: locale.weekStart },\n ...this.configHolder\n };\n }\n}\n","import {\n ChangeDetectorRef,\n Component,\n ElementRef,\n EventEmitter,\n forwardRef,\n HostListener,\n Input,\n OnChanges,\n OnInit,\n Output,\n SimpleChanges,\n ViewChild,\n ViewEncapsulation\n} from '@angular/core';\nimport { FormControl, NG_VALUE_ACCESSOR } from '@angular/forms';\nimport dayjs, { Dayjs } from 'dayjs/esm';\nimport { LocaleConfig } from './daterangepicker.config';\nimport { LocaleService } from './locale.service';\n\nimport localeData from 'dayjs/esm/plugin/localeData';\nimport LocalizedFormat from 'dayjs/esm/plugin/localizedFormat';\nimport isoWeek from 'dayjs/esm/plugin/isoWeek';\nimport week from 'dayjs/esm/plugin/weekOfYear';\nimport customParseFormat from 'dayjs/esm/plugin/customParseFormat';\nimport utc from 'dayjs/esm/plugin/utc';\n\ndayjs.extend(localeData);\ndayjs.extend(LocalizedFormat);\ndayjs.extend(isoWeek);\ndayjs.extend(week);\ndayjs.extend(customParseFormat);\ndayjs.extend(utc);\n\nexport enum SideEnum {\n left = 'left',\n right = 'right'\n}\n\nexport interface DateRanges {\n [index: string]: [Dayjs, Dayjs];\n}\n\nexport interface DateRange {\n label: string;\n dates: [Dayjs, Dayjs];\n}\n\nexport interface ChosenDate {\n chosenLabel: string;\n startDate: Dayjs;\n endDate: Dayjs;\n}\n\nexport interface TimePeriod {\n [index: string]: Dayjs;\n\n startDate: Dayjs;\n endDate: Dayjs;\n}\n\nexport interface StartDate {\n startDate: Dayjs;\n}\n\nexport interface EndDate {\n endDate: Dayjs;\n}\n\ninterface TimePickerVariables {\n secondsLabel: string[];\n selectedMinute: number;\n selectedSecond: number;\n hours: number[];\n seconds: number[];\n disabledHours: number[];\n disabledMinutes: number[];\n minutes: number[];\n minutesLabel: string[];\n selectedHour: number;\n disabledSeconds: number[];\n amDisabled?: boolean;\n pmDisabled?: boolean;\n ampmModel?: string;\n selected: Dayjs;\n}\n\ninterface TimePickerVariablesHolder {\n [index: string]: TimePickerVariables;\n}\n\ninterface CalendarRowClasses {\n [index: number]: string;\n\n classList: string;\n}\n\ninterface CalendarClasses {\n [index: number]: CalendarRowClasses;\n}\n\ninterface Dropdowns {\n inMaxYear: boolean;\n yearArrays: number[];\n maxYear: number;\n minYear: number;\n currentMonth: number;\n inMinYear: boolean;\n monthArrays: number[];\n currentYear: number;\n}\n\ntype CalendarArrayWithProps<T> = T[] & { firstDay?: Dayjs; lastDay?: Dayjs };\n\ninterface CalendarVariables {\n calRows: number[];\n calCols: number[];\n calendar: CalendarArrayWithProps<Dayjs[]>;\n minDate: dayjs.Dayjs;\n year: number;\n classes: CalendarClasses;\n lastMonth: number;\n minute: number;\n second: number;\n daysInMonth: number;\n dayOfWeek: number;\n month: number;\n hour: number;\n firstDay: dayjs.Dayjs;\n lastYear: number;\n lastDay: dayjs.Dayjs;\n maxDate: dayjs.Dayjs;\n daysInLastMonth: number;\n dropdowns?: Dropdowns;\n}\n\ninterface CalendarVariableHolder {\n [index: string]: CalendarVariables;\n}\n\ninterface VisibleCalendar {\n month: Dayjs;\n calendar: CalendarArrayWithProps<Dayjs[]>;\n}\n\n@Component({\n selector: 'ngx-daterangepicker-material',\n styleUrls: ['./daterangepicker.component.scss'],\n templateUrl: './daterangepicker.component.html',\n encapsulation: ViewEncapsulation.None,\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => DaterangepickerComponent),\n multi: true\n }\n ]\n})\nexport class DaterangepickerComponent implements OnInit, OnChanges {\n @Input()\n startDate = dayjs().utc(true).startOf('day');\n\n @Input()\n endDate = dayjs().utc(true).endOf('day');\n\n @Input()\n dateLimit: number = null;\n\n // general\n @Input()\n autoApply = false;\n\n @Input()\n singleDatePicker = false;\n\n @Input()\n showDropdowns = false;\n\n @Input()\n showWeekNumbers = false;\n\n @Input()\n showISOWeekNumbers = false;\n\n @Input()\n linkedCalendars = false;\n\n @Input()\n autoUpdateInput = true;\n\n @Input()\n alwaysShowCalendars = false;\n\n @Input()\n maxSpan = false;\n\n @Input()\n lockStartDate = false;\n\n // timepicker variables\n @Input()\n timePicker = false;\n\n @Input()\n timePicker24Hour = false;\n\n @Input()\n timePickerIncrement = 1;\n\n @Input()\n timePickerSeconds = false;\n\n // end of timepicker variables\n @Input()\n showClearButton = false;\n\n @Input()\n firstMonthDayClass: string = null;\n\n @Input()\n lastMonthDayClass: string = null;\n\n @Input()\n emptyWeekRowClass: string = null;\n\n @Input()\n emptyWeekColumnClass: string = null;\n\n @Input()\n firstDayOfNextMonthClass: string = null;\n\n @Input()\n lastDayOfPreviousMonthClass: string = null;\n\n @Input()\n showCustomRangeLabel: boolean;\n\n @Input()\n showCancel = false;\n\n @Input()\n keepCalendarOpeningWithRange = false;\n\n @Input()\n showRangeLabelOnInput = false;\n\n @Input()\n customRangeDirection = false;\n\n @Input() drops: string;\n @Input() opens: string;\n @Input() closeOnAutoApply = true;\n @Output() choosedDate: EventEmitter<ChosenDate>;\n @Output() rangeClicked: EventEmitter<DateRange>;\n @Output() datesUpdated: EventEmitter<TimePeriod>;\n @Output() startDateChanged: EventEmitter<StartDate>;\n @Output() endDateChanged: EventEmitter<EndDate>;\n @Output() cancelClicked: EventEmitter<void>;\n @Output() clearClicked: EventEmitter<void>;\n @ViewChild('pickerContainer', { static: true }) pickerContainer: ElementRef;\n\n public chosenLabel: string;\n\n public calendarVariables: CalendarVariableHolder = {};\n // tooltiptext = []; // for storing tooltiptext\n public timepickerVariables: TimePickerVariablesHolder = {};\n daterangepicker: { start: FormControl; end: FormControl } = { start: new FormControl(), end: new FormControl() };\n\n public applyBtn: { disabled: boolean } = { disabled: false };\n\n // used in template for compile time support of enum values.\n public sideEnum = SideEnum;\n public chosenRange: string;\n\n public rangesArray: Array<string> = [];\n // some state information\n\n public isShown = false;\n public inline = true;\n leftCalendar: VisibleCalendar = { month: null, calendar: [] };\n rightCalendar: VisibleCalendar = { month: null, calendar: [] };\n public showCalInRanges = false;\n nowHoveredDate = null;\n pickingDate = false;\n // options: any = {}; // should get some opt from user\n // protected\n\n protected minDateHolder: dayjs.Dayjs;\n protected maxDateHolder: dayjs.Dayjs;\n protected localeHolder: LocaleConfig = {};\n protected rangesHolder: DateRanges = {};\n private cachedVersion: { start: Dayjs; end: Dayjs } = { start: null, end: null };\n\n constructor(private el: ElementRef, private ref: ChangeDetectorRef, private localeHolderService: LocaleService) {\n this.choosedDate = new EventEmitter();\n this.rangeClicked = new EventEmitter();\n this.datesUpdated = new EventEmitter();\n this.startDateChanged = new EventEmitter();\n this.endDateChanged = new EventEmitter();\n this.cancelClicked = new EventEmitter();\n this.clearClicked = new EventEmitter();\n }\n\n // accessors\n get minDate(): dayjs.Dayjs {\n return this.minDateHolder;\n }\n\n @Input()\n set minDate(value: dayjs.Dayjs | string) {\n if (dayjs.isDayjs(value)) {\n this.minDateHolder = value;\n } else if (typeof value === 'string') {\n this.minDateHolder = dayjs(value).utc(true);\n } else {\n this.minDateHolder = null;\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/member-ordering\n get locale(): LocaleConfig {\n return this.localeHolder;\n }\n\n @Input() set locale(value: LocaleConfig) {\n this.localeHolder = { ...this.localeHolderService.config, ...value };\n if (value.locale) {\n this.localeHolder = this.localeHolderService.configWithLocale(value.locale);\n }\n }\n\n // custom ranges\n // eslint-disable-next-line @typescript-eslint/member-ordering\n get ranges(): DateRanges {\n return this.rangesHolder;\n }\n\n @Input() set ranges(value: DateRanges) {\n this.rangesHolder = value;\n this.renderRanges();\n }\n\n // eslint-disable-next-line @typescript-eslint/member-ordering\n get maxDate(): dayjs.Dayjs {\n return this.maxDateHolder;\n }\n\n @Input()\n set maxDate(value: dayjs.Dayjs | string) {\n if (dayjs.isDayjs(value)) {\n this.maxDateHolder = value;\n } else if (typeof value === 'string') {\n this.maxDateHolder = dayjs(value).utc(true);\n } else {\n this.maxDateHolder = null;\n }\n }\n\n @Input()\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n isInvalidDate(date: Dayjs): boolean {\n return false;\n }\n // eslint-disable-next-line @typescript-eslint/member-ordering\n\n @Input()\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n isCustomDate(date: Dayjs): boolean {\n return false;\n }\n // eslint-disable-next-line @typescript-eslint/member-ordering\n\n @Input()\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n isTooltipDate(date: Dayjs): string | boolean | null {\n return null;\n }\n\n /**\n * handle click on all element in the component, useful for outside of click\n *\n * @param e event\n */\n @HostListener('click', ['$event'])\n handleInternalClick(e: MouseEvent): void {\n return e.stopPropagation();\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if ((changes.startDate || changes.endDate) && this.inline) {\n this.updateView();\n }\n }\n\n ngOnInit(): void {\n this.buildLocale();\n const daysOfWeek = [...this.locale.daysOfWeek];\n this.locale.firstDay = this.locale.firstDay % 7;\n if (this.locale.firstDay !== 0) {\n let iterator = this.locale.firstDay;\n\n while (iterator > 0) {\n daysOfWeek.push(daysOfWeek.shift());\n iterator--;\n }\n }\n this.locale.daysOfWeek = daysOfWeek;\n if (this.inline) {\n this.cachedVersion.start = this.startDate.clone();\n this.cachedVersion.end = this.endDate.clone();\n }\n\n if (this.startDate && this.timePicker) {\n this.setStartDate(this.startDate);\n this.renderTimePicker(SideEnum.left);\n }\n\n if (this.endDate && this.timePicker) {\n this.setEndDate(this.endDate);\n this.renderTimePicker(SideEnum.right);\n }\n\n this.updateMonthsInView();\n this.renderCalendar(SideEnum.left);\n this.renderCalendar(SideEnum.right);\n this.renderRanges();\n }\n\n renderRanges(): void {\n this.rangesArray = [];\n let start;\n let end;\n if (typeof this.ranges === 'object') {\n for (const range in this.ranges) {\n if (this.ranges[range]) {\n if (typeof this.ranges[range][0] === 'string') {\n start = dayjs(this.ranges[range][0], this.locale.format).utc(true);\n } else {\n start = dayjs(this.ranges[range][0]).utc(true);\n }\n if (typeof this.ranges[range][1] === 'string') {\n end = dayjs(this.ranges[range][1], this.locale.format).utc(true);\n } else {\n end = dayjs(this.ranges[range][1]).utc(true);\n }\n // If the start or end date exceed those allowed by the minDate or maxSpan\n // options, shorten the range to the allowable period.\n if (this.minDate && start.isBefore(this.minDate)) {\n start = this.minDate.clone();\n }\n let maxDate = this.maxDate;\n if (this.maxSpan && maxDate && start.clone().add(this.maxSpan).isAfter(maxDate)) {\n maxDate = start.clone().add(this.maxSpan);\n }\n if (maxDate && end.isAfter(maxDate)) {\n end = maxDate.clone();\n }\n // If the end of the range is before the minimum or the start of the range is\n // after the maximum, don't display this range option at all.\n if (\n (this.minDate && end.isBefore(this.minDate, this.timePicker ? 'minute' : 'day')) ||\n (maxDate && start.isAfter(maxDate, this.timePicker ? 'minute' : 'day'))\n ) {\n continue;\n }\n // Support unicode chars in the range names.\n const elem = document.createElement('textarea');\n elem.innerHTML = range;\n const rangeHtml = elem.value;\n this.ranges[rangeHtml] = [start, end];\n }\n }\n for (const range in this.ranges) {\n if (this.ranges[range]) {\n this.rangesArray.push(range);\n }\n }\n if (this.showCustomRangeLabel) {\n this.rangesArray.push(this.locale.customRangeLabel);\n }\n this.showCalInRanges = !this.rangesArray.length || this.alwaysShowCalendars;\n if (!this.timePicker) {\n this.startDate = this.startDate.startOf('day');\n this.endDate = this.endDate.endOf('day');\n }\n }\n }\n\n renderTimePicker(side: SideEnum): void {\n let selected: Dayjs;\n let minDate: Dayjs;\n const maxDate = this.maxDate;\n if (side === SideEnum.left) {\n selected = this.startDate.clone();\n minDate = this.minDate;\n } else if (side === SideEnum.right && this.endDate) {\n selected = this.endDate.clone();\n minDate = this.startDate;\n } else if (side === SideEnum.right && !this.endDate) {\n // don't have an end date, use the start date then put the selected time for the right side as the time\n selected = this.getDateWithTime(this.startDate, SideEnum.right);\n if (selected.isBefore(this.startDate)) {\n selected = this.startDate.clone(); // set it back to the start date the time was backwards\n }\n minDate = this.startDate;\n }\n const start = this.timePicker24Hour ? 0 : 1;\n const end = this.timePicker24Hour ? 23 : 12;\n this.timepickerVariables[side] = {\n hours: [],\n minutes: [],\n minutesLabel: [],\n seconds: [],\n secondsLabel: [],\n disabledHours: [],\n disabledMinutes: [],\n disabledSeconds: [],\n selectedHour: 0,\n selectedMinute: 0,\n selectedSecond: 0,\n selected\n };\n // generate hours\n for (let i = start; i <= end; i++) {\n let iIn24 = i;\n if (!this.timePicker24Hour) {\n iIn24 = selected.hour() >= 12 ? (i === 12 ? 12 : i + 12) : i === 12 ? 0 : i;\n }\n\n const time = selected.clone().hour(iIn24);\n let disabled = false;\n if (minDate && time.minute(59).isBefore(minDate)) {\n disabled = true;\n }\n if (maxDate && time.minute(0).isAfter(maxDate)) {\n disabled = true;\n }\n\n this.timepickerVariables[side].hours.push(i);\n if (iIn24 === selected.hour() && !disabled) {\n this.timepickerVariables[side].selectedHour = i;\n } else if (disabled) {\n this.timepickerVariables[side].disabledHours.push(i);\n }\n }\n\n // generate minutes\n for (let i = 0; i < 60; i += this.timePickerIncrement) {\n const padded = i < 10 ? `0${i}` : `${i}`;\n const time = selected.clone().minute(i);\n\n let disabled = false;\n if (minDate && time.second(59).isBefore(minDate)) {\n disabled = true;\n }\n if (maxDate && time.second(0).isAfter(maxDate)) {\n disabled = true;\n }\n this.timepickerVariables[side].minutes.push(i);\n this.timepickerVariables[side].minutesLabel.push(padded);\n if (selected.minute() === i && !disabled) {\n this.timepickerVariables[side].selectedMinute = i;\n } else if (disabled) {\n this.timepickerVariables[side].disabledMinutes.push(i);\n }\n }\n // generate seconds\n if (this.timePickerSeconds) {\n for (let i = 0; i < 60; i++) {\n const padded = i < 10 ? `0${i}` : `${i}`;\n const time = selected.clone().second(i);\n\n let disabled = false;\n if (minDate && time.isBefore(minDate)) {\n disabled = true;\n }\n if (maxDate && time.isAfter(maxDate)) {\n disabled = true;\n }\n\n this.timepickerVariables[side].seconds.push(i);\n this.timepickerVariables[side].secondsLabel.push(padded);\n if (selected.second() === i && !disabled) {\n this.timepickerVariables[side].selectedSecond = i;\n } else if (disabled) {\n this.timepickerVariables[side].disabledSeconds.push(i);\n }\n }\n }\n // generate AM/PM\n if (!this.timePicker24Hour) {\n if (minDate && selected.clone().hour(12).minute(0).second(0).isBefore(minDate)) {\n this.timepickerVariables[side].amDisabled = true;\n }\n\n if (maxDate && selected.clone().hour(0).minute(0).second(0).isAfter(maxDate)) {\n this.timepickerVariables[side].pmDisabled = true;\n }\n if (selected.hour() >= 12) {\n this.timepickerVariables[side].ampmModel = 'PM';\n } else {\n this.timepickerVariables[side].ampmModel = 'AM';\n }\n }\n this.timepickerVariables[side].selected = selected;\n }\n\n renderCalendar(side: SideEnum): void {\n // side enum\n const mainCalendar: VisibleCalendar = side === SideEnum.left ? this.leftCalendar : this.rightCalendar;\n const month: number = mainCalendar.month.month();\n const year: number = mainCalendar.month.year();\n const hour: number = mainCalendar.month.hour();\n const minute: number = mainCalendar.month.minute();\n const second: number = mainCalendar.month.second();\n const daysInMonth: number = dayjs(new Date(year, month)).utc(true).daysInMonth();\n const firstDay: Dayjs = dayjs(new Date(year, month, 1)).utc(true);\n const lastDay: Dayjs = dayjs(new Date(year, month, daysInMonth)).utc(true);\n const lastMonth: number = dayjs(firstDay).utc(true).subtract(1, 'month').month();\n const lastYear: number = dayjs(firstDay).utc(true).subtract(1, 'month').year();\n const daysInLastMonth: number = dayjs(new Date(lastYear, lastMonth)).utc(true).daysInMonth();\n const dayOfWeek: number = firstDay.day();\n // initialize 6 rows x 7 columns array for the calendar\n const calendar: CalendarArrayWithProps<Dayjs[]> = [];\n calendar.firstDay = firstDay;\n calendar.lastDay = lastDay;\n\n for (let i = 0; i < 6; i++) {\n calendar[i] = [];\n }\n\n // populate the calendar with date objects\n let startDay = daysInLastMonth - dayOfWeek + this.locale.firstDay + 1;\n if (startDay > daysInLastMonth) {\n startDay -= 7;\n }\n\n if (dayOfWeek === this.locale.firstDay) {\n startDay = daysInLastMonth - 6;\n }\n\n let curDate = dayjs(new Date(lastYear, lastMonth, startDay, 12, minute, second)).utc(true);\n\n for (let i = 0, col = 0, row = 0; i < 42; i++, col++, curDate = dayjs(curDate).utc(true).add(24, 'hours')) {\n if (i > 0 && col % 7 === 0) {\n col = 0;\n row++;\n }\n calendar[row][col] = curDate.clone().hour(hour).minute(minute).second(second);\n curDate = curDate.hour(12);\n\n if (\n this.minDate &&\n calendar[row][col].format('YYYY-MM-DD') === this.minDate.format('YYYY-MM-DD') &&\n calendar[row][col].isBefore(this.minDate) &&\n side === 'left'\n ) {\n calendar[row][col] = this.minDate.clone();\n }\n\n if (\n this.maxDate &&\n calendar[row][col].format('YYYY-MM-DD') === this.maxDate.format('YYYY-MM-DD') &&\n calendar[row][col].isAfter(this.maxDate) &&\n side === 'right'\n ) {\n calendar[row][col] = this.maxDate.clone();\n }\n }\n\n // make the calendar object available to hoverDate/clickDate\n if (side === SideEnum.left) {\n this.leftCalendar.calendar = calendar;\n } else {\n this.rightCalendar.calendar = calendar;\n }\n //\n // Display the calendar\n //\n let minDate = side === 'left' ? this.minDate : this.startDate;\n let maxDate = this.maxDate;\n // adjust maxDate to reflect the dateLimit setting in order to\n // grey out end dates beyond the dateLimit\n if (this.endDate === null && this.dateLimit) {\n const maxLimit = this.startDate.clone().add(this.dateLimit, 'day').endOf('day');\n if (!maxDate || maxLimit.isBefore(maxDate)) {\n maxDate = maxLimit;\n }\n\n if (this.customRangeDirection) {\n minDate = this.minDate;\n const minLimit = this.startDate.clone().subtract(this.dateLimit, 'day').endOf('day');\n if (!minDate || minLimit.isAfter(minDate)) {\n minDate = minLimit;\n }\n }\n }\n\n this.calendarVariables[side] = {\n month,\n year,\n hour,\n minute,\n second,\n daysInMonth,\n firstDay,\n lastDay,\n lastMonth,\n lastYear,\n daysInLastMonth,\n dayOfWeek,\n // other vars\n calRows: Array.from(Array(6).keys()),\n calCols: Array.from(Array(7).keys()),\n classes: {},\n minDate,\n maxDate,\n calendar\n };\n if (this.showDropdowns) {\n const currentMonth: number = calendar[1][1].month();\n const currentYear: number = calendar[1][1].year();\n const realCurrentYear: number = dayjs().utc(true).year();\n const maxYear: number = (maxDate && maxDate.year()) || realCurrentYear + 5;\n const minYear: number = (minDate && minDate.year()) || realCurrentYear - 50;\n const inMinYear: boolean = currentYear === minYear;\n const inMaxYear: boolean = currentYear === maxYear;\n const years: number[] = [];\n for (let y = minYear; y <= maxYear; y++) {\n years.push(y);\n }\n this.calendarVariables[side].dropdowns = {\n currentMonth,\n currentYear,\n maxYear,\n minYear,\n inMinYear,\n inMaxYear,\n monthArrays: Array.from(Array(12).keys()),\n yearArrays: years\n };\n }\n\n this.buildCells(calendar, side);\n }\n\n setStartDate(startDate: string | Dayjs): void {\n if (typeof startDate === 'string') {\n this.startDate = dayjs(startDate, this.locale.format).utc(true);\n }\n\n if (typeof startDate === 'object') {\n this.pickingDate = true;\n this.startDate = dayjs(startDate).utc(true);\n }\n if (!this.timePicker) {\n this.pickingDate = true;\n this.startDate = this.startDate.startOf('day');\n }\n\n if (this.timePicker && this.timePickerIncrement) {\n this.startDate = this.startDate.minute(Math.round(this.startDate.minute() / this.timePickerIncrement) * this.timePickerIncrement);\n }\n\n if (this.minDate && this.startDate.isBefore(this.minDate)) {\n this.startDate = this.minDate.clone();\n if (this.timePicker && this.timePickerIncrement) {\n this.startDate = this.startDate.minute(Math.round(this.startDate.minute() / this.timePickerIncrement) * this.timePickerIncrement);\n }\n }\n\n if (this.maxDate && this.startDate.isAfter(this.maxDate)) {\n this.startDate = this.maxDate.clone();\n if (this.timePicker && this.timePickerIncrement) {\n this.startDate = this.startDate.minute(Math.floor(this.startDate.minute() / this.timePickerIncrement) * this.timePickerIncrement);\n }\n }\n\n if (!this.isShown) {\n this.updateElement();\n }\n this.startDateChanged.emit({ startDate: this.startDate });\n this.updateMonthsInView();\n }\n\n setEndDate(endDate: string | Dayjs): void {\n if (typeof endDate === 'string') {\n this.endDate = dayjs(endDate, this.locale.format).utc(true);\n }\n\n if (typeof endDate === 'object') {\n this.pickingDate = false;\n this.endDate = dayjs(endDate).utc(true);\n }\n if (!this.timePicker) {\n this.pickingDate = false;\n this.endDate = this.endDate.add(1, 'd').startOf('day').subtract(1, 'second');\n }\n\n if (this.timePicker && this.timePickerIncrement) {\n this.endDate.minute(Math.round(this.endDate.minute() / this.timePickerIncrement) * this.timePickerIncrement);\n }\n\n if (this.endDate.isBefore(this.startDate)) {\n this.endDate = this.startDate.clone();\n }\n\n if (this.maxDate && this.endDate.isAfter(this.maxDate)) {\n this.endDate = this.maxDate.clone();\n }\n\n if (this.dateLimit && this.startDate.clone().add(this.dateLimit, 'day').isBefore(this.endDate)) {\n this.endDate = this.startDate.clone().add(this.dateLimit, 'day');\n }\n\n if (!this.isShown) {\n // this.updateElement();\n }\n this.endDateChanged.emit({ endDate: this.endDate });\n this.updateMonthsInView();\n }\n\n updateView(): void {\n if (this.timePicker) {\n this.renderTimePicker(SideEnum.left);\n this.renderTimePicker(SideEnum.right);\n }\n this.updateMonthsInView();\n this.updateCalendars();\n }\n\n updateMonthsInView(): void {\n if (this.endDate) {\n // if both dates are visible already, do nothing\n if (\n !this.singleDatePicker &&\n this.leftCalendar.month &&\n this.rightCalendar.month &&\n ((this.startDate && this.leftCalendar && this.startDate.format('YYYY-MM') === this.leftCalendar.month.format('YYYY-MM')) ||\n (this.startDate && this.rightCalendar && this.startDate.format('YYYY-MM') === this.rightCalendar.month.format('YYYY-MM'))) &&\n (this.endDate.format('YYYY-MM') === this.leftCalendar.month.format('YYYY-MM') ||\n this.endDate.format('YYYY-MM') === this.rightCalendar.month.format('YYYY-MM'))\n ) {\n return;\n }\n if (this.startDate) {\n this.leftCalendar.month = this.startDate.clone().date(2);\n if (!this.linkedCalendars && (this.endDate.month() !== this.startDate.month() || this.endDate.year() !== this.startDate.year())) {\n this.rightCalendar.month = this.endDate.clone().date(2);\n } else {\n this.rightCalendar.month = this.startDate.clone().date(2).add(1, 'month');\n }\n }\n } else {\n if (\n this.leftCalendar.month.format('YYYY-MM') !== this.startDate.format('YYYY-MM') &&\n this.rightCalendar.month.format('YYYY-MM') !== this.startDate.format('YYYY-MM')\n ) {\n this.leftCalendar.month = this.startDate.clone().date(2);\n this.rightCalendar.month = this.startDate.clone().date(2).add(1, 'month');\n }\n }\n if (this.maxDate && this.linkedCalendars && !this.singleDatePicker && this.rightCalendar.month > this.maxDate) {\n this.rightCalendar.month = this.maxDate.clone().date(2);\n this.leftCalendar.month = this.maxDate.clone().date(2).subtract(1, 'month');\n }\n }\n\n /**\n * This is responsible for updating the calendars\n */\n updateCalendars(): void {\n this.renderCalendar(SideEnum.left);\n this.renderCalendar(SideEnum.right);\n\n if (this.endDate === null) {\n return;\n }\n this.calculateChosenLabel();\n }\n\n updateElement(): void {\n const format = this.locale.displayFormat ? this.locale.displayFormat : this.locale.format;\n if (!this.singleDatePicker && this.autoUpdateInput) {\n if (this.startDate && this.endDate) {\n // if we use ranges and should show range label on input\n if (\n this.rangesArray.length &&\n this.showRangeLabelOnInput === true &&\n this.chosenRange &&\n this.locale.customRangeLabel !== this.chosenRange\n ) {\n this.chosenLabel = this.chosenRange;\n } else {\n this.chosenLabel = this.startDate.format(format) + this.locale.separator + this.endDate.format(format);\n }\n }\n } else if (this.autoUpdateInput) {\n this.chosenLabel = this.startDate.format(format);\n }\n }\n\n remove(): void {\n this.isShown = false;\n }\n\n /**\n * this should calculate the label\n */\n calculateChosenLabel(): void {\n if (!this.locale || !this.locale.separator) {\n this.buildLocale();\n }\n let customRange = true;\n let i = 0;\n if (this.rangesArray.length > 0) {\n for (const range in this.ranges) {\n if (this.ranges[range]) {\n if (this.timePicker) {\n const format = this.timePickerSeconds ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm';\n // ignore times when comparing dates if time picker seconds is not enabled\n if (\n this.startDate.format(format) === this.ranges[range][0].format(format) &&\n this.endDate.format(format) === this.ranges[range][1].format(format)\n ) {\n customRange = false;\n this.chosenRange = this.rangesArray[i];\n break;\n }\n } else {\n // ignore times when comparing dates if time picker is not enabled\n if (\n this.startDate.format('YYYY-MM-DD') === this.ranges[range][0].format('YYYY-MM-DD') &&\n this.endDate.format('YYYY-MM-DD') === this.ranges[range][1].format('YYYY-MM-DD')\n ) {\n customRange = false;\n this.chosenRange = this.rangesArray[i];\n break;\n }\n }\n i++;\n }\n }\n if (customRange) {\n if (this.showCustomRangeLabel) {\n this.chosenRange = this.locale.customRangeLabel;\n } else {\n this.chosenRange = null;\n }\n // if custom label: show calendar\n this.showCalInRanges = true;\n }\n }\n\n this.updateElement();\n }\n\n clickApply(e?: MouseEvent): void {\n if (!this.singleDatePicker && this.startDate && !this.endDate) {\n this.endDate = this.getDateWithTime(this.startDate, SideEnum.right);\n\n this.calculateChosenLabel();\n }\n if (this.isInvalidDate && this.startDate && this.endDate) {\n // get if there are invalid date between range\n\n let d = this.startDate.clone();\n while (d.isBefore(this.endDate)) {\n if (this.isInvalidDate(d)) {\n this.endDate = d.subtract(1, 'days');\n this.calculateChosenLabel();\n break;\n }\n d = d.add(1, 'days');\n }\n }\n if (this.chosenLabel) {\n this.choosedDate.emit({ chosenLabel: this.chosenLabel, startDate: this.startDate, endDate: this.endDate });\n }\n\n this.datesUpdated.emit({ startDate: this.startDate, endDate: this.endDate });\n if (e || (this.closeOnAutoApply && !e)) {\n this.hide();\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n clickCancel(e: MouseEvent): void {\n this.startDate = this.cachedVersion.start;\n this.endDate = this.cachedVersion.end;\n if (this.inline) {\n this.updateView();\n }\n this.cancelClicked.emit();\n this.hide();\n }\n\n /**\n * called when month is changed\n *\n * @param monthEvent get value in event.target.value\n * @param side left or right\n */\n monthChanged(monthEvent: Event, side: SideEnum): void {\n const year = this.calendarVariables[side].dropdowns.currentYear;\n const month = parseInt((monthEvent.target as HTMLSelectElement).value, 10);\n this.monthOrYearChanged(month, year, side);\n }\n\n /**\n * called when year is changed\n *\n * @param yearEvent get value in event.target.value\n * @param side left or right\n */\n yearChanged(yearEvent: Event, side: SideEnum): void {\n const month = this.calendarVariables[side].dropdowns.currentMonth;\n const year = parseInt((yearEvent.target as HTMLSelectElement).value, 10);\n this.monthOrYearChanged(month, year, side);\n }\n\n /**\n * called when time is changed\n *\n * @param timeEvent an event\n * @param side left or right\n */\n timeChanged(timeEvent: Event, side: SideEnum): void {\n let hour = parseInt(String(this.timepickerVariables[side].selectedHour), 10);\n const minute = parseInt(String(this.timepickerVariables[side].selectedMinute), 10);\n const second = this.timePickerSeconds ? parseInt(String(this.timepickerVariables[side].selectedSecond), 10) : 0;\n\n if (!this.timePicker24Hour) {\n const ampm = this.timepickerVariables[side].ampmModel;\n if (ampm === 'PM' && hour < 12) {\n hour += 12;\n }\n if (ampm === 'AM' && hour === 12) {\n hour = 0;\n }\n }\n\n if (side === SideEnum.left) {\n let start = this.startDate.clone();\n start = start.hour(hour);\n start = start.minute(minute);\n start = start.second(second);\n this.setStartDate(start);\n if (this.singleDatePicker) {\n this.endDate = this.startDate.clone();\n } else if (this.endDate && this.endDate.format('YYYY-MM-DD') === start.format('YYYY-MM-DD') && this.endDate.isBefore(start)) {\n this.setEndDate(start.clone());\n } else if (!this.endDate && this.timePicker) {\n const startClone = this.getDateWithTime(start, SideEnum.right);\n\n if (startClone.isBefore(start)) {\n this.timepickerVariables[SideEnum.right].selectedHour = hour;\n this.timepickerVariables[SideEnum.right].selectedMinute = minute;\n this.timepickerVariables[SideEnum.right].selectedSecond = second;\n }\n }\n } else if (this.endDate) {\n let end = this.endDate.clone();\n end = end.hour(hour);\n end = end.minute(minute);\n end = end.second(second);\n this.setEndDate(end);\n }\n\n // update the calendars so all clickable dates reflect the new time component\n this.updateCalendars();\n\n // re-render the time pickers because changing one selection can affect what's enabled in another\n this.renderTimePicker(SideEnum.left);\n this.renderTimePicker(SideEnum.right);\n\n if (this.autoApply) {\n this.clickApply();\n }\n }\n\n /**\n * call when month or year changed\n *\n * @param month month number 0 -11\n * @param year year eg: 1995\n * @param side left or right\n */\n monthOrYearChanged(month: number, year: number, side: SideEnum): void {\n const isLeft = side === SideEnum.left;\n let newMonth = month;\n let newYear = year;\n\n if (!isLeft) {\n if (newYear < this.startDate.year() || (newYear === this.startDate.year() && newMonth < this.startDate.month())) {\n newMonth = this.startDate.month();\n newYear = this.startDate.year();\n }\n }\n\n if (this.minDate) {\n if (newYear < this.minDate.year() || (newYear === this.minDate.year() && newMonth < this.minDate.month())) {\n newMonth = this.minDate.month();\n newYear = this.minDate.year();\n }\n }\n\n if (this.maxDate) {\n if (newYear > this.maxDate.year() || (newYear === this.maxDate.year() && newMonth > this.maxDate.month())) {\n newMonth = this.maxDate.month();\n newYear = this.maxDate.year();\n }\n }\n this.calendarVariables[side].dropdowns.currentYear = newYear;\n this.calendarVariables[side].dropdowns.currentMonth = newMonth;\n if (isLeft) {\n this.leftCalendar.month = this.leftCalendar.month.month(newMonth).year(newYear);\n if (this.linkedCalendars) {\n this.rightCalendar.month = this.leftCalendar.month.clone().add(1, 'month');\n }\n } else {\n this.rightCalendar.month = this.rightCalendar.month.month(newMonth).year(newYear);\n if (this.linkedCalendars) {\n this.leftCalendar.month = this.rightCalendar.month.clone().subtract(1, 'month');\n }\n }\n this.updateCalendars();\n }\n\n /**\n * Click on previous month\n *\n * @param side left or right calendar\n */\n clickPrev(side: SideEnum): void {\n if (side === SideEnum.left) {\n this.leftCalendar.month = this.leftCalendar.month.subtract(1, 'month');\n if (this.linkedCalendars) {\n this.rightCalendar.month = this.rightCalendar.month.subtract(1, 'month');\n }\n } else {\n this.rightCalendar.month = this.rightCalendar.month.subtract(1, 'month');\n }\n this.updateCalendars();\n }\n\n /**\n * Click on next month\n *\n * @param side left or right calendar\n */\n clickNext = (side: SideEnum) => {\n if (side === SideEnum.left) {\n this.leftCalendar.month = this.leftCalendar.month.add(1, 'month');\n } else {\n this.rightCalendar.month = this.rightCalendar.month.add(1, 'month');\n if (this.linkedCalendars) {\n this.leftCalendar.month = this.leftCalendar.month.add(1, 'month');\n }\n }\n this.updateCalendars();\n };\n\n /**\n * When hovering a date\n *\n * @param e event: get value by e.target.value\n * @param side left or right\n * @param row row position of the current date clicked\n * @param col col position of the current date clicked\n */\n hoverDate(e: Event, side: SideEnum, row: number, col: number): void {\n const leftCalDate = this.calendarVariables.left.calendar[row][col];\n const rightCalDate = this.calendarVariables.right.calendar[row][col];\n if (this.pickingDate) {\n this.nowHoveredDate = side === SideEnum.left ? leftCalDate : rightCalDate;\n this.renderCalendar(SideEnum.left);\n this.renderCalendar(SideEnum.right);\n }\n // const tooltip = side === SideEnum.left ? this.tooltiptext[leftCalDate] : this.tooltiptext[rightCalDate];\n // if (tooltip.length > 0) {\n // (e.target as HTMLTableCellElement).setAttribute('title', tooltip);\n // }\n }\n\n /**\n * When selecting a date\n *\n * @param e event: get value by e.target.value\n * @param side left or right\n * @param row row position of the current date clicked\n * @param col col position of the current date clicked\n */\n clickDate(e: Event, side: SideEnum, row: number, col: number): void {\n if ((e.target as HTMLTableCellElement).tagName === 'TD') {\n if (!(e.target as HTMLTableCellElement).classList.contains('available')) {\n return;\n }\n } else if ((e.target as HTMLSpanElement).tagName === 'SPAN') {\n if (!(e.target as HTMLSpanElement).parentElement.classList.contains('available')) {\n return;\n }\n }\n if (this.rangesArray.length) {\n this.chosenRange = this.locale.customRangeLabel;\n }\n\n let date = side === SideEnum.left ? this.leftCalendar.calendar[row][col] : this.rightCalendar.calendar[row][col];\n\n if ((this.endDate || (date.isBefore(this.startDate, 'day') && this.customRangeDirection === false)) && this.lockStartDate === false) {\n // picking start\n if (this.timePicker) {\n date = this.getDateWithTime(date, SideEnum.left);\n }\n this.endDate = null;\n this.setStartDate(date.clone());\n } else if (!this.endDate && date.isBefore(this.startDate) && this.customRangeDirection === false) {\n // special case: clicking the same date for start/end,\n // but the time of the end date is before the start date\n this.setEndDate(this.startDate.clone());\n } else {\n // picking end\n if (this.timePicker) {\n date = this.getDateWithTime(date, SideEnum.right);\n }\n if (date.isBefore(this.startDate, 'day') === true && this.customRangeDirection === true) {\n this.setEndDate(this.startDate);\n this.setStartDate(date.clone());\n } else {\n this.setEndDate(date.clone());\n }\n\n if (this.autoApply) {\n this.calculateChosenLabel();\n }\n }\n\n if (this.singleDatePicker) {\n this.setEndDate(this.startDate);\n this.updateElement();\n if (this.autoApply) {\n this.clickApply();\n }\n }\n\n this.updateView();\n\n if (this.autoApply && this.startDate && this.endDate) {\n this.clickApply();\n }\n\n // This is to cancel the blur event handler if the mouse was in one of the inputs\n e.stopPropagation();\n }\n\n /**\n * Click on the custom range\n *\n * @param e: Event\n * @param label\n */\n clickRange(e: MouseEvent, label: string): void {\n this.chosenRange = label;\n if (label === this.locale.customRangeLabel) {\n this.isShown = true; // show calendars\n this.showCalInRanges = true;\n } else {\n const dates = this.ranges[label];\n console.log('1 ', dates);\n this.startDate = dates[0].clone();\n this.endDate = dates[1].clone();\n if (this.showRangeLabelOnInput && label !== this.locale.customRangeLabel) {\n this.chosenLabel = label;\n } else {\n this.calculateChosenLabel();\n }\n this.showCalInRanges = !this.rangesArray.length || this.alwaysShowCalendars;\n\n if (!this.timePicker) {\n this.startDate = this.startDate.startOf('day');\n this.endDate = this.endDate.endOf('day');\n }\n\n if (!this.alwaysShowCalendars) {\n this.isShown = false; // hide calendars\n }\n this.rangeClicked.emit({ label, dates });\n if (!this.keepCalendarOpeningWithRange || this.autoApply) {\n this.clickApply();\n } else {\n if (!this.alwaysShowCalendars) {\n return this.clickApply();\n }\n if (this.maxDate && this.maxDate.isSame(dates[0], 'month')) {\n this.rightCalendar.month = this.rightCalendar.month.month(dates[0].month());\n this.rightCalendar.month = this.rightCalendar.month.year(dates[0].year());\n this.leftCalendar.month = this.leftCalendar.month.month(dates[0].month() - 1);\n this.leftCalendar.month = this.leftCalendar.month.year(dates[1].year());\n } else {\n this.leftCalendar.month = this.leftCalendar.month.month(dates[0].month());\n this.leftCalendar.month = this.leftCalendar.month.year(dates[0].year());\n // get the right calendar value\n const nextMonth = !this.linkedCalendars ? dates[1].clone() : dates[0].clone().add(1, 'month');\n this.rightCalendar.month = this.rightCalendar.month.month(nextMonth.month());\n this.rightCalendar.month = this.rightCalendar.month.year(nextMonth.year());\n }\n this.updateCalendars();\n if (this.timePicker) {\n this.renderTimePicker(SideEnum.left);\n this.renderTimePicker(SideEnum.right);\n }\n }\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n show(e?: Event): void {\n if (this.isShown) {\n return;\n }\n this.cachedVersion.start = this.startDate.clone();\n this.cachedVersion.end = this.endDate.clone();\n this.isShown = true;\n this.updateView();\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n hide(e?: Event): void {\n if (!this.isShown) {\n return;\n }\n // incomplete date selection, revert to last values\n if (!this.endDate) {\n if (this.cachedVersion.start) {\n this.startDate = this.cachedVersion.start.clone();\n }\n if (this.cachedVersion.end) {\n this.endDate = this.cachedVersion.end.clone();\n }\n }\n\n // if a new date range was selected, invoke the user callback function\n if (!this.startDate.isSame(this.cachedVersion.start) || !this.endDate.isSame(this.cachedVersion.end)) {\n // this.callback(this.startDate, this.endDate, this.chosenLabel);\n }\n\n // if picker is attached to a text input, update it\n this.updateElement();\n this.isShown = false;\n this.ref.detectChanges();\n }\n\n /**\n * update the locale options\n *\n * @param locale\n */\n updateLocale(locale: LocaleConfig): void {\n for (const key in locale) {\n if (Object.prototype.hasOwnProperty.call(locale, key)) {\n this.locale[key] = locale[key];\n if (key === 'customRangeLabel') {\n this.renderRanges();\n }\n }\n }\n }\n\n /**\n * clear the daterange picker\n */\n clear(): void {\n this.startDate = dayjs().utc(true).startOf('day');\n this.endDate = dayjs().utc(true).endOf('day');\n this.choosedDate.emit({ chosenLabel: '', startDate: null, endDate: null });\n this.datesUpdated.emit({ startDate: null, endDate: null });\n this.clearClicked.emit();\n this.hide();\n }\n\n /**\n * Find out if the selected range should be disabled if it doesn't\n * fit into minDate and maxDate limitations.\n */\n disableRange(range: string): boolean {\n if (range === this.locale.customRangeLabel) {\n return false;\n }\n const rangeMarkers = this.ranges[range];\n const areBothBefore = rangeMarkers.every((date) => {\n if (!this.minDate) {\n return false;\n }\n return date.isBefore(this.minDate);\n });\n\n const areBothAfter = rangeMarkers.every((date) => {\n if (!this.maxDate) {\n return false;\n }\n return date.isAfter(this.maxDate);\n });\n return areBothBefore || areBothAfter;\n }\n\n /**\n *\n * @param date the date to add time\n * @param side left or right\n */\n private getDateWithTime(date, side: SideEnum): dayjs.Dayjs {\n let hour = parseInt(String(this.timepickerVariables[side].selectedHour), 10);\n if (!this.timePicker24Hour) {\n const ampm = this.timepickerVariables[side].ampmModel;\n if (ampm === 'PM' && hour < 12) {\n hour += 12;\n }\n if (ampm === 'AM' && hour === 12) {\n hour = 0;\n }\n }\n const minute = parseInt(String(this.timepickerVariables[side].selectedMinute), 10);\n const second = this.timePickerSeconds ? parseInt(String(this.timepickerVariables[side].selectedSecond), 10) : 0;\n return date.clone().hour(hour).minute(minute).second(second);\n }\n\n /**\n * build the locale config\n */\n private buildLocale() {\n this.locale = { ...this.localeHolderService.config, ...this.locale };\n if (!this.locale.format) {\n if (this.timePicker) {\n this.locale.format = dayjs.localeData().longDateFormat('lll');\n } else {\n this.locale.format = dayjs.localeData().longDateFormat('L');\n }\n }\n }\n\n private buildCells(calendar, side: SideEnum) {\n for (let row = 0; row < 6; row++) {\n this.calendarVariables[side].classes[row] = { classList: '' };\n const rowClasses = [];\n if (\n this.emptyWeekRowClass &&\n Array.from(Array(7).keys()).some((i) => calendar[row][i].month() !== this.calendarVariables[side].month)\n ) {\n rowClasses.push(this.emptyWeekRowClass);\n }\n for (let col = 0; col < 7; col++) {\n const classes = [];\n // empty week row class\n if (this.emptyWeekColumnClass) {\n if (calendar[row][col].month() !== this.calendarVariables[side].month) {\n classes.push(this.emptyWeekColumnClass);\n }\n }\n // highlight today's date\n if (calendar[row][col].isSame(dayjs().utc(true), 'day')) {\n classes.push('today');\n }\n // highlight weekends\n if (calendar[row][col].isoWeekday() > 5) {\n classes.push('weekend');\n }\n // grey out the dates in other months displayed at beginning and end of this calendar\n if (calendar[row][col].month() !== calendar[1][1].month()) {\n classes.push('off');\n\n // mark the last day of the previous month in this calendar\n if (\n this.lastDayOfPreviousMonthClass &&\n (calendar[row][col].month() < calendar[1][1].month() || calendar[1][1].month() === 0) &&\n calendar[row][col].date() === this.calendarVariables[side].daysInLastMonth\n ) {\n classes.push(this.lastDayOfPreviousMonthClass);\n }\n\n // mark the first day of the next month in this calendar\n if (\n this.firstDayOfNextMonthClass &&\n (calendar[row][col].month() > calendar[1][1].month() || calendar[row][col].