UNPKG

ngx-animating-datepicker

Version:

An Animating Datepicker for Angular 2+, for some smooth date picking :).

1,491 lines (1,483 loc) 185 kB
import { Injectable, Component, ElementRef, EventEmitter, HostBinding, Input, Output, ViewChild, ApplicationRef, ComponentFactoryResolver, Directive, HostListener, Injector, Optional, Renderer2, ViewContainerRef, NgModule } from '@angular/core'; import { NgControl } from '@angular/forms'; import { CommonModule } from '@angular/common'; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class DatepickerService { /** * Get the formatted weekdays * * @param {?} language * @param {?} format * @param {?} start * @return {?} */ static getWeekDays(language, format, start) { /** @type {?} */ const days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']; /** @type {?} */ const index = days.indexOf(start.toLowerCase()); if (index < 0) { throw new Error('Invalid week day start: ' + start); } /** @type {?} */ const weekdays = []; for (let day = 5; day <= 11; day++) { weekdays.push(new Date(1970, 1 - 1, day + index).toLocaleString(language, { weekday: format })); } return weekdays; } /** * Checks if is a value iso code * * @param {?} isoCode * @return {?} */ static isValidIsoCode(isoCode) { /** @type {?} */ const pattern = new RegExp(/([a-z]{2})-([A-Z]{2})/); return pattern.test(isoCode); } /** * Create a week array from the merged day arrays * * @param {?} dayArray * @return {?} */ static createWeekArray(dayArray) { /** @type {?} */ const size = 7; /** @type {?} */ const weeks = []; while (dayArray.length) { weeks.push({ days: dayArray.splice(0, size) }); } return weeks; } /** * @param {?} year * @param {?} month * @return {?} */ static getDaysInMonth(year, month) { return [31, DatepickerService.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; } /** * @param {?} value * @return {?} */ static isValidDate(value) { /** @type {?} */ let validDate = true; for (let i = 0; i < value.length; i++) { if (!DatepickerService.isDate(value[i]) && validDate) { validDate = false; } } return validDate; } /** * Check if year is a leap year * * @param {?} year * @return {?} */ static isLeapYear(year) { return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; } /** * Checks to see if value is a valid date * * @param {?} value * @return {?} */ static isDate(value) { return value instanceof Date; } /** * Get the year of the next month * * @param {?} year * @param {?} month * @return {?} */ static getYearOfNextMonth(year, month) { return month === 11 ? year + 1 : year; } /** * Get the next month * * @param {?} month * @return {?} */ static getNextMonth(month) { return month === 11 ? 0 : month + 1; } /** * Get the year of the previous month * * @param {?} year * @param {?} month * @return {?} */ static getYearOfPreviousMonth(year, month) { return month === 0 ? year - 1 : year; } /** * Get previous motnh * * @param {?} month * @return {?} */ static getPreviousMonth(month) { return month === 0 ? 11 : month - 1; } /** * Check if a date is later * * @param {?} date * @param {?} compareDate * @return {?} */ static isLater(date, compareDate) { return date > compareDate; } /** * Check if a date is ealrier * * @param {?} date * @param {?} compareDate * @return {?} */ static isEarlier(date, compareDate) { return date < compareDate; } } DatepickerService.decorators = [ { type: Injectable }, ]; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class UtilitiesService { /** * @private * @return {?} */ static getScrollOffset() { /** @type {?} */ const x = window.pageXOffset || document.documentElement.scrollLeft; /** @type {?} */ const y = window.pageYOffset || document.documentElement.scrollTop; return { x: x, y: y }; } /** * @param {?} el * @return {?} */ static getPageOffset(el) { /** @type {?} */ const scrollOffset = UtilitiesService.getScrollOffset(); /** @type {?} */ const width = el.offsetWidth; /** @type {?} */ const height = el.offsetHeight; if (el.getBoundingClientRect) { /** @type {?} */ const props = el.getBoundingClientRect(); /** @type {?} */ const position = { top: props.top + scrollOffset.y, left: props.left + scrollOffset.x, right: props.left + scrollOffset.x + width, bottom: props.top + scrollOffset.y + height, forRight: window.innerWidth - props.left, forBottom: window.innerHeight - (props.top + scrollOffset.y) }; return position; } } /** * @param {?} start * @param {?} end * @return {?} */ createArray(start, end) { return new Array(end - start + 1).fill(1).map((_, idx) => start + idx); } } UtilitiesService.decorators = [ { type: Injectable }, ]; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** @type {?} */ const DefaultOptions = { selectMultiple: false, // Select multiple dates closeOnSelect: false, // Close datepicker when date(s) selected animationSpeed: 400, // Animation speed in ms easing: 'ease-in', // Easing type string hideRestDays: false, // Hide the rest days disableRestDays: true, // Disable the click on rest days hideNavigation: false, // Hide the navigation range: false, // Use range functionality currentDate: new Date(), // Tne current displayed date (month, year) timeoutBeforeClosing: 300, // The timeout / delay before closing weekdayFormat: 'short', // "narrow", "short", "long" weekStart: 'monday' // Set the week start day }; /** @type {?} */ const DefaultDirectiveOptions = { appendToBody: true, // Append Datepicker to body openDirection: 'bottom', // The direction it should open to closeOnBlur: true, // Close the datepicker onBlur useAnimatePicker: true // Use the animatepickerComponent }; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class DatepickerComponent { /** * @param {?} utils * @param {?} element */ constructor(utils, element) { this.utils = utils; this.element = element; /* ============================================== * Internal Properties * ============================================== */ this.date = new Date(); this.year = null; this.month = null; this.today = this.date; this.months = null; this.weekdays = ['M', 'T', 'W', 'T', 'F', 'S', 'S']; this.selectedRange = 'startDate'; this.startDate = null; this.endDate = null; this.initialised = false; this.weekStartArray = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']; /* ============================================== * Initial Options * ============================================== */ this._options = DefaultOptions; /* ============================================== * External Properties * ============================================== */ /** * Set the the language manualy. A string with a BCP 47 language tag * @example nl-NL */ this._language = navigator.language; /** * Minimal Date: If set the dates before it will be disabled */ this._minDate = null; /** * Maximal Date: If set the dates after it will be disabled */ this._maxDate = null; /** * Selected Dates: handles the selected dates array. Can be set both internally and externally */ this._selectedDates = []; this.selectedDatesChange = new EventEmitter(); this.theme = ''; this.isOpen = true; this.asDirective = false; this.animate = false; this.topPosition = null; this.leftPosition = null; this.bottomPosition = null; this.rightPosition = null; } /** * @param {?} options * @return {?} */ set options(options) { if (options === undefined || !options) { return; } this._options = Object.assign({}, this._options, options); if (options.currentDate !== undefined) { this.date = this.options.currentDate; } if (this.initialised) { this.goToDate(); } } /** * @return {?} */ get options() { return this._options; } /** * @param {?} value * @return {?} */ set language(value) { if (!value || value === undefined || !DatepickerService.isValidIsoCode(value)) { return; } this._language = value; this.renderWeekdays(); } /** * @return {?} */ get language() { return this._language; } /** * @param {?} value * @return {?} */ set minDate(value) { if (value === undefined || value === this._minDate) { return; } this._minDate = new Date(value); this.goToDate(); } /** * @return {?} */ get minDate() { return this._minDate; } /** * @param {?} value * @return {?} */ set maxDate(value) { if (value === undefined || value === this._minDate) { return; } this._maxDate = new Date(value); this.goToDate(); } /** * @return {?} */ get maxDate() { return this._maxDate; } /** * @param {?} value * @return {?} */ set selectedDates(value) { /** @type {?} */ const _value = Array.isArray(value) ? value : [value]; if (!DatepickerService.isValidDate(_value)) { return; } this._selectedDates = _value; if (this.options.range) { this.resetRange(); } this.goToDate(); this.selectedDatesChange.emit(this._selectedDates); } /** * @return {?} */ get selectedDates() { return this._selectedDates; } /** * @return {?} */ ngOnInit() { this.initialised = true; if (!this.month && !this.year) { this.goToDate(this.options.currentDate); } } /** * Creates a day array * * @param {?} year * @param {?} month * @param {?=} isRestDays * @return {?} */ createDayArray(year, month, isRestDays) { /** @type {?} */ const days = []; /** @type {?} */ const daysInMonth = DatepickerService.getDaysInMonth(year, month); for (let index = 0; index < daysInMonth; index++) { /** @type {?} */ const dayNumber = index + 1; /** @type {?} */ const date = new Date(year, month, dayNumber); /** @type {?} */ const day = { date, dayNumber, isFirst: dayNumber === 1, isLast: dayNumber === daysInMonth, isToday: this.isToday(date), isSelected: this.isSelected(date), isRest: isRestDays, isHidden: isRestDays && this.options.hideRestDays, isDisabled: ((this.minDate || this.maxDate) && this.isDisabled(date)) || (isRestDays && this.options.disableRestDays), isInRange: this.isInRange(date) || ((this.isStartDate(date) || this.isEndDate(date)) && this.startDate && this.endDate), isStartDate: this.isStartDate(date), isEndDate: this.isEndDate(date) }; days.push(day); } return days; } /** * Get the days from the next month * * @param {?} year * @param {?} month * @return {?} */ getNextRestDays(year, month) { /** @type {?} */ const monthLength = DatepickerService.getDaysInMonth(year, month); /** @type {?} */ const weekStartIndex = this.weekStartArray.indexOf(this.options.weekStart); // Get the end of the month number minus the week start index /** @type {?} */ const endOfTheMonth = new Date(year, month, monthLength).getDay() - weekStartIndex; // Flip minus to plus when the end month number is minus. // this occurs when there are less rest days then the week start index /** @type {?} */ const _endOfTheMonth = endOfTheMonth < 0 ? 7 - Math.abs(endOfTheMonth) : endOfTheMonth; /** @type {?} */ const nextDays = this.createDayArray(DatepickerService.getYearOfNextMonth(year, month), DatepickerService.getNextMonth(month), true).slice(0, 7 - _endOfTheMonth); return nextDays.length > 6 ? [] : nextDays; } /** * Get the days of the previous month * * @param {?} year * @param {?} month * @return {?} */ getPreviousRestDays(year, month) { /** @type {?} */ const startOfTheMonth = new Date(year, month, 0).getDay(); /** @type {?} */ const previousDays = this.createDayArray(DatepickerService.getYearOfPreviousMonth(year, month), DatepickerService.getPreviousMonth(month), true); /** @type {?} */ const weekStartIndex = this.weekStartArray.indexOf(this.options.weekStart); /** @type {?} */ const _weekStartIndex = weekStartIndex === 0 ? 0 : (7 - weekStartIndex); /** @type {?} */ let sliceIndex = previousDays.length - startOfTheMonth - _weekStartIndex; sliceIndex = previousDays.length - sliceIndex >= 7 ? sliceIndex + 7 : sliceIndex; return previousDays.slice(sliceIndex, previousDays.length); } /** * Merge all the day arrays together * * @param {?} year * @param {?} month * @return {?} */ getMergedDayArrays(year, month) { return [ ...this.getPreviousRestDays(year, month), ...this.createDayArray(year, month), ...this.getNextRestDays(year, month) ]; } /** * Create the calendar array from the week arrays * * @param {?} year * @param {?} month * @return {?} */ createCalendarArray(year, month) { /** @type {?} */ const dayArray = this.getMergedDayArrays(year, month); /** @type {?} */ const weeks = DatepickerService.createWeekArray(dayArray); return [{ weeks: weeks }]; } /** * Update value is being triggered * * @param {?} date * @return {?} */ updateValue(date) { if (this.options.range) { this.selectRange(date); } else if (!this.isSelected(date)) { if (this.options.selectMultiple) { this.selectDate(date); } else { this.toggleDate(date); } if (this.options.closeOnSelect) { this.close(true); } } else { this.deselectDate(date); if (this.options.closeOnSelect) { this.close(true); } } this.months = this.createCalendarArray(this.year, this.month); } /** * Select range method - contains the logic to select the start- and endrange * * @param {?} date * @return {?} */ selectRange(date) { if (this.isSelected(date)) { this.deselectDate(date); } else if (DatepickerService.isEarlier(date, this.startDate)) { if (this.startDate) { this.toggleDate(date, this.startDate, true); } else { this.selectDate(date); } this.startDate = date; this.selectEndDate(); } else if (this.endDate && DatepickerService.isLater(date, this.endDate)) { this.toggleDate(date, this.endDate); this.endDate = date; this.selectStartDate(); } else if (this.selectedRange === 'startDate') { if (this.startDate) { this.toggleDate(date, this.startDate, true); } else { this.selectDate(date); } this.startDate = date; this.selectEndDate(); } else if (this.selectedRange === 'endDate') { if (this.endDate) { this.toggleDate(date, this.endDate); } else { this.selectDate(date); } this.endDate = date; this.selectStartDate(); if (this.options.closeOnSelect) { this.close(true); } } } /** * Reset the range if the selected dates change externally * @return {?} */ resetRange() { if (this._selectedDates.length === 1) { this.startDate = this._selectedDates[0]; this.endDate = null; } else if (this._selectedDates.length === 0) { this.startDate = null; this.endDate = null; } } /** * Toggle a date. One in, on out. * * @param {?} date - Date to be toggled on * @param {?=} toggleDate - Optional set specific date to toggle off * @param {?=} unshift - Optional set to unshift in the selectedDates array. is passed to selectDate method * @return {?} */ toggleDate(date, toggleDate, unshift) { if (!toggleDate) { this.selectedDates = [date]; } else if (unshift) { this._selectedDates.unshift(date); this.selectedDates = this._selectedDates.filter(selectedDate => { return selectedDate.toDateString() !== toggleDate.toDateString(); }); } else { this._selectedDates.push(date); this.selectedDates = this._selectedDates.filter(selectedDate => { return selectedDate.toDateString() !== toggleDate.toDateString(); }); } } /** * Select a date * * @param {?} date * @param {?=} unshift - Optional set to unshift instead of push the date in the selectedDates array * @return {?} */ selectDate(date, unshift) { /** @type {?} */ const selectedDates = [...this.selectedDates]; if (unshift) { selectedDates.unshift(date); } else { selectedDates.push(date); } this.selectedDates = selectedDates; } /** * Deselect a date * * @param {?} date * @return {?} */ deselectDate(date) { this.selectedDates = this._selectedDates.filter(selectedDate => { return selectedDate.toDateString() !== date.toDateString(); }); } /** * Go to the next month * @return {?} */ goToNextMonth() { this.year = DatepickerService.getYearOfNextMonth(this.year, this.month); this.month = DatepickerService.getNextMonth(this.month); this.totalYearMonth = [{ month: this.month, year: this.year }]; this.months = this.createCalendarArray(this.year, this.month); } /** * Go to the previous month * @return {?} */ goToPreviousMonth() { this.year = DatepickerService.getYearOfPreviousMonth(this.year, this.month); this.month = DatepickerService.getPreviousMonth(this.month); this.totalYearMonth = [{ month: this.month, year: this.year }]; this.months = this.createCalendarArray(this.year, this.month); } /** * Go to a specific month. Is also used to rerender the datepicker * * @param {?=} date - default is the current date. * @return {?} */ goToDate(date = this.date) { this.month = date.getMonth(); this.year = date.getFullYear(); this.totalYearMonth = [{ month: this.month, year: this.year }]; this.months = this.createCalendarArray(this.year, this.month); } /** * Render weekdays when options or language changes * @return {?} */ renderWeekdays() { this.weekdays = DatepickerService.getWeekDays(this._language, this.options.weekdayFormat, this.options.weekStart); } /** * Set the open state to true * @return {?} */ open() { if (this.isOpen) { return; } this.isOpen = true; } /** * Close the datepicker * * @param {?=} useTimeout - optional timeout * @return {?} */ close(useTimeout) { if (!this.isOpen) { return; } /** @type {?} */ const timeout = useTimeout ? this.options.timeoutBeforeClosing : 0; setTimeout(() => { this.isOpen = false; }, timeout); } /** * Select the start date - used for range functionality * @return {?} */ selectStartDate() { this.selectedRange = 'startDate'; } /** * Select the end date - used for range functionality * @return {?} */ selectEndDate() { this.selectedRange = 'endDate'; } // TODO: maybe output the startDate and Endate or just of internal use /** * Check if date is the start date * @param {?} date * @return {?} */ isStartDate(date) { return this.startDate && date.toDateString() === this.startDate.toDateString(); } /** * Check if date is the end date * @param {?} date * @return {?} */ isEndDate(date) { return this.endDate && date.toDateString() === this.endDate.toDateString(); } /** * Check if date is today * @param {?} date * @return {?} */ isToday(date) { return date.toDateString() === this.today.toDateString(); } /** * Check if date is selected * @param {?} dateToCheck * @return {?} */ isSelected(dateToCheck) { return this._selectedDates.map(date => date.toDateString()).indexOf(dateToCheck.toDateString()) !== -1; } /** * Check if date is disabled * @param {?} date * @return {?} */ isDisabled(date) { if (!this.minDate) { return !(date < this.maxDate); } if (!this.maxDate) { return !(date > this.minDate); } return !(date < this.maxDate && date > this.minDate); } /** * Check if date is in range * @param {?} date * @return {?} */ isInRange(date) { return this.startDate && this.endDate && this.startDate < date && date < this.endDate; } } DatepickerComponent.decorators = [ { type: Component, args: [{ selector: 'aa-datepicker', template: `<div class="datepicker__wrapper"> <div> <aa-navigation [hideNavigation]="options.hideNavigation" (previousClick)="goToPreviousMonth()" (nextClick)="goToNextMonth()" [language]="language" [totalYearMonth]="totalYearMonth"></aa-navigation> <div class="datepicker__weekdays-wrapper"> <div class="datepicker__weekdays-container"> <table class="datepicker__weekdays"> <thead> <td class="datepicker__weekday" *ngFor="let weekday of weekdays; index as i">{{weekday}}</td> </thead> </table> </div> </div> </div> <div class="datepicker__calendar-wrapper"> <div *ngFor="let month of months;" class="datepicker__calendar-container"> <table class="datepicker__calendar"> <tbody> <tr *ngFor="let week of month.weeks; index as i"> <td class="datepicker__day" *ngFor="let day of week.days; index as i" [ngClass]="{ 'is-first': day.isFirst, 'is-last': day.isLast, 'is-hidden': day.isHidden, 'is-disabled': day.isDisabled, 'is-today': day.isToday, 'is-selected': day.isSelected, 'is-in-range': day.isInRange, 'is-start-date': day.isStartDate, 'is-end-date': day.isEndDate, 'is-rest': day.isRest }"> <button class="datepicker__button" [disabled]="day.isDisabled || day.isHidden" (click)="updateValue(day.date)">{{day.dayNumber}} </button> </td> </tr> </tbody> </table> </div> </div> <ng-content></ng-content> </div>`, styles: [`:host{font-family:Arial,Helvetica,sans-serif;border:1px solid #d9d9d8;width:300px;position:relative;display:inline-block;z-index:2;border-radius:4px;box-shadow:0 1px 5px rgba(0,0,0,.15);overflow:hidden;background-color:#fff;box-sizing:border-box;visibility:hidden}:host *{box-sizing:border-box}:host .datepicker__calendar-container{padding:0 10px 10px}:host .datepicker__footer{position:relative;z-index:1}:host table{width:100%;table-layout:fixed;border-spacing:0;border-collapse:collapse}:host td{padding:0}:host .datepicker__weekdays-wrapper::after,:host .datepicker__weekdays-wrapper::before{content:' ';display:table}:host .datepicker__weekdays-wrapper::after{clear:both}:host .datepicker__weekdays-container{padding:10px 10px 0;float:left}:host .datepicker__weekdays{table-layout:fixed;width:100%}:host .datepicker__weekday{color:grey;font-size:12px;height:20px;text-align:center}:host .datepicker__day{position:relative;text-align:center;height:40px;width:auto;border:1px solid #d9d9d8}:host .datepicker__day.is-rest{border:none}:host .datepicker__button{padding:0;background-color:transparent;border:none;outline:0;font-style:inherit;cursor:pointer;color:#8e8d8a;width:100%;height:100%}:host .datepicker__button:hover{border:1px solid transparent;background-color:#f2f2f2;color:#8e8d8a}:host .is-hidden{opacity:0;display:table-cell}:host .is-rest{border:none}:host .is-rest .datepicker__button{color:#c0c0be}:host .is-today .datepicker__button{background-color:#eae7dc}:host .is-in-range .datepicker__button{background-color:#e98074;color:#fff}:host .is-in-range .datepicker__button:hover{background-color:#e66c5e}:host .is-selected .datepicker__button{background-color:#e85a4f;color:#fff;font-weight:700}:host .is-selected .datepicker__button:hover{background-color:#e23022}:host .is-start-date .datepicker__button{background-color:#e85a4f;color:#fff}:host .is-end-date .datepicker__button{background-color:#e85a4f;color:#fff}:host .is-disabled .datepicker__button{color:#d9d9d8;cursor:default}:host .is-disabled .datepicker__button:hover{background-color:transparent}:host.is-directive{visibility:hidden;position:absolute}:host.is-open{visibility:visible}:host.is-animate{transition:height .2s ease-in;width:300px}:host.is-animate .datepicker__main{transition:height .2s ease-in}:host.is-animate .datepicker__calendar-wrapper{position:absolute;width:200%;left:0}:host.is-animate .datepicker__calendar{float:left;width:100%}:host.is-animate .datepicker__calendar-container{float:left}`] },] }, ]; DatepickerComponent.ctorParameters = () => [ { type: UtilitiesService }, { type: ElementRef } ]; DatepickerComponent.propDecorators = { options: [{ type: Input, args: ['options',] }], language: [{ type: Input }], minDate: [{ type: Input }], maxDate: [{ type: Input }], selectedDatesChange: [{ type: Output }], selectedDates: [{ type: Input }], calendarContainer: [{ type: ViewChild, args: ['calendarContainer',] }], calendarTopContainer: [{ type: ViewChild, args: ['calendarTopContainer',] }], theme: [{ type: HostBinding, args: ['class',] }, { type: Input }], isOpen: [{ type: HostBinding, args: ['class.is-open',] }, { type: Input }], asDirective: [{ type: HostBinding, args: ['class.is-directive',] }], animate: [{ type: HostBinding, args: ['class.is-animate',] }], topPosition: [{ type: HostBinding, args: ['style.top.px',] }], leftPosition: [{ type: HostBinding, args: ['style.left.px',] }], bottomPosition: [{ type: HostBinding, args: ['style.bottom.px',] }], rightPosition: [{ type: HostBinding, args: ['style.right.px',] }] }; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class AnimatepickerComponent extends DatepickerComponent { /** * @param {?} elementRef * @param {?} utilities */ constructor(elementRef, utilities) { super(utilities, elementRef); this.elementRef = elementRef; this.utilities = utilities; /* ============================================== * Internal Properties * ============================================== */ this.animate = true; this.isAnimating = false; this.leftInnerPosition = 0; this.currentYearMonth = null; this.initialised = false; /* ============================================== * External Properties * ============================================== */ /** * Number of months: the number of months displayed */ this._numberOfMonths = new Array(1); } /** * @param {?} value * @return {?} */ set numberOfMonths(value) { if (value === undefined || value === this._numberOfMonths.length) { return; } this._numberOfMonths = new Array(value); this.setDatePickerDimension(); this.goToDate(this.date); } /** * @return {?} */ get numberOfMonths() { return this._numberOfMonths; } /** * @return {?} */ ngOnInit() { // Get the computed width from the calendar. Set the initial width /** @type {?} */ const computedWidth = window .getComputedStyle(this.elementRef.nativeElement, null) .getPropertyValue('width'); this.initialWidth = parseInt(computedWidth, 10); this.initialised = true; // Set the current year and month object if (!this.month && !this.year) { this.goToDate(this.options.currentDate); } } /** * @return {?} */ ngAfterViewInit() { setTimeout(() => { this.setDatePickerDimension(); this.setDatepickerHeight(true); }); } /** * Set the height and the width properties * @return {?} */ setDatePickerDimension() { this.datepickerHeight = this.calendarContainer.nativeElement.offsetHeight + this.calendarTopContainer.nativeElement.offsetHeight + this.footer.nativeElement.offsetHeight; this.calendarHeight = this.calendarContainer.nativeElement.offsetHeight; this.datepickerWidth = this.initialWidth * this._numberOfMonths.length; } /** * Go to a specific month * * @param {?=} date - optional * @return {?} */ goToDate(date) { if (date) { this.currentYearMonth = this.getNextYearMonthArray(date.getFullYear(), date.getMonth()); } this.calendarWidth = 50 / this._numberOfMonths.length; this.months = this.getNextMonthArray(this.currentYearMonth, true); this.resetStyle(); } /** * Create an array of the next year and months * * @param {?} year * @param {?} month * @return {?} */ getNextYearMonthArray(year, month) { /** @type {?} */ const array = []; for (let index = 0; index < this._numberOfMonths.length; index++) { array.push({ year: year, month: month }); year = DatepickerService.getYearOfNextMonth(year, month); month = DatepickerService.getNextMonth(month); } return array; } /** * Create an array of the previous year and months * * @param {?} year * @param {?} month * @return {?} */ getPreviousYearMonthArray(year, month) { /** @type {?} */ const array = []; for (let index = 0; index < this._numberOfMonths.length; index++) { array.unshift({ year: year, month: month }); year = DatepickerService.getYearOfPreviousMonth(year, month); month = DatepickerService.getPreviousMonth(month); } return array; } /** * Set the datepicker height, used when animating * * @param {?=} directionRight - Set optional when sliding to the right * @return {?} */ setDatepickerHeight(directionRight) { /** @type {?} */ let indexArray; // TODO: Seperate this logic for readability purpose if (this._numberOfMonths.length > 1) { /** @type {?} */ const start = directionRight ? 0 : this._numberOfMonths.length; /** @type {?} */ const end = directionRight ? this._numberOfMonths.length - 1 : this._numberOfMonths.length + this._numberOfMonths.length - 1; indexArray = this.utilities.createArray(start, end); } else { indexArray = directionRight ? [0] : [1]; } /** @type {?} */ const that = this; setTimeout(() => { /** @type {?} */ const calendarArray = that.elementRef.nativeElement.querySelectorAll('.datepicker__calendar-container'); /** @type {?} */ let offsetHeight = 0; indexArray.forEach(el => { if (calendarArray[el].offsetHeight > offsetHeight) { offsetHeight = calendarArray[el].offsetHeight; } }); // TODO: Merge with setHeight function. that.datepickerHeight = offsetHeight + that.calendarTopContainer.nativeElement.offsetHeight + that.footer.nativeElement.offsetHeight; that.calendarHeight = offsetHeight; }); } /** * Get next month array, gets multiple months. * Used when the options animate is set or multiple months are visable * * @param {?} currentYearMonth * @param {?=} keepDate * @param {?=} nextMonthsYearMonthArray * @return {?} Month[] */ getNextMonthArray(currentYearMonth, keepDate = false, nextMonthsYearMonthArray) { // Get the last index, used for selecting the right year month object /** @type {?} */ const lastIndex = this._numberOfMonths.length - 1; // Get next year and month in an Object /** @type {?} */ const nextMonths = nextMonthsYearMonthArray || this.getNextYearMonthArray(DatepickerService.getYearOfNextMonth(currentYearMonth[lastIndex].year, currentYearMonth[lastIndex].month), DatepickerService.getNextMonth(currentYearMonth[lastIndex].month)); // Concatenates the two objects to create a total year and month object this.totalYearMonth = currentYearMonth.concat(nextMonths); // Create the calendar array using the total year and month Object /** @type {?} */ const monthArray = []; this.totalYearMonth.forEach(e => monthArray.push(this.createCalendarArray(e.year, e.month))); // Set the new current year and month object. if (!keepDate) { this.currentYearMonth = nextMonths; } return [].concat.apply([], monthArray); } /** * Gets an array of previous months. * Used for animation and when more months are displayed * * @param {?} currentYearMonth * @param {?=} keepDate * @return {?} */ getPreviousMonthArray(currentYearMonth, keepDate = false) { // Get previous year and month in an Object /** @type {?} */ const previousMonths = this.getPreviousYearMonthArray(DatepickerService.getYearOfPreviousMonth(currentYearMonth[0].year, currentYearMonth[0].month), DatepickerService.getPreviousMonth(currentYearMonth[0].month)); // Concatenates the two objects to create a total year and month object this.totalYearMonth = previousMonths.concat(currentYearMonth); // Create the calendar array using the total year and month Object /** @type {?} */ const monthArray = []; this.totalYearMonth.forEach(e => { monthArray.push(this.createCalendarArray(e['year'], e['month'])); }); // Set the new current year and month object. if (!keepDate) { this.currentYearMonth = previousMonths; } return [].concat.apply([], monthArray); } /** * Update value is being triggered * * @param {?} date * @return {?} */ updateValue(date) { if (this.options.range) { this.selectRange(date); } else if (!this.isSelected(date)) { if (this.options.selectMultiple) { this.selectDate(date); } else { this.toggleDate(date); } if (this.options.closeOnSelect) { this.close(true); } } else { this.deselectDate(date); if (this.options.closeOnSelect) { this.close(true); } } this.months = this.getNextMonthArray(this.currentYearMonth, true); this.resetStyle(); } /** * Go to the next month * @return {?} */ goToNextMonth() { if (this.isAnimating) { return; } this.months = this.getNextMonthArray(this.currentYearMonth); this.resetStyle(); this.setDatepickerHeight(); this.slideLeft(); } /** * Go to the previous month * @return {?} */ goToPreviousMonth() { if (this.isAnimating) { return; } this.months = this.getPreviousMonthArray(this.currentYearMonth); this.resetStyle(true); this.setDatepickerHeight(true); this.slideRight(); } /** * Go to a specific month * TODO: WIP Check if date is in current range, or if it is later or earlier * @param {?} date * @return {?} */ goToMonth(date) { /** @type {?} */ const nextMonths = this.getNextYearMonthArray(date.getFullYear(), date.getMonth()); this.months = this.getNextMonthArray(this.totalYearMonth, false, nextMonths); this.resetStyle(); this.setDatepickerHeight(); this.slideRight(); } /** * Slide to the right * @return {?} */ slideRight() { this.setIsAnimating(); setTimeout(() => { this.transition = 'transform ' + this.options.animationSpeed + 'ms ' + this.options.easing; this.translateX = 50; }); } /** * Slide to the left (criss cross) * @return {?} */ slideLeft() { this.setIsAnimating(); setTimeout(() => { this.transition = 'transform ' + this.options.animationSpeed + 'ms ' + this.options.easing; this.translateX = -50; }); } /** * Set animating state * @return {?} */ setIsAnimating() { this.isAnimating = true; setTimeout(() => { this.isAnimating = false; }, this.options.animationSpeed); } /** * Reset Style * * @param {?=} resetForPrevious - Optional * @return {?} */ resetStyle(resetForPrevious) { this.transition = 'transform 0ms ease-in'; this.translateX = 0; this.leftInnerPosition = resetForPrevious ? -100 : 0; } } AnimatepickerComponent.decorators = [ { type: Component, args: [{ selector: 'aa-animatepicker', template: `<div class="datepicker__wrapper" [ngStyle]="datepickerPosition"> <div #calendarTopContainer> <div class="datepicker__header" #header> <ng-content select="header"></ng-content> </div> <aa-navigation (previousClick)="goToPreviousMonth()" (nextClick)="goToNextMonth()" (subNavigationClick)="goToDate($event)" [language]="language" [animate]="animate" [translateX]="translateX" [transition]="transition" [leftPosition]="leftInnerPosition" [hideNavigation]="options.hideNavigation" [totalYearMonth]="totalYearMonth" ></aa-navigation> <div class="datepicker__weekdays-wrapper"> <div *ngFor="let month of numberOfMonths" [ngStyle]="{ 'width.%': (100 / numberOfMonths.length) }" class="datepicker__weekdays-container"> <table class="datepicker__weekdays"> <thead> <td class="datepicker__weekday" *ngFor="let weekday of weekdays; index as i">{{weekday}}</td> </thead> </table> </div> </div> </div> <div class="datepicker__main" [ngStyle]="{ 'height.px': calendarHeight}"> <div class="datepicker__calendar-wrapper" #calendarContainer [ngStyle]="{ 'transform': 'translateX(' + translateX + '%)', 'transition': transition, 'left.%': leftInnerPosition }" > <div *ngFor="let month of months;" class="datepicker__calendar-container" [ngStyle]="{'width.%': calendarWidth}" > <table class="datepicker__calendar"> <tbody> <tr *ngFor="let week of month.weeks; index as i" class="datepicker__week"> <td class="datepicker__day" *ngFor="let day of week.days; index as i" [ngClass]="{ 'is-first': day.isFirst, 'is-last': day.isLast, 'is-hidden': day.isHidden, 'is-disabled': day.isDisabled, 'is-today': day.isToday, 'is-selected': day.isSelected, 'is-in-range': day.isInRange, 'is-start-date': day.isStartDate, 'is-end-date': day.isEndDate, 'is-rest': day.isRest }"> <button class="datepicker__button" [disabled]="day.isDisabled || day.isHidden" (click)="updateValue(day.date)">{{day.dayNumber}} </button> </td> </tr> </tbody> </table> </div> </div> </div> <div class="datepicker__footer" #footer> <ng-content select="footer"></ng-content> </div> </div> `, styles: [`:host{font-family:Arial,Helvetica,sans-serif;border:1px solid #d9d9d8;width:300px;position:relative;display:inline-block;z-index:2;border-radius:4px;box-shadow:0 1px 5px rgba(0,0,0,.15);overflow:hidden;background-color:#fff;box-sizing:border-box;visibility:hidden}:host *{box-sizing:border-box}:host .datepicker__calendar-container{padding:0 10px 10px}:host .datepicker__footer{position:relative;z-index:1}:host table{width:100%;table-layout:fixed;border-spacing:0;border-collapse:collapse}:host td{padding:0}:host .datepicker__weekdays-wrapper::after,:host .datepicker__weekdays-wrapper::before{content:' ';display:table}:host .datepicker__weekdays-wrapper::after{clear:both}:host .datepicker__weekdays-container{padding:10px 10px 0;float:left}:host .datepicker__weekdays{table-layout:fixed;width:100%}:host .datepicker__weekday{color:grey;font-size:12px;height:20px;text-align:center}:host .datepicker__day{position:relative;text-align:center;height:40px;width:auto;border:1px solid #d9d9d8}:host .datepicker__day.is-rest{border:none}:host .datepicker__button{padding:0;background-color:transparent;border:none;outline:0;font-style:inherit;cursor:pointer;color:#8e8d8a;width:100%;height:100%}:host .datepicker__button:hover{border:1px solid transparent;background-color:#f2f2f2;color:#8e8d8a}:host .is-hidden{opacity:0;display:table-cell}:host .is-rest{border:none}:host .is-rest .datepicker__button{color:#c0c0be}:host .is-today .datepicker__button{background-color:#eae7dc}:host .is-in-range .datepicker__button{background-color:#e98074;color:#fff}:host .is-in-range .datepicker__button:hover{background-color:#e66c5e}:host .is-selected .datepicker__button{background-color:#e85a4f;color:#fff;font-weight:700}:host .is-selected .datepicker__button:hover{background-color:#e23022}:host .is-start-date .datepicker__button{background-color:#e85a4f;color:#fff}:host .is-end-date .datepicker__button{background-color:#e85a4f;color:#fff}:host .is-disabled .datepicker__button{color:#d9d9d8;cursor:default}:host .is-disabled .datepicker__button:hover{background-color:transparent}:host.is-directive{visibility:hidden;position:absolute}:host.is-open{visibility:visible}:host.is-animate{transition:height .2s ease-in;width:300px}:host.is-animate .datepicker__main{transition:height .2s ease-in}:host.is-animate .datepicker__calendar-wrapper{position:absolute;width:200%;left:0}:host.is-animate .datepicker__calendar{float:left;width:100%}:host.is-animate .datepicker__calendar-container{float:left}`] },] }, ]; AnimatepickerComponent.ctorParameters = () => [ { type: ElementRef }, { type: UtilitiesService } ]; AnimatepickerComponent.propDecorators = { numberOfMonths: [{ type: Input }], calendarContainer: [{ type: ViewChild, args: ['calendarContainer',] }], calendarTopContainer: [{ type: ViewChild, args: ['calendarTopContainer',] }], footer: [{ type: ViewChild, args: ['footer',] }], datepickerWidth: [{ type: HostBinding, args: ['style.width.px',] }], datepickerHeight: [{ type: HostBinding, args: ['style.height.px',] }] }; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class DatepickerDirective { /** * @param {?} viewContainerRef * @param {?} componentFactoryResolver * @param {?} appRef * @param {?} injector * @param {?} renderer * @param {?} formControl */ constructor(viewContainerRef, componentFactoryResolver, appRef, injector, renderer, formControl) { this.viewContainerRef = viewContainerRef; this.componentFactoryResolver = componentFactoryResolver; this.appRef = appRef; this.injector = injector; this.renderer = renderer; this.formControl = formControl; this.datepicker = null; // TODO: fix types: DatepickerComponent | AnimatepickerComponent this._options = DefaultDirectiveOptions; /** * Selected Dates: handles the selected dates array. Can be set both internally and externally */ this._selectedDates = []; this.selectedDatesChange = new EventEmitter(); } /** * @param {?} options * @return {?} */ set options(options) { if (options === undefined || !options) { return; } // TODO: could be improved this._options = Object.assign({}, this._options, options); } /** * @return {?} */ get options() { return this._options; } /** * @param {?} options * @return {?} */ set datepickerOptions(options) { this._datepickerOptions = options; if (this.datepicker) { this.datepicker.options = options; } } /** * @return {?} */ get datepickerOptions() { return this._datepickerOptions; } /** * @param {?} value * @return {?} */ set language(value) { this._language = value; if (this.datepicker) { this.datepicker.language = value; } } /** * @return {?} */ get language() { return this._language; } /** * @param {?} value * @return {?} */ set minDate(value) { this._minDate = value; if (this.datepicker) { this.datepicker.minDate = value; } } /** * @return {?} */ get minDate() { return this._minDate; } /** * @param {?} value * @return {?} */ set maxDate(value) { this._maxDate = value; if (this.datepicker) { this.datepicker.maxDate = value; } } /** * @return {?} */ get maxDate() { return this._minDate; } /** * @param {?} value * @return {?} */ set numberOfMonths(value) { this._numberOfMonths = value; if (this.datepicker) { this.datepicker.numberOfMonths = value; } } /** * @return {?} */ get numberOfMonths() { return this._numberOfMonths; } /** * @param {?} value * @return {?} */ set theme(value) { if (this.datepicker) { this.datepicker.theme = value; } } /** * @return {?} */ get theme() { return this._theme; } /** * @param {?} value * @return {?} */ set isOpen(value) { this._isOpen = value; if (this.datepicker) { this.datepicker.isOpen = value; } } /** * @return {?} */ get isOpen() { return this._isOpen; } /** * @param {?} value * @return {?} */