ngx-animating-datepicker
Version:
An Animating Datepicker for Angular 2+, for some smooth date picking :).
1,491 lines (1,483 loc) • 185 kB
JavaScript
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 {?}
*/