@angular/material
Version:
Angular Material
952 lines (946 loc) • 115 kB
JavaScript
import { A11yModule } from '@angular/cdk/a11y';
import { Overlay, OverlayConfig, OverlayModule } from '@angular/cdk/overlay';
import { ComponentPortal, PortalModule } from '@angular/cdk/portal';
import { DOCUMENT, CommonModule } from '@angular/common';
import { Injectable, ɵɵdefineInjectable, EventEmitter, Component, ViewEncapsulation, ChangeDetectionStrategy, ElementRef, NgZone, Input, Output, ChangeDetectorRef, Optional, Inject, ViewChild, forwardRef, InjectionToken, ViewContainerRef, Directive, Attribute, ContentChild, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
import { MAT_DATE_FORMATS, DateAdapter, mixinColor } from '@angular/material/core';
import { Subject, Subscription, merge, of } from 'rxjs';
import { SPACE, ENTER, PAGE_DOWN, PAGE_UP, END, HOME, DOWN_ARROW, UP_ARROW, RIGHT_ARROW, LEFT_ARROW, ESCAPE } from '@angular/cdk/keycodes';
import { Directionality } from '@angular/cdk/bidi';
import { take, startWith, filter } from 'rxjs/operators';
import { __extends } from 'tslib';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { trigger, state, style, transition, animate } from '@angular/animations';
import { NG_VALUE_ACCESSOR, NG_VALIDATORS, Validators } from '@angular/forms';
import { MatFormField } from '@angular/material/form-field';
import { MAT_INPUT_VALUE_ACCESSOR } from '@angular/material/input';
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/** @docs-private */
function createMissingDateImplError(provider) {
return Error("MatDatepicker: No provider found for " + provider + ". You must import one of the following " +
"modules at your application root: MatNativeDateModule, MatMomentDateModule, or provide a " +
"custom implementation.");
}
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/** Datepicker data that requires internationalization. */
var MatDatepickerIntl = /** @class */ (function () {
function MatDatepickerIntl() {
/**
* Stream that emits whenever the labels here are changed. Use this to notify
* components if the labels have changed after initialization.
*/
this.changes = new Subject();
/** A label for the calendar popup (used by screen readers). */
this.calendarLabel = 'Calendar';
/** A label for the button used to open the calendar popup (used by screen readers). */
this.openCalendarLabel = 'Open calendar';
/** A label for the previous month button (used by screen readers). */
this.prevMonthLabel = 'Previous month';
/** A label for the next month button (used by screen readers). */
this.nextMonthLabel = 'Next month';
/** A label for the previous year button (used by screen readers). */
this.prevYearLabel = 'Previous year';
/** A label for the next year button (used by screen readers). */
this.nextYearLabel = 'Next year';
/** A label for the previous multi-year button (used by screen readers). */
this.prevMultiYearLabel = 'Previous 20 years';
/** A label for the next multi-year button (used by screen readers). */
this.nextMultiYearLabel = 'Next 20 years';
/** A label for the 'switch to month view' button (used by screen readers). */
this.switchToMonthViewLabel = 'Choose date';
/** A label for the 'switch to year view' button (used by screen readers). */
this.switchToMultiYearViewLabel = 'Choose month and year';
}
/** Formats a range of years. */
MatDatepickerIntl.prototype.formatYearRange = function (start, end) {
return start + " \u2013 " + end;
};
MatDatepickerIntl.decorators = [
{ type: Injectable, args: [{ providedIn: 'root' },] }
];
MatDatepickerIntl.ɵprov = ɵɵdefineInjectable({ factory: function MatDatepickerIntl_Factory() { return new MatDatepickerIntl(); }, token: MatDatepickerIntl, providedIn: "root" });
return MatDatepickerIntl;
}());
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* An internal class that represents the data corresponding to a single calendar cell.
* @docs-private
*/
var MatCalendarCell = /** @class */ (function () {
function MatCalendarCell(value, displayValue, ariaLabel, enabled, cssClasses) {
if (cssClasses === void 0) { cssClasses = {}; }
this.value = value;
this.displayValue = displayValue;
this.ariaLabel = ariaLabel;
this.enabled = enabled;
this.cssClasses = cssClasses;
}
return MatCalendarCell;
}());
/**
* An internal component used to display calendar data in a table.
* @docs-private
*/
var MatCalendarBody = /** @class */ (function () {
function MatCalendarBody(_elementRef, _ngZone) {
this._elementRef = _elementRef;
this._ngZone = _ngZone;
/** The number of columns in the table. */
this.numCols = 7;
/** The cell number of the active cell in the table. */
this.activeCell = 0;
/**
* The aspect ratio (width / height) to use for the cells in the table. This aspect ratio will be
* maintained even as the table resizes.
*/
this.cellAspectRatio = 1;
/** Emits when a new value is selected. */
this.selectedValueChange = new EventEmitter();
}
MatCalendarBody.prototype._cellClicked = function (cell) {
if (cell.enabled) {
this.selectedValueChange.emit(cell.value);
}
};
MatCalendarBody.prototype.ngOnChanges = function (changes) {
var columnChanges = changes['numCols'];
var _a = this, rows = _a.rows, numCols = _a.numCols;
if (changes['rows'] || columnChanges) {
this._firstRowOffset = rows && rows.length && rows[0].length ? numCols - rows[0].length : 0;
}
if (changes['cellAspectRatio'] || columnChanges || !this._cellPadding) {
this._cellPadding = 50 * this.cellAspectRatio / numCols + "%";
}
if (columnChanges || !this._cellWidth) {
this._cellWidth = 100 / numCols + "%";
}
};
MatCalendarBody.prototype._isActiveCell = function (rowIndex, colIndex) {
var cellNumber = rowIndex * this.numCols + colIndex;
// Account for the fact that the first row may not have as many cells.
if (rowIndex) {
cellNumber -= this._firstRowOffset;
}
return cellNumber == this.activeCell;
};
/** Focuses the active cell after the microtask queue is empty. */
MatCalendarBody.prototype._focusActiveCell = function () {
var _this = this;
this._ngZone.runOutsideAngular(function () {
_this._ngZone.onStable.asObservable().pipe(take(1)).subscribe(function () {
var activeCell = _this._elementRef.nativeElement.querySelector('.mat-calendar-body-active');
if (activeCell) {
activeCell.focus();
}
});
});
};
MatCalendarBody.decorators = [
{ type: Component, args: [{
selector: '[mat-calendar-body]',
template: "<!--\n If there's not enough space in the first row, create a separate label row. We mark this row as\n aria-hidden because we don't want it to be read out as one of the weeks in the month.\n-->\n<tr *ngIf=\"_firstRowOffset < labelMinRequiredCells\" aria-hidden=\"true\">\n <td class=\"mat-calendar-body-label\"\n [attr.colspan]=\"numCols\"\n [style.paddingTop]=\"_cellPadding\"\n [style.paddingBottom]=\"_cellPadding\">\n {{label}}\n </td>\n</tr>\n\n<!-- Create the first row separately so we can include a special spacer cell. -->\n<tr *ngFor=\"let row of rows; let rowIndex = index\" role=\"row\">\n <!--\n We mark this cell as aria-hidden so it doesn't get read out as one of the days in the week.\n The aspect ratio of the table cells is maintained by setting the top and bottom padding as a\n percentage of the width (a variant of the trick described here:\n https://www.w3schools.com/howto/howto_css_aspect_ratio.asp).\n -->\n <td *ngIf=\"rowIndex === 0 && _firstRowOffset\"\n aria-hidden=\"true\"\n class=\"mat-calendar-body-label\"\n [attr.colspan]=\"_firstRowOffset\"\n [style.paddingTop]=\"_cellPadding\"\n [style.paddingBottom]=\"_cellPadding\">\n {{_firstRowOffset >= labelMinRequiredCells ? label : ''}}\n </td>\n <td *ngFor=\"let item of row; let colIndex = index\"\n role=\"gridcell\"\n class=\"mat-calendar-body-cell\"\n [ngClass]=\"item.cssClasses\"\n [tabindex]=\"_isActiveCell(rowIndex, colIndex) ? 0 : -1\"\n [class.mat-calendar-body-disabled]=\"!item.enabled\"\n [class.mat-calendar-body-active]=\"_isActiveCell(rowIndex, colIndex)\"\n [attr.aria-label]=\"item.ariaLabel\"\n [attr.aria-disabled]=\"!item.enabled || null\"\n [attr.aria-selected]=\"selectedValue === item.value\"\n (click)=\"_cellClicked(item)\"\n [style.width]=\"_cellWidth\"\n [style.paddingTop]=\"_cellPadding\"\n role=\"button\"\n [style.paddingBottom]=\"_cellPadding\">\n <div class=\"mat-calendar-body-cell-content\"\n [class.mat-calendar-body-selected]=\"selectedValue === item.value\"\n [class.mat-calendar-body-today]=\"todayValue === item.value\">\n {{item.displayValue}}\n </div>\n </td>\n</tr>\n",
host: {
'class': 'mat-calendar-body',
'role': 'grid',
'aria-readonly': 'true'
},
exportAs: 'matCalendarBody',
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
styles: [".mat-calendar-body{min-width:224px}.mat-calendar-body-label{height:0;line-height:0;text-align:left;padding-left:4.7142857143%;padding-right:4.7142857143%}.mat-calendar-body-cell{position:relative;height:0;line-height:0;text-align:center;outline:none;cursor:pointer}.mat-calendar-body-disabled{cursor:default}.mat-calendar-body-cell-content{position:absolute;top:5%;left:5%;display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:90%;height:90%;line-height:1;border-width:1px;border-style:solid;border-radius:999px}.cdk-high-contrast-active .mat-calendar-body-cell-content{border:none}.cdk-high-contrast-active .mat-datepicker-popup:not(:empty),.cdk-high-contrast-active .mat-calendar-body-selected{outline:solid 1px}.cdk-high-contrast-active .mat-calendar-body-today{outline:dotted 1px}.cdk-high-contrast-active .cdk-keyboard-focused .mat-calendar-body-active>.mat-calendar-body-cell-content:not(.mat-calendar-body-selected),.cdk-high-contrast-active .cdk-program-focused .mat-calendar-body-active>.mat-calendar-body-cell-content:not(.mat-calendar-body-selected){outline:dotted 2px}[dir=rtl] .mat-calendar-body-label{text-align:right}\n"]
}] }
];
/** @nocollapse */
MatCalendarBody.ctorParameters = function () { return [
{ type: ElementRef },
{ type: NgZone }
]; };
MatCalendarBody.propDecorators = {
label: [{ type: Input }],
rows: [{ type: Input }],
todayValue: [{ type: Input }],
selectedValue: [{ type: Input }],
labelMinRequiredCells: [{ type: Input }],
numCols: [{ type: Input }],
activeCell: [{ type: Input }],
cellAspectRatio: [{ type: Input }],
selectedValueChange: [{ type: Output }]
};
return MatCalendarBody;
}());
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var DAYS_PER_WEEK = 7;
/**
* An internal component used to display a single month in the datepicker.
* @docs-private
*/
var MatMonthView = /** @class */ (function () {
function MatMonthView(_changeDetectorRef, _dateFormats, _dateAdapter, _dir) {
this._changeDetectorRef = _changeDetectorRef;
this._dateFormats = _dateFormats;
this._dateAdapter = _dateAdapter;
this._dir = _dir;
this._rerenderSubscription = Subscription.EMPTY;
/** Emits when a new date is selected. */
this.selectedChange = new EventEmitter();
/** Emits when any date is selected. */
this._userSelection = new EventEmitter();
/** Emits when any date is activated. */
this.activeDateChange = new EventEmitter();
if (!this._dateAdapter) {
throw createMissingDateImplError('DateAdapter');
}
if (!this._dateFormats) {
throw createMissingDateImplError('MAT_DATE_FORMATS');
}
this._activeDate = this._dateAdapter.today();
}
Object.defineProperty(MatMonthView.prototype, "activeDate", {
/**
* The date to display in this month view (everything other than the month and year is ignored).
*/
get: function () { return this._activeDate; },
set: function (value) {
var oldActiveDate = this._activeDate;
var validDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value)) || this._dateAdapter.today();
this._activeDate = this._dateAdapter.clampDate(validDate, this.minDate, this.maxDate);
if (!this._hasSameMonthAndYear(oldActiveDate, this._activeDate)) {
this._init();
}
},
enumerable: true,
configurable: true
});
Object.defineProperty(MatMonthView.prototype, "selected", {
/** The currently selected date. */
get: function () { return this._selected; },
set: function (value) {
this._selected = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
this._selectedDate = this._getDateInCurrentMonth(this._selected);
},
enumerable: true,
configurable: true
});
Object.defineProperty(MatMonthView.prototype, "minDate", {
/** The minimum selectable date. */
get: function () { return this._minDate; },
set: function (value) {
this._minDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
},
enumerable: true,
configurable: true
});
Object.defineProperty(MatMonthView.prototype, "maxDate", {
/** The maximum selectable date. */
get: function () { return this._maxDate; },
set: function (value) {
this._maxDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
},
enumerable: true,
configurable: true
});
MatMonthView.prototype.ngAfterContentInit = function () {
var _this = this;
this._rerenderSubscription = this._dateAdapter.localeChanges
.pipe(startWith(null))
.subscribe(function () { return _this._init(); });
};
MatMonthView.prototype.ngOnDestroy = function () {
this._rerenderSubscription.unsubscribe();
};
/** Handles when a new date is selected. */
MatMonthView.prototype._dateSelected = function (date) {
if (this._selectedDate != date) {
var selectedYear = this._dateAdapter.getYear(this.activeDate);
var selectedMonth = this._dateAdapter.getMonth(this.activeDate);
var selectedDate = this._dateAdapter.createDate(selectedYear, selectedMonth, date);
this.selectedChange.emit(selectedDate);
}
this._userSelection.emit();
};
/** Handles keydown events on the calendar body when calendar is in month view. */
MatMonthView.prototype._handleCalendarBodyKeydown = function (event) {
// TODO(mmalerba): We currently allow keyboard navigation to disabled dates, but just prevent
// disabled ones from being selected. This may not be ideal, we should look into whether
// navigation should skip over disabled dates, and if so, how to implement that efficiently.
var oldActiveDate = this._activeDate;
var isRtl = this._isRtl();
switch (event.keyCode) {
case LEFT_ARROW:
this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, isRtl ? 1 : -1);
break;
case RIGHT_ARROW:
this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, isRtl ? -1 : 1);
break;
case UP_ARROW:
this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, -7);
break;
case DOWN_ARROW:
this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, 7);
break;
case HOME:
this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, 1 - this._dateAdapter.getDate(this._activeDate));
break;
case END:
this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, (this._dateAdapter.getNumDaysInMonth(this._activeDate) -
this._dateAdapter.getDate(this._activeDate)));
break;
case PAGE_UP:
this.activeDate = event.altKey ?
this._dateAdapter.addCalendarYears(this._activeDate, -1) :
this._dateAdapter.addCalendarMonths(this._activeDate, -1);
break;
case PAGE_DOWN:
this.activeDate = event.altKey ?
this._dateAdapter.addCalendarYears(this._activeDate, 1) :
this._dateAdapter.addCalendarMonths(this._activeDate, 1);
break;
case ENTER:
case SPACE:
if (!this.dateFilter || this.dateFilter(this._activeDate)) {
this._dateSelected(this._dateAdapter.getDate(this._activeDate));
this._userSelection.emit();
// Prevent unexpected default actions such as form submission.
event.preventDefault();
}
return;
default:
// Don't prevent default or focus active cell on keys that we don't explicitly handle.
return;
}
if (this._dateAdapter.compareDate(oldActiveDate, this.activeDate)) {
this.activeDateChange.emit(this.activeDate);
}
this._focusActiveCell();
// Prevent unexpected default actions such as form submission.
event.preventDefault();
};
/** Initializes this month view. */
MatMonthView.prototype._init = function () {
this._selectedDate = this._getDateInCurrentMonth(this.selected);
this._todayDate = this._getDateInCurrentMonth(this._dateAdapter.today());
this._monthLabel =
this._dateAdapter.getMonthNames('short')[this._dateAdapter.getMonth(this.activeDate)]
.toLocaleUpperCase();
var firstOfMonth = this._dateAdapter.createDate(this._dateAdapter.getYear(this.activeDate), this._dateAdapter.getMonth(this.activeDate), 1);
this._firstWeekOffset =
(DAYS_PER_WEEK + this._dateAdapter.getDayOfWeek(firstOfMonth) -
this._dateAdapter.getFirstDayOfWeek()) % DAYS_PER_WEEK;
this._initWeekdays();
this._createWeekCells();
this._changeDetectorRef.markForCheck();
};
/** Focuses the active cell after the microtask queue is empty. */
MatMonthView.prototype._focusActiveCell = function () {
this._matCalendarBody._focusActiveCell();
};
/** Initializes the weekdays. */
MatMonthView.prototype._initWeekdays = function () {
var firstDayOfWeek = this._dateAdapter.getFirstDayOfWeek();
var narrowWeekdays = this._dateAdapter.getDayOfWeekNames('narrow');
var longWeekdays = this._dateAdapter.getDayOfWeekNames('long');
// Rotate the labels for days of the week based on the configured first day of the week.
var weekdays = longWeekdays.map(function (long, i) {
return { long: long, narrow: narrowWeekdays[i] };
});
this._weekdays = weekdays.slice(firstDayOfWeek).concat(weekdays.slice(0, firstDayOfWeek));
};
/** Creates MatCalendarCells for the dates in this month. */
MatMonthView.prototype._createWeekCells = function () {
var daysInMonth = this._dateAdapter.getNumDaysInMonth(this.activeDate);
var dateNames = this._dateAdapter.getDateNames();
this._weeks = [[]];
for (var i = 0, cell = this._firstWeekOffset; i < daysInMonth; i++, cell++) {
if (cell == DAYS_PER_WEEK) {
this._weeks.push([]);
cell = 0;
}
var date = this._dateAdapter.createDate(this._dateAdapter.getYear(this.activeDate), this._dateAdapter.getMonth(this.activeDate), i + 1);
var enabled = this._shouldEnableDate(date);
var ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.dateA11yLabel);
var cellClasses = this.dateClass ? this.dateClass(date) : undefined;
this._weeks[this._weeks.length - 1]
.push(new MatCalendarCell(i + 1, dateNames[i], ariaLabel, enabled, cellClasses));
}
};
/** Date filter for the month */
MatMonthView.prototype._shouldEnableDate = function (date) {
return !!date &&
(!this.dateFilter || this.dateFilter(date)) &&
(!this.minDate || this._dateAdapter.compareDate(date, this.minDate) >= 0) &&
(!this.maxDate || this._dateAdapter.compareDate(date, this.maxDate) <= 0);
};
/**
* Gets the date in this month that the given Date falls on.
* Returns null if the given Date is in another month.
*/
MatMonthView.prototype._getDateInCurrentMonth = function (date) {
return date && this._hasSameMonthAndYear(date, this.activeDate) ?
this._dateAdapter.getDate(date) : null;
};
/** Checks whether the 2 dates are non-null and fall within the same month of the same year. */
MatMonthView.prototype._hasSameMonthAndYear = function (d1, d2) {
return !!(d1 && d2 && this._dateAdapter.getMonth(d1) == this._dateAdapter.getMonth(d2) &&
this._dateAdapter.getYear(d1) == this._dateAdapter.getYear(d2));
};
/**
* @param obj The object to check.
* @returns The given object if it is both a date instance and valid, otherwise null.
*/
MatMonthView.prototype._getValidDateOrNull = function (obj) {
return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null;
};
/** Determines whether the user has the RTL layout direction. */
MatMonthView.prototype._isRtl = function () {
return this._dir && this._dir.value === 'rtl';
};
MatMonthView.decorators = [
{ type: Component, args: [{
selector: 'mat-month-view',
template: "<table class=\"mat-calendar-table\" role=\"presentation\">\n <thead class=\"mat-calendar-table-header\">\n <tr>\n <th scope=\"col\" *ngFor=\"let day of _weekdays\" [attr.aria-label]=\"day.long\">{{day.narrow}}</th>\n </tr>\n <tr><th class=\"mat-calendar-table-header-divider\" colspan=\"7\" aria-hidden=\"true\"></th></tr>\n </thead>\n <tbody mat-calendar-body\n [label]=\"_monthLabel\"\n [rows]=\"_weeks\"\n [todayValue]=\"_todayDate!\"\n [selectedValue]=\"_selectedDate!\"\n [labelMinRequiredCells]=\"3\"\n [activeCell]=\"_dateAdapter.getDate(activeDate) - 1\"\n (selectedValueChange)=\"_dateSelected($event)\"\n (keydown)=\"_handleCalendarBodyKeydown($event)\">\n </tbody>\n</table>\n",
exportAs: 'matMonthView',
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
}] }
];
/** @nocollapse */
MatMonthView.ctorParameters = function () { return [
{ type: ChangeDetectorRef },
{ type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_DATE_FORMATS,] }] },
{ type: DateAdapter, decorators: [{ type: Optional }] },
{ type: Directionality, decorators: [{ type: Optional }] }
]; };
MatMonthView.propDecorators = {
activeDate: [{ type: Input }],
selected: [{ type: Input }],
minDate: [{ type: Input }],
maxDate: [{ type: Input }],
dateFilter: [{ type: Input }],
dateClass: [{ type: Input }],
selectedChange: [{ type: Output }],
_userSelection: [{ type: Output }],
activeDateChange: [{ type: Output }],
_matCalendarBody: [{ type: ViewChild, args: [MatCalendarBody,] }]
};
return MatMonthView;
}());
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var yearsPerPage = 24;
var yearsPerRow = 4;
/**
* An internal component used to display a year selector in the datepicker.
* @docs-private
*/
var MatMultiYearView = /** @class */ (function () {
function MatMultiYearView(_changeDetectorRef, _dateAdapter, _dir) {
this._changeDetectorRef = _changeDetectorRef;
this._dateAdapter = _dateAdapter;
this._dir = _dir;
this._rerenderSubscription = Subscription.EMPTY;
/** Emits when a new year is selected. */
this.selectedChange = new EventEmitter();
/** Emits the selected year. This doesn't imply a change on the selected date */
this.yearSelected = new EventEmitter();
/** Emits when any date is activated. */
this.activeDateChange = new EventEmitter();
if (!this._dateAdapter) {
throw createMissingDateImplError('DateAdapter');
}
this._activeDate = this._dateAdapter.today();
}
Object.defineProperty(MatMultiYearView.prototype, "activeDate", {
/** The date to display in this multi-year view (everything other than the year is ignored). */
get: function () { return this._activeDate; },
set: function (value) {
var oldActiveDate = this._activeDate;
var validDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value)) || this._dateAdapter.today();
this._activeDate = this._dateAdapter.clampDate(validDate, this.minDate, this.maxDate);
if (!isSameMultiYearView(this._dateAdapter, oldActiveDate, this._activeDate, this.minDate, this.maxDate)) {
this._init();
}
},
enumerable: true,
configurable: true
});
Object.defineProperty(MatMultiYearView.prototype, "selected", {
/** The currently selected date. */
get: function () { return this._selected; },
set: function (value) {
this._selected = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
this._selectedYear = this._selected && this._dateAdapter.getYear(this._selected);
},
enumerable: true,
configurable: true
});
Object.defineProperty(MatMultiYearView.prototype, "minDate", {
/** The minimum selectable date. */
get: function () { return this._minDate; },
set: function (value) {
this._minDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
},
enumerable: true,
configurable: true
});
Object.defineProperty(MatMultiYearView.prototype, "maxDate", {
/** The maximum selectable date. */
get: function () { return this._maxDate; },
set: function (value) {
this._maxDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
},
enumerable: true,
configurable: true
});
MatMultiYearView.prototype.ngAfterContentInit = function () {
var _this = this;
this._rerenderSubscription = this._dateAdapter.localeChanges
.pipe(startWith(null))
.subscribe(function () { return _this._init(); });
};
MatMultiYearView.prototype.ngOnDestroy = function () {
this._rerenderSubscription.unsubscribe();
};
/** Initializes this multi-year view. */
MatMultiYearView.prototype._init = function () {
var _this = this;
this._todayYear = this._dateAdapter.getYear(this._dateAdapter.today());
// We want a range years such that we maximize the number of
// enabled dates visible at once. This prevents issues where the minimum year
// is the last item of a page OR the maximum year is the first item of a page.
// The offset from the active year to the "slot" for the starting year is the
// *actual* first rendered year in the multi-year view.
var activeYear = this._dateAdapter.getYear(this._activeDate);
var minYearOfPage = activeYear - getActiveOffset(this._dateAdapter, this.activeDate, this.minDate, this.maxDate);
this._years = [];
for (var i = 0, row = []; i < yearsPerPage; i++) {
row.push(minYearOfPage + i);
if (row.length == yearsPerRow) {
this._years.push(row.map(function (year) { return _this._createCellForYear(year); }));
row = [];
}
}
this._changeDetectorRef.markForCheck();
};
/** Handles when a new year is selected. */
MatMultiYearView.prototype._yearSelected = function (year) {
this.yearSelected.emit(this._dateAdapter.createDate(year, 0, 1));
var month = this._dateAdapter.getMonth(this.activeDate);
var daysInMonth = this._dateAdapter.getNumDaysInMonth(this._dateAdapter.createDate(year, month, 1));
this.selectedChange.emit(this._dateAdapter.createDate(year, month, Math.min(this._dateAdapter.getDate(this.activeDate), daysInMonth)));
};
/** Handles keydown events on the calendar body when calendar is in multi-year view. */
MatMultiYearView.prototype._handleCalendarBodyKeydown = function (event) {
var oldActiveDate = this._activeDate;
var isRtl = this._isRtl();
switch (event.keyCode) {
case LEFT_ARROW:
this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, isRtl ? 1 : -1);
break;
case RIGHT_ARROW:
this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, isRtl ? -1 : 1);
break;
case UP_ARROW:
this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, -yearsPerRow);
break;
case DOWN_ARROW:
this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, yearsPerRow);
break;
case HOME:
this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, -getActiveOffset(this._dateAdapter, this.activeDate, this.minDate, this.maxDate));
break;
case END:
this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, yearsPerPage - getActiveOffset(this._dateAdapter, this.activeDate, this.minDate, this.maxDate) - 1);
break;
case PAGE_UP:
this.activeDate =
this._dateAdapter.addCalendarYears(this._activeDate, event.altKey ? -yearsPerPage * 10 : -yearsPerPage);
break;
case PAGE_DOWN:
this.activeDate =
this._dateAdapter.addCalendarYears(this._activeDate, event.altKey ? yearsPerPage * 10 : yearsPerPage);
break;
case ENTER:
case SPACE:
this._yearSelected(this._dateAdapter.getYear(this._activeDate));
break;
default:
// Don't prevent default or focus active cell on keys that we don't explicitly handle.
return;
}
if (this._dateAdapter.compareDate(oldActiveDate, this.activeDate)) {
this.activeDateChange.emit(this.activeDate);
}
this._focusActiveCell();
// Prevent unexpected default actions such as form submission.
event.preventDefault();
};
MatMultiYearView.prototype._getActiveCell = function () {
return getActiveOffset(this._dateAdapter, this.activeDate, this.minDate, this.maxDate);
};
/** Focuses the active cell after the microtask queue is empty. */
MatMultiYearView.prototype._focusActiveCell = function () {
this._matCalendarBody._focusActiveCell();
};
/** Creates an MatCalendarCell for the given year. */
MatMultiYearView.prototype._createCellForYear = function (year) {
var yearName = this._dateAdapter.getYearName(this._dateAdapter.createDate(year, 0, 1));
return new MatCalendarCell(year, yearName, yearName, this._shouldEnableYear(year));
};
/** Whether the given year is enabled. */
MatMultiYearView.prototype._shouldEnableYear = function (year) {
// disable if the year is greater than maxDate lower than minDate
if (year === undefined || year === null ||
(this.maxDate && year > this._dateAdapter.getYear(this.maxDate)) ||
(this.minDate && year < this._dateAdapter.getYear(this.minDate))) {
return false;
}
// enable if it reaches here and there's no filter defined
if (!this.dateFilter) {
return true;
}
var firstOfYear = this._dateAdapter.createDate(year, 0, 1);
// If any date in the year is enabled count the year as enabled.
for (var date = firstOfYear; this._dateAdapter.getYear(date) == year; date = this._dateAdapter.addCalendarDays(date, 1)) {
if (this.dateFilter(date)) {
return true;
}
}
return false;
};
/**
* @param obj The object to check.
* @returns The given object if it is both a date instance and valid, otherwise null.
*/
MatMultiYearView.prototype._getValidDateOrNull = function (obj) {
return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null;
};
/** Determines whether the user has the RTL layout direction. */
MatMultiYearView.prototype._isRtl = function () {
return this._dir && this._dir.value === 'rtl';
};
MatMultiYearView.decorators = [
{ type: Component, args: [{
selector: 'mat-multi-year-view',
template: "<table class=\"mat-calendar-table\" role=\"presentation\">\n <thead class=\"mat-calendar-table-header\">\n <tr><th class=\"mat-calendar-table-header-divider\" colspan=\"4\"></th></tr>\n </thead>\n <tbody mat-calendar-body\n [rows]=\"_years\"\n [todayValue]=\"_todayYear\"\n [selectedValue]=\"_selectedYear!\"\n [numCols]=\"4\"\n [cellAspectRatio]=\"4 / 7\"\n [activeCell]=\"_getActiveCell()\"\n (selectedValueChange)=\"_yearSelected($event)\"\n (keydown)=\"_handleCalendarBodyKeydown($event)\">\n </tbody>\n</table>\n",
exportAs: 'matMultiYearView',
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
}] }
];
/** @nocollapse */
MatMultiYearView.ctorParameters = function () { return [
{ type: ChangeDetectorRef },
{ type: DateAdapter, decorators: [{ type: Optional }] },
{ type: Directionality, decorators: [{ type: Optional }] }
]; };
MatMultiYearView.propDecorators = {
activeDate: [{ type: Input }],
selected: [{ type: Input }],
minDate: [{ type: Input }],
maxDate: [{ type: Input }],
dateFilter: [{ type: Input }],
selectedChange: [{ type: Output }],
yearSelected: [{ type: Output }],
activeDateChange: [{ type: Output }],
_matCalendarBody: [{ type: ViewChild, args: [MatCalendarBody,] }]
};
return MatMultiYearView;
}());
function isSameMultiYearView(dateAdapter, date1, date2, minDate, maxDate) {
var year1 = dateAdapter.getYear(date1);
var year2 = dateAdapter.getYear(date2);
var startingYear = getStartingYear(dateAdapter, minDate, maxDate);
return Math.floor((year1 - startingYear) / yearsPerPage) ===
Math.floor((year2 - startingYear) / yearsPerPage);
}
/**
* When the multi-year view is first opened, the active year will be in view.
* So we compute how many years are between the active year and the *slot* where our
* "startingYear" will render when paged into view.
*/
function getActiveOffset(dateAdapter, activeDate, minDate, maxDate) {
var activeYear = dateAdapter.getYear(activeDate);
return euclideanModulo((activeYear - getStartingYear(dateAdapter, minDate, maxDate)), yearsPerPage);
}
/**
* We pick a "starting" year such that either the maximum year would be at the end
* or the minimum year would be at the beginning of a page.
*/
function getStartingYear(dateAdapter, minDate, maxDate) {
var startingYear = 0;
if (maxDate) {
var maxYear = dateAdapter.getYear(maxDate);
startingYear = maxYear - yearsPerPage + 1;
}
else if (minDate) {
startingYear = dateAdapter.getYear(minDate);
}
return startingYear;
}
/** Gets remainder that is non-negative, even if first number is negative */
function euclideanModulo(a, b) {
return (a % b + b) % b;
}
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* An internal component used to display a single year in the datepicker.
* @docs-private
*/
var MatYearView = /** @class */ (function () {
function MatYearView(_changeDetectorRef, _dateFormats, _dateAdapter, _dir) {
this._changeDetectorRef = _changeDetectorRef;
this._dateFormats = _dateFormats;
this._dateAdapter = _dateAdapter;
this._dir = _dir;
this._rerenderSubscription = Subscription.EMPTY;
/** Emits when a new month is selected. */
this.selectedChange = new EventEmitter();
/** Emits the selected month. This doesn't imply a change on the selected date */
this.monthSelected = new EventEmitter();
/** Emits when any date is activated. */
this.activeDateChange = new EventEmitter();
if (!this._dateAdapter) {
throw createMissingDateImplError('DateAdapter');
}
if (!this._dateFormats) {
throw createMissingDateImplError('MAT_DATE_FORMATS');
}
this._activeDate = this._dateAdapter.today();
}
Object.defineProperty(MatYearView.prototype, "activeDate", {
/** The date to display in this year view (everything other than the year is ignored). */
get: function () { return this._activeDate; },
set: function (value) {
var oldActiveDate = this._activeDate;
var validDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value)) || this._dateAdapter.today();
this._activeDate = this._dateAdapter.clampDate(validDate, this.minDate, this.maxDate);
if (this._dateAdapter.getYear(oldActiveDate) !== this._dateAdapter.getYear(this._activeDate)) {
this._init();
}
},
enumerable: true,
configurable: true
});
Object.defineProperty(MatYearView.prototype, "selected", {
/** The currently selected date. */
get: function () { return this._selected; },
set: function (value) {
this._selected = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
this._selectedMonth = this._getMonthInCurrentYear(this._selected);
},
enumerable: true,
configurable: true
});
Object.defineProperty(MatYearView.prototype, "minDate", {
/** The minimum selectable date. */
get: function () { return this._minDate; },
set: function (value) {
this._minDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
},
enumerable: true,
configurable: true
});
Object.defineProperty(MatYearView.prototype, "maxDate", {
/** The maximum selectable date. */
get: function () { return this._maxDate; },
set: function (value) {
this._maxDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
},
enumerable: true,
configurable: true
});
MatYearView.prototype.ngAfterContentInit = function () {
var _this = this;
this._rerenderSubscription = this._dateAdapter.localeChanges
.pipe(startWith(null))
.subscribe(function () { return _this._init(); });
};
MatYearView.prototype.ngOnDestroy = function () {
this._rerenderSubscription.unsubscribe();
};
/** Handles when a new month is selected. */
MatYearView.prototype._monthSelected = function (month) {
var normalizedDate = this._dateAdapter.createDate(this._dateAdapter.getYear(this.activeDate), month, 1);
this.monthSelected.emit(normalizedDate);
var daysInMonth = this._dateAdapter.getNumDaysInMonth(normalizedDate);
this.selectedChange.emit(this._dateAdapter.createDate(this._dateAdapter.getYear(this.activeDate), month, Math.min(this._dateAdapter.getDate(this.activeDate), daysInMonth)));
};
/** Handles keydown events on the calendar body when calendar is in year view. */
MatYearView.prototype._handleCalendarBodyKeydown = function (event) {
// TODO(mmalerba): We currently allow keyboard navigation to disabled dates, but just prevent
// disabled ones from being selected. This may not be ideal, we should look into whether
// navigation should skip over disabled dates, and if so, how to implement that efficiently.
var oldActiveDate = this._activeDate;
var isRtl = this._isRtl();
switch (event.keyCode) {
case LEFT_ARROW:
this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, isRtl ? 1 : -1);
break;
case RIGHT_ARROW:
this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, isRtl ? -1 : 1);
break;
case UP_ARROW:
this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, -4);
break;
case DOWN_ARROW:
this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, 4);
break;
case HOME:
this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, -this._dateAdapter.getMonth(this._activeDate));
break;
case END:
this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, 11 - this._dateAdapter.getMonth(this._activeDate));
break;
case PAGE_UP:
this.activeDate =
this._dateAdapter.addCalendarYears(this._activeDate, event.altKey ? -10 : -1);
break;
case PAGE_DOWN:
this.activeDate =
this._dateAdapter.addCalendarYears(this._activeDate, event.altKey ? 10 : 1);
break;
case ENTER:
case SPACE:
this._monthSelected(this._dateAdapter.getMonth(this._activeDate));
break;
default:
// Don't prevent default or focus active cell on keys that we don't explicitly handle.
return;
}
if (this._dateAdapter.compareDate(oldActiveDate, this.activeDate)) {
this.activeDateChange.emit(this.activeDate);
}
this._focusActiveCell();
// Prevent unexpected default actions such as form submission.
event.preventDefault();
};
/** Initializes this year view. */
MatYearView.prototype._init = function () {
var _this = this;
this._selectedMonth = this._getMonthInCurrentYear(this.selected);
this._todayMonth = this._getMonthInCurrentYear(this._dateAdapter.today());
this._yearLabel = this._dateAdapter.getYearName(this.activeDate);
var monthNames = this._dateAdapter.getMonthNames('short');
// First row of months only contains 5 elements so we can fit the year label on the same row.
this._months = [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]].map(function (row) { return row.map(function (month) { return _this._createCellForMonth(month, monthNames[month]); }); });
this._changeDetectorRef.markForCheck();
};
/** Focuses the active cell after the microtask queue is empty. */
MatYearView.prototype._focusActiveCell = function () {
this._matCalendarBody._focusActiveCell();
};
/**
* Gets the month in this year that the given Date falls on.
* Returns null if the given Date is in another year.
*/
MatYearView.prototype._getMonthInCurrentYear = function (date) {
return date && this._dateAdapter.getYear(date) == this._dateAdapter.getYear(this.activeDate) ?
this._dateAdapter.getMonth(date) : null;
};
/** Creates an MatCalendarCell for the given month. */
MatYearView.prototype._createCellForMonth = function (month, monthName) {
var ariaLabel = this._dateAdapter.format(this._dateAdapter.createDate(this._dateAdapter.getYear(this.activeDate), month, 1), this._dateFormats.display.monthYearA11yLabel);
return new MatCalendarCell(month, monthName.toLocaleUpperCase(), ariaLabel, this._shouldEnableMonth(month));
};
/** Whether the given month is enabled. */
MatYearView.prototype._shouldEnableMonth = function (month) {
var activeYear = this._dateAdapter.getYear(this.activeDate);
if (month === undefined || month === null ||
this._isYearAndMonthAfterMaxDate(activeYear, month) ||
this._isYearAndMonthBeforeMinDate(activeYear, month)) {
return false;
}
if (!this.dateFilter) {
return true;
}
var firstOfMonth = this._dateAdapter.createDate(activeYear, month, 1);
// If any date in the month is enabled count the month as enabled.
for (var date = firstOfMonth; this._dateAdapter.getMonth(date) == month; date = this._dateAdapter.addCalendarDays(date, 1)) {
if (this.dateFilter(date)) {
return true;
}
}
return false;
};
/**
* Tests whether the combination month/year is after this.maxDate, considering
* just the month and year of this.maxDate
*/
MatYearView.prototype._isYearAndMonthAfterMaxDate = function (year, month) {
if (this.maxDate) {
var maxYear = this._dateAdapter.getYear(this.maxDate);
var maxMonth = this._dateAdapter.getMonth(this.maxDate);
return year > maxYear || (year === maxYear && month > maxMonth);
}
return false;
};
/**
* Tests whether the combination month/year is before this.minDate, considering
* just the month and year of this.minDate
*/
MatYearView.prototype._isYearAndMonthBeforeMinDate = function (year, month) {
if (this.minDate) {
var minYear = this._dateAdapter.getYear(this.minDate);
var minMonth = this._dateAdapter.getMonth(this.minDate);
return year < minYear || (year === minYear && month < minMonth);
}
return false;
};
/**
* @param obj The object to check.
* @returns The given object if it is both a date instance and valid, otherwise null.
*/
MatYearView.prototype._getValidDateOrNull = function (obj) {
return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null;
};
/** Determines whether the user has the RTL layout direction. */
MatYearView.prototype._isRtl = function () {
return this._dir && this._dir.value === 'rtl';
};
MatYearView.decorators = [
{ type: Component, args: [{
selector: 'mat-year-view',
template: "<table class=\"mat-calendar-table\" role=\"presentation\">\n <thead class=\"mat-calendar-table-header\">\n <tr><th class=\"mat-calendar-table-header-divider\" colspan=\"4\"></th></tr>\n </thead>\n <tbody mat-calendar-body\n [label]=\"_yearLabel\"\n [rows]=\"_months\"\n [todayValue]=\"_todayMonth!\"\n [selectedValue]=\"_selectedMonth!\"\n [labelMinRequiredCells]=\"2\"\n [numCols]=\"4\"\n [cellAspectRatio]=\"4 / 7\"\n [activeCell]=\"_dateAdapter.getMonth(activeDate)\"\n (selectedValueChange)=\"_monthSelected($event)\"\n (keydown)=\"_handleCalendarBodyKeydown($event)\">\n </tbody>\n</table>\n",
exportAs: 'matYearView',
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
}] }
];
/