source: trip-planner-front/node_modules/@angular/material/fesm2015/datepicker.js@ 8d391a1

Last change on this file since 8d391a1 was e29cc2e, checked in by Ema <ema_spirova@…>, 3 years ago

primeNG components

  • Property mode set to 100644
File size: 181.5 KB
Line 
1import { A11yModule } from '@angular/cdk/a11y';
2import { Overlay, FlexibleConnectedPositionStrategy, OverlayConfig, OverlayModule } from '@angular/cdk/overlay';
3import { ComponentPortal, TemplatePortal, PortalModule } from '@angular/cdk/portal';
4import { DOCUMENT, CommonModule } from '@angular/common';
5import * as i0 from '@angular/core';
6import { Injectable, EventEmitter, Component, ViewEncapsulation, ChangeDetectionStrategy, ElementRef, NgZone, Input, Output, Optional, SkipSelf, InjectionToken, ChangeDetectorRef, Inject, ViewChild, forwardRef, Directive, ViewContainerRef, Attribute, ContentChild, InjectFlags, Injector, Self, TemplateRef, NgModule } from '@angular/core';
7import { MatButtonModule } from '@angular/material/button';
8import { CdkScrollableModule } from '@angular/cdk/scrolling';
9import { DateAdapter, MAT_DATE_FORMATS, mixinColor, ErrorStateMatcher, mixinErrorState, MatCommonModule } from '@angular/material/core';
10import { Subject, Subscription, merge, of } from 'rxjs';
11import { ESCAPE, hasModifierKey, SPACE, ENTER, PAGE_DOWN, PAGE_UP, END, HOME, DOWN_ARROW, UP_ARROW, RIGHT_ARROW, LEFT_ARROW, BACKSPACE } from '@angular/cdk/keycodes';
12import { Directionality } from '@angular/cdk/bidi';
13import { take, startWith, filter } from 'rxjs/operators';
14import { coerceBooleanProperty, coerceStringArray } from '@angular/cdk/coercion';
15import { _getFocusedElementPierceShadowDom } from '@angular/cdk/platform';
16import { trigger, transition, animate, keyframes, style, state } from '@angular/animations';
17import { NG_VALUE_ACCESSOR, NG_VALIDATORS, Validators, NgControl, NgForm, FormGroupDirective, ControlContainer } from '@angular/forms';
18import { MatFormField, MAT_FORM_FIELD, MatFormFieldControl } from '@angular/material/form-field';
19import { MAT_INPUT_VALUE_ACCESSOR } from '@angular/material/input';
20
21/**
22 * @license
23 * Copyright Google LLC All Rights Reserved.
24 *
25 * Use of this source code is governed by an MIT-style license that can be
26 * found in the LICENSE file at https://angular.io/license
27 */
28/** @docs-private */
29function createMissingDateImplError(provider) {
30 return Error(`MatDatepicker: No provider found for ${provider}. You must import one of the following ` +
31 `modules at your application root: MatNativeDateModule, MatMomentDateModule, or provide a ` +
32 `custom implementation.`);
33}
34
35/**
36 * @license
37 * Copyright Google LLC All Rights Reserved.
38 *
39 * Use of this source code is governed by an MIT-style license that can be
40 * found in the LICENSE file at https://angular.io/license
41 */
42/** Datepicker data that requires internationalization. */
43class MatDatepickerIntl {
44 constructor() {
45 /**
46 * Stream that emits whenever the labels here are changed. Use this to notify
47 * components if the labels have changed after initialization.
48 */
49 this.changes = new Subject();
50 /** A label for the calendar popup (used by screen readers). */
51 this.calendarLabel = 'Calendar';
52 /** A label for the button used to open the calendar popup (used by screen readers). */
53 this.openCalendarLabel = 'Open calendar';
54 /** Label for the button used to close the calendar popup. */
55 this.closeCalendarLabel = 'Close calendar';
56 /** A label for the previous month button (used by screen readers). */
57 this.prevMonthLabel = 'Previous month';
58 /** A label for the next month button (used by screen readers). */
59 this.nextMonthLabel = 'Next month';
60 /** A label for the previous year button (used by screen readers). */
61 this.prevYearLabel = 'Previous year';
62 /** A label for the next year button (used by screen readers). */
63 this.nextYearLabel = 'Next year';
64 /** A label for the previous multi-year button (used by screen readers). */
65 this.prevMultiYearLabel = 'Previous 24 years';
66 /** A label for the next multi-year button (used by screen readers). */
67 this.nextMultiYearLabel = 'Next 24 years';
68 /** A label for the 'switch to month view' button (used by screen readers). */
69 this.switchToMonthViewLabel = 'Choose date';
70 /** A label for the 'switch to year view' button (used by screen readers). */
71 this.switchToMultiYearViewLabel = 'Choose month and year';
72 }
73 /** Formats a range of years. */
74 formatYearRange(start, end) {
75 return `${start} \u2013 ${end}`;
76 }
77}
78MatDatepickerIntl.ɵprov = i0.ɵɵdefineInjectable({ factory: function MatDatepickerIntl_Factory() { return new MatDatepickerIntl(); }, token: MatDatepickerIntl, providedIn: "root" });
79MatDatepickerIntl.decorators = [
80 { type: Injectable, args: [{ providedIn: 'root' },] }
81];
82
83/**
84 * @license
85 * Copyright Google LLC All Rights Reserved.
86 *
87 * Use of this source code is governed by an MIT-style license that can be
88 * found in the LICENSE file at https://angular.io/license
89 */
90/**
91 * An internal class that represents the data corresponding to a single calendar cell.
92 * @docs-private
93 */
94class MatCalendarCell {
95 constructor(value, displayValue, ariaLabel, enabled, cssClasses = {}, compareValue = value, rawValue) {
96 this.value = value;
97 this.displayValue = displayValue;
98 this.ariaLabel = ariaLabel;
99 this.enabled = enabled;
100 this.cssClasses = cssClasses;
101 this.compareValue = compareValue;
102 this.rawValue = rawValue;
103 }
104}
105/**
106 * An internal component used to display calendar data in a table.
107 * @docs-private
108 */
109class MatCalendarBody {
110 constructor(_elementRef, _ngZone) {
111 this._elementRef = _elementRef;
112 this._ngZone = _ngZone;
113 /** The number of columns in the table. */
114 this.numCols = 7;
115 /** The cell number of the active cell in the table. */
116 this.activeCell = 0;
117 /** Whether a range is being selected. */
118 this.isRange = false;
119 /**
120 * The aspect ratio (width / height) to use for the cells in the table. This aspect ratio will be
121 * maintained even as the table resizes.
122 */
123 this.cellAspectRatio = 1;
124 /** Start of the preview range. */
125 this.previewStart = null;
126 /** End of the preview range. */
127 this.previewEnd = null;
128 /** Emits when a new value is selected. */
129 this.selectedValueChange = new EventEmitter();
130 /** Emits when the preview has changed as a result of a user action. */
131 this.previewChange = new EventEmitter();
132 /**
133 * Event handler for when the user enters an element
134 * inside the calendar body (e.g. by hovering in or focus).
135 */
136 this._enterHandler = (event) => {
137 if (this._skipNextFocus && event.type === 'focus') {
138 this._skipNextFocus = false;
139 return;
140 }
141 // We only need to hit the zone when we're selecting a range.
142 if (event.target && this.isRange) {
143 const cell = this._getCellFromElement(event.target);
144 if (cell) {
145 this._ngZone.run(() => this.previewChange.emit({ value: cell.enabled ? cell : null, event }));
146 }
147 }
148 };
149 /**
150 * Event handler for when the user's pointer leaves an element
151 * inside the calendar body (e.g. by hovering out or blurring).
152 */
153 this._leaveHandler = (event) => {
154 // We only need to hit the zone when we're selecting a range.
155 if (this.previewEnd !== null && this.isRange) {
156 // Only reset the preview end value when leaving cells. This looks better, because
157 // we have a gap between the cells and the rows and we don't want to remove the
158 // range just for it to show up again when the user moves a few pixels to the side.
159 if (event.target && isTableCell(event.target)) {
160 this._ngZone.run(() => this.previewChange.emit({ value: null, event }));
161 }
162 }
163 };
164 _ngZone.runOutsideAngular(() => {
165 const element = _elementRef.nativeElement;
166 element.addEventListener('mouseenter', this._enterHandler, true);
167 element.addEventListener('focus', this._enterHandler, true);
168 element.addEventListener('mouseleave', this._leaveHandler, true);
169 element.addEventListener('blur', this._leaveHandler, true);
170 });
171 }
172 /** Called when a cell is clicked. */
173 _cellClicked(cell, event) {
174 if (cell.enabled) {
175 this.selectedValueChange.emit({ value: cell.value, event });
176 }
177 }
178 /** Returns whether a cell should be marked as selected. */
179 _isSelected(value) {
180 return this.startValue === value || this.endValue === value;
181 }
182 ngOnChanges(changes) {
183 const columnChanges = changes['numCols'];
184 const { rows, numCols } = this;
185 if (changes['rows'] || columnChanges) {
186 this._firstRowOffset = rows && rows.length && rows[0].length ? numCols - rows[0].length : 0;
187 }
188 if (changes['cellAspectRatio'] || columnChanges || !this._cellPadding) {
189 this._cellPadding = `${50 * this.cellAspectRatio / numCols}%`;
190 }
191 if (columnChanges || !this._cellWidth) {
192 this._cellWidth = `${100 / numCols}%`;
193 }
194 }
195 ngOnDestroy() {
196 const element = this._elementRef.nativeElement;
197 element.removeEventListener('mouseenter', this._enterHandler, true);
198 element.removeEventListener('focus', this._enterHandler, true);
199 element.removeEventListener('mouseleave', this._leaveHandler, true);
200 element.removeEventListener('blur', this._leaveHandler, true);
201 }
202 /** Returns whether a cell is active. */
203 _isActiveCell(rowIndex, colIndex) {
204 let cellNumber = rowIndex * this.numCols + colIndex;
205 // Account for the fact that the first row may not have as many cells.
206 if (rowIndex) {
207 cellNumber -= this._firstRowOffset;
208 }
209 return cellNumber == this.activeCell;
210 }
211 /** Focuses the active cell after the microtask queue is empty. */
212 _focusActiveCell(movePreview = true) {
213 this._ngZone.runOutsideAngular(() => {
214 this._ngZone.onStable.pipe(take(1)).subscribe(() => {
215 const activeCell = this._elementRef.nativeElement.querySelector('.mat-calendar-body-active');
216 if (activeCell) {
217 if (!movePreview) {
218 this._skipNextFocus = true;
219 }
220 activeCell.focus();
221 }
222 });
223 });
224 }
225 /** Gets whether a value is the start of the main range. */
226 _isRangeStart(value) {
227 return isStart(value, this.startValue, this.endValue);
228 }
229 /** Gets whether a value is the end of the main range. */
230 _isRangeEnd(value) {
231 return isEnd(value, this.startValue, this.endValue);
232 }
233 /** Gets whether a value is within the currently-selected range. */
234 _isInRange(value) {
235 return isInRange(value, this.startValue, this.endValue, this.isRange);
236 }
237 /** Gets whether a value is the start of the comparison range. */
238 _isComparisonStart(value) {
239 return isStart(value, this.comparisonStart, this.comparisonEnd);
240 }
241 /** Whether the cell is a start bridge cell between the main and comparison ranges. */
242 _isComparisonBridgeStart(value, rowIndex, colIndex) {
243 if (!this._isComparisonStart(value) || this._isRangeStart(value) || !this._isInRange(value)) {
244 return false;
245 }
246 let previousCell = this.rows[rowIndex][colIndex - 1];
247 if (!previousCell) {
248 const previousRow = this.rows[rowIndex - 1];
249 previousCell = previousRow && previousRow[previousRow.length - 1];
250 }
251 return previousCell && !this._isRangeEnd(previousCell.compareValue);
252 }
253 /** Whether the cell is an end bridge cell between the main and comparison ranges. */
254 _isComparisonBridgeEnd(value, rowIndex, colIndex) {
255 if (!this._isComparisonEnd(value) || this._isRangeEnd(value) || !this._isInRange(value)) {
256 return false;
257 }
258 let nextCell = this.rows[rowIndex][colIndex + 1];
259 if (!nextCell) {
260 const nextRow = this.rows[rowIndex + 1];
261 nextCell = nextRow && nextRow[0];
262 }
263 return nextCell && !this._isRangeStart(nextCell.compareValue);
264 }
265 /** Gets whether a value is the end of the comparison range. */
266 _isComparisonEnd(value) {
267 return isEnd(value, this.comparisonStart, this.comparisonEnd);
268 }
269 /** Gets whether a value is within the current comparison range. */
270 _isInComparisonRange(value) {
271 return isInRange(value, this.comparisonStart, this.comparisonEnd, this.isRange);
272 }
273 /**
274 * Gets whether a value is the same as the start and end of the comparison range.
275 * For context, the functions that we use to determine whether something is the start/end of
276 * a range don't allow for the start and end to be on the same day, because we'd have to use
277 * much more specific CSS selectors to style them correctly in all scenarios. This is fine for
278 * the regular range, because when it happens, the selected styles take over and still show where
279 * the range would've been, however we don't have these selected styles for a comparison range.
280 * This function is used to apply a class that serves the same purpose as the one for selected
281 * dates, but it only applies in the context of a comparison range.
282 */
283 _isComparisonIdentical(value) {
284 // Note that we don't need to null check the start/end
285 // here, because the `value` will always be defined.
286 return this.comparisonStart === this.comparisonEnd && value === this.comparisonStart;
287 }
288 /** Gets whether a value is the start of the preview range. */
289 _isPreviewStart(value) {
290 return isStart(value, this.previewStart, this.previewEnd);
291 }
292 /** Gets whether a value is the end of the preview range. */
293 _isPreviewEnd(value) {
294 return isEnd(value, this.previewStart, this.previewEnd);
295 }
296 /** Gets whether a value is inside the preview range. */
297 _isInPreview(value) {
298 return isInRange(value, this.previewStart, this.previewEnd, this.isRange);
299 }
300 /** Finds the MatCalendarCell that corresponds to a DOM node. */
301 _getCellFromElement(element) {
302 let cell;
303 if (isTableCell(element)) {
304 cell = element;
305 }
306 else if (isTableCell(element.parentNode)) {
307 cell = element.parentNode;
308 }
309 if (cell) {
310 const row = cell.getAttribute('data-mat-row');
311 const col = cell.getAttribute('data-mat-col');
312 if (row && col) {
313 return this.rows[parseInt(row)][parseInt(col)];
314 }
315 }
316 return null;
317 }
318}
319MatCalendarBody.decorators = [
320 { type: Component, args: [{
321 selector: '[mat-calendar-body]',
322 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 This cell is purely decorative, but we can't put `aria-hidden` or `role=\"presentation\"` on it,\n because it throws off the week days for the rest of the row on NVDA. The aspect ratio of the\n table cells is maintained by setting the top and bottom padding as a percentage of the width\n (a variant of the trick described here: https://www.w3schools.com/howto/howto_css_aspect_ratio.asp).\n -->\n <td *ngIf=\"rowIndex === 0 && _firstRowOffset\"\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 [attr.data-mat-row]=\"rowIndex\"\n [attr.data-mat-col]=\"colIndex\"\n [class.mat-calendar-body-disabled]=\"!item.enabled\"\n [class.mat-calendar-body-active]=\"_isActiveCell(rowIndex, colIndex)\"\n [class.mat-calendar-body-range-start]=\"_isRangeStart(item.compareValue)\"\n [class.mat-calendar-body-range-end]=\"_isRangeEnd(item.compareValue)\"\n [class.mat-calendar-body-in-range]=\"_isInRange(item.compareValue)\"\n [class.mat-calendar-body-comparison-bridge-start]=\"_isComparisonBridgeStart(item.compareValue, rowIndex, colIndex)\"\n [class.mat-calendar-body-comparison-bridge-end]=\"_isComparisonBridgeEnd(item.compareValue, rowIndex, colIndex)\"\n [class.mat-calendar-body-comparison-start]=\"_isComparisonStart(item.compareValue)\"\n [class.mat-calendar-body-comparison-end]=\"_isComparisonEnd(item.compareValue)\"\n [class.mat-calendar-body-in-comparison-range]=\"_isInComparisonRange(item.compareValue)\"\n [class.mat-calendar-body-preview-start]=\"_isPreviewStart(item.compareValue)\"\n [class.mat-calendar-body-preview-end]=\"_isPreviewEnd(item.compareValue)\"\n [class.mat-calendar-body-in-preview]=\"_isInPreview(item.compareValue)\"\n [attr.aria-label]=\"item.ariaLabel\"\n [attr.aria-disabled]=\"!item.enabled || null\"\n [attr.aria-selected]=\"_isSelected(item.compareValue)\"\n (click)=\"_cellClicked(item, $event)\"\n [style.width]=\"_cellWidth\"\n [style.paddingTop]=\"_cellPadding\"\n [style.paddingBottom]=\"_cellPadding\">\n <div class=\"mat-calendar-body-cell-content mat-focus-indicator\"\n [class.mat-calendar-body-selected]=\"_isSelected(item.compareValue)\"\n [class.mat-calendar-body-comparison-identical]=\"_isComparisonIdentical(item.compareValue)\"\n [class.mat-calendar-body-today]=\"todayValue === item.compareValue\">\n {{item.displayValue}}\n </div>\n <div class=\"mat-calendar-body-cell-preview\"></div>\n </td>\n</tr>\n",
323 host: {
324 'class': 'mat-calendar-body',
325 },
326 exportAs: 'matCalendarBody',
327 encapsulation: ViewEncapsulation.None,
328 changeDetection: ChangeDetectionStrategy.OnPush,
329 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-cell::before,.mat-calendar-body-cell::after,.mat-calendar-body-cell-preview{content:\"\";position:absolute;top:5%;left:0;z-index:0;box-sizing:border-box;height:90%;width:100%}.mat-calendar-body-range-start:not(.mat-calendar-body-in-comparison-range)::before,.mat-calendar-body-range-start::after,.mat-calendar-body-comparison-start:not(.mat-calendar-body-comparison-bridge-start)::before,.mat-calendar-body-comparison-start::after,.mat-calendar-body-preview-start .mat-calendar-body-cell-preview{left:5%;width:95%;border-top-left-radius:999px;border-bottom-left-radius:999px}[dir=rtl] .mat-calendar-body-range-start:not(.mat-calendar-body-in-comparison-range)::before,[dir=rtl] .mat-calendar-body-range-start::after,[dir=rtl] .mat-calendar-body-comparison-start:not(.mat-calendar-body-comparison-bridge-start)::before,[dir=rtl] .mat-calendar-body-comparison-start::after,[dir=rtl] .mat-calendar-body-preview-start .mat-calendar-body-cell-preview{left:0;border-radius:0;border-top-right-radius:999px;border-bottom-right-radius:999px}.mat-calendar-body-range-end:not(.mat-calendar-body-in-comparison-range)::before,.mat-calendar-body-range-end::after,.mat-calendar-body-comparison-end:not(.mat-calendar-body-comparison-bridge-end)::before,.mat-calendar-body-comparison-end::after,.mat-calendar-body-preview-end .mat-calendar-body-cell-preview{width:95%;border-top-right-radius:999px;border-bottom-right-radius:999px}[dir=rtl] .mat-calendar-body-range-end:not(.mat-calendar-body-in-comparison-range)::before,[dir=rtl] .mat-calendar-body-range-end::after,[dir=rtl] .mat-calendar-body-comparison-end:not(.mat-calendar-body-comparison-bridge-end)::before,[dir=rtl] .mat-calendar-body-comparison-end::after,[dir=rtl] .mat-calendar-body-preview-end .mat-calendar-body-cell-preview{left:5%;border-radius:0;border-top-left-radius:999px;border-bottom-left-radius:999px}[dir=rtl] .mat-calendar-body-comparison-bridge-start.mat-calendar-body-range-end::after,[dir=rtl] .mat-calendar-body-comparison-bridge-end.mat-calendar-body-range-start::after{width:95%;border-top-right-radius:999px;border-bottom-right-radius:999px}.mat-calendar-body-comparison-start.mat-calendar-body-range-end::after,[dir=rtl] .mat-calendar-body-comparison-start.mat-calendar-body-range-end::after,.mat-calendar-body-comparison-end.mat-calendar-body-range-start::after,[dir=rtl] .mat-calendar-body-comparison-end.mat-calendar-body-range-start::after{width:90%}.mat-calendar-body-in-preview .mat-calendar-body-cell-preview{border-top:dashed 1px;border-bottom:dashed 1px}.mat-calendar-body-preview-start .mat-calendar-body-cell-preview{border-left:dashed 1px}[dir=rtl] .mat-calendar-body-preview-start .mat-calendar-body-cell-preview{border-left:0;border-right:dashed 1px}.mat-calendar-body-preview-end .mat-calendar-body-cell-preview{border-right:dashed 1px}[dir=rtl] .mat-calendar-body-preview-end .mat-calendar-body-cell-preview{border-right:0;border-left:dashed 1px}.mat-calendar-body-disabled{cursor:default}.cdk-high-contrast-active .mat-calendar-body-disabled{opacity:.5}.mat-calendar-body-cell-content{top:5%;left:5%;z-index:1;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}.mat-calendar-body-cell-content.mat-focus-indicator{position:absolute}.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-cell:not(.mat-calendar-body-in-range) .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}.cdk-high-contrast-active .mat-calendar-body-cell::before,.cdk-high-contrast-active .mat-calendar-body-cell::after,.cdk-high-contrast-active .mat-calendar-body-selected{background:none}.cdk-high-contrast-active .mat-calendar-body-in-range::before,.cdk-high-contrast-active .mat-calendar-body-comparison-bridge-start::before,.cdk-high-contrast-active .mat-calendar-body-comparison-bridge-end::before{border-top:solid 1px;border-bottom:solid 1px}.cdk-high-contrast-active .mat-calendar-body-range-start::before{border-left:solid 1px}[dir=rtl] .cdk-high-contrast-active .mat-calendar-body-range-start::before{border-left:0;border-right:solid 1px}.cdk-high-contrast-active .mat-calendar-body-range-end::before{border-right:solid 1px}[dir=rtl] .cdk-high-contrast-active .mat-calendar-body-range-end::before{border-right:0;border-left:solid 1px}.cdk-high-contrast-active .mat-calendar-body-in-comparison-range::before{border-top:dashed 1px;border-bottom:dashed 1px}.cdk-high-contrast-active .mat-calendar-body-comparison-start::before{border-left:dashed 1px}[dir=rtl] .cdk-high-contrast-active .mat-calendar-body-comparison-start::before{border-left:0;border-right:dashed 1px}.cdk-high-contrast-active .mat-calendar-body-comparison-end::before{border-right:dashed 1px}[dir=rtl] .cdk-high-contrast-active .mat-calendar-body-comparison-end::before{border-right:0;border-left:dashed 1px}[dir=rtl] .mat-calendar-body-label{text-align:right}@media(hover: none){.mat-calendar-body-cell:not(.mat-calendar-body-disabled):hover>.mat-calendar-body-cell-content:not(.mat-calendar-body-selected){background-color:transparent}}\n"]
330 },] }
331];
332MatCalendarBody.ctorParameters = () => [
333 { type: ElementRef },
334 { type: NgZone }
335];
336MatCalendarBody.propDecorators = {
337 label: [{ type: Input }],
338 rows: [{ type: Input }],
339 todayValue: [{ type: Input }],
340 startValue: [{ type: Input }],
341 endValue: [{ type: Input }],
342 labelMinRequiredCells: [{ type: Input }],
343 numCols: [{ type: Input }],
344 activeCell: [{ type: Input }],
345 isRange: [{ type: Input }],
346 cellAspectRatio: [{ type: Input }],
347 comparisonStart: [{ type: Input }],
348 comparisonEnd: [{ type: Input }],
349 previewStart: [{ type: Input }],
350 previewEnd: [{ type: Input }],
351 selectedValueChange: [{ type: Output }],
352 previewChange: [{ type: Output }]
353};
354/** Checks whether a node is a table cell element. */
355function isTableCell(node) {
356 return node.nodeName === 'TD';
357}
358/** Checks whether a value is the start of a range. */
359function isStart(value, start, end) {
360 return end !== null && start !== end && value < end && value === start;
361}
362/** Checks whether a value is the end of a range. */
363function isEnd(value, start, end) {
364 return start !== null && start !== end && value >= start && value === end;
365}
366/** Checks whether a value is inside of a range. */
367function isInRange(value, start, end, rangeEnabled) {
368 return rangeEnabled && start !== null && end !== null && start !== end &&
369 value >= start && value <= end;
370}
371
372/**
373 * @license
374 * Copyright Google LLC All Rights Reserved.
375 *
376 * Use of this source code is governed by an MIT-style license that can be
377 * found in the LICENSE file at https://angular.io/license
378 */
379/** A class representing a range of dates. */
380class DateRange {
381 constructor(
382 /** The start date of the range. */
383 start,
384 /** The end date of the range. */
385 end) {
386 this.start = start;
387 this.end = end;
388 }
389}
390/**
391 * A selection model containing a date selection.
392 * @docs-private
393 */
394class MatDateSelectionModel {
395 constructor(
396 /** The current selection. */
397 selection, _adapter) {
398 this.selection = selection;
399 this._adapter = _adapter;
400 this._selectionChanged = new Subject();
401 /** Emits when the selection has changed. */
402 this.selectionChanged = this._selectionChanged;
403 this.selection = selection;
404 }
405 /**
406 * Updates the current selection in the model.
407 * @param value New selection that should be assigned.
408 * @param source Object that triggered the selection change.
409 */
410 updateSelection(value, source) {
411 const oldValue = this.selection;
412 this.selection = value;
413 this._selectionChanged.next({ selection: value, source, oldValue });
414 }
415 ngOnDestroy() {
416 this._selectionChanged.complete();
417 }
418 _isValidDateInstance(date) {
419 return this._adapter.isDateInstance(date) && this._adapter.isValid(date);
420 }
421}
422MatDateSelectionModel.decorators = [
423 { type: Injectable }
424];
425MatDateSelectionModel.ctorParameters = () => [
426 { type: undefined },
427 { type: DateAdapter }
428];
429/**
430 * A selection model that contains a single date.
431 * @docs-private
432 */
433class MatSingleDateSelectionModel extends MatDateSelectionModel {
434 constructor(adapter) {
435 super(null, adapter);
436 }
437 /**
438 * Adds a date to the current selection. In the case of a single date selection, the added date
439 * simply overwrites the previous selection
440 */
441 add(date) {
442 super.updateSelection(date, this);
443 }
444 /** Checks whether the current selection is valid. */
445 isValid() {
446 return this.selection != null && this._isValidDateInstance(this.selection);
447 }
448 /**
449 * Checks whether the current selection is complete. In the case of a single date selection, this
450 * is true if the current selection is not null.
451 */
452 isComplete() {
453 return this.selection != null;
454 }
455 /** Clones the selection model. */
456 clone() {
457 const clone = new MatSingleDateSelectionModel(this._adapter);
458 clone.updateSelection(this.selection, this);
459 return clone;
460 }
461}
462MatSingleDateSelectionModel.decorators = [
463 { type: Injectable }
464];
465MatSingleDateSelectionModel.ctorParameters = () => [
466 { type: DateAdapter }
467];
468/**
469 * A selection model that contains a date range.
470 * @docs-private
471 */
472class MatRangeDateSelectionModel extends MatDateSelectionModel {
473 constructor(adapter) {
474 super(new DateRange(null, null), adapter);
475 }
476 /**
477 * Adds a date to the current selection. In the case of a date range selection, the added date
478 * fills in the next `null` value in the range. If both the start and the end already have a date,
479 * the selection is reset so that the given date is the new `start` and the `end` is null.
480 */
481 add(date) {
482 let { start, end } = this.selection;
483 if (start == null) {
484 start = date;
485 }
486 else if (end == null) {
487 end = date;
488 }
489 else {
490 start = date;
491 end = null;
492 }
493 super.updateSelection(new DateRange(start, end), this);
494 }
495 /** Checks whether the current selection is valid. */
496 isValid() {
497 const { start, end } = this.selection;
498 // Empty ranges are valid.
499 if (start == null && end == null) {
500 return true;
501 }
502 // Complete ranges are only valid if both dates are valid and the start is before the end.
503 if (start != null && end != null) {
504 return this._isValidDateInstance(start) && this._isValidDateInstance(end) &&
505 this._adapter.compareDate(start, end) <= 0;
506 }
507 // Partial ranges are valid if the start/end is valid.
508 return (start == null || this._isValidDateInstance(start)) &&
509 (end == null || this._isValidDateInstance(end));
510 }
511 /**
512 * Checks whether the current selection is complete. In the case of a date range selection, this
513 * is true if the current selection has a non-null `start` and `end`.
514 */
515 isComplete() {
516 return this.selection.start != null && this.selection.end != null;
517 }
518 /** Clones the selection model. */
519 clone() {
520 const clone = new MatRangeDateSelectionModel(this._adapter);
521 clone.updateSelection(this.selection, this);
522 return clone;
523 }
524}
525MatRangeDateSelectionModel.decorators = [
526 { type: Injectable }
527];
528MatRangeDateSelectionModel.ctorParameters = () => [
529 { type: DateAdapter }
530];
531/** @docs-private */
532function MAT_SINGLE_DATE_SELECTION_MODEL_FACTORY(parent, adapter) {
533 return parent || new MatSingleDateSelectionModel(adapter);
534}
535/**
536 * Used to provide a single selection model to a component.
537 * @docs-private
538 */
539const MAT_SINGLE_DATE_SELECTION_MODEL_PROVIDER = {
540 provide: MatDateSelectionModel,
541 deps: [[new Optional(), new SkipSelf(), MatDateSelectionModel], DateAdapter],
542 useFactory: MAT_SINGLE_DATE_SELECTION_MODEL_FACTORY,
543};
544/** @docs-private */
545function MAT_RANGE_DATE_SELECTION_MODEL_FACTORY(parent, adapter) {
546 return parent || new MatRangeDateSelectionModel(adapter);
547}
548/**
549 * Used to provide a range selection model to a component.
550 * @docs-private
551 */
552const MAT_RANGE_DATE_SELECTION_MODEL_PROVIDER = {
553 provide: MatDateSelectionModel,
554 deps: [[new Optional(), new SkipSelf(), MatDateSelectionModel], DateAdapter],
555 useFactory: MAT_RANGE_DATE_SELECTION_MODEL_FACTORY,
556};
557
558/**
559 * @license
560 * Copyright Google LLC All Rights Reserved.
561 *
562 * Use of this source code is governed by an MIT-style license that can be
563 * found in the LICENSE file at https://angular.io/license
564 */
565/** Injection token used to customize the date range selection behavior. */
566const MAT_DATE_RANGE_SELECTION_STRATEGY = new InjectionToken('MAT_DATE_RANGE_SELECTION_STRATEGY');
567/** Provides the default date range selection behavior. */
568class DefaultMatCalendarRangeStrategy {
569 constructor(_dateAdapter) {
570 this._dateAdapter = _dateAdapter;
571 }
572 selectionFinished(date, currentRange) {
573 let { start, end } = currentRange;
574 if (start == null) {
575 start = date;
576 }
577 else if (end == null && date && this._dateAdapter.compareDate(date, start) >= 0) {
578 end = date;
579 }
580 else {
581 start = date;
582 end = null;
583 }
584 return new DateRange(start, end);
585 }
586 createPreview(activeDate, currentRange) {
587 let start = null;
588 let end = null;
589 if (currentRange.start && !currentRange.end && activeDate) {
590 start = currentRange.start;
591 end = activeDate;
592 }
593 return new DateRange(start, end);
594 }
595}
596DefaultMatCalendarRangeStrategy.decorators = [
597 { type: Injectable }
598];
599DefaultMatCalendarRangeStrategy.ctorParameters = () => [
600 { type: DateAdapter }
601];
602/** @docs-private */
603function MAT_CALENDAR_RANGE_STRATEGY_PROVIDER_FACTORY(parent, adapter) {
604 return parent || new DefaultMatCalendarRangeStrategy(adapter);
605}
606/** @docs-private */
607const MAT_CALENDAR_RANGE_STRATEGY_PROVIDER = {
608 provide: MAT_DATE_RANGE_SELECTION_STRATEGY,
609 deps: [[new Optional(), new SkipSelf(), MAT_DATE_RANGE_SELECTION_STRATEGY], DateAdapter],
610 useFactory: MAT_CALENDAR_RANGE_STRATEGY_PROVIDER_FACTORY,
611};
612
613/**
614 * @license
615 * Copyright Google LLC All Rights Reserved.
616 *
617 * Use of this source code is governed by an MIT-style license that can be
618 * found in the LICENSE file at https://angular.io/license
619 */
620const DAYS_PER_WEEK = 7;
621/**
622 * An internal component used to display a single month in the datepicker.
623 * @docs-private
624 */
625class MatMonthView {
626 constructor(_changeDetectorRef, _dateFormats, _dateAdapter, _dir, _rangeStrategy) {
627 this._changeDetectorRef = _changeDetectorRef;
628 this._dateFormats = _dateFormats;
629 this._dateAdapter = _dateAdapter;
630 this._dir = _dir;
631 this._rangeStrategy = _rangeStrategy;
632 this._rerenderSubscription = Subscription.EMPTY;
633 /** Emits when a new date is selected. */
634 this.selectedChange = new EventEmitter();
635 /** Emits when any date is selected. */
636 this._userSelection = new EventEmitter();
637 /** Emits when any date is activated. */
638 this.activeDateChange = new EventEmitter();
639 if (typeof ngDevMode === 'undefined' || ngDevMode) {
640 if (!this._dateAdapter) {
641 throw createMissingDateImplError('DateAdapter');
642 }
643 if (!this._dateFormats) {
644 throw createMissingDateImplError('MAT_DATE_FORMATS');
645 }
646 }
647 this._activeDate = this._dateAdapter.today();
648 }
649 /**
650 * The date to display in this month view (everything other than the month and year is ignored).
651 */
652 get activeDate() { return this._activeDate; }
653 set activeDate(value) {
654 const oldActiveDate = this._activeDate;
655 const validDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)) || this._dateAdapter.today();
656 this._activeDate = this._dateAdapter.clampDate(validDate, this.minDate, this.maxDate);
657 if (!this._hasSameMonthAndYear(oldActiveDate, this._activeDate)) {
658 this._init();
659 }
660 }
661 /** The currently selected date. */
662 get selected() { return this._selected; }
663 set selected(value) {
664 if (value instanceof DateRange) {
665 this._selected = value;
666 }
667 else {
668 this._selected = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
669 }
670 this._setRanges(this._selected);
671 }
672 /** The minimum selectable date. */
673 get minDate() { return this._minDate; }
674 set minDate(value) {
675 this._minDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
676 }
677 /** The maximum selectable date. */
678 get maxDate() { return this._maxDate; }
679 set maxDate(value) {
680 this._maxDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
681 }
682 ngAfterContentInit() {
683 this._rerenderSubscription = this._dateAdapter.localeChanges
684 .pipe(startWith(null))
685 .subscribe(() => this._init());
686 }
687 ngOnChanges(changes) {
688 const comparisonChange = changes['comparisonStart'] || changes['comparisonEnd'];
689 if (comparisonChange && !comparisonChange.firstChange) {
690 this._setRanges(this.selected);
691 }
692 }
693 ngOnDestroy() {
694 this._rerenderSubscription.unsubscribe();
695 }
696 /** Handles when a new date is selected. */
697 _dateSelected(event) {
698 const date = event.value;
699 const selectedYear = this._dateAdapter.getYear(this.activeDate);
700 const selectedMonth = this._dateAdapter.getMonth(this.activeDate);
701 const selectedDate = this._dateAdapter.createDate(selectedYear, selectedMonth, date);
702 let rangeStartDate;
703 let rangeEndDate;
704 if (this._selected instanceof DateRange) {
705 rangeStartDate = this._getDateInCurrentMonth(this._selected.start);
706 rangeEndDate = this._getDateInCurrentMonth(this._selected.end);
707 }
708 else {
709 rangeStartDate = rangeEndDate = this._getDateInCurrentMonth(this._selected);
710 }
711 if (rangeStartDate !== date || rangeEndDate !== date) {
712 this.selectedChange.emit(selectedDate);
713 }
714 this._userSelection.emit({ value: selectedDate, event: event.event });
715 this._previewStart = this._previewEnd = null;
716 this._changeDetectorRef.markForCheck();
717 }
718 /** Handles keydown events on the calendar body when calendar is in month view. */
719 _handleCalendarBodyKeydown(event) {
720 // TODO(mmalerba): We currently allow keyboard navigation to disabled dates, but just prevent
721 // disabled ones from being selected. This may not be ideal, we should look into whether
722 // navigation should skip over disabled dates, and if so, how to implement that efficiently.
723 const oldActiveDate = this._activeDate;
724 const isRtl = this._isRtl();
725 switch (event.keyCode) {
726 case LEFT_ARROW:
727 this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, isRtl ? 1 : -1);
728 break;
729 case RIGHT_ARROW:
730 this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, isRtl ? -1 : 1);
731 break;
732 case UP_ARROW:
733 this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, -7);
734 break;
735 case DOWN_ARROW:
736 this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, 7);
737 break;
738 case HOME:
739 this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, 1 - this._dateAdapter.getDate(this._activeDate));
740 break;
741 case END:
742 this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, (this._dateAdapter.getNumDaysInMonth(this._activeDate) -
743 this._dateAdapter.getDate(this._activeDate)));
744 break;
745 case PAGE_UP:
746 this.activeDate = event.altKey ?
747 this._dateAdapter.addCalendarYears(this._activeDate, -1) :
748 this._dateAdapter.addCalendarMonths(this._activeDate, -1);
749 break;
750 case PAGE_DOWN:
751 this.activeDate = event.altKey ?
752 this._dateAdapter.addCalendarYears(this._activeDate, 1) :
753 this._dateAdapter.addCalendarMonths(this._activeDate, 1);
754 break;
755 case ENTER:
756 case SPACE:
757 this._selectionKeyPressed = true;
758 if (this._canSelect(this._activeDate)) {
759 // Prevent unexpected default actions such as form submission.
760 // Note that we only prevent the default action here while the selection happens in
761 // `keyup` below. We can't do the selection here, because it can cause the calendar to
762 // reopen if focus is restored immediately. We also can't call `preventDefault` on `keyup`
763 // because it's too late (see #23305).
764 event.preventDefault();
765 }
766 return;
767 case ESCAPE:
768 // Abort the current range selection if the user presses escape mid-selection.
769 if (this._previewEnd != null && !hasModifierKey(event)) {
770 this._previewStart = this._previewEnd = null;
771 this.selectedChange.emit(null);
772 this._userSelection.emit({ value: null, event });
773 event.preventDefault();
774 event.stopPropagation(); // Prevents the overlay from closing.
775 }
776 return;
777 default:
778 // Don't prevent default or focus active cell on keys that we don't explicitly handle.
779 return;
780 }
781 if (this._dateAdapter.compareDate(oldActiveDate, this.activeDate)) {
782 this.activeDateChange.emit(this.activeDate);
783 }
784 this._focusActiveCell();
785 // Prevent unexpected default actions such as form submission.
786 event.preventDefault();
787 }
788 /** Handles keyup events on the calendar body when calendar is in month view. */
789 _handleCalendarBodyKeyup(event) {
790 if (event.keyCode === SPACE || event.keyCode === ENTER) {
791 if (this._selectionKeyPressed && this._canSelect(this._activeDate)) {
792 this._dateSelected({ value: this._dateAdapter.getDate(this._activeDate), event });
793 }
794 this._selectionKeyPressed = false;
795 }
796 }
797 /** Initializes this month view. */
798 _init() {
799 this._setRanges(this.selected);
800 this._todayDate = this._getCellCompareValue(this._dateAdapter.today());
801 this._monthLabel = this._dateFormats.display.monthLabel
802 ? this._dateAdapter.format(this.activeDate, this._dateFormats.display.monthLabel)
803 : this._dateAdapter.getMonthNames('short')[this._dateAdapter.getMonth(this.activeDate)]
804 .toLocaleUpperCase();
805 let firstOfMonth = this._dateAdapter.createDate(this._dateAdapter.getYear(this.activeDate), this._dateAdapter.getMonth(this.activeDate), 1);
806 this._firstWeekOffset =
807 (DAYS_PER_WEEK + this._dateAdapter.getDayOfWeek(firstOfMonth) -
808 this._dateAdapter.getFirstDayOfWeek()) % DAYS_PER_WEEK;
809 this._initWeekdays();
810 this._createWeekCells();
811 this._changeDetectorRef.markForCheck();
812 }
813 /** Focuses the active cell after the microtask queue is empty. */
814 _focusActiveCell(movePreview) {
815 this._matCalendarBody._focusActiveCell(movePreview);
816 }
817 /** Called when the user has activated a new cell and the preview needs to be updated. */
818 _previewChanged({ event, value: cell }) {
819 if (this._rangeStrategy) {
820 // We can assume that this will be a range, because preview
821 // events aren't fired for single date selections.
822 const value = cell ? cell.rawValue : null;
823 const previewRange = this._rangeStrategy.createPreview(value, this.selected, event);
824 this._previewStart = this._getCellCompareValue(previewRange.start);
825 this._previewEnd = this._getCellCompareValue(previewRange.end);
826 // Note that here we need to use `detectChanges`, rather than `markForCheck`, because
827 // the way `_focusActiveCell` is set up at the moment makes it fire at the wrong time
828 // when navigating one month back using the keyboard which will cause this handler
829 // to throw a "changed after checked" error when updating the preview state.
830 this._changeDetectorRef.detectChanges();
831 }
832 }
833 /** Initializes the weekdays. */
834 _initWeekdays() {
835 const firstDayOfWeek = this._dateAdapter.getFirstDayOfWeek();
836 const narrowWeekdays = this._dateAdapter.getDayOfWeekNames('narrow');
837 const longWeekdays = this._dateAdapter.getDayOfWeekNames('long');
838 // Rotate the labels for days of the week based on the configured first day of the week.
839 let weekdays = longWeekdays.map((long, i) => {
840 return { long, narrow: narrowWeekdays[i] };
841 });
842 this._weekdays = weekdays.slice(firstDayOfWeek).concat(weekdays.slice(0, firstDayOfWeek));
843 }
844 /** Creates MatCalendarCells for the dates in this month. */
845 _createWeekCells() {
846 const daysInMonth = this._dateAdapter.getNumDaysInMonth(this.activeDate);
847 const dateNames = this._dateAdapter.getDateNames();
848 this._weeks = [[]];
849 for (let i = 0, cell = this._firstWeekOffset; i < daysInMonth; i++, cell++) {
850 if (cell == DAYS_PER_WEEK) {
851 this._weeks.push([]);
852 cell = 0;
853 }
854 const date = this._dateAdapter.createDate(this._dateAdapter.getYear(this.activeDate), this._dateAdapter.getMonth(this.activeDate), i + 1);
855 const enabled = this._shouldEnableDate(date);
856 const ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.dateA11yLabel);
857 const cellClasses = this.dateClass ? this.dateClass(date, 'month') : undefined;
858 this._weeks[this._weeks.length - 1].push(new MatCalendarCell(i + 1, dateNames[i], ariaLabel, enabled, cellClasses, this._getCellCompareValue(date), date));
859 }
860 }
861 /** Date filter for the month */
862 _shouldEnableDate(date) {
863 return !!date &&
864 (!this.minDate || this._dateAdapter.compareDate(date, this.minDate) >= 0) &&
865 (!this.maxDate || this._dateAdapter.compareDate(date, this.maxDate) <= 0) &&
866 (!this.dateFilter || this.dateFilter(date));
867 }
868 /**
869 * Gets the date in this month that the given Date falls on.
870 * Returns null if the given Date is in another month.
871 */
872 _getDateInCurrentMonth(date) {
873 return date && this._hasSameMonthAndYear(date, this.activeDate) ?
874 this._dateAdapter.getDate(date) : null;
875 }
876 /** Checks whether the 2 dates are non-null and fall within the same month of the same year. */
877 _hasSameMonthAndYear(d1, d2) {
878 return !!(d1 && d2 && this._dateAdapter.getMonth(d1) == this._dateAdapter.getMonth(d2) &&
879 this._dateAdapter.getYear(d1) == this._dateAdapter.getYear(d2));
880 }
881 /** Gets the value that will be used to one cell to another. */
882 _getCellCompareValue(date) {
883 if (date) {
884 // We use the time since the Unix epoch to compare dates in this view, rather than the
885 // cell values, because we need to support ranges that span across multiple months/years.
886 const year = this._dateAdapter.getYear(date);
887 const month = this._dateAdapter.getMonth(date);
888 const day = this._dateAdapter.getDate(date);
889 return new Date(year, month, day).getTime();
890 }
891 return null;
892 }
893 /** Determines whether the user has the RTL layout direction. */
894 _isRtl() {
895 return this._dir && this._dir.value === 'rtl';
896 }
897 /** Sets the current range based on a model value. */
898 _setRanges(selectedValue) {
899 if (selectedValue instanceof DateRange) {
900 this._rangeStart = this._getCellCompareValue(selectedValue.start);
901 this._rangeEnd = this._getCellCompareValue(selectedValue.end);
902 this._isRange = true;
903 }
904 else {
905 this._rangeStart = this._rangeEnd = this._getCellCompareValue(selectedValue);
906 this._isRange = false;
907 }
908 this._comparisonRangeStart = this._getCellCompareValue(this.comparisonStart);
909 this._comparisonRangeEnd = this._getCellCompareValue(this.comparisonEnd);
910 }
911 /** Gets whether a date can be selected in the month view. */
912 _canSelect(date) {
913 return !this.dateFilter || this.dateFilter(date);
914 }
915}
916MatMonthView.decorators = [
917 { type: Component, args: [{
918 selector: 'mat-month-view',
919 template: "<table class=\"mat-calendar-table\" role=\"grid\">\n <thead class=\"mat-calendar-table-header\">\n <tr>\n <!-- For the day-of-the-week column header, we use an `<abbr>` element because VoiceOver\n ignores the `aria-label`. ChromeVox, however, does not read the full name\n for the `<abbr>`, so we still set `aria-label` on the header element. -->\n <th scope=\"col\" *ngFor=\"let day of _weekdays\" [attr.aria-label]=\"day.long\">\n <abbr class=\"mat-calendar-abbr\" [attr.title]=\"day.long\">{{day.narrow}}</abbr>\n </th>\n </tr>\n <tr><th aria-hidden=\"true\" class=\"mat-calendar-table-header-divider\" colspan=\"7\"></th></tr>\n </thead>\n <tbody mat-calendar-body\n [label]=\"_monthLabel\"\n [rows]=\"_weeks\"\n [todayValue]=\"_todayDate!\"\n [startValue]=\"_rangeStart!\"\n [endValue]=\"_rangeEnd!\"\n [comparisonStart]=\"_comparisonRangeStart\"\n [comparisonEnd]=\"_comparisonRangeEnd\"\n [previewStart]=\"_previewStart\"\n [previewEnd]=\"_previewEnd\"\n [isRange]=\"_isRange\"\n [labelMinRequiredCells]=\"3\"\n [activeCell]=\"_dateAdapter.getDate(activeDate) - 1\"\n (selectedValueChange)=\"_dateSelected($event)\"\n (previewChange)=\"_previewChanged($event)\"\n (keyup)=\"_handleCalendarBodyKeyup($event)\"\n (keydown)=\"_handleCalendarBodyKeydown($event)\">\n </tbody>\n</table>\n",
920 exportAs: 'matMonthView',
921 encapsulation: ViewEncapsulation.None,
922 changeDetection: ChangeDetectionStrategy.OnPush
923 },] }
924];
925MatMonthView.ctorParameters = () => [
926 { type: ChangeDetectorRef },
927 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_DATE_FORMATS,] }] },
928 { type: DateAdapter, decorators: [{ type: Optional }] },
929 { type: Directionality, decorators: [{ type: Optional }] },
930 { type: undefined, decorators: [{ type: Inject, args: [MAT_DATE_RANGE_SELECTION_STRATEGY,] }, { type: Optional }] }
931];
932MatMonthView.propDecorators = {
933 activeDate: [{ type: Input }],
934 selected: [{ type: Input }],
935 minDate: [{ type: Input }],
936 maxDate: [{ type: Input }],
937 dateFilter: [{ type: Input }],
938 dateClass: [{ type: Input }],
939 comparisonStart: [{ type: Input }],
940 comparisonEnd: [{ type: Input }],
941 selectedChange: [{ type: Output }],
942 _userSelection: [{ type: Output }],
943 activeDateChange: [{ type: Output }],
944 _matCalendarBody: [{ type: ViewChild, args: [MatCalendarBody,] }]
945};
946
947/**
948 * @license
949 * Copyright Google LLC All Rights Reserved.
950 *
951 * Use of this source code is governed by an MIT-style license that can be
952 * found in the LICENSE file at https://angular.io/license
953 */
954const yearsPerPage = 24;
955const yearsPerRow = 4;
956/**
957 * An internal component used to display a year selector in the datepicker.
958 * @docs-private
959 */
960class MatMultiYearView {
961 constructor(_changeDetectorRef, _dateAdapter, _dir) {
962 this._changeDetectorRef = _changeDetectorRef;
963 this._dateAdapter = _dateAdapter;
964 this._dir = _dir;
965 this._rerenderSubscription = Subscription.EMPTY;
966 /** Emits when a new year is selected. */
967 this.selectedChange = new EventEmitter();
968 /** Emits the selected year. This doesn't imply a change on the selected date */
969 this.yearSelected = new EventEmitter();
970 /** Emits when any date is activated. */
971 this.activeDateChange = new EventEmitter();
972 if (!this._dateAdapter && (typeof ngDevMode === 'undefined' || ngDevMode)) {
973 throw createMissingDateImplError('DateAdapter');
974 }
975 this._activeDate = this._dateAdapter.today();
976 }
977 /** The date to display in this multi-year view (everything other than the year is ignored). */
978 get activeDate() { return this._activeDate; }
979 set activeDate(value) {
980 let oldActiveDate = this._activeDate;
981 const validDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)) || this._dateAdapter.today();
982 this._activeDate = this._dateAdapter.clampDate(validDate, this.minDate, this.maxDate);
983 if (!isSameMultiYearView(this._dateAdapter, oldActiveDate, this._activeDate, this.minDate, this.maxDate)) {
984 this._init();
985 }
986 }
987 /** The currently selected date. */
988 get selected() { return this._selected; }
989 set selected(value) {
990 if (value instanceof DateRange) {
991 this._selected = value;
992 }
993 else {
994 this._selected = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
995 }
996 this._setSelectedYear(value);
997 }
998 /** The minimum selectable date. */
999 get minDate() { return this._minDate; }
1000 set minDate(value) {
1001 this._minDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
1002 }
1003 /** The maximum selectable date. */
1004 get maxDate() { return this._maxDate; }
1005 set maxDate(value) {
1006 this._maxDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
1007 }
1008 ngAfterContentInit() {
1009 this._rerenderSubscription = this._dateAdapter.localeChanges
1010 .pipe(startWith(null))
1011 .subscribe(() => this._init());
1012 }
1013 ngOnDestroy() {
1014 this._rerenderSubscription.unsubscribe();
1015 }
1016 /** Initializes this multi-year view. */
1017 _init() {
1018 this._todayYear = this._dateAdapter.getYear(this._dateAdapter.today());
1019 // We want a range years such that we maximize the number of
1020 // enabled dates visible at once. This prevents issues where the minimum year
1021 // is the last item of a page OR the maximum year is the first item of a page.
1022 // The offset from the active year to the "slot" for the starting year is the
1023 // *actual* first rendered year in the multi-year view.
1024 const activeYear = this._dateAdapter.getYear(this._activeDate);
1025 const minYearOfPage = activeYear - getActiveOffset(this._dateAdapter, this.activeDate, this.minDate, this.maxDate);
1026 this._years = [];
1027 for (let i = 0, row = []; i < yearsPerPage; i++) {
1028 row.push(minYearOfPage + i);
1029 if (row.length == yearsPerRow) {
1030 this._years.push(row.map(year => this._createCellForYear(year)));
1031 row = [];
1032 }
1033 }
1034 this._changeDetectorRef.markForCheck();
1035 }
1036 /** Handles when a new year is selected. */
1037 _yearSelected(event) {
1038 const year = event.value;
1039 this.yearSelected.emit(this._dateAdapter.createDate(year, 0, 1));
1040 let month = this._dateAdapter.getMonth(this.activeDate);
1041 let daysInMonth = this._dateAdapter.getNumDaysInMonth(this._dateAdapter.createDate(year, month, 1));
1042 this.selectedChange.emit(this._dateAdapter.createDate(year, month, Math.min(this._dateAdapter.getDate(this.activeDate), daysInMonth)));
1043 }
1044 /** Handles keydown events on the calendar body when calendar is in multi-year view. */
1045 _handleCalendarBodyKeydown(event) {
1046 const oldActiveDate = this._activeDate;
1047 const isRtl = this._isRtl();
1048 switch (event.keyCode) {
1049 case LEFT_ARROW:
1050 this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, isRtl ? 1 : -1);
1051 break;
1052 case RIGHT_ARROW:
1053 this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, isRtl ? -1 : 1);
1054 break;
1055 case UP_ARROW:
1056 this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, -yearsPerRow);
1057 break;
1058 case DOWN_ARROW:
1059 this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, yearsPerRow);
1060 break;
1061 case HOME:
1062 this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, -getActiveOffset(this._dateAdapter, this.activeDate, this.minDate, this.maxDate));
1063 break;
1064 case END:
1065 this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, yearsPerPage - getActiveOffset(this._dateAdapter, this.activeDate, this.minDate, this.maxDate) - 1);
1066 break;
1067 case PAGE_UP:
1068 this.activeDate =
1069 this._dateAdapter.addCalendarYears(this._activeDate, event.altKey ? -yearsPerPage * 10 : -yearsPerPage);
1070 break;
1071 case PAGE_DOWN:
1072 this.activeDate =
1073 this._dateAdapter.addCalendarYears(this._activeDate, event.altKey ? yearsPerPage * 10 : yearsPerPage);
1074 break;
1075 case ENTER:
1076 case SPACE:
1077 // Note that we only prevent the default action here while the selection happens in
1078 // `keyup` below. We can't do the selection here, because it can cause the calendar to
1079 // reopen if focus is restored immediately. We also can't call `preventDefault` on `keyup`
1080 // because it's too late (see #23305).
1081 this._selectionKeyPressed = true;
1082 break;
1083 default:
1084 // Don't prevent default or focus active cell on keys that we don't explicitly handle.
1085 return;
1086 }
1087 if (this._dateAdapter.compareDate(oldActiveDate, this.activeDate)) {
1088 this.activeDateChange.emit(this.activeDate);
1089 }
1090 this._focusActiveCell();
1091 // Prevent unexpected default actions such as form submission.
1092 event.preventDefault();
1093 }
1094 /** Handles keyup events on the calendar body when calendar is in multi-year view. */
1095 _handleCalendarBodyKeyup(event) {
1096 if (event.keyCode === SPACE || event.keyCode === ENTER) {
1097 if (this._selectionKeyPressed) {
1098 this._yearSelected({ value: this._dateAdapter.getYear(this._activeDate), event });
1099 }
1100 this._selectionKeyPressed = false;
1101 }
1102 }
1103 _getActiveCell() {
1104 return getActiveOffset(this._dateAdapter, this.activeDate, this.minDate, this.maxDate);
1105 }
1106 /** Focuses the active cell after the microtask queue is empty. */
1107 _focusActiveCell() {
1108 this._matCalendarBody._focusActiveCell();
1109 }
1110 /** Creates an MatCalendarCell for the given year. */
1111 _createCellForYear(year) {
1112 const date = this._dateAdapter.createDate(year, 0, 1);
1113 const yearName = this._dateAdapter.getYearName(date);
1114 const cellClasses = this.dateClass ? this.dateClass(date, 'multi-year') : undefined;
1115 return new MatCalendarCell(year, yearName, yearName, this._shouldEnableYear(year), cellClasses);
1116 }
1117 /** Whether the given year is enabled. */
1118 _shouldEnableYear(year) {
1119 // disable if the year is greater than maxDate lower than minDate
1120 if (year === undefined || year === null ||
1121 (this.maxDate && year > this._dateAdapter.getYear(this.maxDate)) ||
1122 (this.minDate && year < this._dateAdapter.getYear(this.minDate))) {
1123 return false;
1124 }
1125 // enable if it reaches here and there's no filter defined
1126 if (!this.dateFilter) {
1127 return true;
1128 }
1129 const firstOfYear = this._dateAdapter.createDate(year, 0, 1);
1130 // If any date in the year is enabled count the year as enabled.
1131 for (let date = firstOfYear; this._dateAdapter.getYear(date) == year; date = this._dateAdapter.addCalendarDays(date, 1)) {
1132 if (this.dateFilter(date)) {
1133 return true;
1134 }
1135 }
1136 return false;
1137 }
1138 /** Determines whether the user has the RTL layout direction. */
1139 _isRtl() {
1140 return this._dir && this._dir.value === 'rtl';
1141 }
1142 /** Sets the currently-highlighted year based on a model value. */
1143 _setSelectedYear(value) {
1144 this._selectedYear = null;
1145 if (value instanceof DateRange) {
1146 const displayValue = value.start || value.end;
1147 if (displayValue) {
1148 this._selectedYear = this._dateAdapter.getYear(displayValue);
1149 }
1150 }
1151 else if (value) {
1152 this._selectedYear = this._dateAdapter.getYear(value);
1153 }
1154 }
1155}
1156MatMultiYearView.decorators = [
1157 { type: Component, args: [{
1158 selector: 'mat-multi-year-view',
1159 template: "<table class=\"mat-calendar-table\" role=\"grid\">\n <thead aria-hidden=\"true\" 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 [startValue]=\"_selectedYear!\"\n [endValue]=\"_selectedYear!\"\n [numCols]=\"4\"\n [cellAspectRatio]=\"4 / 7\"\n [activeCell]=\"_getActiveCell()\"\n (selectedValueChange)=\"_yearSelected($event)\"\n (keyup)=\"_handleCalendarBodyKeyup($event)\"\n (keydown)=\"_handleCalendarBodyKeydown($event)\">\n </tbody>\n</table>\n",
1160 exportAs: 'matMultiYearView',
1161 encapsulation: ViewEncapsulation.None,
1162 changeDetection: ChangeDetectionStrategy.OnPush
1163 },] }
1164];
1165MatMultiYearView.ctorParameters = () => [
1166 { type: ChangeDetectorRef },
1167 { type: DateAdapter, decorators: [{ type: Optional }] },
1168 { type: Directionality, decorators: [{ type: Optional }] }
1169];
1170MatMultiYearView.propDecorators = {
1171 activeDate: [{ type: Input }],
1172 selected: [{ type: Input }],
1173 minDate: [{ type: Input }],
1174 maxDate: [{ type: Input }],
1175 dateFilter: [{ type: Input }],
1176 dateClass: [{ type: Input }],
1177 selectedChange: [{ type: Output }],
1178 yearSelected: [{ type: Output }],
1179 activeDateChange: [{ type: Output }],
1180 _matCalendarBody: [{ type: ViewChild, args: [MatCalendarBody,] }]
1181};
1182function isSameMultiYearView(dateAdapter, date1, date2, minDate, maxDate) {
1183 const year1 = dateAdapter.getYear(date1);
1184 const year2 = dateAdapter.getYear(date2);
1185 const startingYear = getStartingYear(dateAdapter, minDate, maxDate);
1186 return Math.floor((year1 - startingYear) / yearsPerPage) ===
1187 Math.floor((year2 - startingYear) / yearsPerPage);
1188}
1189/**
1190 * When the multi-year view is first opened, the active year will be in view.
1191 * So we compute how many years are between the active year and the *slot* where our
1192 * "startingYear" will render when paged into view.
1193 */
1194function getActiveOffset(dateAdapter, activeDate, minDate, maxDate) {
1195 const activeYear = dateAdapter.getYear(activeDate);
1196 return euclideanModulo((activeYear - getStartingYear(dateAdapter, minDate, maxDate)), yearsPerPage);
1197}
1198/**
1199 * We pick a "starting" year such that either the maximum year would be at the end
1200 * or the minimum year would be at the beginning of a page.
1201 */
1202function getStartingYear(dateAdapter, minDate, maxDate) {
1203 let startingYear = 0;
1204 if (maxDate) {
1205 const maxYear = dateAdapter.getYear(maxDate);
1206 startingYear = maxYear - yearsPerPage + 1;
1207 }
1208 else if (minDate) {
1209 startingYear = dateAdapter.getYear(minDate);
1210 }
1211 return startingYear;
1212}
1213/** Gets remainder that is non-negative, even if first number is negative */
1214function euclideanModulo(a, b) {
1215 return (a % b + b) % b;
1216}
1217
1218/**
1219 * @license
1220 * Copyright Google LLC All Rights Reserved.
1221 *
1222 * Use of this source code is governed by an MIT-style license that can be
1223 * found in the LICENSE file at https://angular.io/license
1224 */
1225/**
1226 * An internal component used to display a single year in the datepicker.
1227 * @docs-private
1228 */
1229class MatYearView {
1230 constructor(_changeDetectorRef, _dateFormats, _dateAdapter, _dir) {
1231 this._changeDetectorRef = _changeDetectorRef;
1232 this._dateFormats = _dateFormats;
1233 this._dateAdapter = _dateAdapter;
1234 this._dir = _dir;
1235 this._rerenderSubscription = Subscription.EMPTY;
1236 /** Emits when a new month is selected. */
1237 this.selectedChange = new EventEmitter();
1238 /** Emits the selected month. This doesn't imply a change on the selected date */
1239 this.monthSelected = new EventEmitter();
1240 /** Emits when any date is activated. */
1241 this.activeDateChange = new EventEmitter();
1242 if (typeof ngDevMode === 'undefined' || ngDevMode) {
1243 if (!this._dateAdapter) {
1244 throw createMissingDateImplError('DateAdapter');
1245 }
1246 if (!this._dateFormats) {
1247 throw createMissingDateImplError('MAT_DATE_FORMATS');
1248 }
1249 }
1250 this._activeDate = this._dateAdapter.today();
1251 }
1252 /** The date to display in this year view (everything other than the year is ignored). */
1253 get activeDate() { return this._activeDate; }
1254 set activeDate(value) {
1255 let oldActiveDate = this._activeDate;
1256 const validDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)) || this._dateAdapter.today();
1257 this._activeDate = this._dateAdapter.clampDate(validDate, this.minDate, this.maxDate);
1258 if (this._dateAdapter.getYear(oldActiveDate) !== this._dateAdapter.getYear(this._activeDate)) {
1259 this._init();
1260 }
1261 }
1262 /** The currently selected date. */
1263 get selected() { return this._selected; }
1264 set selected(value) {
1265 if (value instanceof DateRange) {
1266 this._selected = value;
1267 }
1268 else {
1269 this._selected = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
1270 }
1271 this._setSelectedMonth(value);
1272 }
1273 /** The minimum selectable date. */
1274 get minDate() { return this._minDate; }
1275 set minDate(value) {
1276 this._minDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
1277 }
1278 /** The maximum selectable date. */
1279 get maxDate() { return this._maxDate; }
1280 set maxDate(value) {
1281 this._maxDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
1282 }
1283 ngAfterContentInit() {
1284 this._rerenderSubscription = this._dateAdapter.localeChanges
1285 .pipe(startWith(null))
1286 .subscribe(() => this._init());
1287 }
1288 ngOnDestroy() {
1289 this._rerenderSubscription.unsubscribe();
1290 }
1291 /** Handles when a new month is selected. */
1292 _monthSelected(event) {
1293 const month = event.value;
1294 const normalizedDate = this._dateAdapter.createDate(this._dateAdapter.getYear(this.activeDate), month, 1);
1295 this.monthSelected.emit(normalizedDate);
1296 const daysInMonth = this._dateAdapter.getNumDaysInMonth(normalizedDate);
1297 this.selectedChange.emit(this._dateAdapter.createDate(this._dateAdapter.getYear(this.activeDate), month, Math.min(this._dateAdapter.getDate(this.activeDate), daysInMonth)));
1298 }
1299 /** Handles keydown events on the calendar body when calendar is in year view. */
1300 _handleCalendarBodyKeydown(event) {
1301 // TODO(mmalerba): We currently allow keyboard navigation to disabled dates, but just prevent
1302 // disabled ones from being selected. This may not be ideal, we should look into whether
1303 // navigation should skip over disabled dates, and if so, how to implement that efficiently.
1304 const oldActiveDate = this._activeDate;
1305 const isRtl = this._isRtl();
1306 switch (event.keyCode) {
1307 case LEFT_ARROW:
1308 this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, isRtl ? 1 : -1);
1309 break;
1310 case RIGHT_ARROW:
1311 this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, isRtl ? -1 : 1);
1312 break;
1313 case UP_ARROW:
1314 this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, -4);
1315 break;
1316 case DOWN_ARROW:
1317 this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, 4);
1318 break;
1319 case HOME:
1320 this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, -this._dateAdapter.getMonth(this._activeDate));
1321 break;
1322 case END:
1323 this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, 11 - this._dateAdapter.getMonth(this._activeDate));
1324 break;
1325 case PAGE_UP:
1326 this.activeDate =
1327 this._dateAdapter.addCalendarYears(this._activeDate, event.altKey ? -10 : -1);
1328 break;
1329 case PAGE_DOWN:
1330 this.activeDate =
1331 this._dateAdapter.addCalendarYears(this._activeDate, event.altKey ? 10 : 1);
1332 break;
1333 case ENTER:
1334 case SPACE:
1335 // Note that we only prevent the default action here while the selection happens in
1336 // `keyup` below. We can't do the selection here, because it can cause the calendar to
1337 // reopen if focus is restored immediately. We also can't call `preventDefault` on `keyup`
1338 // because it's too late (see #23305).
1339 this._selectionKeyPressed = true;
1340 break;
1341 default:
1342 // Don't prevent default or focus active cell on keys that we don't explicitly handle.
1343 return;
1344 }
1345 if (this._dateAdapter.compareDate(oldActiveDate, this.activeDate)) {
1346 this.activeDateChange.emit(this.activeDate);
1347 }
1348 this._focusActiveCell();
1349 // Prevent unexpected default actions such as form submission.
1350 event.preventDefault();
1351 }
1352 /** Handles keyup events on the calendar body when calendar is in year view. */
1353 _handleCalendarBodyKeyup(event) {
1354 if (event.keyCode === SPACE || event.keyCode === ENTER) {
1355 if (this._selectionKeyPressed) {
1356 this._monthSelected({ value: this._dateAdapter.getMonth(this._activeDate), event });
1357 }
1358 this._selectionKeyPressed = false;
1359 }
1360 }
1361 /** Initializes this year view. */
1362 _init() {
1363 this._setSelectedMonth(this.selected);
1364 this._todayMonth = this._getMonthInCurrentYear(this._dateAdapter.today());
1365 this._yearLabel = this._dateAdapter.getYearName(this.activeDate);
1366 let monthNames = this._dateAdapter.getMonthNames('short');
1367 // First row of months only contains 5 elements so we can fit the year label on the same row.
1368 this._months = [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]].map(row => row.map(month => this._createCellForMonth(month, monthNames[month])));
1369 this._changeDetectorRef.markForCheck();
1370 }
1371 /** Focuses the active cell after the microtask queue is empty. */
1372 _focusActiveCell() {
1373 this._matCalendarBody._focusActiveCell();
1374 }
1375 /**
1376 * Gets the month in this year that the given Date falls on.
1377 * Returns null if the given Date is in another year.
1378 */
1379 _getMonthInCurrentYear(date) {
1380 return date && this._dateAdapter.getYear(date) == this._dateAdapter.getYear(this.activeDate) ?
1381 this._dateAdapter.getMonth(date) : null;
1382 }
1383 /** Creates an MatCalendarCell for the given month. */
1384 _createCellForMonth(month, monthName) {
1385 const date = this._dateAdapter.createDate(this._dateAdapter.getYear(this.activeDate), month, 1);
1386 const ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.monthYearA11yLabel);
1387 const cellClasses = this.dateClass ? this.dateClass(date, 'year') : undefined;
1388 return new MatCalendarCell(month, monthName.toLocaleUpperCase(), ariaLabel, this._shouldEnableMonth(month), cellClasses);
1389 }
1390 /** Whether the given month is enabled. */
1391 _shouldEnableMonth(month) {
1392 const activeYear = this._dateAdapter.getYear(this.activeDate);
1393 if (month === undefined || month === null ||
1394 this._isYearAndMonthAfterMaxDate(activeYear, month) ||
1395 this._isYearAndMonthBeforeMinDate(activeYear, month)) {
1396 return false;
1397 }
1398 if (!this.dateFilter) {
1399 return true;
1400 }
1401 const firstOfMonth = this._dateAdapter.createDate(activeYear, month, 1);
1402 // If any date in the month is enabled count the month as enabled.
1403 for (let date = firstOfMonth; this._dateAdapter.getMonth(date) == month; date = this._dateAdapter.addCalendarDays(date, 1)) {
1404 if (this.dateFilter(date)) {
1405 return true;
1406 }
1407 }
1408 return false;
1409 }
1410 /**
1411 * Tests whether the combination month/year is after this.maxDate, considering
1412 * just the month and year of this.maxDate
1413 */
1414 _isYearAndMonthAfterMaxDate(year, month) {
1415 if (this.maxDate) {
1416 const maxYear = this._dateAdapter.getYear(this.maxDate);
1417 const maxMonth = this._dateAdapter.getMonth(this.maxDate);
1418 return year > maxYear || (year === maxYear && month > maxMonth);
1419 }
1420 return false;
1421 }
1422 /**
1423 * Tests whether the combination month/year is before this.minDate, considering
1424 * just the month and year of this.minDate
1425 */
1426 _isYearAndMonthBeforeMinDate(year, month) {
1427 if (this.minDate) {
1428 const minYear = this._dateAdapter.getYear(this.minDate);
1429 const minMonth = this._dateAdapter.getMonth(this.minDate);
1430 return year < minYear || (year === minYear && month < minMonth);
1431 }
1432 return false;
1433 }
1434 /** Determines whether the user has the RTL layout direction. */
1435 _isRtl() {
1436 return this._dir && this._dir.value === 'rtl';
1437 }
1438 /** Sets the currently-selected month based on a model value. */
1439 _setSelectedMonth(value) {
1440 if (value instanceof DateRange) {
1441 this._selectedMonth = this._getMonthInCurrentYear(value.start) ||
1442 this._getMonthInCurrentYear(value.end);
1443 }
1444 else {
1445 this._selectedMonth = this._getMonthInCurrentYear(value);
1446 }
1447 }
1448}
1449MatYearView.decorators = [
1450 { type: Component, args: [{
1451 selector: 'mat-year-view',
1452 template: "<table class=\"mat-calendar-table\" role=\"grid\">\n <thead aria-hidden=\"true\" 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 [startValue]=\"_selectedMonth!\"\n [endValue]=\"_selectedMonth!\"\n [labelMinRequiredCells]=\"2\"\n [numCols]=\"4\"\n [cellAspectRatio]=\"4 / 7\"\n [activeCell]=\"_dateAdapter.getMonth(activeDate)\"\n (selectedValueChange)=\"_monthSelected($event)\"\n (keyup)=\"_handleCalendarBodyKeyup($event)\"\n (keydown)=\"_handleCalendarBodyKeydown($event)\">\n </tbody>\n</table>\n",
1453 exportAs: 'matYearView',
1454 encapsulation: ViewEncapsulation.None,
1455 changeDetection: ChangeDetectionStrategy.OnPush
1456 },] }
1457];
1458MatYearView.ctorParameters = () => [
1459 { type: ChangeDetectorRef },
1460 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_DATE_FORMATS,] }] },
1461 { type: DateAdapter, decorators: [{ type: Optional }] },
1462 { type: Directionality, decorators: [{ type: Optional }] }
1463];
1464MatYearView.propDecorators = {
1465 activeDate: [{ type: Input }],
1466 selected: [{ type: Input }],
1467 minDate: [{ type: Input }],
1468 maxDate: [{ type: Input }],
1469 dateFilter: [{ type: Input }],
1470 dateClass: [{ type: Input }],
1471 selectedChange: [{ type: Output }],
1472 monthSelected: [{ type: Output }],
1473 activeDateChange: [{ type: Output }],
1474 _matCalendarBody: [{ type: ViewChild, args: [MatCalendarBody,] }]
1475};
1476
1477/**
1478 * @license
1479 * Copyright Google LLC All Rights Reserved.
1480 *
1481 * Use of this source code is governed by an MIT-style license that can be
1482 * found in the LICENSE file at https://angular.io/license
1483 */
1484/** Counter used to generate unique IDs. */
1485let uniqueId = 0;
1486/** Default header for MatCalendar */
1487class MatCalendarHeader {
1488 constructor(_intl, calendar, _dateAdapter, _dateFormats, changeDetectorRef) {
1489 this._intl = _intl;
1490 this.calendar = calendar;
1491 this._dateAdapter = _dateAdapter;
1492 this._dateFormats = _dateFormats;
1493 this._buttonDescriptionId = `mat-calendar-button-${uniqueId++}`;
1494 this.calendar.stateChanges.subscribe(() => changeDetectorRef.markForCheck());
1495 }
1496 /** The label for the current calendar view. */
1497 get periodButtonText() {
1498 if (this.calendar.currentView == 'month') {
1499 return this._dateAdapter
1500 .format(this.calendar.activeDate, this._dateFormats.display.monthYearLabel)
1501 .toLocaleUpperCase();
1502 }
1503 if (this.calendar.currentView == 'year') {
1504 return this._dateAdapter.getYearName(this.calendar.activeDate);
1505 }
1506 // The offset from the active year to the "slot" for the starting year is the
1507 // *actual* first rendered year in the multi-year view, and the last year is
1508 // just yearsPerPage - 1 away.
1509 const activeYear = this._dateAdapter.getYear(this.calendar.activeDate);
1510 const minYearOfPage = activeYear - getActiveOffset(this._dateAdapter, this.calendar.activeDate, this.calendar.minDate, this.calendar.maxDate);
1511 const maxYearOfPage = minYearOfPage + yearsPerPage - 1;
1512 const minYearName = this._dateAdapter.getYearName(this._dateAdapter.createDate(minYearOfPage, 0, 1));
1513 const maxYearName = this._dateAdapter.getYearName(this._dateAdapter.createDate(maxYearOfPage, 0, 1));
1514 return this._intl.formatYearRange(minYearName, maxYearName);
1515 }
1516 get periodButtonLabel() {
1517 return this.calendar.currentView == 'month' ?
1518 this._intl.switchToMultiYearViewLabel : this._intl.switchToMonthViewLabel;
1519 }
1520 /** The label for the previous button. */
1521 get prevButtonLabel() {
1522 return {
1523 'month': this._intl.prevMonthLabel,
1524 'year': this._intl.prevYearLabel,
1525 'multi-year': this._intl.prevMultiYearLabel
1526 }[this.calendar.currentView];
1527 }
1528 /** The label for the next button. */
1529 get nextButtonLabel() {
1530 return {
1531 'month': this._intl.nextMonthLabel,
1532 'year': this._intl.nextYearLabel,
1533 'multi-year': this._intl.nextMultiYearLabel
1534 }[this.calendar.currentView];
1535 }
1536 /** Handles user clicks on the period label. */
1537 currentPeriodClicked() {
1538 this.calendar.currentView = this.calendar.currentView == 'month' ? 'multi-year' : 'month';
1539 }
1540 /** Handles user clicks on the previous button. */
1541 previousClicked() {
1542 this.calendar.activeDate = this.calendar.currentView == 'month' ?
1543 this._dateAdapter.addCalendarMonths(this.calendar.activeDate, -1) :
1544 this._dateAdapter.addCalendarYears(this.calendar.activeDate, this.calendar.currentView == 'year' ? -1 : -yearsPerPage);
1545 }
1546 /** Handles user clicks on the next button. */
1547 nextClicked() {
1548 this.calendar.activeDate = this.calendar.currentView == 'month' ?
1549 this._dateAdapter.addCalendarMonths(this.calendar.activeDate, 1) :
1550 this._dateAdapter.addCalendarYears(this.calendar.activeDate, this.calendar.currentView == 'year' ? 1 : yearsPerPage);
1551 }
1552 /** Whether the previous period button is enabled. */
1553 previousEnabled() {
1554 if (!this.calendar.minDate) {
1555 return true;
1556 }
1557 return !this.calendar.minDate ||
1558 !this._isSameView(this.calendar.activeDate, this.calendar.minDate);
1559 }
1560 /** Whether the next period button is enabled. */
1561 nextEnabled() {
1562 return !this.calendar.maxDate ||
1563 !this._isSameView(this.calendar.activeDate, this.calendar.maxDate);
1564 }
1565 /** Whether the two dates represent the same view in the current view mode (month or year). */
1566 _isSameView(date1, date2) {
1567 if (this.calendar.currentView == 'month') {
1568 return this._dateAdapter.getYear(date1) == this._dateAdapter.getYear(date2) &&
1569 this._dateAdapter.getMonth(date1) == this._dateAdapter.getMonth(date2);
1570 }
1571 if (this.calendar.currentView == 'year') {
1572 return this._dateAdapter.getYear(date1) == this._dateAdapter.getYear(date2);
1573 }
1574 // Otherwise we are in 'multi-year' view.
1575 return isSameMultiYearView(this._dateAdapter, date1, date2, this.calendar.minDate, this.calendar.maxDate);
1576 }
1577}
1578MatCalendarHeader.decorators = [
1579 { type: Component, args: [{
1580 selector: 'mat-calendar-header',
1581 template: "<div class=\"mat-calendar-header\">\n <div class=\"mat-calendar-controls\">\n <button mat-button type=\"button\" class=\"mat-calendar-period-button\"\n (click)=\"currentPeriodClicked()\" [attr.aria-label]=\"periodButtonLabel\"\n [attr.aria-describedby]=\"_buttonDescriptionId\"\n cdkAriaLive=\"polite\">\n <span [attr.id]=\"_buttonDescriptionId\">{{periodButtonText}}</span>\n <div class=\"mat-calendar-arrow\"\n [class.mat-calendar-invert]=\"calendar.currentView !== 'month'\"></div>\n </button>\n\n <div class=\"mat-calendar-spacer\"></div>\n\n <ng-content></ng-content>\n\n <button mat-icon-button type=\"button\" class=\"mat-calendar-previous-button\"\n [disabled]=\"!previousEnabled()\" (click)=\"previousClicked()\"\n [attr.aria-label]=\"prevButtonLabel\">\n </button>\n\n <button mat-icon-button type=\"button\" class=\"mat-calendar-next-button\"\n [disabled]=\"!nextEnabled()\" (click)=\"nextClicked()\"\n [attr.aria-label]=\"nextButtonLabel\">\n </button>\n </div>\n</div>\n",
1582 exportAs: 'matCalendarHeader',
1583 encapsulation: ViewEncapsulation.None,
1584 changeDetection: ChangeDetectionStrategy.OnPush
1585 },] }
1586];
1587MatCalendarHeader.ctorParameters = () => [
1588 { type: MatDatepickerIntl },
1589 { type: MatCalendar, decorators: [{ type: Inject, args: [forwardRef(() => MatCalendar),] }] },
1590 { type: DateAdapter, decorators: [{ type: Optional }] },
1591 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_DATE_FORMATS,] }] },
1592 { type: ChangeDetectorRef }
1593];
1594/** A calendar that is used as part of the datepicker. */
1595class MatCalendar {
1596 constructor(_intl, _dateAdapter, _dateFormats, _changeDetectorRef) {
1597 this._dateAdapter = _dateAdapter;
1598 this._dateFormats = _dateFormats;
1599 this._changeDetectorRef = _changeDetectorRef;
1600 /**
1601 * Used for scheduling that focus should be moved to the active cell on the next tick.
1602 * We need to schedule it, rather than do it immediately, because we have to wait
1603 * for Angular to re-evaluate the view children.
1604 */
1605 this._moveFocusOnNextTick = false;
1606 /** Whether the calendar should be started in month or year view. */
1607 this.startView = 'month';
1608 /** Emits when the currently selected date changes. */
1609 this.selectedChange = new EventEmitter();
1610 /**
1611 * Emits the year chosen in multiyear view.
1612 * This doesn't imply a change on the selected date.
1613 */
1614 this.yearSelected = new EventEmitter();
1615 /**
1616 * Emits the month chosen in year view.
1617 * This doesn't imply a change on the selected date.
1618 */
1619 this.monthSelected = new EventEmitter();
1620 /**
1621 * Emits when the current view changes.
1622 */
1623 this.viewChanged = new EventEmitter(true);
1624 /** Emits when any date is selected. */
1625 this._userSelection = new EventEmitter();
1626 /**
1627 * Emits whenever there is a state change that the header may need to respond to.
1628 */
1629 this.stateChanges = new Subject();
1630 if (typeof ngDevMode === 'undefined' || ngDevMode) {
1631 if (!this._dateAdapter) {
1632 throw createMissingDateImplError('DateAdapter');
1633 }
1634 if (!this._dateFormats) {
1635 throw createMissingDateImplError('MAT_DATE_FORMATS');
1636 }
1637 }
1638 this._intlChanges = _intl.changes.subscribe(() => {
1639 _changeDetectorRef.markForCheck();
1640 this.stateChanges.next();
1641 });
1642 }
1643 /** A date representing the period (month or year) to start the calendar in. */
1644 get startAt() { return this._startAt; }
1645 set startAt(value) {
1646 this._startAt = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
1647 }
1648 /** The currently selected date. */
1649 get selected() { return this._selected; }
1650 set selected(value) {
1651 if (value instanceof DateRange) {
1652 this._selected = value;
1653 }
1654 else {
1655 this._selected = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
1656 }
1657 }
1658 /** The minimum selectable date. */
1659 get minDate() { return this._minDate; }
1660 set minDate(value) {
1661 this._minDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
1662 }
1663 /** The maximum selectable date. */
1664 get maxDate() { return this._maxDate; }
1665 set maxDate(value) {
1666 this._maxDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
1667 }
1668 /**
1669 * The current active date. This determines which time period is shown and which date is
1670 * highlighted when using keyboard navigation.
1671 */
1672 get activeDate() { return this._clampedActiveDate; }
1673 set activeDate(value) {
1674 this._clampedActiveDate = this._dateAdapter.clampDate(value, this.minDate, this.maxDate);
1675 this.stateChanges.next();
1676 this._changeDetectorRef.markForCheck();
1677 }
1678 /** Whether the calendar is in month view. */
1679 get currentView() { return this._currentView; }
1680 set currentView(value) {
1681 const viewChangedResult = this._currentView !== value ? value : null;
1682 this._currentView = value;
1683 this._moveFocusOnNextTick = true;
1684 this._changeDetectorRef.markForCheck();
1685 if (viewChangedResult) {
1686 this.viewChanged.emit(viewChangedResult);
1687 }
1688 }
1689 ngAfterContentInit() {
1690 this._calendarHeaderPortal = new ComponentPortal(this.headerComponent || MatCalendarHeader);
1691 this.activeDate = this.startAt || this._dateAdapter.today();
1692 // Assign to the private property since we don't want to move focus on init.
1693 this._currentView = this.startView;
1694 }
1695 ngAfterViewChecked() {
1696 if (this._moveFocusOnNextTick) {
1697 this._moveFocusOnNextTick = false;
1698 this.focusActiveCell();
1699 }
1700 }
1701 ngOnDestroy() {
1702 this._intlChanges.unsubscribe();
1703 this.stateChanges.complete();
1704 }
1705 ngOnChanges(changes) {
1706 const change = changes['minDate'] || changes['maxDate'] || changes['dateFilter'];
1707 if (change && !change.firstChange) {
1708 const view = this._getCurrentViewComponent();
1709 if (view) {
1710 // We need to `detectChanges` manually here, because the `minDate`, `maxDate` etc. are
1711 // passed down to the view via data bindings which won't be up-to-date when we call `_init`.
1712 this._changeDetectorRef.detectChanges();
1713 view._init();
1714 }
1715 }
1716 this.stateChanges.next();
1717 }
1718 /** Focuses the active date. */
1719 focusActiveCell() {
1720 this._getCurrentViewComponent()._focusActiveCell(false);
1721 }
1722 /** Updates today's date after an update of the active date */
1723 updateTodaysDate() {
1724 this._getCurrentViewComponent()._init();
1725 }
1726 /** Handles date selection in the month view. */
1727 _dateSelected(event) {
1728 const date = event.value;
1729 if (this.selected instanceof DateRange ||
1730 (date && !this._dateAdapter.sameDate(date, this.selected))) {
1731 this.selectedChange.emit(date);
1732 }
1733 this._userSelection.emit(event);
1734 }
1735 /** Handles year selection in the multiyear view. */
1736 _yearSelectedInMultiYearView(normalizedYear) {
1737 this.yearSelected.emit(normalizedYear);
1738 }
1739 /** Handles month selection in the year view. */
1740 _monthSelectedInYearView(normalizedMonth) {
1741 this.monthSelected.emit(normalizedMonth);
1742 }
1743 /** Handles year/month selection in the multi-year/year views. */
1744 _goToDateInView(date, view) {
1745 this.activeDate = date;
1746 this.currentView = view;
1747 }
1748 /** Returns the component instance that corresponds to the current calendar view. */
1749 _getCurrentViewComponent() {
1750 // The return type is explicitly written as a union to ensure that the Closure compiler does
1751 // not optimize calls to _init(). Without the explict return type, TypeScript narrows it to
1752 // only the first component type. See https://github.com/angular/components/issues/22996.
1753 return this.monthView || this.yearView || this.multiYearView;
1754 }
1755}
1756MatCalendar.decorators = [
1757 { type: Component, args: [{
1758 selector: 'mat-calendar',
1759 template: "<ng-template [cdkPortalOutlet]=\"_calendarHeaderPortal\"></ng-template>\n\n<div class=\"mat-calendar-content\" [ngSwitch]=\"currentView\" cdkMonitorSubtreeFocus tabindex=\"-1\">\n <mat-month-view\n *ngSwitchCase=\"'month'\"\n [(activeDate)]=\"activeDate\"\n [selected]=\"selected\"\n [dateFilter]=\"dateFilter\"\n [maxDate]=\"maxDate\"\n [minDate]=\"minDate\"\n [dateClass]=\"dateClass\"\n [comparisonStart]=\"comparisonStart\"\n [comparisonEnd]=\"comparisonEnd\"\n (_userSelection)=\"_dateSelected($event)\">\n </mat-month-view>\n\n <mat-year-view\n *ngSwitchCase=\"'year'\"\n [(activeDate)]=\"activeDate\"\n [selected]=\"selected\"\n [dateFilter]=\"dateFilter\"\n [maxDate]=\"maxDate\"\n [minDate]=\"minDate\"\n [dateClass]=\"dateClass\"\n (monthSelected)=\"_monthSelectedInYearView($event)\"\n (selectedChange)=\"_goToDateInView($event, 'month')\">\n </mat-year-view>\n\n <mat-multi-year-view\n *ngSwitchCase=\"'multi-year'\"\n [(activeDate)]=\"activeDate\"\n [selected]=\"selected\"\n [dateFilter]=\"dateFilter\"\n [maxDate]=\"maxDate\"\n [minDate]=\"minDate\"\n [dateClass]=\"dateClass\"\n (yearSelected)=\"_yearSelectedInMultiYearView($event)\"\n (selectedChange)=\"_goToDateInView($event, 'year')\">\n </mat-multi-year-view>\n</div>\n",
1760 host: {
1761 'class': 'mat-calendar',
1762 },
1763 exportAs: 'matCalendar',
1764 encapsulation: ViewEncapsulation.None,
1765 changeDetection: ChangeDetectionStrategy.OnPush,
1766 providers: [MAT_SINGLE_DATE_SELECTION_MODEL_PROVIDER],
1767 styles: [".mat-calendar{display:block}.mat-calendar-header{padding:8px 8px 0 8px}.mat-calendar-content{padding:0 8px 8px 8px;outline:none}.mat-calendar-controls{display:flex;margin:5% calc(33% / 7 - 16px)}.mat-calendar-controls .mat-icon-button:hover .mat-button-focus-overlay{opacity:.04}.mat-calendar-spacer{flex:1 1 auto}.mat-calendar-period-button{min-width:0}.mat-calendar-arrow{display:inline-block;width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top-width:5px;border-top-style:solid;margin:0 0 0 5px;vertical-align:middle}.mat-calendar-arrow.mat-calendar-invert{transform:rotate(180deg)}[dir=rtl] .mat-calendar-arrow{margin:0 5px 0 0}.mat-calendar-previous-button,.mat-calendar-next-button{position:relative}.mat-calendar-previous-button::after,.mat-calendar-next-button::after{top:0;left:0;right:0;bottom:0;position:absolute;content:\"\";margin:15.5px;border:0 solid currentColor;border-top-width:2px}[dir=rtl] .mat-calendar-previous-button,[dir=rtl] .mat-calendar-next-button{transform:rotate(180deg)}.mat-calendar-previous-button::after{border-left-width:2px;transform:translateX(2px) rotate(-45deg)}.mat-calendar-next-button::after{border-right-width:2px;transform:translateX(-2px) rotate(45deg)}.mat-calendar-table{border-spacing:0;border-collapse:collapse;width:100%}.mat-calendar-table-header th{text-align:center;padding:0 0 8px 0}.mat-calendar-table-header-divider{position:relative;height:1px}.mat-calendar-table-header-divider::after{content:\"\";position:absolute;top:0;left:-8px;right:-8px;height:1px}.mat-calendar-abbr{text-decoration:none}\n"]
1768 },] }
1769];
1770MatCalendar.ctorParameters = () => [
1771 { type: MatDatepickerIntl },
1772 { type: DateAdapter, decorators: [{ type: Optional }] },
1773 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_DATE_FORMATS,] }] },
1774 { type: ChangeDetectorRef }
1775];
1776MatCalendar.propDecorators = {
1777 headerComponent: [{ type: Input }],
1778 startAt: [{ type: Input }],
1779 startView: [{ type: Input }],
1780 selected: [{ type: Input }],
1781 minDate: [{ type: Input }],
1782 maxDate: [{ type: Input }],
1783 dateFilter: [{ type: Input }],
1784 dateClass: [{ type: Input }],
1785 comparisonStart: [{ type: Input }],
1786 comparisonEnd: [{ type: Input }],
1787 selectedChange: [{ type: Output }],
1788 yearSelected: [{ type: Output }],
1789 monthSelected: [{ type: Output }],
1790 viewChanged: [{ type: Output }],
1791 _userSelection: [{ type: Output }],
1792 monthView: [{ type: ViewChild, args: [MatMonthView,] }],
1793 yearView: [{ type: ViewChild, args: [MatYearView,] }],
1794 multiYearView: [{ type: ViewChild, args: [MatMultiYearView,] }]
1795};
1796
1797/**
1798 * @license
1799 * Copyright Google LLC All Rights Reserved.
1800 *
1801 * Use of this source code is governed by an MIT-style license that can be
1802 * found in the LICENSE file at https://angular.io/license
1803 */
1804/**
1805 * Animations used by the Material datepicker.
1806 * @docs-private
1807 */
1808const matDatepickerAnimations = {
1809 /** Transforms the height of the datepicker's calendar. */
1810 transformPanel: trigger('transformPanel', [
1811 transition('void => enter-dropdown', animate('120ms cubic-bezier(0, 0, 0.2, 1)', keyframes([
1812 style({ opacity: 0, transform: 'scale(1, 0.8)' }),
1813 style({ opacity: 1, transform: 'scale(1, 1)' })
1814 ]))),
1815 transition('void => enter-dialog', animate('150ms cubic-bezier(0, 0, 0.2, 1)', keyframes([
1816 style({ opacity: 0, transform: 'scale(0.7)' }),
1817 style({ transform: 'none', opacity: 1 })
1818 ]))),
1819 transition('* => void', animate('100ms linear', style({ opacity: 0 })))
1820 ]),
1821 /** Fades in the content of the calendar. */
1822 fadeInCalendar: trigger('fadeInCalendar', [
1823 state('void', style({ opacity: 0 })),
1824 state('enter', style({ opacity: 1 })),
1825 // TODO(crisbeto): this animation should be removed since it isn't quite on spec, but we
1826 // need to keep it until #12440 gets in, otherwise the exit animation will look glitchy.
1827 transition('void => *', animate('120ms 100ms cubic-bezier(0.55, 0, 0.55, 0.2)'))
1828 ])
1829};
1830
1831/**
1832 * @license
1833 * Copyright Google LLC All Rights Reserved.
1834 *
1835 * Use of this source code is governed by an MIT-style license that can be
1836 * found in the LICENSE file at https://angular.io/license
1837 */
1838/** Used to generate a unique ID for each datepicker instance. */
1839let datepickerUid = 0;
1840/** Injection token that determines the scroll handling while the calendar is open. */
1841const MAT_DATEPICKER_SCROLL_STRATEGY = new InjectionToken('mat-datepicker-scroll-strategy');
1842/** @docs-private */
1843function MAT_DATEPICKER_SCROLL_STRATEGY_FACTORY(overlay) {
1844 return () => overlay.scrollStrategies.reposition();
1845}
1846/** @docs-private */
1847const MAT_DATEPICKER_SCROLL_STRATEGY_FACTORY_PROVIDER = {
1848 provide: MAT_DATEPICKER_SCROLL_STRATEGY,
1849 deps: [Overlay],
1850 useFactory: MAT_DATEPICKER_SCROLL_STRATEGY_FACTORY,
1851};
1852// Boilerplate for applying mixins to MatDatepickerContent.
1853/** @docs-private */
1854const _MatDatepickerContentBase = mixinColor(class {
1855 constructor(_elementRef) {
1856 this._elementRef = _elementRef;
1857 }
1858});
1859/**
1860 * Component used as the content for the datepicker overlay. We use this instead of using
1861 * MatCalendar directly as the content so we can control the initial focus. This also gives us a
1862 * place to put additional features of the overlay that are not part of the calendar itself in the
1863 * future. (e.g. confirmation buttons).
1864 * @docs-private
1865 */
1866class MatDatepickerContent extends _MatDatepickerContentBase {
1867 constructor(elementRef, _changeDetectorRef, _globalModel, _dateAdapter, _rangeSelectionStrategy, intl) {
1868 super(elementRef);
1869 this._changeDetectorRef = _changeDetectorRef;
1870 this._globalModel = _globalModel;
1871 this._dateAdapter = _dateAdapter;
1872 this._rangeSelectionStrategy = _rangeSelectionStrategy;
1873 this._subscriptions = new Subscription();
1874 /** Emits when an animation has finished. */
1875 this._animationDone = new Subject();
1876 /** Portal with projected action buttons. */
1877 this._actionsPortal = null;
1878 this._closeButtonText = intl.closeCalendarLabel;
1879 }
1880 ngOnInit() {
1881 // If we have actions, clone the model so that we have the ability to cancel the selection,
1882 // otherwise update the global model directly. Note that we want to assign this as soon as
1883 // possible, but `_actionsPortal` isn't available in the constructor so we do it in `ngOnInit`.
1884 this._model = this._actionsPortal ? this._globalModel.clone() : this._globalModel;
1885 this._animationState = this.datepicker.touchUi ? 'enter-dialog' : 'enter-dropdown';
1886 }
1887 ngAfterViewInit() {
1888 this._subscriptions.add(this.datepicker.stateChanges.subscribe(() => {
1889 this._changeDetectorRef.markForCheck();
1890 }));
1891 this._calendar.focusActiveCell();
1892 }
1893 ngOnDestroy() {
1894 this._subscriptions.unsubscribe();
1895 this._animationDone.complete();
1896 }
1897 _handleUserSelection(event) {
1898 const selection = this._model.selection;
1899 const value = event.value;
1900 const isRange = selection instanceof DateRange;
1901 // If we're selecting a range and we have a selection strategy, always pass the value through
1902 // there. Otherwise don't assign null values to the model, unless we're selecting a range.
1903 // A null value when picking a range means that the user cancelled the selection (e.g. by
1904 // pressing escape), whereas when selecting a single value it means that the value didn't
1905 // change. This isn't very intuitive, but it's here for backwards-compatibility.
1906 if (isRange && this._rangeSelectionStrategy) {
1907 const newSelection = this._rangeSelectionStrategy.selectionFinished(value, selection, event.event);
1908 this._model.updateSelection(newSelection, this);
1909 }
1910 else if (value && (isRange ||
1911 !this._dateAdapter.sameDate(value, selection))) {
1912 this._model.add(value);
1913 }
1914 // Delegate closing the overlay to the actions.
1915 if ((!this._model || this._model.isComplete()) && !this._actionsPortal) {
1916 this.datepicker.close();
1917 }
1918 }
1919 _startExitAnimation() {
1920 this._animationState = 'void';
1921 this._changeDetectorRef.markForCheck();
1922 }
1923 _getSelected() {
1924 return this._model.selection;
1925 }
1926 /** Applies the current pending selection to the global model. */
1927 _applyPendingSelection() {
1928 if (this._model !== this._globalModel) {
1929 this._globalModel.updateSelection(this._model.selection, this);
1930 }
1931 }
1932}
1933MatDatepickerContent.decorators = [
1934 { type: Component, args: [{
1935 selector: 'mat-datepicker-content',
1936 template: "<div\n cdkTrapFocus\n class=\"mat-datepicker-content-container\"\n [class.mat-datepicker-content-container-with-actions]=\"_actionsPortal\">\n <mat-calendar\n [id]=\"datepicker.id\"\n [ngClass]=\"datepicker.panelClass\"\n [startAt]=\"datepicker.startAt\"\n [startView]=\"datepicker.startView\"\n [minDate]=\"datepicker._getMinDate()\"\n [maxDate]=\"datepicker._getMaxDate()\"\n [dateFilter]=\"datepicker._getDateFilter()\"\n [headerComponent]=\"datepicker.calendarHeaderComponent\"\n [selected]=\"_getSelected()\"\n [dateClass]=\"datepicker.dateClass\"\n [comparisonStart]=\"comparisonStart\"\n [comparisonEnd]=\"comparisonEnd\"\n [@fadeInCalendar]=\"'enter'\"\n (yearSelected)=\"datepicker._selectYear($event)\"\n (monthSelected)=\"datepicker._selectMonth($event)\"\n (viewChanged)=\"datepicker._viewChanged($event)\"\n (_userSelection)=\"_handleUserSelection($event)\"></mat-calendar>\n\n <ng-template [cdkPortalOutlet]=\"_actionsPortal\"></ng-template>\n\n <!-- Invisible close button for screen reader users. -->\n <button\n type=\"button\"\n mat-raised-button\n [color]=\"color || 'primary'\"\n class=\"mat-datepicker-close-button\"\n [class.cdk-visually-hidden]=\"!_closeButtonFocused\"\n (focus)=\"_closeButtonFocused = true\"\n (blur)=\"_closeButtonFocused = false\"\n (click)=\"datepicker.close()\">{{ _closeButtonText }}</button>\n</div>\n",
1937 host: {
1938 'class': 'mat-datepicker-content',
1939 '[@transformPanel]': '_animationState',
1940 '(@transformPanel.done)': '_animationDone.next()',
1941 '[class.mat-datepicker-content-touch]': 'datepicker.touchUi',
1942 },
1943 animations: [
1944 matDatepickerAnimations.transformPanel,
1945 matDatepickerAnimations.fadeInCalendar,
1946 ],
1947 exportAs: 'matDatepickerContent',
1948 encapsulation: ViewEncapsulation.None,
1949 changeDetection: ChangeDetectionStrategy.OnPush,
1950 inputs: ['color'],
1951 styles: [".mat-datepicker-content{display:block;border-radius:4px}.mat-datepicker-content .mat-calendar{width:296px;height:354px}.mat-datepicker-content .mat-datepicker-close-button{position:absolute;top:100%;left:0;margin-top:8px}.ng-animating .mat-datepicker-content .mat-datepicker-close-button{display:none}.mat-datepicker-content-container{display:flex;flex-direction:column;justify-content:space-between}.mat-datepicker-content-touch{display:block;max-height:80vh;position:relative;overflow:visible}.mat-datepicker-content-touch .mat-datepicker-content-container{min-height:312px;max-height:788px;min-width:250px;max-width:750px}.mat-datepicker-content-touch .mat-calendar{width:100%;height:auto}@media all and (orientation: landscape){.mat-datepicker-content-touch .mat-datepicker-content-container{width:64vh;height:80vh}}@media all and (orientation: portrait){.mat-datepicker-content-touch .mat-datepicker-content-container{width:80vw;height:100vw}.mat-datepicker-content-touch .mat-datepicker-content-container-with-actions{height:115vw}}\n"]
1952 },] }
1953];
1954MatDatepickerContent.ctorParameters = () => [
1955 { type: ElementRef },
1956 { type: ChangeDetectorRef },
1957 { type: MatDateSelectionModel },
1958 { type: DateAdapter },
1959 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_DATE_RANGE_SELECTION_STRATEGY,] }] },
1960 { type: MatDatepickerIntl }
1961];
1962MatDatepickerContent.propDecorators = {
1963 _calendar: [{ type: ViewChild, args: [MatCalendar,] }]
1964};
1965/** Base class for a datepicker. */
1966class MatDatepickerBase {
1967 constructor(
1968 /**
1969 * @deprecated `_dialog` parameter is no longer being used and it will be removed.
1970 * @breaking-change 13.0.0
1971 */
1972 _dialog, _overlay, _ngZone, _viewContainerRef, scrollStrategy, _dateAdapter, _dir,
1973 /**
1974 * @deprecated No longer being used. To be removed.
1975 * @breaking-change 13.0.0
1976 */
1977 _document, _model) {
1978 this._overlay = _overlay;
1979 this._ngZone = _ngZone;
1980 this._viewContainerRef = _viewContainerRef;
1981 this._dateAdapter = _dateAdapter;
1982 this._dir = _dir;
1983 this._model = _model;
1984 this._inputStateChanges = Subscription.EMPTY;
1985 /** The view that the calendar should start in. */
1986 this.startView = 'month';
1987 this._touchUi = false;
1988 /** Preferred position of the datepicker in the X axis. */
1989 this.xPosition = 'start';
1990 /** Preferred position of the datepicker in the Y axis. */
1991 this.yPosition = 'below';
1992 this._restoreFocus = true;
1993 /**
1994 * Emits selected year in multiyear view.
1995 * This doesn't imply a change on the selected date.
1996 */
1997 this.yearSelected = new EventEmitter();
1998 /**
1999 * Emits selected month in year view.
2000 * This doesn't imply a change on the selected date.
2001 */
2002 this.monthSelected = new EventEmitter();
2003 /**
2004 * Emits when the current view changes.
2005 */
2006 this.viewChanged = new EventEmitter(true);
2007 /** Emits when the datepicker has been opened. */
2008 this.openedStream = new EventEmitter();
2009 /** Emits when the datepicker has been closed. */
2010 this.closedStream = new EventEmitter();
2011 this._opened = false;
2012 /** The id for the datepicker calendar. */
2013 this.id = `mat-datepicker-${datepickerUid++}`;
2014 /** The element that was focused before the datepicker was opened. */
2015 this._focusedElementBeforeOpen = null;
2016 /** Unique class that will be added to the backdrop so that the test harnesses can look it up. */
2017 this._backdropHarnessClass = `${this.id}-backdrop`;
2018 /** Emits when the datepicker's state changes. */
2019 this.stateChanges = new Subject();
2020 if (!this._dateAdapter && (typeof ngDevMode === 'undefined' || ngDevMode)) {
2021 throw createMissingDateImplError('DateAdapter');
2022 }
2023 this._scrollStrategy = scrollStrategy;
2024 }
2025 /** The date to open the calendar to initially. */
2026 get startAt() {
2027 // If an explicit startAt is set we start there, otherwise we start at whatever the currently
2028 // selected value is.
2029 return this._startAt || (this.datepickerInput ? this.datepickerInput.getStartValue() : null);
2030 }
2031 set startAt(value) {
2032 this._startAt = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
2033 }
2034 /** Color palette to use on the datepicker's calendar. */
2035 get color() {
2036 return this._color ||
2037 (this.datepickerInput ? this.datepickerInput.getThemePalette() : undefined);
2038 }
2039 set color(value) {
2040 this._color = value;
2041 }
2042 /**
2043 * Whether the calendar UI is in touch mode. In touch mode the calendar opens in a dialog rather
2044 * than a dropdown and elements have more padding to allow for bigger touch targets.
2045 */
2046 get touchUi() { return this._touchUi; }
2047 set touchUi(value) {
2048 this._touchUi = coerceBooleanProperty(value);
2049 }
2050 /** Whether the datepicker pop-up should be disabled. */
2051 get disabled() {
2052 return this._disabled === undefined && this.datepickerInput ?
2053 this.datepickerInput.disabled : !!this._disabled;
2054 }
2055 set disabled(value) {
2056 const newValue = coerceBooleanProperty(value);
2057 if (newValue !== this._disabled) {
2058 this._disabled = newValue;
2059 this.stateChanges.next(undefined);
2060 }
2061 }
2062 /**
2063 * Whether to restore focus to the previously-focused element when the calendar is closed.
2064 * Note that automatic focus restoration is an accessibility feature and it is recommended that
2065 * you provide your own equivalent, if you decide to turn it off.
2066 */
2067 get restoreFocus() { return this._restoreFocus; }
2068 set restoreFocus(value) {
2069 this._restoreFocus = coerceBooleanProperty(value);
2070 }
2071 /**
2072 * Classes to be passed to the date picker panel.
2073 * Supports string and string array values, similar to `ngClass`.
2074 */
2075 get panelClass() { return this._panelClass; }
2076 set panelClass(value) {
2077 this._panelClass = coerceStringArray(value);
2078 }
2079 /** Whether the calendar is open. */
2080 get opened() { return this._opened; }
2081 set opened(value) {
2082 coerceBooleanProperty(value) ? this.open() : this.close();
2083 }
2084 /** The minimum selectable date. */
2085 _getMinDate() {
2086 return this.datepickerInput && this.datepickerInput.min;
2087 }
2088 /** The maximum selectable date. */
2089 _getMaxDate() {
2090 return this.datepickerInput && this.datepickerInput.max;
2091 }
2092 _getDateFilter() {
2093 return this.datepickerInput && this.datepickerInput.dateFilter;
2094 }
2095 ngOnChanges(changes) {
2096 const positionChange = changes['xPosition'] || changes['yPosition'];
2097 if (positionChange && !positionChange.firstChange && this._overlayRef) {
2098 const positionStrategy = this._overlayRef.getConfig().positionStrategy;
2099 if (positionStrategy instanceof FlexibleConnectedPositionStrategy) {
2100 this._setConnectedPositions(positionStrategy);
2101 if (this.opened) {
2102 this._overlayRef.updatePosition();
2103 }
2104 }
2105 }
2106 this.stateChanges.next(undefined);
2107 }
2108 ngOnDestroy() {
2109 this._destroyOverlay();
2110 this.close();
2111 this._inputStateChanges.unsubscribe();
2112 this.stateChanges.complete();
2113 }
2114 /** Selects the given date */
2115 select(date) {
2116 this._model.add(date);
2117 }
2118 /** Emits the selected year in multiyear view */
2119 _selectYear(normalizedYear) {
2120 this.yearSelected.emit(normalizedYear);
2121 }
2122 /** Emits selected month in year view */
2123 _selectMonth(normalizedMonth) {
2124 this.monthSelected.emit(normalizedMonth);
2125 }
2126 /** Emits changed view */
2127 _viewChanged(view) {
2128 this.viewChanged.emit(view);
2129 }
2130 /**
2131 * Register an input with this datepicker.
2132 * @param input The datepicker input to register with this datepicker.
2133 * @returns Selection model that the input should hook itself up to.
2134 */
2135 registerInput(input) {
2136 if (this.datepickerInput && (typeof ngDevMode === 'undefined' || ngDevMode)) {
2137 throw Error('A MatDatepicker can only be associated with a single input.');
2138 }
2139 this._inputStateChanges.unsubscribe();
2140 this.datepickerInput = input;
2141 this._inputStateChanges =
2142 input.stateChanges.subscribe(() => this.stateChanges.next(undefined));
2143 return this._model;
2144 }
2145 /**
2146 * Registers a portal containing action buttons with the datepicker.
2147 * @param portal Portal to be registered.
2148 */
2149 registerActions(portal) {
2150 if (this._actionsPortal && (typeof ngDevMode === 'undefined' || ngDevMode)) {
2151 throw Error('A MatDatepicker can only be associated with a single actions row.');
2152 }
2153 this._actionsPortal = portal;
2154 }
2155 /**
2156 * Removes a portal containing action buttons from the datepicker.
2157 * @param portal Portal to be removed.
2158 */
2159 removeActions(portal) {
2160 if (portal === this._actionsPortal) {
2161 this._actionsPortal = null;
2162 }
2163 }
2164 /** Open the calendar. */
2165 open() {
2166 if (this._opened || this.disabled) {
2167 return;
2168 }
2169 if (!this.datepickerInput && (typeof ngDevMode === 'undefined' || ngDevMode)) {
2170 throw Error('Attempted to open an MatDatepicker with no associated input.');
2171 }
2172 this._focusedElementBeforeOpen = _getFocusedElementPierceShadowDom();
2173 this._openOverlay();
2174 this._opened = true;
2175 this.openedStream.emit();
2176 }
2177 /** Close the calendar. */
2178 close() {
2179 if (!this._opened) {
2180 return;
2181 }
2182 if (this._componentRef) {
2183 const instance = this._componentRef.instance;
2184 instance._startExitAnimation();
2185 instance._animationDone.pipe(take(1)).subscribe(() => this._destroyOverlay());
2186 }
2187 const completeClose = () => {
2188 // The `_opened` could've been reset already if
2189 // we got two events in quick succession.
2190 if (this._opened) {
2191 this._opened = false;
2192 this.closedStream.emit();
2193 this._focusedElementBeforeOpen = null;
2194 }
2195 };
2196 if (this._restoreFocus && this._focusedElementBeforeOpen &&
2197 typeof this._focusedElementBeforeOpen.focus === 'function') {
2198 // Because IE moves focus asynchronously, we can't count on it being restored before we've
2199 // marked the datepicker as closed. If the event fires out of sequence and the element that
2200 // we're refocusing opens the datepicker on focus, the user could be stuck with not being
2201 // able to close the calendar at all. We work around it by making the logic, that marks
2202 // the datepicker as closed, async as well.
2203 this._focusedElementBeforeOpen.focus();
2204 setTimeout(completeClose);
2205 }
2206 else {
2207 completeClose();
2208 }
2209 }
2210 /** Applies the current pending selection on the overlay to the model. */
2211 _applyPendingSelection() {
2212 var _a, _b;
2213 (_b = (_a = this._componentRef) === null || _a === void 0 ? void 0 : _a.instance) === null || _b === void 0 ? void 0 : _b._applyPendingSelection();
2214 }
2215 /** Forwards relevant values from the datepicker to the datepicker content inside the overlay. */
2216 _forwardContentValues(instance) {
2217 instance.datepicker = this;
2218 instance.color = this.color;
2219 instance._actionsPortal = this._actionsPortal;
2220 }
2221 /** Opens the overlay with the calendar. */
2222 _openOverlay() {
2223 this._destroyOverlay();
2224 const isDialog = this.touchUi;
2225 const labelId = this.datepickerInput.getOverlayLabelId();
2226 const portal = new ComponentPortal(MatDatepickerContent, this._viewContainerRef);
2227 const overlayRef = this._overlayRef = this._overlay.create(new OverlayConfig({
2228 positionStrategy: isDialog ? this._getDialogStrategy() : this._getDropdownStrategy(),
2229 hasBackdrop: true,
2230 backdropClass: [
2231 isDialog ? 'cdk-overlay-dark-backdrop' : 'mat-overlay-transparent-backdrop',
2232 this._backdropHarnessClass
2233 ],
2234 direction: this._dir,
2235 scrollStrategy: isDialog ? this._overlay.scrollStrategies.block() : this._scrollStrategy(),
2236 panelClass: `mat-datepicker-${isDialog ? 'dialog' : 'popup'}`,
2237 }));
2238 const overlayElement = overlayRef.overlayElement;
2239 overlayElement.setAttribute('role', 'dialog');
2240 if (labelId) {
2241 overlayElement.setAttribute('aria-labelledby', labelId);
2242 }
2243 if (isDialog) {
2244 overlayElement.setAttribute('aria-modal', 'true');
2245 }
2246 this._getCloseStream(overlayRef).subscribe(event => {
2247 if (event) {
2248 event.preventDefault();
2249 }
2250 this.close();
2251 });
2252 this._componentRef = overlayRef.attach(portal);
2253 this._forwardContentValues(this._componentRef.instance);
2254 // Update the position once the calendar has rendered. Only relevant in dropdown mode.
2255 if (!isDialog) {
2256 this._ngZone.onStable.pipe(take(1)).subscribe(() => overlayRef.updatePosition());
2257 }
2258 }
2259 /** Destroys the current overlay. */
2260 _destroyOverlay() {
2261 if (this._overlayRef) {
2262 this._overlayRef.dispose();
2263 this._overlayRef = this._componentRef = null;
2264 }
2265 }
2266 /** Gets a position strategy that will open the calendar as a dropdown. */
2267 _getDialogStrategy() {
2268 return this._overlay.position().global().centerHorizontally().centerVertically();
2269 }
2270 /** Gets a position strategy that will open the calendar as a dropdown. */
2271 _getDropdownStrategy() {
2272 const strategy = this._overlay.position()
2273 .flexibleConnectedTo(this.datepickerInput.getConnectedOverlayOrigin())
2274 .withTransformOriginOn('.mat-datepicker-content')
2275 .withFlexibleDimensions(false)
2276 .withViewportMargin(8)
2277 .withLockedPosition();
2278 return this._setConnectedPositions(strategy);
2279 }
2280 /** Sets the positions of the datepicker in dropdown mode based on the current configuration. */
2281 _setConnectedPositions(strategy) {
2282 const primaryX = this.xPosition === 'end' ? 'end' : 'start';
2283 const secondaryX = primaryX === 'start' ? 'end' : 'start';
2284 const primaryY = this.yPosition === 'above' ? 'bottom' : 'top';
2285 const secondaryY = primaryY === 'top' ? 'bottom' : 'top';
2286 return strategy.withPositions([
2287 {
2288 originX: primaryX,
2289 originY: secondaryY,
2290 overlayX: primaryX,
2291 overlayY: primaryY
2292 },
2293 {
2294 originX: primaryX,
2295 originY: primaryY,
2296 overlayX: primaryX,
2297 overlayY: secondaryY
2298 },
2299 {
2300 originX: secondaryX,
2301 originY: secondaryY,
2302 overlayX: secondaryX,
2303 overlayY: primaryY
2304 },
2305 {
2306 originX: secondaryX,
2307 originY: primaryY,
2308 overlayX: secondaryX,
2309 overlayY: secondaryY
2310 }
2311 ]);
2312 }
2313 /** Gets an observable that will emit when the overlay is supposed to be closed. */
2314 _getCloseStream(overlayRef) {
2315 return merge(overlayRef.backdropClick(), overlayRef.detachments(), overlayRef.keydownEvents().pipe(filter(event => {
2316 // Closing on alt + up is only valid when there's an input associated with the datepicker.
2317 return (event.keyCode === ESCAPE && !hasModifierKey(event)) || (this.datepickerInput &&
2318 hasModifierKey(event, 'altKey') && event.keyCode === UP_ARROW);
2319 })));
2320 }
2321}
2322MatDatepickerBase.decorators = [
2323 { type: Directive }
2324];
2325MatDatepickerBase.ctorParameters = () => [
2326 { type: undefined, decorators: [{ type: Inject, args: [ElementRef,] }] },
2327 { type: Overlay },
2328 { type: NgZone },
2329 { type: ViewContainerRef },
2330 { type: undefined, decorators: [{ type: Inject, args: [MAT_DATEPICKER_SCROLL_STRATEGY,] }] },
2331 { type: DateAdapter, decorators: [{ type: Optional }] },
2332 { type: Directionality, decorators: [{ type: Optional }] },
2333 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [DOCUMENT,] }] },
2334 { type: MatDateSelectionModel }
2335];
2336MatDatepickerBase.propDecorators = {
2337 calendarHeaderComponent: [{ type: Input }],
2338 startAt: [{ type: Input }],
2339 startView: [{ type: Input }],
2340 color: [{ type: Input }],
2341 touchUi: [{ type: Input }],
2342 disabled: [{ type: Input }],
2343 xPosition: [{ type: Input }],
2344 yPosition: [{ type: Input }],
2345 restoreFocus: [{ type: Input }],
2346 yearSelected: [{ type: Output }],
2347 monthSelected: [{ type: Output }],
2348 viewChanged: [{ type: Output }],
2349 dateClass: [{ type: Input }],
2350 openedStream: [{ type: Output, args: ['opened',] }],
2351 closedStream: [{ type: Output, args: ['closed',] }],
2352 panelClass: [{ type: Input }],
2353 opened: [{ type: Input }]
2354};
2355
2356/**
2357 * @license
2358 * Copyright Google LLC All Rights Reserved.
2359 *
2360 * Use of this source code is governed by an MIT-style license that can be
2361 * found in the LICENSE file at https://angular.io/license
2362 */
2363// TODO(mmalerba): We use a component instead of a directive here so the user can use implicit
2364// template reference variables (e.g. #d vs #d="matDatepicker"). We can change this to a directive
2365// if angular adds support for `exportAs: '$implicit'` on directives.
2366/** Component responsible for managing the datepicker popup/dialog. */
2367class MatDatepicker extends MatDatepickerBase {
2368}
2369MatDatepicker.decorators = [
2370 { type: Component, args: [{
2371 selector: 'mat-datepicker',
2372 template: '',
2373 exportAs: 'matDatepicker',
2374 changeDetection: ChangeDetectionStrategy.OnPush,
2375 encapsulation: ViewEncapsulation.None,
2376 providers: [
2377 MAT_SINGLE_DATE_SELECTION_MODEL_PROVIDER,
2378 { provide: MatDatepickerBase, useExisting: MatDatepicker },
2379 ]
2380 },] }
2381];
2382
2383/**
2384 * @license
2385 * Copyright Google LLC All Rights Reserved.
2386 *
2387 * Use of this source code is governed by an MIT-style license that can be
2388 * found in the LICENSE file at https://angular.io/license
2389 */
2390/**
2391 * An event used for datepicker input and change events. We don't always have access to a native
2392 * input or change event because the event may have been triggered by the user clicking on the
2393 * calendar popup. For consistency, we always use MatDatepickerInputEvent instead.
2394 */
2395class MatDatepickerInputEvent {
2396 constructor(
2397 /** Reference to the datepicker input component that emitted the event. */
2398 target,
2399 /** Reference to the native input element associated with the datepicker input. */
2400 targetElement) {
2401 this.target = target;
2402 this.targetElement = targetElement;
2403 this.value = this.target.value;
2404 }
2405}
2406/** Base class for datepicker inputs. */
2407class MatDatepickerInputBase {
2408 constructor(_elementRef, _dateAdapter, _dateFormats) {
2409 this._elementRef = _elementRef;
2410 this._dateAdapter = _dateAdapter;
2411 this._dateFormats = _dateFormats;
2412 /** Emits when a `change` event is fired on this `<input>`. */
2413 this.dateChange = new EventEmitter();
2414 /** Emits when an `input` event is fired on this `<input>`. */
2415 this.dateInput = new EventEmitter();
2416 /** Emits when the internal state has changed */
2417 this.stateChanges = new Subject();
2418 this._onTouched = () => { };
2419 this._validatorOnChange = () => { };
2420 this._cvaOnChange = () => { };
2421 this._valueChangesSubscription = Subscription.EMPTY;
2422 this._localeSubscription = Subscription.EMPTY;
2423 /** The form control validator for whether the input parses. */
2424 this._parseValidator = () => {
2425 return this._lastValueValid ?
2426 null : { 'matDatepickerParse': { 'text': this._elementRef.nativeElement.value } };
2427 };
2428 /** The form control validator for the date filter. */
2429 this._filterValidator = (control) => {
2430 const controlValue = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(control.value));
2431 return !controlValue || this._matchesFilter(controlValue) ?
2432 null : { 'matDatepickerFilter': true };
2433 };
2434 /** The form control validator for the min date. */
2435 this._minValidator = (control) => {
2436 const controlValue = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(control.value));
2437 const min = this._getMinDate();
2438 return (!min || !controlValue ||
2439 this._dateAdapter.compareDate(min, controlValue) <= 0) ?
2440 null : { 'matDatepickerMin': { 'min': min, 'actual': controlValue } };
2441 };
2442 /** The form control validator for the max date. */
2443 this._maxValidator = (control) => {
2444 const controlValue = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(control.value));
2445 const max = this._getMaxDate();
2446 return (!max || !controlValue ||
2447 this._dateAdapter.compareDate(max, controlValue) >= 0) ?
2448 null : { 'matDatepickerMax': { 'max': max, 'actual': controlValue } };
2449 };
2450 /** Whether the last value set on the input was valid. */
2451 this._lastValueValid = false;
2452 if (typeof ngDevMode === 'undefined' || ngDevMode) {
2453 if (!this._dateAdapter) {
2454 throw createMissingDateImplError('DateAdapter');
2455 }
2456 if (!this._dateFormats) {
2457 throw createMissingDateImplError('MAT_DATE_FORMATS');
2458 }
2459 }
2460 // Update the displayed date when the locale changes.
2461 this._localeSubscription = _dateAdapter.localeChanges.subscribe(() => {
2462 this._assignValueProgrammatically(this.value);
2463 });
2464 }
2465 /** The value of the input. */
2466 get value() {
2467 return this._model ? this._getValueFromModel(this._model.selection) : this._pendingValue;
2468 }
2469 set value(value) {
2470 this._assignValueProgrammatically(value);
2471 }
2472 /** Whether the datepicker-input is disabled. */
2473 get disabled() { return !!this._disabled || this._parentDisabled(); }
2474 set disabled(value) {
2475 const newValue = coerceBooleanProperty(value);
2476 const element = this._elementRef.nativeElement;
2477 if (this._disabled !== newValue) {
2478 this._disabled = newValue;
2479 this.stateChanges.next(undefined);
2480 }
2481 // We need to null check the `blur` method, because it's undefined during SSR.
2482 // In Ivy static bindings are invoked earlier, before the element is attached to the DOM.
2483 // This can cause an error to be thrown in some browsers (IE/Edge) which assert that the
2484 // element has been inserted.
2485 if (newValue && this._isInitialized && element.blur) {
2486 // Normally, native input elements automatically blur if they turn disabled. This behavior
2487 // is problematic, because it would mean that it triggers another change detection cycle,
2488 // which then causes a changed after checked error if the input element was focused before.
2489 element.blur();
2490 }
2491 }
2492 /** Gets the base validator functions. */
2493 _getValidators() {
2494 return [this._parseValidator, this._minValidator, this._maxValidator, this._filterValidator];
2495 }
2496 /** Registers a date selection model with the input. */
2497 _registerModel(model) {
2498 this._model = model;
2499 this._valueChangesSubscription.unsubscribe();
2500 if (this._pendingValue) {
2501 this._assignValue(this._pendingValue);
2502 }
2503 this._valueChangesSubscription = this._model.selectionChanged.subscribe(event => {
2504 if (this._shouldHandleChangeEvent(event)) {
2505 const value = this._getValueFromModel(event.selection);
2506 this._lastValueValid = this._isValidValue(value);
2507 this._cvaOnChange(value);
2508 this._onTouched();
2509 this._formatValue(value);
2510 this.dateInput.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement));
2511 this.dateChange.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement));
2512 }
2513 });
2514 }
2515 ngAfterViewInit() {
2516 this._isInitialized = true;
2517 }
2518 ngOnChanges(changes) {
2519 if (dateInputsHaveChanged(changes, this._dateAdapter)) {
2520 this.stateChanges.next(undefined);
2521 }
2522 }
2523 ngOnDestroy() {
2524 this._valueChangesSubscription.unsubscribe();
2525 this._localeSubscription.unsubscribe();
2526 this.stateChanges.complete();
2527 }
2528 /** @docs-private */
2529 registerOnValidatorChange(fn) {
2530 this._validatorOnChange = fn;
2531 }
2532 /** @docs-private */
2533 validate(c) {
2534 return this._validator ? this._validator(c) : null;
2535 }
2536 // Implemented as part of ControlValueAccessor.
2537 writeValue(value) {
2538 this._assignValueProgrammatically(value);
2539 }
2540 // Implemented as part of ControlValueAccessor.
2541 registerOnChange(fn) {
2542 this._cvaOnChange = fn;
2543 }
2544 // Implemented as part of ControlValueAccessor.
2545 registerOnTouched(fn) {
2546 this._onTouched = fn;
2547 }
2548 // Implemented as part of ControlValueAccessor.
2549 setDisabledState(isDisabled) {
2550 this.disabled = isDisabled;
2551 }
2552 _onKeydown(event) {
2553 const isAltDownArrow = event.altKey && event.keyCode === DOWN_ARROW;
2554 if (isAltDownArrow && !this._elementRef.nativeElement.readOnly) {
2555 this._openPopup();
2556 event.preventDefault();
2557 }
2558 }
2559 _onInput(value) {
2560 const lastValueWasValid = this._lastValueValid;
2561 let date = this._dateAdapter.parse(value, this._dateFormats.parse.dateInput);
2562 this._lastValueValid = this._isValidValue(date);
2563 date = this._dateAdapter.getValidDateOrNull(date);
2564 if (!this._dateAdapter.sameDate(date, this.value)) {
2565 this._assignValue(date);
2566 this._cvaOnChange(date);
2567 this.dateInput.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement));
2568 }
2569 else {
2570 // Call the CVA change handler for invalid values
2571 // since this is what marks the control as dirty.
2572 if (value && !this.value) {
2573 this._cvaOnChange(date);
2574 }
2575 if (lastValueWasValid !== this._lastValueValid) {
2576 this._validatorOnChange();
2577 }
2578 }
2579 }
2580 _onChange() {
2581 this.dateChange.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement));
2582 }
2583 /** Handles blur events on the input. */
2584 _onBlur() {
2585 // Reformat the input only if we have a valid value.
2586 if (this.value) {
2587 this._formatValue(this.value);
2588 }
2589 this._onTouched();
2590 }
2591 /** Formats a value and sets it on the input element. */
2592 _formatValue(value) {
2593 this._elementRef.nativeElement.value =
2594 value ? this._dateAdapter.format(value, this._dateFormats.display.dateInput) : '';
2595 }
2596 /** Assigns a value to the model. */
2597 _assignValue(value) {
2598 // We may get some incoming values before the model was
2599 // assigned. Save the value so that we can assign it later.
2600 if (this._model) {
2601 this._assignValueToModel(value);
2602 this._pendingValue = null;
2603 }
2604 else {
2605 this._pendingValue = value;
2606 }
2607 }
2608 /** Whether a value is considered valid. */
2609 _isValidValue(value) {
2610 return !value || this._dateAdapter.isValid(value);
2611 }
2612 /**
2613 * Checks whether a parent control is disabled. This is in place so that it can be overridden
2614 * by inputs extending this one which can be placed inside of a group that can be disabled.
2615 */
2616 _parentDisabled() {
2617 return false;
2618 }
2619 /** Programmatically assigns a value to the input. */
2620 _assignValueProgrammatically(value) {
2621 value = this._dateAdapter.deserialize(value);
2622 this._lastValueValid = this._isValidValue(value);
2623 value = this._dateAdapter.getValidDateOrNull(value);
2624 this._assignValue(value);
2625 this._formatValue(value);
2626 }
2627 /** Gets whether a value matches the current date filter. */
2628 _matchesFilter(value) {
2629 const filter = this._getDateFilter();
2630 return !filter || filter(value);
2631 }
2632}
2633MatDatepickerInputBase.decorators = [
2634 { type: Directive }
2635];
2636MatDatepickerInputBase.ctorParameters = () => [
2637 { type: ElementRef },
2638 { type: DateAdapter, decorators: [{ type: Optional }] },
2639 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_DATE_FORMATS,] }] }
2640];
2641MatDatepickerInputBase.propDecorators = {
2642 value: [{ type: Input }],
2643 disabled: [{ type: Input }],
2644 dateChange: [{ type: Output }],
2645 dateInput: [{ type: Output }]
2646};
2647/**
2648 * Checks whether the `SimpleChanges` object from an `ngOnChanges`
2649 * callback has any changes, accounting for date objects.
2650 */
2651function dateInputsHaveChanged(changes, adapter) {
2652 const keys = Object.keys(changes);
2653 for (let key of keys) {
2654 const { previousValue, currentValue } = changes[key];
2655 if (adapter.isDateInstance(previousValue) && adapter.isDateInstance(currentValue)) {
2656 if (!adapter.sameDate(previousValue, currentValue)) {
2657 return true;
2658 }
2659 }
2660 else {
2661 return true;
2662 }
2663 }
2664 return false;
2665}
2666
2667/**
2668 * @license
2669 * Copyright Google LLC All Rights Reserved.
2670 *
2671 * Use of this source code is governed by an MIT-style license that can be
2672 * found in the LICENSE file at https://angular.io/license
2673 */
2674/** @docs-private */
2675const MAT_DATEPICKER_VALUE_ACCESSOR = {
2676 provide: NG_VALUE_ACCESSOR,
2677 useExisting: forwardRef(() => MatDatepickerInput),
2678 multi: true
2679};
2680/** @docs-private */
2681const MAT_DATEPICKER_VALIDATORS = {
2682 provide: NG_VALIDATORS,
2683 useExisting: forwardRef(() => MatDatepickerInput),
2684 multi: true
2685};
2686/** Directive used to connect an input to a MatDatepicker. */
2687class MatDatepickerInput extends MatDatepickerInputBase {
2688 constructor(elementRef, dateAdapter, dateFormats, _formField) {
2689 super(elementRef, dateAdapter, dateFormats);
2690 this._formField = _formField;
2691 this._closedSubscription = Subscription.EMPTY;
2692 this._validator = Validators.compose(super._getValidators());
2693 }
2694 /** The datepicker that this input is associated with. */
2695 set matDatepicker(datepicker) {
2696 if (datepicker) {
2697 this._datepicker = datepicker;
2698 this._closedSubscription = datepicker.closedStream.subscribe(() => this._onTouched());
2699 this._registerModel(datepicker.registerInput(this));
2700 }
2701 }
2702 /** The minimum valid date. */
2703 get min() { return this._min; }
2704 set min(value) {
2705 const validValue = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
2706 if (!this._dateAdapter.sameDate(validValue, this._min)) {
2707 this._min = validValue;
2708 this._validatorOnChange();
2709 }
2710 }
2711 /** The maximum valid date. */
2712 get max() { return this._max; }
2713 set max(value) {
2714 const validValue = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
2715 if (!this._dateAdapter.sameDate(validValue, this._max)) {
2716 this._max = validValue;
2717 this._validatorOnChange();
2718 }
2719 }
2720 /** Function that can be used to filter out dates within the datepicker. */
2721 get dateFilter() { return this._dateFilter; }
2722 set dateFilter(value) {
2723 const wasMatchingValue = this._matchesFilter(this.value);
2724 this._dateFilter = value;
2725 if (this._matchesFilter(this.value) !== wasMatchingValue) {
2726 this._validatorOnChange();
2727 }
2728 }
2729 /**
2730 * Gets the element that the datepicker popup should be connected to.
2731 * @return The element to connect the popup to.
2732 */
2733 getConnectedOverlayOrigin() {
2734 return this._formField ? this._formField.getConnectedOverlayOrigin() : this._elementRef;
2735 }
2736 /** Gets the ID of an element that should be used a description for the calendar overlay. */
2737 getOverlayLabelId() {
2738 if (this._formField) {
2739 return this._formField.getLabelId();
2740 }
2741 return this._elementRef.nativeElement.getAttribute('aria-labelledby');
2742 }
2743 /** Returns the palette used by the input's form field, if any. */
2744 getThemePalette() {
2745 return this._formField ? this._formField.color : undefined;
2746 }
2747 /** Gets the value at which the calendar should start. */
2748 getStartValue() {
2749 return this.value;
2750 }
2751 ngOnDestroy() {
2752 super.ngOnDestroy();
2753 this._closedSubscription.unsubscribe();
2754 }
2755 /** Opens the associated datepicker. */
2756 _openPopup() {
2757 if (this._datepicker) {
2758 this._datepicker.open();
2759 }
2760 }
2761 _getValueFromModel(modelValue) {
2762 return modelValue;
2763 }
2764 _assignValueToModel(value) {
2765 if (this._model) {
2766 this._model.updateSelection(value, this);
2767 }
2768 }
2769 /** Gets the input's minimum date. */
2770 _getMinDate() {
2771 return this._min;
2772 }
2773 /** Gets the input's maximum date. */
2774 _getMaxDate() {
2775 return this._max;
2776 }
2777 /** Gets the input's date filtering function. */
2778 _getDateFilter() {
2779 return this._dateFilter;
2780 }
2781 _shouldHandleChangeEvent(event) {
2782 return event.source !== this;
2783 }
2784}
2785MatDatepickerInput.decorators = [
2786 { type: Directive, args: [{
2787 selector: 'input[matDatepicker]',
2788 providers: [
2789 MAT_DATEPICKER_VALUE_ACCESSOR,
2790 MAT_DATEPICKER_VALIDATORS,
2791 { provide: MAT_INPUT_VALUE_ACCESSOR, useExisting: MatDatepickerInput },
2792 ],
2793 host: {
2794 'class': 'mat-datepicker-input',
2795 '[attr.aria-haspopup]': '_datepicker ? "dialog" : null',
2796 '[attr.aria-owns]': '(_datepicker?.opened && _datepicker.id) || null',
2797 '[attr.min]': 'min ? _dateAdapter.toIso8601(min) : null',
2798 '[attr.max]': 'max ? _dateAdapter.toIso8601(max) : null',
2799 // Used by the test harness to tie this input to its calendar. We can't depend on
2800 // `aria-owns` for this, because it's only defined while the calendar is open.
2801 '[attr.data-mat-calendar]': '_datepicker ? _datepicker.id : null',
2802 '[disabled]': 'disabled',
2803 '(input)': '_onInput($event.target.value)',
2804 '(change)': '_onChange()',
2805 '(blur)': '_onBlur()',
2806 '(keydown)': '_onKeydown($event)',
2807 },
2808 exportAs: 'matDatepickerInput',
2809 },] }
2810];
2811MatDatepickerInput.ctorParameters = () => [
2812 { type: ElementRef },
2813 { type: DateAdapter, decorators: [{ type: Optional }] },
2814 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_DATE_FORMATS,] }] },
2815 { type: MatFormField, decorators: [{ type: Optional }, { type: Inject, args: [MAT_FORM_FIELD,] }] }
2816];
2817MatDatepickerInput.propDecorators = {
2818 matDatepicker: [{ type: Input }],
2819 min: [{ type: Input }],
2820 max: [{ type: Input }],
2821 dateFilter: [{ type: Input, args: ['matDatepickerFilter',] }]
2822};
2823
2824/**
2825 * @license
2826 * Copyright Google LLC All Rights Reserved.
2827 *
2828 * Use of this source code is governed by an MIT-style license that can be
2829 * found in the LICENSE file at https://angular.io/license
2830 */
2831/** Can be used to override the icon of a `matDatepickerToggle`. */
2832class MatDatepickerToggleIcon {
2833}
2834MatDatepickerToggleIcon.decorators = [
2835 { type: Directive, args: [{
2836 selector: '[matDatepickerToggleIcon]'
2837 },] }
2838];
2839class MatDatepickerToggle {
2840 constructor(_intl, _changeDetectorRef, defaultTabIndex) {
2841 this._intl = _intl;
2842 this._changeDetectorRef = _changeDetectorRef;
2843 this._stateChanges = Subscription.EMPTY;
2844 const parsedTabIndex = Number(defaultTabIndex);
2845 this.tabIndex = (parsedTabIndex || parsedTabIndex === 0) ? parsedTabIndex : null;
2846 }
2847 /** Whether the toggle button is disabled. */
2848 get disabled() {
2849 if (this._disabled === undefined && this.datepicker) {
2850 return this.datepicker.disabled;
2851 }
2852 return !!this._disabled;
2853 }
2854 set disabled(value) {
2855 this._disabled = coerceBooleanProperty(value);
2856 }
2857 ngOnChanges(changes) {
2858 if (changes['datepicker']) {
2859 this._watchStateChanges();
2860 }
2861 }
2862 ngOnDestroy() {
2863 this._stateChanges.unsubscribe();
2864 }
2865 ngAfterContentInit() {
2866 this._watchStateChanges();
2867 }
2868 _open(event) {
2869 if (this.datepicker && !this.disabled) {
2870 this.datepicker.open();
2871 event.stopPropagation();
2872 }
2873 }
2874 _watchStateChanges() {
2875 const datepickerStateChanged = this.datepicker ? this.datepicker.stateChanges : of();
2876 const inputStateChanged = this.datepicker && this.datepicker.datepickerInput ?
2877 this.datepicker.datepickerInput.stateChanges : of();
2878 const datepickerToggled = this.datepicker ?
2879 merge(this.datepicker.openedStream, this.datepicker.closedStream) :
2880 of();
2881 this._stateChanges.unsubscribe();
2882 this._stateChanges = merge(this._intl.changes, datepickerStateChanged, inputStateChanged, datepickerToggled).subscribe(() => this._changeDetectorRef.markForCheck());
2883 }
2884}
2885MatDatepickerToggle.decorators = [
2886 { type: Component, args: [{
2887 selector: 'mat-datepicker-toggle',
2888 template: "<button\n #button\n mat-icon-button\n type=\"button\"\n [attr.aria-haspopup]=\"datepicker ? 'dialog' : null\"\n [attr.aria-label]=\"ariaLabel || _intl.openCalendarLabel\"\n [attr.tabindex]=\"disabled ? -1 : tabIndex\"\n [disabled]=\"disabled\"\n [disableRipple]=\"disableRipple\">\n\n <svg\n *ngIf=\"!_customIcon\"\n class=\"mat-datepicker-toggle-default-icon\"\n viewBox=\"0 0 24 24\"\n width=\"24px\"\n height=\"24px\"\n fill=\"currentColor\"\n focusable=\"false\">\n <path d=\"M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7z\"/>\n </svg>\n\n <ng-content select=\"[matDatepickerToggleIcon]\"></ng-content>\n</button>\n",
2889 host: {
2890 'class': 'mat-datepicker-toggle',
2891 '[attr.tabindex]': 'null',
2892 '[class.mat-datepicker-toggle-active]': 'datepicker && datepicker.opened',
2893 '[class.mat-accent]': 'datepicker && datepicker.color === "accent"',
2894 '[class.mat-warn]': 'datepicker && datepicker.color === "warn"',
2895 // Used by the test harness to tie this toggle to its datepicker.
2896 '[attr.data-mat-calendar]': 'datepicker ? datepicker.id : null',
2897 // Bind the `click` on the host, rather than the inner `button`, so that we can call
2898 // `stopPropagation` on it without affecting the user's `click` handlers. We need to stop
2899 // it so that the input doesn't get focused automatically by the form field (See #21836).
2900 '(click)': '_open($event)',
2901 },
2902 exportAs: 'matDatepickerToggle',
2903 encapsulation: ViewEncapsulation.None,
2904 changeDetection: ChangeDetectionStrategy.OnPush,
2905 styles: [".mat-form-field-appearance-legacy .mat-form-field-prefix .mat-datepicker-toggle-default-icon,.mat-form-field-appearance-legacy .mat-form-field-suffix .mat-datepicker-toggle-default-icon{width:1em}.mat-form-field:not(.mat-form-field-appearance-legacy) .mat-form-field-prefix .mat-datepicker-toggle-default-icon,.mat-form-field:not(.mat-form-field-appearance-legacy) .mat-form-field-suffix .mat-datepicker-toggle-default-icon{display:block;width:1.5em;height:1.5em}.mat-form-field:not(.mat-form-field-appearance-legacy) .mat-form-field-prefix .mat-icon-button .mat-datepicker-toggle-default-icon,.mat-form-field:not(.mat-form-field-appearance-legacy) .mat-form-field-suffix .mat-icon-button .mat-datepicker-toggle-default-icon{margin:auto}.cdk-high-contrast-active .mat-datepicker-toggle-default-icon{color:CanvasText}\n"]
2906 },] }
2907];
2908MatDatepickerToggle.ctorParameters = () => [
2909 { type: MatDatepickerIntl },
2910 { type: ChangeDetectorRef },
2911 { type: String, decorators: [{ type: Attribute, args: ['tabindex',] }] }
2912];
2913MatDatepickerToggle.propDecorators = {
2914 datepicker: [{ type: Input, args: ['for',] }],
2915 tabIndex: [{ type: Input }],
2916 ariaLabel: [{ type: Input, args: ['aria-label',] }],
2917 disabled: [{ type: Input }],
2918 disableRipple: [{ type: Input }],
2919 _customIcon: [{ type: ContentChild, args: [MatDatepickerToggleIcon,] }],
2920 _button: [{ type: ViewChild, args: ['button',] }]
2921};
2922
2923/**
2924 * @license
2925 * Copyright Google LLC All Rights Reserved.
2926 *
2927 * Use of this source code is governed by an MIT-style license that can be
2928 * found in the LICENSE file at https://angular.io/license
2929 */
2930/**
2931 * Used to provide the date range input wrapper component
2932 * to the parts without circular dependencies.
2933 */
2934const MAT_DATE_RANGE_INPUT_PARENT = new InjectionToken('MAT_DATE_RANGE_INPUT_PARENT');
2935/**
2936 * Base class for the individual inputs that can be projected inside a `mat-date-range-input`.
2937 */
2938class MatDateRangeInputPartBase extends MatDatepickerInputBase {
2939 constructor(_rangeInput, elementRef, _defaultErrorStateMatcher, _injector, _parentForm, _parentFormGroup, dateAdapter, dateFormats) {
2940 super(elementRef, dateAdapter, dateFormats);
2941 this._rangeInput = _rangeInput;
2942 this._defaultErrorStateMatcher = _defaultErrorStateMatcher;
2943 this._injector = _injector;
2944 this._parentForm = _parentForm;
2945 this._parentFormGroup = _parentFormGroup;
2946 }
2947 ngOnInit() {
2948 // We need the date input to provide itself as a `ControlValueAccessor` and a `Validator`, while
2949 // injecting its `NgControl` so that the error state is handled correctly. This introduces a
2950 // circular dependency, because both `ControlValueAccessor` and `Validator` depend on the input
2951 // itself. Usually we can work around it for the CVA, but there's no API to do it for the
2952 // validator. We work around it here by injecting the `NgControl` in `ngOnInit`, after
2953 // everything has been resolved.
2954 // tslint:disable-next-line:no-bitwise
2955 const ngControl = this._injector.get(NgControl, null, InjectFlags.Self | InjectFlags.Optional);
2956 if (ngControl) {
2957 this.ngControl = ngControl;
2958 }
2959 }
2960 ngDoCheck() {
2961 if (this.ngControl) {
2962 // We need to re-evaluate this on every change detection cycle, because there are some
2963 // error triggers that we can't subscribe to (e.g. parent form submissions). This means
2964 // that whatever logic is in here has to be super lean or we risk destroying the performance.
2965 this.updateErrorState();
2966 }
2967 }
2968 /** Gets whether the input is empty. */
2969 isEmpty() {
2970 return this._elementRef.nativeElement.value.length === 0;
2971 }
2972 /** Gets the placeholder of the input. */
2973 _getPlaceholder() {
2974 return this._elementRef.nativeElement.placeholder;
2975 }
2976 /** Focuses the input. */
2977 focus() {
2978 this._elementRef.nativeElement.focus();
2979 }
2980 /** Handles `input` events on the input element. */
2981 _onInput(value) {
2982 super._onInput(value);
2983 this._rangeInput._handleChildValueChange();
2984 }
2985 /** Opens the datepicker associated with the input. */
2986 _openPopup() {
2987 this._rangeInput._openDatepicker();
2988 }
2989 /** Gets the minimum date from the range input. */
2990 _getMinDate() {
2991 return this._rangeInput.min;
2992 }
2993 /** Gets the maximum date from the range input. */
2994 _getMaxDate() {
2995 return this._rangeInput.max;
2996 }
2997 /** Gets the date filter function from the range input. */
2998 _getDateFilter() {
2999 return this._rangeInput.dateFilter;
3000 }
3001 _parentDisabled() {
3002 return this._rangeInput._groupDisabled;
3003 }
3004 _shouldHandleChangeEvent({ source }) {
3005 return source !== this._rangeInput._startInput && source !== this._rangeInput._endInput;
3006 }
3007 _assignValueProgrammatically(value) {
3008 super._assignValueProgrammatically(value);
3009 const opposite = (this === this._rangeInput._startInput ? this._rangeInput._endInput :
3010 this._rangeInput._startInput);
3011 opposite === null || opposite === void 0 ? void 0 : opposite._validatorOnChange();
3012 }
3013}
3014MatDateRangeInputPartBase.decorators = [
3015 { type: Directive }
3016];
3017MatDateRangeInputPartBase.ctorParameters = () => [
3018 { type: undefined, decorators: [{ type: Inject, args: [MAT_DATE_RANGE_INPUT_PARENT,] }] },
3019 { type: ElementRef },
3020 { type: ErrorStateMatcher },
3021 { type: Injector },
3022 { type: NgForm, decorators: [{ type: Optional }] },
3023 { type: FormGroupDirective, decorators: [{ type: Optional }] },
3024 { type: DateAdapter, decorators: [{ type: Optional }] },
3025 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_DATE_FORMATS,] }] }
3026];
3027const _MatDateRangeInputBase = mixinErrorState(MatDateRangeInputPartBase);
3028/** Input for entering the start date in a `mat-date-range-input`. */
3029class MatStartDate extends _MatDateRangeInputBase {
3030 constructor(rangeInput, elementRef, defaultErrorStateMatcher, injector, parentForm, parentFormGroup, dateAdapter, dateFormats) {
3031 // TODO(crisbeto): this constructor shouldn't be necessary, but ViewEngine doesn't seem to
3032 // handle DI correctly when it is inherited from `MatDateRangeInputPartBase`. We can drop this
3033 // constructor once ViewEngine is removed.
3034 super(rangeInput, elementRef, defaultErrorStateMatcher, injector, parentForm, parentFormGroup, dateAdapter, dateFormats);
3035 /** Validator that checks that the start date isn't after the end date. */
3036 this._startValidator = (control) => {
3037 const start = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(control.value));
3038 const end = this._model ? this._model.selection.end : null;
3039 return (!start || !end ||
3040 this._dateAdapter.compareDate(start, end) <= 0) ?
3041 null : { 'matStartDateInvalid': { 'end': end, 'actual': start } };
3042 };
3043 this._validator = Validators.compose([...super._getValidators(), this._startValidator]);
3044 }
3045 ngOnInit() {
3046 // Normally this happens automatically, but it seems to break if not added explicitly when all
3047 // of the criteria below are met:
3048 // 1) The class extends a TS mixin.
3049 // 2) The application is running in ViewEngine.
3050 // 3) The application is being transpiled through tsickle.
3051 // This can be removed once google3 is completely migrated to Ivy.
3052 super.ngOnInit();
3053 }
3054 ngDoCheck() {
3055 // Normally this happens automatically, but it seems to break if not added explicitly when all
3056 // of the criteria below are met:
3057 // 1) The class extends a TS mixin.
3058 // 2) The application is running in ViewEngine.
3059 // 3) The application is being transpiled through tsickle.
3060 // This can be removed once google3 is completely migrated to Ivy.
3061 super.ngDoCheck();
3062 }
3063 _getValueFromModel(modelValue) {
3064 return modelValue.start;
3065 }
3066 _shouldHandleChangeEvent(change) {
3067 var _a;
3068 if (!super._shouldHandleChangeEvent(change)) {
3069 return false;
3070 }
3071 else {
3072 return !((_a = change.oldValue) === null || _a === void 0 ? void 0 : _a.start) ? !!change.selection.start :
3073 !change.selection.start ||
3074 !!this._dateAdapter.compareDate(change.oldValue.start, change.selection.start);
3075 }
3076 }
3077 _assignValueToModel(value) {
3078 if (this._model) {
3079 const range = new DateRange(value, this._model.selection.end);
3080 this._model.updateSelection(range, this);
3081 }
3082 }
3083 _formatValue(value) {
3084 super._formatValue(value);
3085 // Any time the input value is reformatted we need to tell the parent.
3086 this._rangeInput._handleChildValueChange();
3087 }
3088 /** Gets the value that should be used when mirroring the input's size. */
3089 getMirrorValue() {
3090 const element = this._elementRef.nativeElement;
3091 const value = element.value;
3092 return value.length > 0 ? value : element.placeholder;
3093 }
3094}
3095MatStartDate.decorators = [
3096 { type: Directive, args: [{
3097 selector: 'input[matStartDate]',
3098 host: {
3099 'class': 'mat-start-date mat-date-range-input-inner',
3100 '[disabled]': 'disabled',
3101 '(input)': '_onInput($event.target.value)',
3102 '(change)': '_onChange()',
3103 '(keydown)': '_onKeydown($event)',
3104 '[attr.id]': '_rangeInput.id',
3105 '[attr.aria-haspopup]': '_rangeInput.rangePicker ? "dialog" : null',
3106 '[attr.aria-owns]': '(_rangeInput.rangePicker?.opened && _rangeInput.rangePicker.id) || null',
3107 '[attr.min]': '_getMinDate() ? _dateAdapter.toIso8601(_getMinDate()) : null',
3108 '[attr.max]': '_getMaxDate() ? _dateAdapter.toIso8601(_getMaxDate()) : null',
3109 '(blur)': '_onBlur()',
3110 'type': 'text',
3111 },
3112 providers: [
3113 { provide: NG_VALUE_ACCESSOR, useExisting: MatStartDate, multi: true },
3114 { provide: NG_VALIDATORS, useExisting: MatStartDate, multi: true }
3115 ],
3116 // These need to be specified explicitly, because some tooling doesn't
3117 // seem to pick them up from the base class. See #20932.
3118 outputs: ['dateChange', 'dateInput'],
3119 inputs: ['errorStateMatcher']
3120 },] }
3121];
3122MatStartDate.ctorParameters = () => [
3123 { type: undefined, decorators: [{ type: Inject, args: [MAT_DATE_RANGE_INPUT_PARENT,] }] },
3124 { type: ElementRef },
3125 { type: ErrorStateMatcher },
3126 { type: Injector },
3127 { type: NgForm, decorators: [{ type: Optional }] },
3128 { type: FormGroupDirective, decorators: [{ type: Optional }] },
3129 { type: DateAdapter, decorators: [{ type: Optional }] },
3130 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_DATE_FORMATS,] }] }
3131];
3132/** Input for entering the end date in a `mat-date-range-input`. */
3133class MatEndDate extends _MatDateRangeInputBase {
3134 constructor(rangeInput, elementRef, defaultErrorStateMatcher, injector, parentForm, parentFormGroup, dateAdapter, dateFormats) {
3135 // TODO(crisbeto): this constructor shouldn't be necessary, but ViewEngine doesn't seem to
3136 // handle DI correctly when it is inherited from `MatDateRangeInputPartBase`. We can drop this
3137 // constructor once ViewEngine is removed.
3138 super(rangeInput, elementRef, defaultErrorStateMatcher, injector, parentForm, parentFormGroup, dateAdapter, dateFormats);
3139 /** Validator that checks that the end date isn't before the start date. */
3140 this._endValidator = (control) => {
3141 const end = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(control.value));
3142 const start = this._model ? this._model.selection.start : null;
3143 return (!end || !start ||
3144 this._dateAdapter.compareDate(end, start) >= 0) ?
3145 null : { 'matEndDateInvalid': { 'start': start, 'actual': end } };
3146 };
3147 this._validator = Validators.compose([...super._getValidators(), this._endValidator]);
3148 }
3149 ngOnInit() {
3150 // Normally this happens automatically, but it seems to break if not added explicitly when all
3151 // of the criteria below are met:
3152 // 1) The class extends a TS mixin.
3153 // 2) The application is running in ViewEngine.
3154 // 3) The application is being transpiled through tsickle.
3155 // This can be removed once google3 is completely migrated to Ivy.
3156 super.ngOnInit();
3157 }
3158 ngDoCheck() {
3159 // Normally this happens automatically, but it seems to break if not added explicitly when all
3160 // of the criteria below are met:
3161 // 1) The class extends a TS mixin.
3162 // 2) The application is running in ViewEngine.
3163 // 3) The application is being transpiled through tsickle.
3164 // This can be removed once google3 is completely migrated to Ivy.
3165 super.ngDoCheck();
3166 }
3167 _getValueFromModel(modelValue) {
3168 return modelValue.end;
3169 }
3170 _shouldHandleChangeEvent(change) {
3171 var _a;
3172 if (!super._shouldHandleChangeEvent(change)) {
3173 return false;
3174 }
3175 else {
3176 return !((_a = change.oldValue) === null || _a === void 0 ? void 0 : _a.end) ? !!change.selection.end :
3177 !change.selection.end ||
3178 !!this._dateAdapter.compareDate(change.oldValue.end, change.selection.end);
3179 }
3180 }
3181 _assignValueToModel(value) {
3182 if (this._model) {
3183 const range = new DateRange(this._model.selection.start, value);
3184 this._model.updateSelection(range, this);
3185 }
3186 }
3187 _onKeydown(event) {
3188 // If the user is pressing backspace on an empty end input, move focus back to the start.
3189 if (event.keyCode === BACKSPACE && !this._elementRef.nativeElement.value) {
3190 this._rangeInput._startInput.focus();
3191 }
3192 super._onKeydown(event);
3193 }
3194}
3195MatEndDate.decorators = [
3196 { type: Directive, args: [{
3197 selector: 'input[matEndDate]',
3198 host: {
3199 'class': 'mat-end-date mat-date-range-input-inner',
3200 '[disabled]': 'disabled',
3201 '(input)': '_onInput($event.target.value)',
3202 '(change)': '_onChange()',
3203 '(keydown)': '_onKeydown($event)',
3204 '[attr.aria-haspopup]': '_rangeInput.rangePicker ? "dialog" : null',
3205 '[attr.aria-owns]': '(_rangeInput.rangePicker?.opened && _rangeInput.rangePicker.id) || null',
3206 '[attr.min]': '_getMinDate() ? _dateAdapter.toIso8601(_getMinDate()) : null',
3207 '[attr.max]': '_getMaxDate() ? _dateAdapter.toIso8601(_getMaxDate()) : null',
3208 '(blur)': '_onBlur()',
3209 'type': 'text',
3210 },
3211 providers: [
3212 { provide: NG_VALUE_ACCESSOR, useExisting: MatEndDate, multi: true },
3213 { provide: NG_VALIDATORS, useExisting: MatEndDate, multi: true }
3214 ],
3215 // These need to be specified explicitly, because some tooling doesn't
3216 // seem to pick them up from the base class. See #20932.
3217 outputs: ['dateChange', 'dateInput'],
3218 inputs: ['errorStateMatcher']
3219 },] }
3220];
3221MatEndDate.ctorParameters = () => [
3222 { type: undefined, decorators: [{ type: Inject, args: [MAT_DATE_RANGE_INPUT_PARENT,] }] },
3223 { type: ElementRef },
3224 { type: ErrorStateMatcher },
3225 { type: Injector },
3226 { type: NgForm, decorators: [{ type: Optional }] },
3227 { type: FormGroupDirective, decorators: [{ type: Optional }] },
3228 { type: DateAdapter, decorators: [{ type: Optional }] },
3229 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_DATE_FORMATS,] }] }
3230];
3231
3232/**
3233 * @license
3234 * Copyright Google LLC All Rights Reserved.
3235 *
3236 * Use of this source code is governed by an MIT-style license that can be
3237 * found in the LICENSE file at https://angular.io/license
3238 */
3239let nextUniqueId = 0;
3240class MatDateRangeInput {
3241 constructor(_changeDetectorRef, _elementRef, control, _dateAdapter, _formField) {
3242 this._changeDetectorRef = _changeDetectorRef;
3243 this._elementRef = _elementRef;
3244 this._dateAdapter = _dateAdapter;
3245 this._formField = _formField;
3246 this._closedSubscription = Subscription.EMPTY;
3247 /** Unique ID for the input. */
3248 this.id = `mat-date-range-input-${nextUniqueId++}`;
3249 /** Whether the control is focused. */
3250 this.focused = false;
3251 /** Name of the form control. */
3252 this.controlType = 'mat-date-range-input';
3253 this._groupDisabled = false;
3254 /** Value for the `aria-describedby` attribute of the inputs. */
3255 this._ariaDescribedBy = null;
3256 /** Separator text to be shown between the inputs. */
3257 this.separator = '–';
3258 /** Start of the comparison range that should be shown in the calendar. */
3259 this.comparisonStart = null;
3260 /** End of the comparison range that should be shown in the calendar. */
3261 this.comparisonEnd = null;
3262 /** Emits when the input's state has changed. */
3263 this.stateChanges = new Subject();
3264 if (!_dateAdapter && (typeof ngDevMode === 'undefined' || ngDevMode)) {
3265 throw createMissingDateImplError('DateAdapter');
3266 }
3267 // The datepicker module can be used both with MDC and non-MDC form fields. We have
3268 // to conditionally add the MDC input class so that the range picker looks correctly.
3269 if (_formField === null || _formField === void 0 ? void 0 : _formField._elementRef.nativeElement.classList.contains('mat-mdc-form-field')) {
3270 const classList = _elementRef.nativeElement.classList;
3271 classList.add('mat-mdc-input-element');
3272 classList.add('mat-mdc-form-field-input-control');
3273 }
3274 // TODO(crisbeto): remove `as any` after #18206 lands.
3275 this.ngControl = control;
3276 }
3277 /** Current value of the range input. */
3278 get value() {
3279 return this._model ? this._model.selection : null;
3280 }
3281 /** Whether the control's label should float. */
3282 get shouldLabelFloat() {
3283 return this.focused || !this.empty;
3284 }
3285 /**
3286 * Implemented as a part of `MatFormFieldControl`.
3287 * Set the placeholder attribute on `matStartDate` and `matEndDate`.
3288 * @docs-private
3289 */
3290 get placeholder() {
3291 var _a, _b;
3292 const start = ((_a = this._startInput) === null || _a === void 0 ? void 0 : _a._getPlaceholder()) || '';
3293 const end = ((_b = this._endInput) === null || _b === void 0 ? void 0 : _b._getPlaceholder()) || '';
3294 return (start || end) ? `${start} ${this.separator} ${end}` : '';
3295 }
3296 /** The range picker that this input is associated with. */
3297 get rangePicker() { return this._rangePicker; }
3298 set rangePicker(rangePicker) {
3299 if (rangePicker) {
3300 this._model = rangePicker.registerInput(this);
3301 this._rangePicker = rangePicker;
3302 this._closedSubscription.unsubscribe();
3303 this._closedSubscription = rangePicker.closedStream.subscribe(() => {
3304 var _a, _b;
3305 (_a = this._startInput) === null || _a === void 0 ? void 0 : _a._onTouched();
3306 (_b = this._endInput) === null || _b === void 0 ? void 0 : _b._onTouched();
3307 });
3308 this._registerModel(this._model);
3309 }
3310 }
3311 /** Whether the input is required. */
3312 get required() { return !!this._required; }
3313 set required(value) {
3314 this._required = coerceBooleanProperty(value);
3315 }
3316 /** Function that can be used to filter out dates within the date range picker. */
3317 get dateFilter() { return this._dateFilter; }
3318 set dateFilter(value) {
3319 const start = this._startInput;
3320 const end = this._endInput;
3321 const wasMatchingStart = start && start._matchesFilter(start.value);
3322 const wasMatchingEnd = end && end._matchesFilter(start.value);
3323 this._dateFilter = value;
3324 if (start && start._matchesFilter(start.value) !== wasMatchingStart) {
3325 start._validatorOnChange();
3326 }
3327 if (end && end._matchesFilter(end.value) !== wasMatchingEnd) {
3328 end._validatorOnChange();
3329 }
3330 }
3331 /** The minimum valid date. */
3332 get min() { return this._min; }
3333 set min(value) {
3334 const validValue = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
3335 if (!this._dateAdapter.sameDate(validValue, this._min)) {
3336 this._min = validValue;
3337 this._revalidate();
3338 }
3339 }
3340 /** The maximum valid date. */
3341 get max() { return this._max; }
3342 set max(value) {
3343 const validValue = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
3344 if (!this._dateAdapter.sameDate(validValue, this._max)) {
3345 this._max = validValue;
3346 this._revalidate();
3347 }
3348 }
3349 /** Whether the input is disabled. */
3350 get disabled() {
3351 return (this._startInput && this._endInput) ?
3352 (this._startInput.disabled && this._endInput.disabled) :
3353 this._groupDisabled;
3354 }
3355 set disabled(value) {
3356 const newValue = coerceBooleanProperty(value);
3357 if (newValue !== this._groupDisabled) {
3358 this._groupDisabled = newValue;
3359 this.stateChanges.next(undefined);
3360 }
3361 }
3362 /** Whether the input is in an error state. */
3363 get errorState() {
3364 if (this._startInput && this._endInput) {
3365 return this._startInput.errorState || this._endInput.errorState;
3366 }
3367 return false;
3368 }
3369 /** Whether the datepicker input is empty. */
3370 get empty() {
3371 const startEmpty = this._startInput ? this._startInput.isEmpty() : false;
3372 const endEmpty = this._endInput ? this._endInput.isEmpty() : false;
3373 return startEmpty && endEmpty;
3374 }
3375 /**
3376 * Implemented as a part of `MatFormFieldControl`.
3377 * @docs-private
3378 */
3379 setDescribedByIds(ids) {
3380 this._ariaDescribedBy = ids.length ? ids.join(' ') : null;
3381 }
3382 /**
3383 * Implemented as a part of `MatFormFieldControl`.
3384 * @docs-private
3385 */
3386 onContainerClick() {
3387 if (!this.focused && !this.disabled) {
3388 if (!this._model || !this._model.selection.start) {
3389 this._startInput.focus();
3390 }
3391 else {
3392 this._endInput.focus();
3393 }
3394 }
3395 }
3396 ngAfterContentInit() {
3397 if (typeof ngDevMode === 'undefined' || ngDevMode) {
3398 if (!this._startInput) {
3399 throw Error('mat-date-range-input must contain a matStartDate input');
3400 }
3401 if (!this._endInput) {
3402 throw Error('mat-date-range-input must contain a matEndDate input');
3403 }
3404 }
3405 if (this._model) {
3406 this._registerModel(this._model);
3407 }
3408 // We don't need to unsubscribe from this, because we
3409 // know that the input streams will be completed on destroy.
3410 merge(this._startInput.stateChanges, this._endInput.stateChanges).subscribe(() => {
3411 this.stateChanges.next(undefined);
3412 });
3413 }
3414 ngOnChanges(changes) {
3415 if (dateInputsHaveChanged(changes, this._dateAdapter)) {
3416 this.stateChanges.next(undefined);
3417 }
3418 }
3419 ngOnDestroy() {
3420 this._closedSubscription.unsubscribe();
3421 this.stateChanges.complete();
3422 }
3423 /** Gets the date at which the calendar should start. */
3424 getStartValue() {
3425 return this.value ? this.value.start : null;
3426 }
3427 /** Gets the input's theme palette. */
3428 getThemePalette() {
3429 return this._formField ? this._formField.color : undefined;
3430 }
3431 /** Gets the element to which the calendar overlay should be attached. */
3432 getConnectedOverlayOrigin() {
3433 return this._formField ? this._formField.getConnectedOverlayOrigin() : this._elementRef;
3434 }
3435 /** Gets the ID of an element that should be used a description for the calendar overlay. */
3436 getOverlayLabelId() {
3437 return this._formField ? this._formField.getLabelId() : null;
3438 }
3439 /** Gets the value that is used to mirror the state input. */
3440 _getInputMirrorValue() {
3441 return this._startInput ? this._startInput.getMirrorValue() : '';
3442 }
3443 /** Whether the input placeholders should be hidden. */
3444 _shouldHidePlaceholders() {
3445 return this._startInput ? !this._startInput.isEmpty() : false;
3446 }
3447 /** Handles the value in one of the child inputs changing. */
3448 _handleChildValueChange() {
3449 this.stateChanges.next(undefined);
3450 this._changeDetectorRef.markForCheck();
3451 }
3452 /** Opens the date range picker associated with the input. */
3453 _openDatepicker() {
3454 if (this._rangePicker) {
3455 this._rangePicker.open();
3456 }
3457 }
3458 /** Whether the separate text should be hidden. */
3459 _shouldHideSeparator() {
3460 return (!this._formField || (this._formField.getLabelId() &&
3461 !this._formField._shouldLabelFloat())) && this.empty;
3462 }
3463 /** Gets the value for the `aria-labelledby` attribute of the inputs. */
3464 _getAriaLabelledby() {
3465 const formField = this._formField;
3466 return formField && formField._hasFloatingLabel() ? formField._labelId : null;
3467 }
3468 /** Updates the focused state of the range input. */
3469 _updateFocus(origin) {
3470 this.focused = origin !== null;
3471 this.stateChanges.next();
3472 }
3473 /** Re-runs the validators on the start/end inputs. */
3474 _revalidate() {
3475 if (this._startInput) {
3476 this._startInput._validatorOnChange();
3477 }
3478 if (this._endInput) {
3479 this._endInput._validatorOnChange();
3480 }
3481 }
3482 /** Registers the current date selection model with the start/end inputs. */
3483 _registerModel(model) {
3484 if (this._startInput) {
3485 this._startInput._registerModel(model);
3486 }
3487 if (this._endInput) {
3488 this._endInput._registerModel(model);
3489 }
3490 }
3491}
3492MatDateRangeInput.decorators = [
3493 { type: Component, args: [{
3494 selector: 'mat-date-range-input',
3495 template: "<div\n class=\"mat-date-range-input-container\"\n cdkMonitorSubtreeFocus\n (cdkFocusChange)=\"_updateFocus($event)\">\n <div class=\"mat-date-range-input-start-wrapper\">\n <ng-content select=\"input[matStartDate]\"></ng-content>\n <span\n class=\"mat-date-range-input-mirror\"\n aria-hidden=\"true\">{{_getInputMirrorValue()}}</span>\n </div>\n\n <span\n class=\"mat-date-range-input-separator\"\n [class.mat-date-range-input-separator-hidden]=\"_shouldHideSeparator()\">{{separator}}</span>\n\n <div class=\"mat-date-range-input-end-wrapper\">\n <ng-content select=\"input[matEndDate]\"></ng-content>\n </div>\n</div>\n\n",
3496 exportAs: 'matDateRangeInput',
3497 host: {
3498 'class': 'mat-date-range-input',
3499 '[class.mat-date-range-input-hide-placeholders]': '_shouldHidePlaceholders()',
3500 '[class.mat-date-range-input-required]': 'required',
3501 '[attr.id]': 'null',
3502 'role': 'group',
3503 '[attr.aria-labelledby]': '_getAriaLabelledby()',
3504 '[attr.aria-describedby]': '_ariaDescribedBy',
3505 // Used by the test harness to tie this input to its calendar. We can't depend on
3506 // `aria-owns` for this, because it's only defined while the calendar is open.
3507 '[attr.data-mat-calendar]': 'rangePicker ? rangePicker.id : null',
3508 },
3509 changeDetection: ChangeDetectionStrategy.OnPush,
3510 encapsulation: ViewEncapsulation.None,
3511 providers: [
3512 { provide: MatFormFieldControl, useExisting: MatDateRangeInput },
3513 { provide: MAT_DATE_RANGE_INPUT_PARENT, useExisting: MatDateRangeInput },
3514 ],
3515 styles: [".mat-date-range-input{display:block;width:100%}.mat-date-range-input-container{display:flex;align-items:center}.mat-date-range-input-separator{transition:opacity 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1);margin:0 4px}.mat-date-range-input-separator-hidden{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;opacity:0;transition:none}.mat-date-range-input-inner{font:inherit;background:transparent;color:currentColor;border:none;outline:none;padding:0;margin:0;vertical-align:bottom;text-align:inherit;-webkit-appearance:none;width:100%}.mat-date-range-input-inner::-ms-clear,.mat-date-range-input-inner::-ms-reveal{display:none}.mat-date-range-input-inner:-moz-ui-invalid{box-shadow:none}.mat-date-range-input-inner::placeholder{transition:color 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1)}.mat-date-range-input-inner::-moz-placeholder{transition:color 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1)}.mat-date-range-input-inner::-webkit-input-placeholder{transition:color 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1)}.mat-date-range-input-inner:-ms-input-placeholder{transition:color 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1)}.mat-form-field-hide-placeholder .mat-date-range-input-inner::placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::placeholder{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;color:transparent !important;-webkit-text-fill-color:transparent;transition:none}.cdk-high-contrast-active .mat-form-field-hide-placeholder .mat-date-range-input-inner::placeholder,.cdk-high-contrast-active .mat-date-range-input-hide-placeholders .mat-date-range-input-inner::placeholder{opacity:0}.mat-form-field-hide-placeholder .mat-date-range-input-inner::-moz-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::-moz-placeholder{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;color:transparent !important;-webkit-text-fill-color:transparent;transition:none}.cdk-high-contrast-active .mat-form-field-hide-placeholder .mat-date-range-input-inner::-moz-placeholder,.cdk-high-contrast-active .mat-date-range-input-hide-placeholders .mat-date-range-input-inner::-moz-placeholder{opacity:0}.mat-form-field-hide-placeholder .mat-date-range-input-inner::-webkit-input-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::-webkit-input-placeholder{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;color:transparent !important;-webkit-text-fill-color:transparent;transition:none}.cdk-high-contrast-active .mat-form-field-hide-placeholder .mat-date-range-input-inner::-webkit-input-placeholder,.cdk-high-contrast-active .mat-date-range-input-hide-placeholders .mat-date-range-input-inner::-webkit-input-placeholder{opacity:0}.mat-form-field-hide-placeholder .mat-date-range-input-inner:-ms-input-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner:-ms-input-placeholder{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;color:transparent !important;-webkit-text-fill-color:transparent;transition:none}.cdk-high-contrast-active .mat-form-field-hide-placeholder .mat-date-range-input-inner:-ms-input-placeholder,.cdk-high-contrast-active .mat-date-range-input-hide-placeholders .mat-date-range-input-inner:-ms-input-placeholder{opacity:0}.mat-date-range-input-mirror{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;visibility:hidden;white-space:nowrap;display:inline-block;min-width:2px}.mat-date-range-input-start-wrapper{position:relative;overflow:hidden;max-width:calc(50% - 4px)}.mat-date-range-input-start-wrapper .mat-date-range-input-inner{position:absolute;top:0;left:0}.mat-date-range-input-end-wrapper{flex-grow:1;max-width:calc(50% - 4px)}.mat-form-field-type-mat-date-range-input .mat-form-field-infix{width:200px}\n"]
3516 },] }
3517];
3518MatDateRangeInput.ctorParameters = () => [
3519 { type: ChangeDetectorRef },
3520 { type: ElementRef },
3521 { type: ControlContainer, decorators: [{ type: Optional }, { type: Self }] },
3522 { type: DateAdapter, decorators: [{ type: Optional }] },
3523 { type: MatFormField, decorators: [{ type: Optional }, { type: Inject, args: [MAT_FORM_FIELD,] }] }
3524];
3525MatDateRangeInput.propDecorators = {
3526 rangePicker: [{ type: Input }],
3527 required: [{ type: Input }],
3528 dateFilter: [{ type: Input }],
3529 min: [{ type: Input }],
3530 max: [{ type: Input }],
3531 disabled: [{ type: Input }],
3532 separator: [{ type: Input }],
3533 comparisonStart: [{ type: Input }],
3534 comparisonEnd: [{ type: Input }],
3535 _startInput: [{ type: ContentChild, args: [MatStartDate,] }],
3536 _endInput: [{ type: ContentChild, args: [MatEndDate,] }]
3537};
3538
3539/**
3540 * @license
3541 * Copyright Google LLC All Rights Reserved.
3542 *
3543 * Use of this source code is governed by an MIT-style license that can be
3544 * found in the LICENSE file at https://angular.io/license
3545 */
3546// TODO(mmalerba): We use a component instead of a directive here so the user can use implicit
3547// template reference variables (e.g. #d vs #d="matDateRangePicker"). We can change this to a
3548// directive if angular adds support for `exportAs: '$implicit'` on directives.
3549/** Component responsible for managing the date range picker popup/dialog. */
3550class MatDateRangePicker extends MatDatepickerBase {
3551 _forwardContentValues(instance) {
3552 super._forwardContentValues(instance);
3553 const input = this.datepickerInput;
3554 if (input) {
3555 instance.comparisonStart = input.comparisonStart;
3556 instance.comparisonEnd = input.comparisonEnd;
3557 }
3558 }
3559}
3560MatDateRangePicker.decorators = [
3561 { type: Component, args: [{
3562 selector: 'mat-date-range-picker',
3563 template: '',
3564 exportAs: 'matDateRangePicker',
3565 changeDetection: ChangeDetectionStrategy.OnPush,
3566 encapsulation: ViewEncapsulation.None,
3567 providers: [
3568 MAT_RANGE_DATE_SELECTION_MODEL_PROVIDER,
3569 MAT_CALENDAR_RANGE_STRATEGY_PROVIDER,
3570 { provide: MatDatepickerBase, useExisting: MatDateRangePicker },
3571 ]
3572 },] }
3573];
3574
3575/**
3576 * @license
3577 * Copyright Google LLC All Rights Reserved.
3578 *
3579 * Use of this source code is governed by an MIT-style license that can be
3580 * found in the LICENSE file at https://angular.io/license
3581 */
3582/** Button that will close the datepicker and assign the current selection to the data model. */
3583class MatDatepickerApply {
3584 constructor(_datepicker) {
3585 this._datepicker = _datepicker;
3586 }
3587 _applySelection() {
3588 this._datepicker._applyPendingSelection();
3589 this._datepicker.close();
3590 }
3591}
3592MatDatepickerApply.decorators = [
3593 { type: Directive, args: [{
3594 selector: '[matDatepickerApply], [matDateRangePickerApply]',
3595 host: { '(click)': '_applySelection()' }
3596 },] }
3597];
3598MatDatepickerApply.ctorParameters = () => [
3599 { type: MatDatepickerBase }
3600];
3601/** Button that will close the datepicker and discard the current selection. */
3602class MatDatepickerCancel {
3603 constructor(_datepicker) {
3604 this._datepicker = _datepicker;
3605 }
3606}
3607MatDatepickerCancel.decorators = [
3608 { type: Directive, args: [{
3609 selector: '[matDatepickerCancel], [matDateRangePickerCancel]',
3610 host: { '(click)': '_datepicker.close()' }
3611 },] }
3612];
3613MatDatepickerCancel.ctorParameters = () => [
3614 { type: MatDatepickerBase }
3615];
3616/**
3617 * Container that can be used to project a row of action buttons
3618 * to the bottom of a datepicker or date range picker.
3619 */
3620class MatDatepickerActions {
3621 constructor(_datepicker, _viewContainerRef) {
3622 this._datepicker = _datepicker;
3623 this._viewContainerRef = _viewContainerRef;
3624 }
3625 ngAfterViewInit() {
3626 this._portal = new TemplatePortal(this._template, this._viewContainerRef);
3627 this._datepicker.registerActions(this._portal);
3628 }
3629 ngOnDestroy() {
3630 var _a;
3631 this._datepicker.removeActions(this._portal);
3632 // Needs to be null checked since we initialize it in `ngAfterViewInit`.
3633 if (this._portal && this._portal.isAttached) {
3634 (_a = this._portal) === null || _a === void 0 ? void 0 : _a.detach();
3635 }
3636 }
3637}
3638MatDatepickerActions.decorators = [
3639 { type: Component, args: [{
3640 selector: 'mat-datepicker-actions, mat-date-range-picker-actions',
3641 template: `
3642 <ng-template>
3643 <div class="mat-datepicker-actions">
3644 <ng-content></ng-content>
3645 </div>
3646 </ng-template>
3647 `,
3648 changeDetection: ChangeDetectionStrategy.OnPush,
3649 encapsulation: ViewEncapsulation.None,
3650 styles: [".mat-datepicker-actions{display:flex;justify-content:flex-end;align-items:center;padding:0 8px 8px 8px}.mat-datepicker-actions .mat-button-base+.mat-button-base{margin-left:8px}[dir=rtl] .mat-datepicker-actions .mat-button-base+.mat-button-base{margin-left:0;margin-right:8px}\n"]
3651 },] }
3652];
3653MatDatepickerActions.ctorParameters = () => [
3654 { type: MatDatepickerBase },
3655 { type: ViewContainerRef }
3656];
3657MatDatepickerActions.propDecorators = {
3658 _template: [{ type: ViewChild, args: [TemplateRef,] }]
3659};
3660
3661/**
3662 * @license
3663 * Copyright Google LLC All Rights Reserved.
3664 *
3665 * Use of this source code is governed by an MIT-style license that can be
3666 * found in the LICENSE file at https://angular.io/license
3667 */
3668class MatDatepickerModule {
3669}
3670MatDatepickerModule.decorators = [
3671 { type: NgModule, args: [{
3672 imports: [
3673 CommonModule,
3674 MatButtonModule,
3675 OverlayModule,
3676 A11yModule,
3677 PortalModule,
3678 MatCommonModule,
3679 ],
3680 exports: [
3681 CdkScrollableModule,
3682 MatCalendar,
3683 MatCalendarBody,
3684 MatDatepicker,
3685 MatDatepickerContent,
3686 MatDatepickerInput,
3687 MatDatepickerToggle,
3688 MatDatepickerToggleIcon,
3689 MatMonthView,
3690 MatYearView,
3691 MatMultiYearView,
3692 MatCalendarHeader,
3693 MatDateRangeInput,
3694 MatStartDate,
3695 MatEndDate,
3696 MatDateRangePicker,
3697 MatDatepickerActions,
3698 MatDatepickerCancel,
3699 MatDatepickerApply
3700 ],
3701 declarations: [
3702 MatCalendar,
3703 MatCalendarBody,
3704 MatDatepicker,
3705 MatDatepickerContent,
3706 MatDatepickerInput,
3707 MatDatepickerToggle,
3708 MatDatepickerToggleIcon,
3709 MatMonthView,
3710 MatYearView,
3711 MatMultiYearView,
3712 MatCalendarHeader,
3713 MatDateRangeInput,
3714 MatStartDate,
3715 MatEndDate,
3716 MatDateRangePicker,
3717 MatDatepickerActions,
3718 MatDatepickerCancel,
3719 MatDatepickerApply
3720 ],
3721 providers: [
3722 MatDatepickerIntl,
3723 MAT_DATEPICKER_SCROLL_STRATEGY_FACTORY_PROVIDER
3724 ],
3725 entryComponents: [
3726 MatDatepickerContent,
3727 MatCalendarHeader,
3728 ]
3729 },] }
3730];
3731
3732/**
3733 * @license
3734 * Copyright Google LLC All Rights Reserved.
3735 *
3736 * Use of this source code is governed by an MIT-style license that can be
3737 * found in the LICENSE file at https://angular.io/license
3738 */
3739
3740/**
3741 * Generated bundle index. Do not edit.
3742 */
3743
3744export { DateRange, DefaultMatCalendarRangeStrategy, MAT_DATEPICKER_SCROLL_STRATEGY, MAT_DATEPICKER_SCROLL_STRATEGY_FACTORY, MAT_DATEPICKER_SCROLL_STRATEGY_FACTORY_PROVIDER, MAT_DATEPICKER_VALIDATORS, MAT_DATEPICKER_VALUE_ACCESSOR, MAT_DATE_RANGE_SELECTION_STRATEGY, MAT_RANGE_DATE_SELECTION_MODEL_FACTORY, MAT_RANGE_DATE_SELECTION_MODEL_PROVIDER, MAT_SINGLE_DATE_SELECTION_MODEL_FACTORY, MAT_SINGLE_DATE_SELECTION_MODEL_PROVIDER, MatCalendar, MatCalendarBody, MatCalendarCell, MatCalendarHeader, MatDateRangeInput, MatDateRangePicker, MatDateSelectionModel, MatDatepicker, MatDatepickerActions, MatDatepickerApply, MatDatepickerCancel, MatDatepickerContent, MatDatepickerInput, MatDatepickerInputEvent, MatDatepickerIntl, MatDatepickerModule, MatDatepickerToggle, MatDatepickerToggleIcon, MatEndDate, MatMonthView, MatMultiYearView, MatRangeDateSelectionModel, MatSingleDateSelectionModel, MatStartDate, MatYearView, matDatepickerAnimations, yearsPerPage, yearsPerRow, MAT_CALENDAR_RANGE_STRATEGY_PROVIDER_FACTORY as ɵangular_material_src_material_datepicker_datepicker_a, MAT_CALENDAR_RANGE_STRATEGY_PROVIDER as ɵangular_material_src_material_datepicker_datepicker_b, MatDatepickerBase as ɵangular_material_src_material_datepicker_datepicker_c, MatDatepickerInputBase as ɵangular_material_src_material_datepicker_datepicker_d, MAT_DATE_RANGE_INPUT_PARENT as ɵangular_material_src_material_datepicker_datepicker_e };
3745//# sourceMappingURL=datepicker.js.map
Note: See TracBrowser for help on using the repository browser.