@angular/material
Version:
Angular Material
1,358 lines (1,352 loc) • 140 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 { 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';
/**
* @fileoverview added by tsickle
* Generated from: src/material/datepicker/datepicker-errors.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @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
* @param {?} provider
* @return {?}
*/
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.`);
}
/**
* @fileoverview added by tsickle
* Generated from: src/material/datepicker/datepicker-intl.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* Datepicker data that requires internationalization.
*/
class MatDatepickerIntl {
constructor() {
/**
* 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.
* @param {?} start
* @param {?} end
* @return {?}
*/
formatYearRange(start, end) {
return `${start} \u2013 ${end}`;
}
}
MatDatepickerIntl.decorators = [
{ type: Injectable, args: [{ providedIn: 'root' },] }
];
/** @nocollapse */ MatDatepickerIntl.ɵprov = ɵɵdefineInjectable({ factory: function MatDatepickerIntl_Factory() { return new MatDatepickerIntl(); }, token: MatDatepickerIntl, providedIn: "root" });
if (false) {
/**
* Stream that emits whenever the labels here are changed. Use this to notify
* components if the labels have changed after initialization.
* @type {?}
*/
MatDatepickerIntl.prototype.changes;
/**
* A label for the calendar popup (used by screen readers).
* @type {?}
*/
MatDatepickerIntl.prototype.calendarLabel;
/**
* A label for the button used to open the calendar popup (used by screen readers).
* @type {?}
*/
MatDatepickerIntl.prototype.openCalendarLabel;
/**
* A label for the previous month button (used by screen readers).
* @type {?}
*/
MatDatepickerIntl.prototype.prevMonthLabel;
/**
* A label for the next month button (used by screen readers).
* @type {?}
*/
MatDatepickerIntl.prototype.nextMonthLabel;
/**
* A label for the previous year button (used by screen readers).
* @type {?}
*/
MatDatepickerIntl.prototype.prevYearLabel;
/**
* A label for the next year button (used by screen readers).
* @type {?}
*/
MatDatepickerIntl.prototype.nextYearLabel;
/**
* A label for the previous multi-year button (used by screen readers).
* @type {?}
*/
MatDatepickerIntl.prototype.prevMultiYearLabel;
/**
* A label for the next multi-year button (used by screen readers).
* @type {?}
*/
MatDatepickerIntl.prototype.nextMultiYearLabel;
/**
* A label for the 'switch to month view' button (used by screen readers).
* @type {?}
*/
MatDatepickerIntl.prototype.switchToMonthViewLabel;
/**
* A label for the 'switch to year view' button (used by screen readers).
* @type {?}
*/
MatDatepickerIntl.prototype.switchToMultiYearViewLabel;
}
/**
* @fileoverview added by tsickle
* Generated from: src/material/datepicker/calendar-body.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* An internal class that represents the data corresponding to a single calendar cell.
* \@docs-private
*/
class MatCalendarCell {
/**
* @param {?} value
* @param {?} displayValue
* @param {?} ariaLabel
* @param {?} enabled
* @param {?=} cssClasses
*/
constructor(value, displayValue, ariaLabel, enabled, cssClasses = {}) {
this.value = value;
this.displayValue = displayValue;
this.ariaLabel = ariaLabel;
this.enabled = enabled;
this.cssClasses = cssClasses;
}
}
if (false) {
/** @type {?} */
MatCalendarCell.prototype.value;
/** @type {?} */
MatCalendarCell.prototype.displayValue;
/** @type {?} */
MatCalendarCell.prototype.ariaLabel;
/** @type {?} */
MatCalendarCell.prototype.enabled;
/** @type {?} */
MatCalendarCell.prototype.cssClasses;
}
/**
* An internal component used to display calendar data in a table.
* \@docs-private
*/
class MatCalendarBody {
/**
* @param {?} _elementRef
* @param {?} _ngZone
*/
constructor(_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();
}
/**
* @param {?} cell
* @return {?}
*/
_cellClicked(cell) {
if (cell.enabled) {
this.selectedValueChange.emit(cell.value);
}
}
/**
* @param {?} changes
* @return {?}
*/
ngOnChanges(changes) {
/** @type {?} */
const columnChanges = changes['numCols'];
const { rows, numCols } = this;
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}%`;
}
}
/**
* @param {?} rowIndex
* @param {?} colIndex
* @return {?}
*/
_isActiveCell(rowIndex, colIndex) {
/** @type {?} */
let 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.
* @return {?}
*/
_focusActiveCell() {
this._ngZone.runOutsideAngular((/**
* @return {?}
*/
() => {
this._ngZone.onStable.asObservable().pipe(take(1)).subscribe((/**
* @return {?}
*/
() => {
/** @type {?} */
const 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 = () => [
{ 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 }]
};
if (false) {
/**
* The label for the table. (e.g. "Jan 2017").
* @type {?}
*/
MatCalendarBody.prototype.label;
/**
* The cells to display in the table.
* @type {?}
*/
MatCalendarBody.prototype.rows;
/**
* The value in the table that corresponds to today.
* @type {?}
*/
MatCalendarBody.prototype.todayValue;
/**
* The value in the table that is currently selected.
* @type {?}
*/
MatCalendarBody.prototype.selectedValue;
/**
* The minimum number of free cells needed to fit the label in the first row.
* @type {?}
*/
MatCalendarBody.prototype.labelMinRequiredCells;
/**
* The number of columns in the table.
* @type {?}
*/
MatCalendarBody.prototype.numCols;
/**
* The cell number of the active cell in the table.
* @type {?}
*/
MatCalendarBody.prototype.activeCell;
/**
* The aspect ratio (width / height) to use for the cells in the table. This aspect ratio will be
* maintained even as the table resizes.
* @type {?}
*/
MatCalendarBody.prototype.cellAspectRatio;
/**
* Emits when a new value is selected.
* @type {?}
*/
MatCalendarBody.prototype.selectedValueChange;
/**
* The number of blank cells to put at the beginning for the first row.
* @type {?}
*/
MatCalendarBody.prototype._firstRowOffset;
/**
* Padding for the individual date cells.
* @type {?}
*/
MatCalendarBody.prototype._cellPadding;
/**
* Width of an individual cell.
* @type {?}
*/
MatCalendarBody.prototype._cellWidth;
/**
* @type {?}
* @private
*/
MatCalendarBody.prototype._elementRef;
/**
* @type {?}
* @private
*/
MatCalendarBody.prototype._ngZone;
}
/**
* @fileoverview added by tsickle
* Generated from: src/material/datepicker/month-view.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/** @type {?} */
const DAYS_PER_WEEK = 7;
/**
* An internal component used to display a single month in the datepicker.
* \@docs-private
* @template D
*/
class MatMonthView {
/**
* @param {?} _changeDetectorRef
* @param {?} _dateFormats
* @param {?} _dateAdapter
* @param {?=} _dir
*/
constructor(_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();
}
/**
* The date to display in this month view (everything other than the month and year is ignored).
* @return {?}
*/
get activeDate() { return this._activeDate; }
/**
* @param {?} value
* @return {?}
*/
set activeDate(value) {
/** @type {?} */
const oldActiveDate = this._activeDate;
/** @type {?} */
const 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();
}
}
/**
* The currently selected date.
* @return {?}
*/
get selected() { return this._selected; }
/**
* @param {?} value
* @return {?}
*/
set selected(value) {
this._selected = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
this._selectedDate = this._getDateInCurrentMonth(this._selected);
}
/**
* The minimum selectable date.
* @return {?}
*/
get minDate() { return this._minDate; }
/**
* @param {?} value
* @return {?}
*/
set minDate(value) {
this._minDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
}
/**
* The maximum selectable date.
* @return {?}
*/
get maxDate() { return this._maxDate; }
/**
* @param {?} value
* @return {?}
*/
set maxDate(value) {
this._maxDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
}
/**
* @return {?}
*/
ngAfterContentInit() {
this._rerenderSubscription = this._dateAdapter.localeChanges
.pipe(startWith(null))
.subscribe((/**
* @return {?}
*/
() => this._init()));
}
/**
* @return {?}
*/
ngOnDestroy() {
this._rerenderSubscription.unsubscribe();
}
/**
* Handles when a new date is selected.
* @param {?} date
* @return {?}
*/
_dateSelected(date) {
if (this._selectedDate != date) {
/** @type {?} */
const selectedYear = this._dateAdapter.getYear(this.activeDate);
/** @type {?} */
const selectedMonth = this._dateAdapter.getMonth(this.activeDate);
/** @type {?} */
const 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.
* @param {?} event
* @return {?}
*/
_handleCalendarBodyKeydown(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.
// 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.
/** @type {?} */
const oldActiveDate = this._activeDate;
/** @type {?} */
const 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.
* @return {?}
*/
_init() {
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();
/** @type {?} */
let 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.
* @return {?}
*/
_focusActiveCell() {
this._matCalendarBody._focusActiveCell();
}
/**
* Initializes the weekdays.
* @private
* @return {?}
*/
_initWeekdays() {
/** @type {?} */
const firstDayOfWeek = this._dateAdapter.getFirstDayOfWeek();
/** @type {?} */
const narrowWeekdays = this._dateAdapter.getDayOfWeekNames('narrow');
/** @type {?} */
const longWeekdays = this._dateAdapter.getDayOfWeekNames('long');
// Rotate the labels for days of the week based on the configured first day of the week.
/** @type {?} */
let weekdays = longWeekdays.map((/**
* @param {?} long
* @param {?} i
* @return {?}
*/
(long, i) => {
return { long, narrow: narrowWeekdays[i] };
}));
this._weekdays = weekdays.slice(firstDayOfWeek).concat(weekdays.slice(0, firstDayOfWeek));
}
/**
* Creates MatCalendarCells for the dates in this month.
* @private
* @return {?}
*/
_createWeekCells() {
/** @type {?} */
const daysInMonth = this._dateAdapter.getNumDaysInMonth(this.activeDate);
/** @type {?} */
const dateNames = this._dateAdapter.getDateNames();
this._weeks = [[]];
for (let i = 0, cell = this._firstWeekOffset; i < daysInMonth; i++, cell++) {
if (cell == DAYS_PER_WEEK) {
this._weeks.push([]);
cell = 0;
}
/** @type {?} */
const date = this._dateAdapter.createDate(this._dateAdapter.getYear(this.activeDate), this._dateAdapter.getMonth(this.activeDate), i + 1);
/** @type {?} */
const enabled = this._shouldEnableDate(date);
/** @type {?} */
const ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.dateA11yLabel);
/** @type {?} */
const 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
* @private
* @param {?} date
* @return {?}
*/
_shouldEnableDate(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.
* @private
* @param {?} date
* @return {?}
*/
_getDateInCurrentMonth(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.
* @private
* @param {?} d1
* @param {?} d2
* @return {?}
*/
_hasSameMonthAndYear(d1, d2) {
return !!(d1 && d2 && this._dateAdapter.getMonth(d1) == this._dateAdapter.getMonth(d2) &&
this._dateAdapter.getYear(d1) == this._dateAdapter.getYear(d2));
}
/**
* @private
* @param {?} obj The object to check.
* @return {?} The given object if it is both a date instance and valid, otherwise null.
*/
_getValidDateOrNull(obj) {
return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null;
}
/**
* Determines whether the user has the RTL layout direction.
* @private
* @return {?}
*/
_isRtl() {
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 = () => [
{ 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,] }]
};
if (false) {
/**
* @type {?}
* @private
*/
MatMonthView.prototype._rerenderSubscription;
/**
* @type {?}
* @private
*/
MatMonthView.prototype._activeDate;
/**
* @type {?}
* @private
*/
MatMonthView.prototype._selected;
/**
* @type {?}
* @private
*/
MatMonthView.prototype._minDate;
/**
* @type {?}
* @private
*/
MatMonthView.prototype._maxDate;
/**
* Function used to filter which dates are selectable.
* @type {?}
*/
MatMonthView.prototype.dateFilter;
/**
* Function that can be used to add custom CSS classes to dates.
* @type {?}
*/
MatMonthView.prototype.dateClass;
/**
* Emits when a new date is selected.
* @type {?}
*/
MatMonthView.prototype.selectedChange;
/**
* Emits when any date is selected.
* @type {?}
*/
MatMonthView.prototype._userSelection;
/**
* Emits when any date is activated.
* @type {?}
*/
MatMonthView.prototype.activeDateChange;
/**
* The body of calendar table
* @type {?}
*/
MatMonthView.prototype._matCalendarBody;
/**
* The label for this month (e.g. "January 2017").
* @type {?}
*/
MatMonthView.prototype._monthLabel;
/**
* Grid of calendar cells representing the dates of the month.
* @type {?}
*/
MatMonthView.prototype._weeks;
/**
* The number of blank cells in the first row before the 1st of the month.
* @type {?}
*/
MatMonthView.prototype._firstWeekOffset;
/**
* The date of the month that the currently selected Date falls on.
* Null if the currently selected Date is in another month.
* @type {?}
*/
MatMonthView.prototype._selectedDate;
/**
* The date of the month that today falls on. Null if today is in another month.
* @type {?}
*/
MatMonthView.prototype._todayDate;
/**
* The names of the weekdays.
* @type {?}
*/
MatMonthView.prototype._weekdays;
/**
* @type {?}
* @private
*/
MatMonthView.prototype._changeDetectorRef;
/**
* @type {?}
* @private
*/
MatMonthView.prototype._dateFormats;
/** @type {?} */
MatMonthView.prototype._dateAdapter;
/**
* @type {?}
* @private
*/
MatMonthView.prototype._dir;
}
/**
* @fileoverview added by tsickle
* Generated from: src/material/datepicker/multi-year-view.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/** @type {?} */
const yearsPerPage = 24;
/** @type {?} */
const yearsPerRow = 4;
/**
* An internal component used to display a year selector in the datepicker.
* \@docs-private
* @template D
*/
class MatMultiYearView {
/**
* @param {?} _changeDetectorRef
* @param {?} _dateAdapter
* @param {?=} _dir
*/
constructor(_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();
}
/**
* The date to display in this multi-year view (everything other than the year is ignored).
* @return {?}
*/
get activeDate() { return this._activeDate; }
/**
* @param {?} value
* @return {?}
*/
set activeDate(value) {
/** @type {?} */
let oldActiveDate = this._activeDate;
/** @type {?} */
const 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();
}
}
/**
* The currently selected date.
* @return {?}
*/
get selected() { return this._selected; }
/**
* @param {?} value
* @return {?}
*/
set selected(value) {
this._selected = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
this._selectedYear = this._selected && this._dateAdapter.getYear(this._selected);
}
/**
* The minimum selectable date.
* @return {?}
*/
get minDate() { return this._minDate; }
/**
* @param {?} value
* @return {?}
*/
set minDate(value) {
this._minDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
}
/**
* The maximum selectable date.
* @return {?}
*/
get maxDate() { return this._maxDate; }
/**
* @param {?} value
* @return {?}
*/
set maxDate(value) {
this._maxDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
}
/**
* @return {?}
*/
ngAfterContentInit() {
this._rerenderSubscription = this._dateAdapter.localeChanges
.pipe(startWith(null))
.subscribe((/**
* @return {?}
*/
() => this._init()));
}
/**
* @return {?}
*/
ngOnDestroy() {
this._rerenderSubscription.unsubscribe();
}
/**
* Initializes this multi-year view.
* @return {?}
*/
_init() {
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.
/** @type {?} */
const activeYear = this._dateAdapter.getYear(this._activeDate);
/** @type {?} */
const minYearOfPage = activeYear - getActiveOffset(this._dateAdapter, this.activeDate, this.minDate, this.maxDate);
this._years = [];
for (let i = 0, row = []; i < yearsPerPage; i++) {
row.push(minYearOfPage + i);
if (row.length == yearsPerRow) {
this._years.push(row.map((/**
* @param {?} year
* @return {?}
*/
year => this._createCellForYear(year))));
row = [];
}
}
this._changeDetectorRef.markForCheck();
}
/**
* Handles when a new year is selected.
* @param {?} year
* @return {?}
*/
_yearSelected(year) {
this.yearSelected.emit(this._dateAdapter.createDate(year, 0, 1));
/** @type {?} */
let month = this._dateAdapter.getMonth(this.activeDate);
/** @type {?} */
let 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.
* @param {?} event
* @return {?}
*/
_handleCalendarBodyKeydown(event) {
/** @type {?} */
const oldActiveDate = this._activeDate;
/** @type {?} */
const 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();
}
/**
* @return {?}
*/
_getActiveCell() {
return getActiveOffset(this._dateAdapter, this.activeDate, this.minDate, this.maxDate);
}
/**
* Focuses the active cell after the microtask queue is empty.
* @return {?}
*/
_focusActiveCell() {
this._matCalendarBody._focusActiveCell();
}
/**
* Creates an MatCalendarCell for the given year.
* @private
* @param {?} year
* @return {?}
*/
_createCellForYear(year) {
/** @type {?} */
let 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.
* @private
* @param {?} year
* @return {?}
*/
_shouldEnableYear(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;
}
/** @type {?} */
const firstOfYear = this._dateAdapter.createDate(year, 0, 1);
// If any date in the year is enabled count the year as enabled.
for (let date = firstOfYear; this._dateAdapter.getYear(date) == year; date = this._dateAdapter.addCalendarDays(date, 1)) {
if (this.dateFilter(date)) {
return true;
}
}
return false;
}
/**
* @private
* @param {?} obj The object to check.
* @return {?} The given object if it is both a date instance and valid, otherwise null.
*/
_getValidDateOrNull(obj) {
return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null;
}
/**
* Determines whether the user has the RTL layout direction.
* @private
* @return {?}
*/
_isRtl() {
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 = () => [
{ 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,] }]
};
if (false) {
/**
* @type {?}
* @private
*/
MatMultiYearView.prototype._rerenderSubscription;
/**
* @type {?}
* @private
*/
MatMultiYearView.prototype._activeDate;
/**
* @type {?}
* @private
*/
MatMultiYearView.prototype._selected;
/**
* @type {?}
* @private
*/
MatMultiYearView.prototype._minDate;
/**
* @type {?}
* @private
*/
MatMultiYearView.prototype._maxDate;
/**
* A function used to filter which dates are selectable.
* @type {?}
*/
MatMultiYearView.prototype.dateFilter;
/**
* Emits when a new year is selected.
* @type {?}
*/
MatMultiYearView.prototype.selectedChange;
/**
* Emits the selected year. This doesn't imply a change on the selected date
* @type {?}
*/
MatMultiYearView.prototype.yearSelected;
/**
* Emits when any date is activated.
* @type {?}
*/
MatMultiYearView.prototype.activeDateChange;
/**
* The body of calendar table
* @type {?}
*/
MatMultiYearView.prototype._matCalendarBody;
/**
* Grid of calendar cells representing the currently displayed years.
* @type {?}
*/
MatMultiYearView.prototype._years;
/**
* The year that today falls on.
* @type {?}
*/
MatMultiYearView.prototype._todayYear;
/**
* The year of the selected date. Null if the selected date is null.
* @type {?}
*/
MatMultiYearView.prototype._selectedYear;
/**
* @type {?}
* @private
*/
MatMultiYearView.prototype._changeDetectorRef;
/** @type {?} */
MatMultiYearView.prototype._dateAdapter;
/**
* @type {?}
* @private
*/
MatMultiYearView.prototype._dir;
}
/**
* @template D
* @param {?} dateAdapter
* @param {?} date1
* @param {?} date2
* @param {?} minDate
* @param {?} maxDate
* @return {?}
*/
function isSameMultiYearView(dateAdapter, date1, date2, minDate, maxDate) {
/** @type {?} */
const year1 = dateAdapter.getYear(date1);
/** @type {?} */
const year2 = dateAdapter.getYear(date2);
/** @type {?} */
const 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.
* @template D
* @param {?} dateAdapter
* @param {?} activeDate
* @param {?} minDate
* @param {?} maxDate
* @return {?}
*/
function getActiveOffset(dateAdapter, activeDate, minDate, maxDate) {
/** @type {?} */
const 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.
* @template D
* @param {?} dateAdapter
* @param {?} minDate
* @param {?} maxDate
* @return {?}
*/
function getStartingYear(dateAdapter, minDate, maxDate) {
/** @type {?} */
let startingYear = 0;
if (maxDate) {
/** @type {?} */
const 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
* @param {?} a
* @param {?} b
* @return {?}
*/
function euclideanModulo(a, b) {
return (a % b + b) % b;
}
/**
* @fileoverview added by tsickle
* Generated from: src/material/datepicker/year-view.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* An internal component used to display a single year in the datepicker.
* \@docs-private
* @template D
*/
class MatYearView {
/**
* @param {?} _changeDetectorRef
* @param {?} _dateFormats
* @param {?} _dateAdapter
* @param {?=} _dir
*/
constructor(_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 c