import { A11yModule } from '@angular/cdk/a11y'; import { Overlay, FlexibleConnectedPositionStrategy, OverlayConfig, OverlayModule } from '@angular/cdk/overlay'; import { ComponentPortal, TemplatePortal, PortalModule } from '@angular/cdk/portal'; import { DOCUMENT, CommonModule } from '@angular/common'; import * as i0 from '@angular/core'; import { 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'; import { MatButtonModule } from '@angular/material/button'; import { CdkScrollableModule } from '@angular/cdk/scrolling'; import { DateAdapter, MAT_DATE_FORMATS, mixinColor, ErrorStateMatcher, mixinErrorState, MatCommonModule } from '@angular/material/core'; import { Subject, Subscription, merge, of } from 'rxjs'; import { ESCAPE, hasModifierKey, SPACE, ENTER, PAGE_DOWN, PAGE_UP, END, HOME, DOWN_ARROW, UP_ARROW, RIGHT_ARROW, LEFT_ARROW, BACKSPACE } from '@angular/cdk/keycodes'; import { Directionality } from '@angular/cdk/bidi'; import { take, startWith, filter } from 'rxjs/operators'; import { coerceBooleanProperty, coerceStringArray } from '@angular/cdk/coercion'; import { _getFocusedElementPierceShadowDom } from '@angular/cdk/platform'; import { trigger, transition, animate, keyframes, style, state } from '@angular/animations'; import { NG_VALUE_ACCESSOR, NG_VALIDATORS, Validators, NgControl, NgForm, FormGroupDirective, ControlContainer } from '@angular/forms'; import { MatFormField, MAT_FORM_FIELD, MatFormFieldControl } from '@angular/material/form-field'; import { MAT_INPUT_VALUE_ACCESSOR } from '@angular/material/input'; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ /** @docs-private */ function createMissingDateImplError(provider) { return Error(`MatDatepicker: No provider found for ${provider}. You must import one of the following ` + `modules at your application root: MatNativeDateModule, MatMomentDateModule, or provide a ` + `custom implementation.`); } /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ /** Datepicker data that requires internationalization. */ class MatDatepickerIntl { constructor() { /** * Stream that emits whenever the labels here are changed. Use this to notify * components if the labels have changed after initialization. */ this.changes = new Subject(); /** A label for the calendar popup (used by screen readers). */ this.calendarLabel = 'Calendar'; /** A label for the button used to open the calendar popup (used by screen readers). */ this.openCalendarLabel = 'Open calendar'; /** Label for the button used to close the calendar popup. */ this.closeCalendarLabel = 'Close calendar'; /** A label for the previous month button (used by screen readers). */ this.prevMonthLabel = 'Previous month'; /** A label for the next month button (used by screen readers). */ this.nextMonthLabel = 'Next month'; /** A label for the previous year button (used by screen readers). */ this.prevYearLabel = 'Previous year'; /** A label for the next year button (used by screen readers). */ this.nextYearLabel = 'Next year'; /** A label for the previous multi-year button (used by screen readers). */ this.prevMultiYearLabel = 'Previous 24 years'; /** A label for the next multi-year button (used by screen readers). */ this.nextMultiYearLabel = 'Next 24 years'; /** A label for the 'switch to month view' button (used by screen readers). */ this.switchToMonthViewLabel = 'Choose date'; /** A label for the 'switch to year view' button (used by screen readers). */ this.switchToMultiYearViewLabel = 'Choose month and year'; } /** Formats a range of years. */ formatYearRange(start, end) { return `${start} \u2013 ${end}`; } } MatDatepickerIntl.ɵprov = i0.ɵɵdefineInjectable({ factory: function MatDatepickerIntl_Factory() { return new MatDatepickerIntl(); }, token: MatDatepickerIntl, providedIn: "root" }); MatDatepickerIntl.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] } ]; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ /** * An internal class that represents the data corresponding to a single calendar cell. * @docs-private */ class MatCalendarCell { constructor(value, displayValue, ariaLabel, enabled, cssClasses = {}, compareValue = value, rawValue) { this.value = value; this.displayValue = displayValue; this.ariaLabel = ariaLabel; this.enabled = enabled; this.cssClasses = cssClasses; this.compareValue = compareValue; this.rawValue = rawValue; } } /** * An internal component used to display calendar data in a table. * @docs-private */ class MatCalendarBody { constructor(_elementRef, _ngZone) { this._elementRef = _elementRef; this._ngZone = _ngZone; /** The number of columns in the table. */ this.numCols = 7; /** The cell number of the active cell in the table. */ this.activeCell = 0; /** Whether a range is being selected. */ this.isRange = false; /** * The aspect ratio (width / height) to use for the cells in the table. This aspect ratio will be * maintained even as the table resizes. */ this.cellAspectRatio = 1; /** Start of the preview range. */ this.previewStart = null; /** End of the preview range. */ this.previewEnd = null; /** Emits when a new value is selected. */ this.selectedValueChange = new EventEmitter(); /** Emits when the preview has changed as a result of a user action. */ this.previewChange = new EventEmitter(); /** * Event handler for when the user enters an element * inside the calendar body (e.g. by hovering in or focus). */ this._enterHandler = (event) => { if (this._skipNextFocus && event.type === 'focus') { this._skipNextFocus = false; return; } // We only need to hit the zone when we're selecting a range. if (event.target && this.isRange) { const cell = this._getCellFromElement(event.target); if (cell) { this._ngZone.run(() => this.previewChange.emit({ value: cell.enabled ? cell : null, event })); } } }; /** * Event handler for when the user's pointer leaves an element * inside the calendar body (e.g. by hovering out or blurring). */ this._leaveHandler = (event) => { // We only need to hit the zone when we're selecting a range. if (this.previewEnd !== null && this.isRange) { // Only reset the preview end value when leaving cells. This looks better, because // we have a gap between the cells and the rows and we don't want to remove the // range just for it to show up again when the user moves a few pixels to the side. if (event.target && isTableCell(event.target)) { this._ngZone.run(() => this.previewChange.emit({ value: null, event })); } } }; _ngZone.runOutsideAngular(() => { const element = _elementRef.nativeElement; element.addEventListener('mouseenter', this._enterHandler, true); element.addEventListener('focus', this._enterHandler, true); element.addEventListener('mouseleave', this._leaveHandler, true); element.addEventListener('blur', this._leaveHandler, true); }); } /** Called when a cell is clicked. */ _cellClicked(cell, event) { if (cell.enabled) { this.selectedValueChange.emit({ value: cell.value, event }); } } /** Returns whether a cell should be marked as selected. */ _isSelected(value) { return this.startValue === value || this.endValue === value; } ngOnChanges(changes) { const columnChanges = changes['numCols']; const { rows, numCols } = this; if (changes['rows'] || columnChanges) { this._firstRowOffset = rows && rows.length && rows[0].length ? numCols - rows[0].length : 0; } if (changes['cellAspectRatio'] || columnChanges || !this._cellPadding) { this._cellPadding = `${50 * this.cellAspectRatio / numCols}%`; } if (columnChanges || !this._cellWidth) { this._cellWidth = `${100 / numCols}%`; } } ngOnDestroy() { const element = this._elementRef.nativeElement; element.removeEventListener('mouseenter', this._enterHandler, true); element.removeEventListener('focus', this._enterHandler, true); element.removeEventListener('mouseleave', this._leaveHandler, true); element.removeEventListener('blur', this._leaveHandler, true); } /** Returns whether a cell is active. */ _isActiveCell(rowIndex, colIndex) { let cellNumber = rowIndex * this.numCols + colIndex; // Account for the fact that the first row may not have as many cells. if (rowIndex) { cellNumber -= this._firstRowOffset; } return cellNumber == this.activeCell; } /** Focuses the active cell after the microtask queue is empty. */ _focusActiveCell(movePreview = true) { this._ngZone.runOutsideAngular(() => { this._ngZone.onStable.pipe(take(1)).subscribe(() => { const activeCell = this._elementRef.nativeElement.querySelector('.mat-calendar-body-active'); if (activeCell) { if (!movePreview) { this._skipNextFocus = true; } activeCell.focus(); } }); }); } /** Gets whether a value is the start of the main range. */ _isRangeStart(value) { return isStart(value, this.startValue, this.endValue); } /** Gets whether a value is the end of the main range. */ _isRangeEnd(value) { return isEnd(value, this.startValue, this.endValue); } /** Gets whether a value is within the currently-selected range. */ _isInRange(value) { return isInRange(value, this.startValue, this.endValue, this.isRange); } /** Gets whether a value is the start of the comparison range. */ _isComparisonStart(value) { return isStart(value, this.comparisonStart, this.comparisonEnd); } /** Whether the cell is a start bridge cell between the main and comparison ranges. */ _isComparisonBridgeStart(value, rowIndex, colIndex) { if (!this._isComparisonStart(value) || this._isRangeStart(value) || !this._isInRange(value)) { return false; } let previousCell = this.rows[rowIndex][colIndex - 1]; if (!previousCell) { const previousRow = this.rows[rowIndex - 1]; previousCell = previousRow && previousRow[previousRow.length - 1]; } return previousCell && !this._isRangeEnd(previousCell.compareValue); } /** Whether the cell is an end bridge cell between the main and comparison ranges. */ _isComparisonBridgeEnd(value, rowIndex, colIndex) { if (!this._isComparisonEnd(value) || this._isRangeEnd(value) || !this._isInRange(value)) { return false; } let nextCell = this.rows[rowIndex][colIndex + 1]; if (!nextCell) { const nextRow = this.rows[rowIndex + 1]; nextCell = nextRow && nextRow[0]; } return nextCell && !this._isRangeStart(nextCell.compareValue); } /** Gets whether a value is the end of the comparison range. */ _isComparisonEnd(value) { return isEnd(value, this.comparisonStart, this.comparisonEnd); } /** Gets whether a value is within the current comparison range. */ _isInComparisonRange(value) { return isInRange(value, this.comparisonStart, this.comparisonEnd, this.isRange); } /** * Gets whether a value is the same as the start and end of the comparison range. * For context, the functions that we use to determine whether something is the start/end of * a range don't allow for the start and end to be on the same day, because we'd have to use * much more specific CSS selectors to style them correctly in all scenarios. This is fine for * the regular range, because when it happens, the selected styles take over and still show where * the range would've been, however we don't have these selected styles for a comparison range. * This function is used to apply a class that serves the same purpose as the one for selected * dates, but it only applies in the context of a comparison range. */ _isComparisonIdentical(value) { // Note that we don't need to null check the start/end // here, because the `value` will always be defined. return this.comparisonStart === this.comparisonEnd && value === this.comparisonStart; } /** Gets whether a value is the start of the preview range. */ _isPreviewStart(value) { return isStart(value, this.previewStart, this.previewEnd); } /** Gets whether a value is the end of the preview range. */ _isPreviewEnd(value) { return isEnd(value, this.previewStart, this.previewEnd); } /** Gets whether a value is inside the preview range. */ _isInPreview(value) { return isInRange(value, this.previewStart, this.previewEnd, this.isRange); } /** Finds the MatCalendarCell that corresponds to a DOM node. */ _getCellFromElement(element) { let cell; if (isTableCell(element)) { cell = element; } else if (isTableCell(element.parentNode)) { cell = element.parentNode; } if (cell) { const row = cell.getAttribute('data-mat-row'); const col = cell.getAttribute('data-mat-col'); if (row && col) { return this.rows[parseInt(row)][parseInt(col)]; } } return null; } } MatCalendarBody.decorators = [ { type: Component, args: [{ selector: '[mat-calendar-body]', template: "\n\n \n {{label}}\n \n\n\n\n\n \n \n {{_firstRowOffset >= labelMinRequiredCells ? label : ''}}\n \n \n
\n {{item.displayValue}}\n
\n
\n \n\n", host: { 'class': 'mat-calendar-body', }, exportAs: 'matCalendarBody', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".mat-calendar-body{min-width:224px}.mat-calendar-body-label{height:0;line-height:0;text-align:left;padding-left:4.7142857143%;padding-right:4.7142857143%}.mat-calendar-body-cell{position:relative;height:0;line-height:0;text-align:center;outline:none;cursor:pointer}.mat-calendar-body-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"] },] } ]; MatCalendarBody.ctorParameters = () => [ { type: ElementRef }, { type: NgZone } ]; MatCalendarBody.propDecorators = { label: [{ type: Input }], rows: [{ type: Input }], todayValue: [{ type: Input }], startValue: [{ type: Input }], endValue: [{ type: Input }], labelMinRequiredCells: [{ type: Input }], numCols: [{ type: Input }], activeCell: [{ type: Input }], isRange: [{ type: Input }], cellAspectRatio: [{ type: Input }], comparisonStart: [{ type: Input }], comparisonEnd: [{ type: Input }], previewStart: [{ type: Input }], previewEnd: [{ type: Input }], selectedValueChange: [{ type: Output }], previewChange: [{ type: Output }] }; /** Checks whether a node is a table cell element. */ function isTableCell(node) { return node.nodeName === 'TD'; } /** Checks whether a value is the start of a range. */ function isStart(value, start, end) { return end !== null && start !== end && value < end && value === start; } /** Checks whether a value is the end of a range. */ function isEnd(value, start, end) { return start !== null && start !== end && value >= start && value === end; } /** Checks whether a value is inside of a range. */ function isInRange(value, start, end, rangeEnabled) { return rangeEnabled && start !== null && end !== null && start !== end && value >= start && value <= end; } /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ /** A class representing a range of dates. */ class DateRange { constructor( /** The start date of the range. */ start, /** The end date of the range. */ end) { this.start = start; this.end = end; } } /** * A selection model containing a date selection. * @docs-private */ class MatDateSelectionModel { constructor( /** The current selection. */ selection, _adapter) { this.selection = selection; this._adapter = _adapter; this._selectionChanged = new Subject(); /** Emits when the selection has changed. */ this.selectionChanged = this._selectionChanged; this.selection = selection; } /** * Updates the current selection in the model. * @param value New selection that should be assigned. * @param source Object that triggered the selection change. */ updateSelection(value, source) { const oldValue = this.selection; this.selection = value; this._selectionChanged.next({ selection: value, source, oldValue }); } ngOnDestroy() { this._selectionChanged.complete(); } _isValidDateInstance(date) { return this._adapter.isDateInstance(date) && this._adapter.isValid(date); } } MatDateSelectionModel.decorators = [ { type: Injectable } ]; MatDateSelectionModel.ctorParameters = () => [ { type: undefined }, { type: DateAdapter } ]; /** * A selection model that contains a single date. * @docs-private */ class MatSingleDateSelectionModel extends MatDateSelectionModel { constructor(adapter) { super(null, adapter); } /** * Adds a date to the current selection. In the case of a single date selection, the added date * simply overwrites the previous selection */ add(date) { super.updateSelection(date, this); } /** Checks whether the current selection is valid. */ isValid() { return this.selection != null && this._isValidDateInstance(this.selection); } /** * Checks whether the current selection is complete. In the case of a single date selection, this * is true if the current selection is not null. */ isComplete() { return this.selection != null; } /** Clones the selection model. */ clone() { const clone = new MatSingleDateSelectionModel(this._adapter); clone.updateSelection(this.selection, this); return clone; } } MatSingleDateSelectionModel.decorators = [ { type: Injectable } ]; MatSingleDateSelectionModel.ctorParameters = () => [ { type: DateAdapter } ]; /** * A selection model that contains a date range. * @docs-private */ class MatRangeDateSelectionModel extends MatDateSelectionModel { constructor(adapter) { super(new DateRange(null, null), adapter); } /** * Adds a date to the current selection. In the case of a date range selection, the added date * fills in the next `null` value in the range. If both the start and the end already have a date, * the selection is reset so that the given date is the new `start` and the `end` is null. */ add(date) { let { start, end } = this.selection; if (start == null) { start = date; } else if (end == null) { end = date; } else { start = date; end = null; } super.updateSelection(new DateRange(start, end), this); } /** Checks whether the current selection is valid. */ isValid() { const { start, end } = this.selection; // Empty ranges are valid. if (start == null && end == null) { return true; } // Complete ranges are only valid if both dates are valid and the start is before the end. if (start != null && end != null) { return this._isValidDateInstance(start) && this._isValidDateInstance(end) && this._adapter.compareDate(start, end) <= 0; } // Partial ranges are valid if the start/end is valid. return (start == null || this._isValidDateInstance(start)) && (end == null || this._isValidDateInstance(end)); } /** * Checks whether the current selection is complete. In the case of a date range selection, this * is true if the current selection has a non-null `start` and `end`. */ isComplete() { return this.selection.start != null && this.selection.end != null; } /** Clones the selection model. */ clone() { const clone = new MatRangeDateSelectionModel(this._adapter); clone.updateSelection(this.selection, this); return clone; } } MatRangeDateSelectionModel.decorators = [ { type: Injectable } ]; MatRangeDateSelectionModel.ctorParameters = () => [ { type: DateAdapter } ]; /** @docs-private */ function MAT_SINGLE_DATE_SELECTION_MODEL_FACTORY(parent, adapter) { return parent || new MatSingleDateSelectionModel(adapter); } /** * Used to provide a single selection model to a component. * @docs-private */ const MAT_SINGLE_DATE_SELECTION_MODEL_PROVIDER = { provide: MatDateSelectionModel, deps: [[new Optional(), new SkipSelf(), MatDateSelectionModel], DateAdapter], useFactory: MAT_SINGLE_DATE_SELECTION_MODEL_FACTORY, }; /** @docs-private */ function MAT_RANGE_DATE_SELECTION_MODEL_FACTORY(parent, adapter) { return parent || new MatRangeDateSelectionModel(adapter); } /** * Used to provide a range selection model to a component. * @docs-private */ const MAT_RANGE_DATE_SELECTION_MODEL_PROVIDER = { provide: MatDateSelectionModel, deps: [[new Optional(), new SkipSelf(), MatDateSelectionModel], DateAdapter], useFactory: MAT_RANGE_DATE_SELECTION_MODEL_FACTORY, }; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ /** Injection token used to customize the date range selection behavior. */ const MAT_DATE_RANGE_SELECTION_STRATEGY = new InjectionToken('MAT_DATE_RANGE_SELECTION_STRATEGY'); /** Provides the default date range selection behavior. */ class DefaultMatCalendarRangeStrategy { constructor(_dateAdapter) { this._dateAdapter = _dateAdapter; } selectionFinished(date, currentRange) { let { start, end } = currentRange; if (start == null) { start = date; } else if (end == null && date && this._dateAdapter.compareDate(date, start) >= 0) { end = date; } else { start = date; end = null; } return new DateRange(start, end); } createPreview(activeDate, currentRange) { let start = null; let end = null; if (currentRange.start && !currentRange.end && activeDate) { start = currentRange.start; end = activeDate; } return new DateRange(start, end); } } DefaultMatCalendarRangeStrategy.decorators = [ { type: Injectable } ]; DefaultMatCalendarRangeStrategy.ctorParameters = () => [ { type: DateAdapter } ]; /** @docs-private */ function MAT_CALENDAR_RANGE_STRATEGY_PROVIDER_FACTORY(parent, adapter) { return parent || new DefaultMatCalendarRangeStrategy(adapter); } /** @docs-private */ const MAT_CALENDAR_RANGE_STRATEGY_PROVIDER = { provide: MAT_DATE_RANGE_SELECTION_STRATEGY, deps: [[new Optional(), new SkipSelf(), MAT_DATE_RANGE_SELECTION_STRATEGY], DateAdapter], useFactory: MAT_CALENDAR_RANGE_STRATEGY_PROVIDER_FACTORY, }; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ const DAYS_PER_WEEK = 7; /** * An internal component used to display a single month in the datepicker. * @docs-private */ class MatMonthView { constructor(_changeDetectorRef, _dateFormats, _dateAdapter, _dir, _rangeStrategy) { this._changeDetectorRef = _changeDetectorRef; this._dateFormats = _dateFormats; this._dateAdapter = _dateAdapter; this._dir = _dir; this._rangeStrategy = _rangeStrategy; this._rerenderSubscription = Subscription.EMPTY; /** Emits when a new date is selected. */ this.selectedChange = new EventEmitter(); /** Emits when any date is selected. */ this._userSelection = new EventEmitter(); /** Emits when any date is activated. */ this.activeDateChange = new EventEmitter(); if (typeof ngDevMode === 'undefined' || ngDevMode) { if (!this._dateAdapter) { throw createMissingDateImplError('DateAdapter'); } if (!this._dateFormats) { throw createMissingDateImplError('MAT_DATE_FORMATS'); } } this._activeDate = this._dateAdapter.today(); } /** * The date to display in this month view (everything other than the month and year is ignored). */ get activeDate() { return this._activeDate; } set activeDate(value) { const oldActiveDate = this._activeDate; const validDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)) || this._dateAdapter.today(); this._activeDate = this._dateAdapter.clampDate(validDate, this.minDate, this.maxDate); if (!this._hasSameMonthAndYear(oldActiveDate, this._activeDate)) { this._init(); } } /** The currently selected date. */ get selected() { return this._selected; } set selected(value) { if (value instanceof DateRange) { this._selected = value; } else { this._selected = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } this._setRanges(this._selected); } /** The minimum selectable date. */ get minDate() { return this._minDate; } set minDate(value) { this._minDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } /** The maximum selectable date. */ get maxDate() { return this._maxDate; } set maxDate(value) { this._maxDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } ngAfterContentInit() { this._rerenderSubscription = this._dateAdapter.localeChanges .pipe(startWith(null)) .subscribe(() => this._init()); } ngOnChanges(changes) { const comparisonChange = changes['comparisonStart'] || changes['comparisonEnd']; if (comparisonChange && !comparisonChange.firstChange) { this._setRanges(this.selected); } } ngOnDestroy() { this._rerenderSubscription.unsubscribe(); } /** Handles when a new date is selected. */ _dateSelected(event) { const date = event.value; const selectedYear = this._dateAdapter.getYear(this.activeDate); const selectedMonth = this._dateAdapter.getMonth(this.activeDate); const selectedDate = this._dateAdapter.createDate(selectedYear, selectedMonth, date); let rangeStartDate; let rangeEndDate; if (this._selected instanceof DateRange) { rangeStartDate = this._getDateInCurrentMonth(this._selected.start); rangeEndDate = this._getDateInCurrentMonth(this._selected.end); } else { rangeStartDate = rangeEndDate = this._getDateInCurrentMonth(this._selected); } if (rangeStartDate !== date || rangeEndDate !== date) { this.selectedChange.emit(selectedDate); } this._userSelection.emit({ value: selectedDate, event: event.event }); this._previewStart = this._previewEnd = null; this._changeDetectorRef.markForCheck(); } /** Handles keydown events on the calendar body when calendar is in month view. */ _handleCalendarBodyKeydown(event) { // TODO(mmalerba): We currently allow keyboard navigation to disabled dates, but just prevent // disabled ones from being selected. This may not be ideal, we should look into whether // navigation should skip over disabled dates, and if so, how to implement that efficiently. const oldActiveDate = this._activeDate; const isRtl = this._isRtl(); switch (event.keyCode) { case LEFT_ARROW: this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, isRtl ? 1 : -1); break; case RIGHT_ARROW: this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, isRtl ? -1 : 1); break; case UP_ARROW: this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, -7); break; case DOWN_ARROW: this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, 7); break; case HOME: this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, 1 - this._dateAdapter.getDate(this._activeDate)); break; case END: this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, (this._dateAdapter.getNumDaysInMonth(this._activeDate) - this._dateAdapter.getDate(this._activeDate))); break; case PAGE_UP: this.activeDate = event.altKey ? this._dateAdapter.addCalendarYears(this._activeDate, -1) : this._dateAdapter.addCalendarMonths(this._activeDate, -1); break; case PAGE_DOWN: this.activeDate = event.altKey ? this._dateAdapter.addCalendarYears(this._activeDate, 1) : this._dateAdapter.addCalendarMonths(this._activeDate, 1); break; case ENTER: case SPACE: this._selectionKeyPressed = true; if (this._canSelect(this._activeDate)) { // Prevent unexpected default actions such as form submission. // Note that we only prevent the default action here while the selection happens in // `keyup` below. We can't do the selection here, because it can cause the calendar to // reopen if focus is restored immediately. We also can't call `preventDefault` on `keyup` // because it's too late (see #23305). event.preventDefault(); } return; case ESCAPE: // Abort the current range selection if the user presses escape mid-selection. if (this._previewEnd != null && !hasModifierKey(event)) { this._previewStart = this._previewEnd = null; this.selectedChange.emit(null); this._userSelection.emit({ value: null, event }); event.preventDefault(); event.stopPropagation(); // Prevents the overlay from closing. } return; default: // Don't prevent default or focus active cell on keys that we don't explicitly handle. return; } if (this._dateAdapter.compareDate(oldActiveDate, this.activeDate)) { this.activeDateChange.emit(this.activeDate); } this._focusActiveCell(); // Prevent unexpected default actions such as form submission. event.preventDefault(); } /** Handles keyup events on the calendar body when calendar is in month view. */ _handleCalendarBodyKeyup(event) { if (event.keyCode === SPACE || event.keyCode === ENTER) { if (this._selectionKeyPressed && this._canSelect(this._activeDate)) { this._dateSelected({ value: this._dateAdapter.getDate(this._activeDate), event }); } this._selectionKeyPressed = false; } } /** Initializes this month view. */ _init() { this._setRanges(this.selected); this._todayDate = this._getCellCompareValue(this._dateAdapter.today()); this._monthLabel = this._dateFormats.display.monthLabel ? this._dateAdapter.format(this.activeDate, this._dateFormats.display.monthLabel) : this._dateAdapter.getMonthNames('short')[this._dateAdapter.getMonth(this.activeDate)] .toLocaleUpperCase(); let firstOfMonth = this._dateAdapter.createDate(this._dateAdapter.getYear(this.activeDate), this._dateAdapter.getMonth(this.activeDate), 1); this._firstWeekOffset = (DAYS_PER_WEEK + this._dateAdapter.getDayOfWeek(firstOfMonth) - this._dateAdapter.getFirstDayOfWeek()) % DAYS_PER_WEEK; this._initWeekdays(); this._createWeekCells(); this._changeDetectorRef.markForCheck(); } /** Focuses the active cell after the microtask queue is empty. */ _focusActiveCell(movePreview) { this._matCalendarBody._focusActiveCell(movePreview); } /** Called when the user has activated a new cell and the preview needs to be updated. */ _previewChanged({ event, value: cell }) { if (this._rangeStrategy) { // We can assume that this will be a range, because preview // events aren't fired for single date selections. const value = cell ? cell.rawValue : null; const previewRange = this._rangeStrategy.createPreview(value, this.selected, event); this._previewStart = this._getCellCompareValue(previewRange.start); this._previewEnd = this._getCellCompareValue(previewRange.end); // Note that here we need to use `detectChanges`, rather than `markForCheck`, because // the way `_focusActiveCell` is set up at the moment makes it fire at the wrong time // when navigating one month back using the keyboard which will cause this handler // to throw a "changed after checked" error when updating the preview state. this._changeDetectorRef.detectChanges(); } } /** Initializes the weekdays. */ _initWeekdays() { const firstDayOfWeek = this._dateAdapter.getFirstDayOfWeek(); const narrowWeekdays = this._dateAdapter.getDayOfWeekNames('narrow'); const longWeekdays = this._dateAdapter.getDayOfWeekNames('long'); // Rotate the labels for days of the week based on the configured first day of the week. let weekdays = longWeekdays.map((long, i) => { return { long, narrow: narrowWeekdays[i] }; }); this._weekdays = weekdays.slice(firstDayOfWeek).concat(weekdays.slice(0, firstDayOfWeek)); } /** Creates MatCalendarCells for the dates in this month. */ _createWeekCells() { const daysInMonth = this._dateAdapter.getNumDaysInMonth(this.activeDate); const dateNames = this._dateAdapter.getDateNames(); this._weeks = [[]]; for (let i = 0, cell = this._firstWeekOffset; i < daysInMonth; i++, cell++) { if (cell == DAYS_PER_WEEK) { this._weeks.push([]); cell = 0; } const date = this._dateAdapter.createDate(this._dateAdapter.getYear(this.activeDate), this._dateAdapter.getMonth(this.activeDate), i + 1); const enabled = this._shouldEnableDate(date); const ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.dateA11yLabel); const cellClasses = this.dateClass ? this.dateClass(date, 'month') : undefined; this._weeks[this._weeks.length - 1].push(new MatCalendarCell(i + 1, dateNames[i], ariaLabel, enabled, cellClasses, this._getCellCompareValue(date), date)); } } /** Date filter for the month */ _shouldEnableDate(date) { return !!date && (!this.minDate || this._dateAdapter.compareDate(date, this.minDate) >= 0) && (!this.maxDate || this._dateAdapter.compareDate(date, this.maxDate) <= 0) && (!this.dateFilter || this.dateFilter(date)); } /** * Gets the date in this month that the given Date falls on. * Returns null if the given Date is in another month. */ _getDateInCurrentMonth(date) { return date && this._hasSameMonthAndYear(date, this.activeDate) ? this._dateAdapter.getDate(date) : null; } /** Checks whether the 2 dates are non-null and fall within the same month of the same year. */ _hasSameMonthAndYear(d1, d2) { return !!(d1 && d2 && this._dateAdapter.getMonth(d1) == this._dateAdapter.getMonth(d2) && this._dateAdapter.getYear(d1) == this._dateAdapter.getYear(d2)); } /** Gets the value that will be used to one cell to another. */ _getCellCompareValue(date) { if (date) { // We use the time since the Unix epoch to compare dates in this view, rather than the // cell values, because we need to support ranges that span across multiple months/years. const year = this._dateAdapter.getYear(date); const month = this._dateAdapter.getMonth(date); const day = this._dateAdapter.getDate(date); return new Date(year, month, day).getTime(); } return null; } /** Determines whether the user has the RTL layout direction. */ _isRtl() { return this._dir && this._dir.value === 'rtl'; } /** Sets the current range based on a model value. */ _setRanges(selectedValue) { if (selectedValue instanceof DateRange) { this._rangeStart = this._getCellCompareValue(selectedValue.start); this._rangeEnd = this._getCellCompareValue(selectedValue.end); this._isRange = true; } else { this._rangeStart = this._rangeEnd = this._getCellCompareValue(selectedValue); this._isRange = false; } this._comparisonRangeStart = this._getCellCompareValue(this.comparisonStart); this._comparisonRangeEnd = this._getCellCompareValue(this.comparisonEnd); } /** Gets whether a date can be selected in the month view. */ _canSelect(date) { return !this.dateFilter || this.dateFilter(date); } } MatMonthView.decorators = [ { type: Component, args: [{ selector: 'mat-month-view', template: "\n \n \n \n \n \n \n \n \n \n
\n {{day.narrow}}\n
\n", exportAs: 'matMonthView', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush },] } ]; MatMonthView.ctorParameters = () => [ { type: ChangeDetectorRef }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_DATE_FORMATS,] }] }, { type: DateAdapter, decorators: [{ type: Optional }] }, { type: Directionality, decorators: [{ type: Optional }] }, { type: undefined, decorators: [{ type: Inject, args: [MAT_DATE_RANGE_SELECTION_STRATEGY,] }, { type: Optional }] } ]; MatMonthView.propDecorators = { activeDate: [{ type: Input }], selected: [{ type: Input }], minDate: [{ type: Input }], maxDate: [{ type: Input }], dateFilter: [{ type: Input }], dateClass: [{ type: Input }], comparisonStart: [{ type: Input }], comparisonEnd: [{ type: Input }], selectedChange: [{ type: Output }], _userSelection: [{ type: Output }], activeDateChange: [{ type: Output }], _matCalendarBody: [{ type: ViewChild, args: [MatCalendarBody,] }] }; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ const yearsPerPage = 24; const yearsPerRow = 4; /** * An internal component used to display a year selector in the datepicker. * @docs-private */ class MatMultiYearView { constructor(_changeDetectorRef, _dateAdapter, _dir) { this._changeDetectorRef = _changeDetectorRef; this._dateAdapter = _dateAdapter; this._dir = _dir; this._rerenderSubscription = Subscription.EMPTY; /** Emits when a new year is selected. */ this.selectedChange = new EventEmitter(); /** Emits the selected year. This doesn't imply a change on the selected date */ this.yearSelected = new EventEmitter(); /** Emits when any date is activated. */ this.activeDateChange = new EventEmitter(); if (!this._dateAdapter && (typeof ngDevMode === 'undefined' || ngDevMode)) { throw createMissingDateImplError('DateAdapter'); } this._activeDate = this._dateAdapter.today(); } /** The date to display in this multi-year view (everything other than the year is ignored). */ get activeDate() { return this._activeDate; } set activeDate(value) { let oldActiveDate = this._activeDate; const validDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)) || this._dateAdapter.today(); this._activeDate = this._dateAdapter.clampDate(validDate, this.minDate, this.maxDate); if (!isSameMultiYearView(this._dateAdapter, oldActiveDate, this._activeDate, this.minDate, this.maxDate)) { this._init(); } } /** The currently selected date. */ get selected() { return this._selected; } set selected(value) { if (value instanceof DateRange) { this._selected = value; } else { this._selected = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } this._setSelectedYear(value); } /** The minimum selectable date. */ get minDate() { return this._minDate; } set minDate(value) { this._minDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } /** The maximum selectable date. */ get maxDate() { return this._maxDate; } set maxDate(value) { this._maxDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } ngAfterContentInit() { this._rerenderSubscription = this._dateAdapter.localeChanges .pipe(startWith(null)) .subscribe(() => this._init()); } ngOnDestroy() { this._rerenderSubscription.unsubscribe(); } /** Initializes this multi-year view. */ _init() { this._todayYear = this._dateAdapter.getYear(this._dateAdapter.today()); // We want a range years such that we maximize the number of // enabled dates visible at once. This prevents issues where the minimum year // is the last item of a page OR the maximum year is the first item of a page. // The offset from the active year to the "slot" for the starting year is the // *actual* first rendered year in the multi-year view. const activeYear = this._dateAdapter.getYear(this._activeDate); const minYearOfPage = activeYear - getActiveOffset(this._dateAdapter, this.activeDate, this.minDate, this.maxDate); this._years = []; for (let i = 0, row = []; i < yearsPerPage; i++) { row.push(minYearOfPage + i); if (row.length == yearsPerRow) { this._years.push(row.map(year => this._createCellForYear(year))); row = []; } } this._changeDetectorRef.markForCheck(); } /** Handles when a new year is selected. */ _yearSelected(event) { const year = event.value; this.yearSelected.emit(this._dateAdapter.createDate(year, 0, 1)); let month = this._dateAdapter.getMonth(this.activeDate); let daysInMonth = this._dateAdapter.getNumDaysInMonth(this._dateAdapter.createDate(year, month, 1)); this.selectedChange.emit(this._dateAdapter.createDate(year, month, Math.min(this._dateAdapter.getDate(this.activeDate), daysInMonth))); } /** Handles keydown events on the calendar body when calendar is in multi-year view. */ _handleCalendarBodyKeydown(event) { const oldActiveDate = this._activeDate; const isRtl = this._isRtl(); switch (event.keyCode) { case LEFT_ARROW: this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, isRtl ? 1 : -1); break; case RIGHT_ARROW: this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, isRtl ? -1 : 1); break; case UP_ARROW: this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, -yearsPerRow); break; case DOWN_ARROW: this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, yearsPerRow); break; case HOME: this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, -getActiveOffset(this._dateAdapter, this.activeDate, this.minDate, this.maxDate)); break; case END: this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, yearsPerPage - getActiveOffset(this._dateAdapter, this.activeDate, this.minDate, this.maxDate) - 1); break; case PAGE_UP: this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, event.altKey ? -yearsPerPage * 10 : -yearsPerPage); break; case PAGE_DOWN: this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, event.altKey ? yearsPerPage * 10 : yearsPerPage); break; case ENTER: case SPACE: // Note that we only prevent the default action here while the selection happens in // `keyup` below. We can't do the selection here, because it can cause the calendar to // reopen if focus is restored immediately. We also can't call `preventDefault` on `keyup` // because it's too late (see #23305). this._selectionKeyPressed = true; break; default: // Don't prevent default or focus active cell on keys that we don't explicitly handle. return; } if (this._dateAdapter.compareDate(oldActiveDate, this.activeDate)) { this.activeDateChange.emit(this.activeDate); } this._focusActiveCell(); // Prevent unexpected default actions such as form submission. event.preventDefault(); } /** Handles keyup events on the calendar body when calendar is in multi-year view. */ _handleCalendarBodyKeyup(event) { if (event.keyCode === SPACE || event.keyCode === ENTER) { if (this._selectionKeyPressed) { this._yearSelected({ value: this._dateAdapter.getYear(this._activeDate), event }); } this._selectionKeyPressed = false; } } _getActiveCell() { return getActiveOffset(this._dateAdapter, this.activeDate, this.minDate, this.maxDate); } /** Focuses the active cell after the microtask queue is empty. */ _focusActiveCell() { this._matCalendarBody._focusActiveCell(); } /** Creates an MatCalendarCell for the given year. */ _createCellForYear(year) { const date = this._dateAdapter.createDate(year, 0, 1); const yearName = this._dateAdapter.getYearName(date); const cellClasses = this.dateClass ? this.dateClass(date, 'multi-year') : undefined; return new MatCalendarCell(year, yearName, yearName, this._shouldEnableYear(year), cellClasses); } /** Whether the given year is enabled. */ _shouldEnableYear(year) { // disable if the year is greater than maxDate lower than minDate if (year === undefined || year === null || (this.maxDate && year > this._dateAdapter.getYear(this.maxDate)) || (this.minDate && year < this._dateAdapter.getYear(this.minDate))) { return false; } // enable if it reaches here and there's no filter defined if (!this.dateFilter) { return true; } const firstOfYear = this._dateAdapter.createDate(year, 0, 1); // If any date in the year is enabled count the year as enabled. for (let date = firstOfYear; this._dateAdapter.getYear(date) == year; date = this._dateAdapter.addCalendarDays(date, 1)) { if (this.dateFilter(date)) { return true; } } return false; } /** Determines whether the user has the RTL layout direction. */ _isRtl() { return this._dir && this._dir.value === 'rtl'; } /** Sets the currently-highlighted year based on a model value. */ _setSelectedYear(value) { this._selectedYear = null; if (value instanceof DateRange) { const displayValue = value.start || value.end; if (displayValue) { this._selectedYear = this._dateAdapter.getYear(displayValue); } } else if (value) { this._selectedYear = this._dateAdapter.getYear(value); } } } MatMultiYearView.decorators = [ { type: Component, args: [{ selector: 'mat-multi-year-view', template: "\n \n \n \n \n \n
\n", exportAs: 'matMultiYearView', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush },] } ]; MatMultiYearView.ctorParameters = () => [ { type: ChangeDetectorRef }, { type: DateAdapter, decorators: [{ type: Optional }] }, { type: Directionality, decorators: [{ type: Optional }] } ]; MatMultiYearView.propDecorators = { activeDate: [{ type: Input }], selected: [{ type: Input }], minDate: [{ type: Input }], maxDate: [{ type: Input }], dateFilter: [{ type: Input }], dateClass: [{ type: Input }], selectedChange: [{ type: Output }], yearSelected: [{ type: Output }], activeDateChange: [{ type: Output }], _matCalendarBody: [{ type: ViewChild, args: [MatCalendarBody,] }] }; function isSameMultiYearView(dateAdapter, date1, date2, minDate, maxDate) { const year1 = dateAdapter.getYear(date1); const year2 = dateAdapter.getYear(date2); const startingYear = getStartingYear(dateAdapter, minDate, maxDate); return Math.floor((year1 - startingYear) / yearsPerPage) === Math.floor((year2 - startingYear) / yearsPerPage); } /** * When the multi-year view is first opened, the active year will be in view. * So we compute how many years are between the active year and the *slot* where our * "startingYear" will render when paged into view. */ function getActiveOffset(dateAdapter, activeDate, minDate, maxDate) { const activeYear = dateAdapter.getYear(activeDate); return euclideanModulo((activeYear - getStartingYear(dateAdapter, minDate, maxDate)), yearsPerPage); } /** * We pick a "starting" year such that either the maximum year would be at the end * or the minimum year would be at the beginning of a page. */ function getStartingYear(dateAdapter, minDate, maxDate) { let startingYear = 0; if (maxDate) { const maxYear = dateAdapter.getYear(maxDate); startingYear = maxYear - yearsPerPage + 1; } else if (minDate) { startingYear = dateAdapter.getYear(minDate); } return startingYear; } /** Gets remainder that is non-negative, even if first number is negative */ function euclideanModulo(a, b) { return (a % b + b) % b; } /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ /** * An internal component used to display a single year in the datepicker. * @docs-private */ class MatYearView { constructor(_changeDetectorRef, _dateFormats, _dateAdapter, _dir) { this._changeDetectorRef = _changeDetectorRef; this._dateFormats = _dateFormats; this._dateAdapter = _dateAdapter; this._dir = _dir; this._rerenderSubscription = Subscription.EMPTY; /** Emits when a new month is selected. */ this.selectedChange = new EventEmitter(); /** Emits the selected month. This doesn't imply a change on the selected date */ this.monthSelected = new EventEmitter(); /** Emits when any date is activated. */ this.activeDateChange = new EventEmitter(); if (typeof ngDevMode === 'undefined' || ngDevMode) { if (!this._dateAdapter) { throw createMissingDateImplError('DateAdapter'); } if (!this._dateFormats) { throw createMissingDateImplError('MAT_DATE_FORMATS'); } } this._activeDate = this._dateAdapter.today(); } /** The date to display in this year view (everything other than the year is ignored). */ get activeDate() { return this._activeDate; } set activeDate(value) { let oldActiveDate = this._activeDate; const validDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)) || this._dateAdapter.today(); this._activeDate = this._dateAdapter.clampDate(validDate, this.minDate, this.maxDate); if (this._dateAdapter.getYear(oldActiveDate) !== this._dateAdapter.getYear(this._activeDate)) { this._init(); } } /** The currently selected date. */ get selected() { return this._selected; } set selected(value) { if (value instanceof DateRange) { this._selected = value; } else { this._selected = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } this._setSelectedMonth(value); } /** The minimum selectable date. */ get minDate() { return this._minDate; } set minDate(value) { this._minDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } /** The maximum selectable date. */ get maxDate() { return this._maxDate; } set maxDate(value) { this._maxDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } ngAfterContentInit() { this._rerenderSubscription = this._dateAdapter.localeChanges .pipe(startWith(null)) .subscribe(() => this._init()); } ngOnDestroy() { this._rerenderSubscription.unsubscribe(); } /** Handles when a new month is selected. */ _monthSelected(event) { const month = event.value; const normalizedDate = this._dateAdapter.createDate(this._dateAdapter.getYear(this.activeDate), month, 1); this.monthSelected.emit(normalizedDate); const daysInMonth = this._dateAdapter.getNumDaysInMonth(normalizedDate); this.selectedChange.emit(this._dateAdapter.createDate(this._dateAdapter.getYear(this.activeDate), month, Math.min(this._dateAdapter.getDate(this.activeDate), daysInMonth))); } /** Handles keydown events on the calendar body when calendar is in year view. */ _handleCalendarBodyKeydown(event) { // TODO(mmalerba): We currently allow keyboard navigation to disabled dates, but just prevent // disabled ones from being selected. This may not be ideal, we should look into whether // navigation should skip over disabled dates, and if so, how to implement that efficiently. const oldActiveDate = this._activeDate; const isRtl = this._isRtl(); switch (event.keyCode) { case LEFT_ARROW: this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, isRtl ? 1 : -1); break; case RIGHT_ARROW: this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, isRtl ? -1 : 1); break; case UP_ARROW: this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, -4); break; case DOWN_ARROW: this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, 4); break; case HOME: this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, -this._dateAdapter.getMonth(this._activeDate)); break; case END: this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, 11 - this._dateAdapter.getMonth(this._activeDate)); break; case PAGE_UP: this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, event.altKey ? -10 : -1); break; case PAGE_DOWN: this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, event.altKey ? 10 : 1); break; case ENTER: case SPACE: // Note that we only prevent the default action here while the selection happens in // `keyup` below. We can't do the selection here, because it can cause the calendar to // reopen if focus is restored immediately. We also can't call `preventDefault` on `keyup` // because it's too late (see #23305). this._selectionKeyPressed = true; break; default: // Don't prevent default or focus active cell on keys that we don't explicitly handle. return; } if (this._dateAdapter.compareDate(oldActiveDate, this.activeDate)) { this.activeDateChange.emit(this.activeDate); } this._focusActiveCell(); // Prevent unexpected default actions such as form submission. event.preventDefault(); } /** Handles keyup events on the calendar body when calendar is in year view. */ _handleCalendarBodyKeyup(event) { if (event.keyCode === SPACE || event.keyCode === ENTER) { if (this._selectionKeyPressed) { this._monthSelected({ value: this._dateAdapter.getMonth(this._activeDate), event }); } this._selectionKeyPressed = false; } } /** Initializes this year view. */ _init() { this._setSelectedMonth(this.selected); this._todayMonth = this._getMonthInCurrentYear(this._dateAdapter.today()); this._yearLabel = this._dateAdapter.getYearName(this.activeDate); let monthNames = this._dateAdapter.getMonthNames('short'); // First row of months only contains 5 elements so we can fit the year label on the same row. this._months = [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]].map(row => row.map(month => this._createCellForMonth(month, monthNames[month]))); this._changeDetectorRef.markForCheck(); } /** Focuses the active cell after the microtask queue is empty. */ _focusActiveCell() { this._matCalendarBody._focusActiveCell(); } /** * Gets the month in this year that the given Date falls on. * Returns null if the given Date is in another year. */ _getMonthInCurrentYear(date) { return date && this._dateAdapter.getYear(date) == this._dateAdapter.getYear(this.activeDate) ? this._dateAdapter.getMonth(date) : null; } /** Creates an MatCalendarCell for the given month. */ _createCellForMonth(month, monthName) { const date = this._dateAdapter.createDate(this._dateAdapter.getYear(this.activeDate), month, 1); const ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.monthYearA11yLabel); const cellClasses = this.dateClass ? this.dateClass(date, 'year') : undefined; return new MatCalendarCell(month, monthName.toLocaleUpperCase(), ariaLabel, this._shouldEnableMonth(month), cellClasses); } /** Whether the given month is enabled. */ _shouldEnableMonth(month) { const activeYear = this._dateAdapter.getYear(this.activeDate); if (month === undefined || month === null || this._isYearAndMonthAfterMaxDate(activeYear, month) || this._isYearAndMonthBeforeMinDate(activeYear, month)) { return false; } if (!this.dateFilter) { return true; } const firstOfMonth = this._dateAdapter.createDate(activeYear, month, 1); // If any date in the month is enabled count the month as enabled. for (let date = firstOfMonth; this._dateAdapter.getMonth(date) == month; date = this._dateAdapter.addCalendarDays(date, 1)) { if (this.dateFilter(date)) { return true; } } return false; } /** * Tests whether the combination month/year is after this.maxDate, considering * just the month and year of this.maxDate */ _isYearAndMonthAfterMaxDate(year, month) { if (this.maxDate) { const maxYear = this._dateAdapter.getYear(this.maxDate); const maxMonth = this._dateAdapter.getMonth(this.maxDate); return year > maxYear || (year === maxYear && month > maxMonth); } return false; } /** * Tests whether the combination month/year is before this.minDate, considering * just the month and year of this.minDate */ _isYearAndMonthBeforeMinDate(year, month) { if (this.minDate) { const minYear = this._dateAdapter.getYear(this.minDate); const minMonth = this._dateAdapter.getMonth(this.minDate); return year < minYear || (year === minYear && month < minMonth); } return false; } /** Determines whether the user has the RTL layout direction. */ _isRtl() { return this._dir && this._dir.value === 'rtl'; } /** Sets the currently-selected month based on a model value. */ _setSelectedMonth(value) { if (value instanceof DateRange) { this._selectedMonth = this._getMonthInCurrentYear(value.start) || this._getMonthInCurrentYear(value.end); } else { this._selectedMonth = this._getMonthInCurrentYear(value); } } } MatYearView.decorators = [ { type: Component, args: [{ selector: 'mat-year-view', template: "\n \n \n \n \n \n
\n", exportAs: 'matYearView', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush },] } ]; MatYearView.ctorParameters = () => [ { type: ChangeDetectorRef }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_DATE_FORMATS,] }] }, { type: DateAdapter, decorators: [{ type: Optional }] }, { type: Directionality, decorators: [{ type: Optional }] } ]; MatYearView.propDecorators = { activeDate: [{ type: Input }], selected: [{ type: Input }], minDate: [{ type: Input }], maxDate: [{ type: Input }], dateFilter: [{ type: Input }], dateClass: [{ type: Input }], selectedChange: [{ type: Output }], monthSelected: [{ type: Output }], activeDateChange: [{ type: Output }], _matCalendarBody: [{ type: ViewChild, args: [MatCalendarBody,] }] }; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ /** Counter used to generate unique IDs. */ let uniqueId = 0; /** Default header for MatCalendar */ class MatCalendarHeader { constructor(_intl, calendar, _dateAdapter, _dateFormats, changeDetectorRef) { this._intl = _intl; this.calendar = calendar; this._dateAdapter = _dateAdapter; this._dateFormats = _dateFormats; this._buttonDescriptionId = `mat-calendar-button-${uniqueId++}`; this.calendar.stateChanges.subscribe(() => changeDetectorRef.markForCheck()); } /** The label for the current calendar view. */ get periodButtonText() { if (this.calendar.currentView == 'month') { return this._dateAdapter .format(this.calendar.activeDate, this._dateFormats.display.monthYearLabel) .toLocaleUpperCase(); } if (this.calendar.currentView == 'year') { return this._dateAdapter.getYearName(this.calendar.activeDate); } // The offset from the active year to the "slot" for the starting year is the // *actual* first rendered year in the multi-year view, and the last year is // just yearsPerPage - 1 away. const activeYear = this._dateAdapter.getYear(this.calendar.activeDate); const minYearOfPage = activeYear - getActiveOffset(this._dateAdapter, this.calendar.activeDate, this.calendar.minDate, this.calendar.maxDate); const maxYearOfPage = minYearOfPage + yearsPerPage - 1; const minYearName = this._dateAdapter.getYearName(this._dateAdapter.createDate(minYearOfPage, 0, 1)); const maxYearName = this._dateAdapter.getYearName(this._dateAdapter.createDate(maxYearOfPage, 0, 1)); return this._intl.formatYearRange(minYearName, maxYearName); } get periodButtonLabel() { return this.calendar.currentView == 'month' ? this._intl.switchToMultiYearViewLabel : this._intl.switchToMonthViewLabel; } /** The label for the previous button. */ get prevButtonLabel() { return { 'month': this._intl.prevMonthLabel, 'year': this._intl.prevYearLabel, 'multi-year': this._intl.prevMultiYearLabel }[this.calendar.currentView]; } /** The label for the next button. */ get nextButtonLabel() { return { 'month': this._intl.nextMonthLabel, 'year': this._intl.nextYearLabel, 'multi-year': this._intl.nextMultiYearLabel }[this.calendar.currentView]; } /** Handles user clicks on the period label. */ currentPeriodClicked() { this.calendar.currentView = this.calendar.currentView == 'month' ? 'multi-year' : 'month'; } /** Handles user clicks on the previous button. */ previousClicked() { this.calendar.activeDate = this.calendar.currentView == 'month' ? this._dateAdapter.addCalendarMonths(this.calendar.activeDate, -1) : this._dateAdapter.addCalendarYears(this.calendar.activeDate, this.calendar.currentView == 'year' ? -1 : -yearsPerPage); } /** Handles user clicks on the next button. */ nextClicked() { this.calendar.activeDate = this.calendar.currentView == 'month' ? this._dateAdapter.addCalendarMonths(this.calendar.activeDate, 1) : this._dateAdapter.addCalendarYears(this.calendar.activeDate, this.calendar.currentView == 'year' ? 1 : yearsPerPage); } /** Whether the previous period button is enabled. */ previousEnabled() { if (!this.calendar.minDate) { return true; } return !this.calendar.minDate || !this._isSameView(this.calendar.activeDate, this.calendar.minDate); } /** Whether the next period button is enabled. */ nextEnabled() { return !this.calendar.maxDate || !this._isSameView(this.calendar.activeDate, this.calendar.maxDate); } /** Whether the two dates represent the same view in the current view mode (month or year). */ _isSameView(date1, date2) { if (this.calendar.currentView == 'month') { return this._dateAdapter.getYear(date1) == this._dateAdapter.getYear(date2) && this._dateAdapter.getMonth(date1) == this._dateAdapter.getMonth(date2); } if (this.calendar.currentView == 'year') { return this._dateAdapter.getYear(date1) == this._dateAdapter.getYear(date2); } // Otherwise we are in 'multi-year' view. return isSameMultiYearView(this._dateAdapter, date1, date2, this.calendar.minDate, this.calendar.maxDate); } } MatCalendarHeader.decorators = [ { type: Component, args: [{ selector: 'mat-calendar-header', template: "
\n
\n \n\n
\n\n \n\n \n\n \n
\n
\n", exportAs: 'matCalendarHeader', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush },] } ]; MatCalendarHeader.ctorParameters = () => [ { type: MatDatepickerIntl }, { type: MatCalendar, decorators: [{ type: Inject, args: [forwardRef(() => MatCalendar),] }] }, { type: DateAdapter, decorators: [{ type: Optional }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_DATE_FORMATS,] }] }, { type: ChangeDetectorRef } ]; /** A calendar that is used as part of the datepicker. */ class MatCalendar { constructor(_intl, _dateAdapter, _dateFormats, _changeDetectorRef) { this._dateAdapter = _dateAdapter; this._dateFormats = _dateFormats; this._changeDetectorRef = _changeDetectorRef; /** * Used for scheduling that focus should be moved to the active cell on the next tick. * We need to schedule it, rather than do it immediately, because we have to wait * for Angular to re-evaluate the view children. */ this._moveFocusOnNextTick = false; /** Whether the calendar should be started in month or year view. */ this.startView = 'month'; /** Emits when the currently selected date changes. */ this.selectedChange = new EventEmitter(); /** * Emits the year chosen in multiyear view. * This doesn't imply a change on the selected date. */ this.yearSelected = new EventEmitter(); /** * Emits the month chosen in year view. * This doesn't imply a change on the selected date. */ this.monthSelected = new EventEmitter(); /** * Emits when the current view changes. */ this.viewChanged = new EventEmitter(true); /** Emits when any date is selected. */ this._userSelection = new EventEmitter(); /** * Emits whenever there is a state change that the header may need to respond to. */ this.stateChanges = new Subject(); if (typeof ngDevMode === 'undefined' || ngDevMode) { if (!this._dateAdapter) { throw createMissingDateImplError('DateAdapter'); } if (!this._dateFormats) { throw createMissingDateImplError('MAT_DATE_FORMATS'); } } this._intlChanges = _intl.changes.subscribe(() => { _changeDetectorRef.markForCheck(); this.stateChanges.next(); }); } /** A date representing the period (month or year) to start the calendar in. */ get startAt() { return this._startAt; } set startAt(value) { this._startAt = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } /** The currently selected date. */ get selected() { return this._selected; } set selected(value) { if (value instanceof DateRange) { this._selected = value; } else { this._selected = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } } /** The minimum selectable date. */ get minDate() { return this._minDate; } set minDate(value) { this._minDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } /** The maximum selectable date. */ get maxDate() { return this._maxDate; } set maxDate(value) { this._maxDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } /** * The current active date. This determines which time period is shown and which date is * highlighted when using keyboard navigation. */ get activeDate() { return this._clampedActiveDate; } set activeDate(value) { this._clampedActiveDate = this._dateAdapter.clampDate(value, this.minDate, this.maxDate); this.stateChanges.next(); this._changeDetectorRef.markForCheck(); } /** Whether the calendar is in month view. */ get currentView() { return this._currentView; } set currentView(value) { const viewChangedResult = this._currentView !== value ? value : null; this._currentView = value; this._moveFocusOnNextTick = true; this._changeDetectorRef.markForCheck(); if (viewChangedResult) { this.viewChanged.emit(viewChangedResult); } } ngAfterContentInit() { this._calendarHeaderPortal = new ComponentPortal(this.headerComponent || MatCalendarHeader); this.activeDate = this.startAt || this._dateAdapter.today(); // Assign to the private property since we don't want to move focus on init. this._currentView = this.startView; } ngAfterViewChecked() { if (this._moveFocusOnNextTick) { this._moveFocusOnNextTick = false; this.focusActiveCell(); } } ngOnDestroy() { this._intlChanges.unsubscribe(); this.stateChanges.complete(); } ngOnChanges(changes) { const change = changes['minDate'] || changes['maxDate'] || changes['dateFilter']; if (change && !change.firstChange) { const view = this._getCurrentViewComponent(); if (view) { // We need to `detectChanges` manually here, because the `minDate`, `maxDate` etc. are // passed down to the view via data bindings which won't be up-to-date when we call `_init`. this._changeDetectorRef.detectChanges(); view._init(); } } this.stateChanges.next(); } /** Focuses the active date. */ focusActiveCell() { this._getCurrentViewComponent()._focusActiveCell(false); } /** Updates today's date after an update of the active date */ updateTodaysDate() { this._getCurrentViewComponent()._init(); } /** Handles date selection in the month view. */ _dateSelected(event) { const date = event.value; if (this.selected instanceof DateRange || (date && !this._dateAdapter.sameDate(date, this.selected))) { this.selectedChange.emit(date); } this._userSelection.emit(event); } /** Handles year selection in the multiyear view. */ _yearSelectedInMultiYearView(normalizedYear) { this.yearSelected.emit(normalizedYear); } /** Handles month selection in the year view. */ _monthSelectedInYearView(normalizedMonth) { this.monthSelected.emit(normalizedMonth); } /** Handles year/month selection in the multi-year/year views. */ _goToDateInView(date, view) { this.activeDate = date; this.currentView = view; } /** Returns the component instance that corresponds to the current calendar view. */ _getCurrentViewComponent() { // The return type is explicitly written as a union to ensure that the Closure compiler does // not optimize calls to _init(). Without the explict return type, TypeScript narrows it to // only the first component type. See https://github.com/angular/components/issues/22996. return this.monthView || this.yearView || this.multiYearView; } } MatCalendar.decorators = [ { type: Component, args: [{ selector: 'mat-calendar', template: "\n\n
\n \n \n\n \n \n\n \n \n
\n", host: { 'class': 'mat-calendar', }, exportAs: 'matCalendar', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, providers: [MAT_SINGLE_DATE_SELECTION_MODEL_PROVIDER], 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:10px;height:5px;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}.cdk-high-contrast-active .mat-calendar-arrow{fill:CanvasText}.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"] },] } ]; MatCalendar.ctorParameters = () => [ { type: MatDatepickerIntl }, { type: DateAdapter, decorators: [{ type: Optional }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_DATE_FORMATS,] }] }, { type: ChangeDetectorRef } ]; MatCalendar.propDecorators = { headerComponent: [{ type: Input }], startAt: [{ type: Input }], startView: [{ type: Input }], selected: [{ type: Input }], minDate: [{ type: Input }], maxDate: [{ type: Input }], dateFilter: [{ type: Input }], dateClass: [{ type: Input }], comparisonStart: [{ type: Input }], comparisonEnd: [{ type: Input }], selectedChange: [{ type: Output }], yearSelected: [{ type: Output }], monthSelected: [{ type: Output }], viewChanged: [{ type: Output }], _userSelection: [{ type: Output }], monthView: [{ type: ViewChild, args: [MatMonthView,] }], yearView: [{ type: ViewChild, args: [MatYearView,] }], multiYearView: [{ type: ViewChild, args: [MatMultiYearView,] }] }; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ /** * Animations used by the Material datepicker. * @docs-private */ const matDatepickerAnimations = { /** Transforms the height of the datepicker's calendar. */ transformPanel: trigger('transformPanel', [ transition('void => enter-dropdown', animate('120ms cubic-bezier(0, 0, 0.2, 1)', keyframes([ style({ opacity: 0, transform: 'scale(1, 0.8)' }), style({ opacity: 1, transform: 'scale(1, 1)' }) ]))), transition('void => enter-dialog', animate('150ms cubic-bezier(0, 0, 0.2, 1)', keyframes([ style({ opacity: 0, transform: 'scale(0.7)' }), style({ transform: 'none', opacity: 1 }) ]))), transition('* => void', animate('100ms linear', style({ opacity: 0 }))) ]), /** Fades in the content of the calendar. */ fadeInCalendar: trigger('fadeInCalendar', [ state('void', style({ opacity: 0 })), state('enter', style({ opacity: 1 })), // TODO(crisbeto): this animation should be removed since it isn't quite on spec, but we // need to keep it until #12440 gets in, otherwise the exit animation will look glitchy. transition('void => *', animate('120ms 100ms cubic-bezier(0.55, 0, 0.55, 0.2)')) ]) }; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ /** Used to generate a unique ID for each datepicker instance. */ let datepickerUid = 0; /** Injection token that determines the scroll handling while the calendar is open. */ const MAT_DATEPICKER_SCROLL_STRATEGY = new InjectionToken('mat-datepicker-scroll-strategy'); /** @docs-private */ function MAT_DATEPICKER_SCROLL_STRATEGY_FACTORY(overlay) { return () => overlay.scrollStrategies.reposition(); } /** @docs-private */ const MAT_DATEPICKER_SCROLL_STRATEGY_FACTORY_PROVIDER = { provide: MAT_DATEPICKER_SCROLL_STRATEGY, deps: [Overlay], useFactory: MAT_DATEPICKER_SCROLL_STRATEGY_FACTORY, }; // Boilerplate for applying mixins to MatDatepickerContent. /** @docs-private */ const _MatDatepickerContentBase = mixinColor(class { constructor(_elementRef) { this._elementRef = _elementRef; } }); /** * Component used as the content for the datepicker overlay. We use this instead of using * MatCalendar directly as the content so we can control the initial focus. This also gives us a * place to put additional features of the overlay that are not part of the calendar itself in the * future. (e.g. confirmation buttons). * @docs-private */ class MatDatepickerContent extends _MatDatepickerContentBase { constructor(elementRef, _changeDetectorRef, _globalModel, _dateAdapter, _rangeSelectionStrategy, intl) { super(elementRef); this._changeDetectorRef = _changeDetectorRef; this._globalModel = _globalModel; this._dateAdapter = _dateAdapter; this._rangeSelectionStrategy = _rangeSelectionStrategy; this._subscriptions = new Subscription(); /** Emits when an animation has finished. */ this._animationDone = new Subject(); /** Portal with projected action buttons. */ this._actionsPortal = null; this._closeButtonText = intl.closeCalendarLabel; } ngOnInit() { // If we have actions, clone the model so that we have the ability to cancel the selection, // otherwise update the global model directly. Note that we want to assign this as soon as // possible, but `_actionsPortal` isn't available in the constructor so we do it in `ngOnInit`. this._model = this._actionsPortal ? this._globalModel.clone() : this._globalModel; this._animationState = this.datepicker.touchUi ? 'enter-dialog' : 'enter-dropdown'; } ngAfterViewInit() { this._subscriptions.add(this.datepicker.stateChanges.subscribe(() => { this._changeDetectorRef.markForCheck(); })); this._calendar.focusActiveCell(); } ngOnDestroy() { this._subscriptions.unsubscribe(); this._animationDone.complete(); } _handleUserSelection(event) { const selection = this._model.selection; const value = event.value; const isRange = selection instanceof DateRange; // If we're selecting a range and we have a selection strategy, always pass the value through // there. Otherwise don't assign null values to the model, unless we're selecting a range. // A null value when picking a range means that the user cancelled the selection (e.g. by // pressing escape), whereas when selecting a single value it means that the value didn't // change. This isn't very intuitive, but it's here for backwards-compatibility. if (isRange && this._rangeSelectionStrategy) { const newSelection = this._rangeSelectionStrategy.selectionFinished(value, selection, event.event); this._model.updateSelection(newSelection, this); } else if (value && (isRange || !this._dateAdapter.sameDate(value, selection))) { this._model.add(value); } // Delegate closing the overlay to the actions. if ((!this._model || this._model.isComplete()) && !this._actionsPortal) { this.datepicker.close(); } } _startExitAnimation() { this._animationState = 'void'; this._changeDetectorRef.markForCheck(); } _getSelected() { return this._model.selection; } /** Applies the current pending selection to the global model. */ _applyPendingSelection() { if (this._model !== this._globalModel) { this._globalModel.updateSelection(this._model.selection, this); } } } MatDatepickerContent.decorators = [ { type: Component, args: [{ selector: 'mat-datepicker-content', template: "\n \n\n \n\n \n {{ _closeButtonText }}\n\n", host: { 'class': 'mat-datepicker-content', '[@transformPanel]': '_animationState', '(@transformPanel.done)': '_animationDone.next()', '[class.mat-datepicker-content-touch]': 'datepicker.touchUi', }, animations: [ matDatepickerAnimations.transformPanel, matDatepickerAnimations.fadeInCalendar, ], exportAs: 'matDatepickerContent', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, inputs: ['color'], 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"] },] } ]; MatDatepickerContent.ctorParameters = () => [ { type: ElementRef }, { type: ChangeDetectorRef }, { type: MatDateSelectionModel }, { type: DateAdapter }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_DATE_RANGE_SELECTION_STRATEGY,] }] }, { type: MatDatepickerIntl } ]; MatDatepickerContent.propDecorators = { _calendar: [{ type: ViewChild, args: [MatCalendar,] }] }; /** Base class for a datepicker. */ class MatDatepickerBase { constructor( /** * @deprecated `_dialog` parameter is no longer being used and it will be removed. * @breaking-change 13.0.0 */ _dialog, _overlay, _ngZone, _viewContainerRef, scrollStrategy, _dateAdapter, _dir, /** * @deprecated No longer being used. To be removed. * @breaking-change 13.0.0 */ _document, _model) { this._overlay = _overlay; this._ngZone = _ngZone; this._viewContainerRef = _viewContainerRef; this._dateAdapter = _dateAdapter; this._dir = _dir; this._model = _model; this._inputStateChanges = Subscription.EMPTY; /** The view that the calendar should start in. */ this.startView = 'month'; this._touchUi = false; /** Preferred position of the datepicker in the X axis. */ this.xPosition = 'start'; /** Preferred position of the datepicker in the Y axis. */ this.yPosition = 'below'; this._restoreFocus = true; /** * Emits selected year in multiyear view. * This doesn't imply a change on the selected date. */ this.yearSelected = new EventEmitter(); /** * Emits selected month in year view. * This doesn't imply a change on the selected date. */ this.monthSelected = new EventEmitter(); /** * Emits when the current view changes. */ this.viewChanged = new EventEmitter(true); /** Emits when the datepicker has been opened. */ this.openedStream = new EventEmitter(); /** Emits when the datepicker has been closed. */ this.closedStream = new EventEmitter(); this._opened = false; /** The id for the datepicker calendar. */ this.id = `mat-datepicker-${datepickerUid++}`; /** The element that was focused before the datepicker was opened. */ this._focusedElementBeforeOpen = null; /** Unique class that will be added to the backdrop so that the test harnesses can look it up. */ this._backdropHarnessClass = `${this.id}-backdrop`; /** Emits when the datepicker's state changes. */ this.stateChanges = new Subject(); if (!this._dateAdapter && (typeof ngDevMode === 'undefined' || ngDevMode)) { throw createMissingDateImplError('DateAdapter'); } this._scrollStrategy = scrollStrategy; } /** The date to open the calendar to initially. */ get startAt() { // If an explicit startAt is set we start there, otherwise we start at whatever the currently // selected value is. return this._startAt || (this.datepickerInput ? this.datepickerInput.getStartValue() : null); } set startAt(value) { this._startAt = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } /** Color palette to use on the datepicker's calendar. */ get color() { return this._color || (this.datepickerInput ? this.datepickerInput.getThemePalette() : undefined); } set color(value) { this._color = value; } /** * Whether the calendar UI is in touch mode. In touch mode the calendar opens in a dialog rather * than a dropdown and elements have more padding to allow for bigger touch targets. */ get touchUi() { return this._touchUi; } set touchUi(value) { this._touchUi = coerceBooleanProperty(value); } /** Whether the datepicker pop-up should be disabled. */ get disabled() { return this._disabled === undefined && this.datepickerInput ? this.datepickerInput.disabled : !!this._disabled; } set disabled(value) { const newValue = coerceBooleanProperty(value); if (newValue !== this._disabled) { this._disabled = newValue; this.stateChanges.next(undefined); } } /** * Whether to restore focus to the previously-focused element when the calendar is closed. * Note that automatic focus restoration is an accessibility feature and it is recommended that * you provide your own equivalent, if you decide to turn it off. */ get restoreFocus() { return this._restoreFocus; } set restoreFocus(value) { this._restoreFocus = coerceBooleanProperty(value); } /** * Classes to be passed to the date picker panel. * Supports string and string array values, similar to `ngClass`. */ get panelClass() { return this._panelClass; } set panelClass(value) { this._panelClass = coerceStringArray(value); } /** Whether the calendar is open. */ get opened() { return this._opened; } set opened(value) { coerceBooleanProperty(value) ? this.open() : this.close(); } /** The minimum selectable date. */ _getMinDate() { return this.datepickerInput && this.datepickerInput.min; } /** The maximum selectable date. */ _getMaxDate() { return this.datepickerInput && this.datepickerInput.max; } _getDateFilter() { return this.datepickerInput && this.datepickerInput.dateFilter; } ngOnChanges(changes) { const positionChange = changes['xPosition'] || changes['yPosition']; if (positionChange && !positionChange.firstChange && this._overlayRef) { const positionStrategy = this._overlayRef.getConfig().positionStrategy; if (positionStrategy instanceof FlexibleConnectedPositionStrategy) { this._setConnectedPositions(positionStrategy); if (this.opened) { this._overlayRef.updatePosition(); } } } this.stateChanges.next(undefined); } ngOnDestroy() { this._destroyOverlay(); this.close(); this._inputStateChanges.unsubscribe(); this.stateChanges.complete(); } /** Selects the given date */ select(date) { this._model.add(date); } /** Emits the selected year in multiyear view */ _selectYear(normalizedYear) { this.yearSelected.emit(normalizedYear); } /** Emits selected month in year view */ _selectMonth(normalizedMonth) { this.monthSelected.emit(normalizedMonth); } /** Emits changed view */ _viewChanged(view) { this.viewChanged.emit(view); } /** * Register an input with this datepicker. * @param input The datepicker input to register with this datepicker. * @returns Selection model that the input should hook itself up to. */ registerInput(input) { if (this.datepickerInput && (typeof ngDevMode === 'undefined' || ngDevMode)) { throw Error('A MatDatepicker can only be associated with a single input.'); } this._inputStateChanges.unsubscribe(); this.datepickerInput = input; this._inputStateChanges = input.stateChanges.subscribe(() => this.stateChanges.next(undefined)); return this._model; } /** * Registers a portal containing action buttons with the datepicker. * @param portal Portal to be registered. */ registerActions(portal) { if (this._actionsPortal && (typeof ngDevMode === 'undefined' || ngDevMode)) { throw Error('A MatDatepicker can only be associated with a single actions row.'); } this._actionsPortal = portal; } /** * Removes a portal containing action buttons from the datepicker. * @param portal Portal to be removed. */ removeActions(portal) { if (portal === this._actionsPortal) { this._actionsPortal = null; } } /** Open the calendar. */ open() { if (this._opened || this.disabled) { return; } if (!this.datepickerInput && (typeof ngDevMode === 'undefined' || ngDevMode)) { throw Error('Attempted to open an MatDatepicker with no associated input.'); } this._focusedElementBeforeOpen = _getFocusedElementPierceShadowDom(); this._openOverlay(); this._opened = true; this.openedStream.emit(); } /** Close the calendar. */ close() { if (!this._opened) { return; } if (this._componentRef) { const instance = this._componentRef.instance; instance._startExitAnimation(); instance._animationDone.pipe(take(1)).subscribe(() => this._destroyOverlay()); } const completeClose = () => { // The `_opened` could've been reset already if // we got two events in quick succession. if (this._opened) { this._opened = false; this.closedStream.emit(); this._focusedElementBeforeOpen = null; } }; if (this._restoreFocus && this._focusedElementBeforeOpen && typeof this._focusedElementBeforeOpen.focus === 'function') { // Because IE moves focus asynchronously, we can't count on it being restored before we've // marked the datepicker as closed. If the event fires out of sequence and the element that // we're refocusing opens the datepicker on focus, the user could be stuck with not being // able to close the calendar at all. We work around it by making the logic, that marks // the datepicker as closed, async as well. this._focusedElementBeforeOpen.focus(); setTimeout(completeClose); } else { completeClose(); } } /** Applies the current pending selection on the overlay to the model. */ _applyPendingSelection() { var _a, _b; (_b = (_a = this._componentRef) === null || _a === void 0 ? void 0 : _a.instance) === null || _b === void 0 ? void 0 : _b._applyPendingSelection(); } /** Forwards relevant values from the datepicker to the datepicker content inside the overlay. */ _forwardContentValues(instance) { instance.datepicker = this; instance.color = this.color; instance._actionsPortal = this._actionsPortal; } /** Opens the overlay with the calendar. */ _openOverlay() { this._destroyOverlay(); const isDialog = this.touchUi; const labelId = this.datepickerInput.getOverlayLabelId(); const portal = new ComponentPortal(MatDatepickerContent, this._viewContainerRef); const overlayRef = this._overlayRef = this._overlay.create(new OverlayConfig({ positionStrategy: isDialog ? this._getDialogStrategy() : this._getDropdownStrategy(), hasBackdrop: true, backdropClass: [ isDialog ? 'cdk-overlay-dark-backdrop' : 'mat-overlay-transparent-backdrop', this._backdropHarnessClass ], direction: this._dir, scrollStrategy: isDialog ? this._overlay.scrollStrategies.block() : this._scrollStrategy(), panelClass: `mat-datepicker-${isDialog ? 'dialog' : 'popup'}`, })); const overlayElement = overlayRef.overlayElement; overlayElement.setAttribute('role', 'dialog'); if (labelId) { overlayElement.setAttribute('aria-labelledby', labelId); } if (isDialog) { overlayElement.setAttribute('aria-modal', 'true'); } this._getCloseStream(overlayRef).subscribe(event => { if (event) { event.preventDefault(); } this.close(); }); this._componentRef = overlayRef.attach(portal); this._forwardContentValues(this._componentRef.instance); // Update the position once the calendar has rendered. Only relevant in dropdown mode. if (!isDialog) { this._ngZone.onStable.pipe(take(1)).subscribe(() => overlayRef.updatePosition()); } } /** Destroys the current overlay. */ _destroyOverlay() { if (this._overlayRef) { this._overlayRef.dispose(); this._overlayRef = this._componentRef = null; } } /** Gets a position strategy that will open the calendar as a dropdown. */ _getDialogStrategy() { return this._overlay.position().global().centerHorizontally().centerVertically(); } /** Gets a position strategy that will open the calendar as a dropdown. */ _getDropdownStrategy() { const strategy = this._overlay.position() .flexibleConnectedTo(this.datepickerInput.getConnectedOverlayOrigin()) .withTransformOriginOn('.mat-datepicker-content') .withFlexibleDimensions(false) .withViewportMargin(8) .withLockedPosition(); return this._setConnectedPositions(strategy); } /** Sets the positions of the datepicker in dropdown mode based on the current configuration. */ _setConnectedPositions(strategy) { const primaryX = this.xPosition === 'end' ? 'end' : 'start'; const secondaryX = primaryX === 'start' ? 'end' : 'start'; const primaryY = this.yPosition === 'above' ? 'bottom' : 'top'; const secondaryY = primaryY === 'top' ? 'bottom' : 'top'; return strategy.withPositions([ { originX: primaryX, originY: secondaryY, overlayX: primaryX, overlayY: primaryY }, { originX: primaryX, originY: primaryY, overlayX: primaryX, overlayY: secondaryY }, { originX: secondaryX, originY: secondaryY, overlayX: secondaryX, overlayY: primaryY }, { originX: secondaryX, originY: primaryY, overlayX: secondaryX, overlayY: secondaryY } ]); } /** Gets an observable that will emit when the overlay is supposed to be closed. */ _getCloseStream(overlayRef) { return merge(overlayRef.backdropClick(), overlayRef.detachments(), overlayRef.keydownEvents().pipe(filter(event => { // Closing on alt + up is only valid when there's an input associated with the datepicker. return (event.keyCode === ESCAPE && !hasModifierKey(event)) || (this.datepickerInput && hasModifierKey(event, 'altKey') && event.keyCode === UP_ARROW); }))); } } MatDatepickerBase.decorators = [ { type: Directive } ]; MatDatepickerBase.ctorParameters = () => [ { type: undefined, decorators: [{ type: Inject, args: [ElementRef,] }] }, { type: Overlay }, { type: NgZone }, { type: ViewContainerRef }, { type: undefined, decorators: [{ type: Inject, args: [MAT_DATEPICKER_SCROLL_STRATEGY,] }] }, { type: DateAdapter, decorators: [{ type: Optional }] }, { type: Directionality, decorators: [{ type: Optional }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [DOCUMENT,] }] }, { type: MatDateSelectionModel } ]; MatDatepickerBase.propDecorators = { calendarHeaderComponent: [{ type: Input }], startAt: [{ type: Input }], startView: [{ type: Input }], color: [{ type: Input }], touchUi: [{ type: Input }], disabled: [{ type: Input }], xPosition: [{ type: Input }], yPosition: [{ type: Input }], restoreFocus: [{ type: Input }], yearSelected: [{ type: Output }], monthSelected: [{ type: Output }], viewChanged: [{ type: Output }], dateClass: [{ type: Input }], openedStream: [{ type: Output, args: ['opened',] }], closedStream: [{ type: Output, args: ['closed',] }], panelClass: [{ type: Input }], opened: [{ type: Input }] }; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ // TODO(mmalerba): We use a component instead of a directive here so the user can use implicit // template reference variables (e.g. #d vs #d="matDatepicker"). We can change this to a directive // if angular adds support for `exportAs: '$implicit'` on directives. /** Component responsible for managing the datepicker popup/dialog. */ class MatDatepicker extends MatDatepickerBase { } MatDatepicker.decorators = [ { type: Component, args: [{ selector: 'mat-datepicker', template: '', exportAs: 'matDatepicker', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [ MAT_SINGLE_DATE_SELECTION_MODEL_PROVIDER, { provide: MatDatepickerBase, useExisting: MatDatepicker }, ] },] } ]; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ /** * An event used for datepicker input and change events. We don't always have access to a native * input or change event because the event may have been triggered by the user clicking on the * calendar popup. For consistency, we always use MatDatepickerInputEvent instead. */ class MatDatepickerInputEvent { constructor( /** Reference to the datepicker input component that emitted the event. */ target, /** Reference to the native input element associated with the datepicker input. */ targetElement) { this.target = target; this.targetElement = targetElement; this.value = this.target.value; } } /** Base class for datepicker inputs. */ class MatDatepickerInputBase { constructor(_elementRef, _dateAdapter, _dateFormats) { this._elementRef = _elementRef; this._dateAdapter = _dateAdapter; this._dateFormats = _dateFormats; /** Emits when a `change` event is fired on this ``. */ this.dateChange = new EventEmitter(); /** Emits when an `input` event is fired on this ``. */ this.dateInput = new EventEmitter(); /** Emits when the internal state has changed */ this.stateChanges = new Subject(); this._onTouched = () => { }; this._validatorOnChange = () => { }; this._cvaOnChange = () => { }; this._valueChangesSubscription = Subscription.EMPTY; this._localeSubscription = Subscription.EMPTY; /** The form control validator for whether the input parses. */ this._parseValidator = () => { return this._lastValueValid ? null : { 'matDatepickerParse': { 'text': this._elementRef.nativeElement.value } }; }; /** The form control validator for the date filter. */ this._filterValidator = (control) => { const controlValue = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(control.value)); return !controlValue || this._matchesFilter(controlValue) ? null : { 'matDatepickerFilter': true }; }; /** The form control validator for the min date. */ this._minValidator = (control) => { const controlValue = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(control.value)); const min = this._getMinDate(); return (!min || !controlValue || this._dateAdapter.compareDate(min, controlValue) <= 0) ? null : { 'matDatepickerMin': { 'min': min, 'actual': controlValue } }; }; /** The form control validator for the max date. */ this._maxValidator = (control) => { const controlValue = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(control.value)); const max = this._getMaxDate(); return (!max || !controlValue || this._dateAdapter.compareDate(max, controlValue) >= 0) ? null : { 'matDatepickerMax': { 'max': max, 'actual': controlValue } }; }; /** Whether the last value set on the input was valid. */ this._lastValueValid = false; if (typeof ngDevMode === 'undefined' || ngDevMode) { if (!this._dateAdapter) { throw createMissingDateImplError('DateAdapter'); } if (!this._dateFormats) { throw createMissingDateImplError('MAT_DATE_FORMATS'); } } // Update the displayed date when the locale changes. this._localeSubscription = _dateAdapter.localeChanges.subscribe(() => { this._assignValueProgrammatically(this.value); }); } /** The value of the input. */ get value() { return this._model ? this._getValueFromModel(this._model.selection) : this._pendingValue; } set value(value) { this._assignValueProgrammatically(value); } /** Whether the datepicker-input is disabled. */ get disabled() { return !!this._disabled || this._parentDisabled(); } set disabled(value) { const newValue = coerceBooleanProperty(value); const element = this._elementRef.nativeElement; if (this._disabled !== newValue) { this._disabled = newValue; this.stateChanges.next(undefined); } // We need to null check the `blur` method, because it's undefined during SSR. // In Ivy static bindings are invoked earlier, before the element is attached to the DOM. // This can cause an error to be thrown in some browsers (IE/Edge) which assert that the // element has been inserted. if (newValue && this._isInitialized && element.blur) { // Normally, native input elements automatically blur if they turn disabled. This behavior // is problematic, because it would mean that it triggers another change detection cycle, // which then causes a changed after checked error if the input element was focused before. element.blur(); } } /** Gets the base validator functions. */ _getValidators() { return [this._parseValidator, this._minValidator, this._maxValidator, this._filterValidator]; } /** Registers a date selection model with the input. */ _registerModel(model) { this._model = model; this._valueChangesSubscription.unsubscribe(); if (this._pendingValue) { this._assignValue(this._pendingValue); } this._valueChangesSubscription = this._model.selectionChanged.subscribe(event => { if (this._shouldHandleChangeEvent(event)) { const value = this._getValueFromModel(event.selection); this._lastValueValid = this._isValidValue(value); this._cvaOnChange(value); this._onTouched(); this._formatValue(value); this.dateInput.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement)); this.dateChange.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement)); } }); } ngAfterViewInit() { this._isInitialized = true; } ngOnChanges(changes) { if (dateInputsHaveChanged(changes, this._dateAdapter)) { this.stateChanges.next(undefined); } } ngOnDestroy() { this._valueChangesSubscription.unsubscribe(); this._localeSubscription.unsubscribe(); this.stateChanges.complete(); } /** @docs-private */ registerOnValidatorChange(fn) { this._validatorOnChange = fn; } /** @docs-private */ validate(c) { return this._validator ? this._validator(c) : null; } // Implemented as part of ControlValueAccessor. writeValue(value) { this._assignValueProgrammatically(value); } // Implemented as part of ControlValueAccessor. registerOnChange(fn) { this._cvaOnChange = fn; } // Implemented as part of ControlValueAccessor. registerOnTouched(fn) { this._onTouched = fn; } // Implemented as part of ControlValueAccessor. setDisabledState(isDisabled) { this.disabled = isDisabled; } _onKeydown(event) { const isAltDownArrow = event.altKey && event.keyCode === DOWN_ARROW; if (isAltDownArrow && !this._elementRef.nativeElement.readOnly) { this._openPopup(); event.preventDefault(); } } _onInput(value) { const lastValueWasValid = this._lastValueValid; let date = this._dateAdapter.parse(value, this._dateFormats.parse.dateInput); this._lastValueValid = this._isValidValue(date); date = this._dateAdapter.getValidDateOrNull(date); if (!this._dateAdapter.sameDate(date, this.value)) { this._assignValue(date); this._cvaOnChange(date); this.dateInput.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement)); } else { // Call the CVA change handler for invalid values // since this is what marks the control as dirty. if (value && !this.value) { this._cvaOnChange(date); } if (lastValueWasValid !== this._lastValueValid) { this._validatorOnChange(); } } } _onChange() { this.dateChange.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement)); } /** Handles blur events on the input. */ _onBlur() { // Reformat the input only if we have a valid value. if (this.value) { this._formatValue(this.value); } this._onTouched(); } /** Formats a value and sets it on the input element. */ _formatValue(value) { this._elementRef.nativeElement.value = value ? this._dateAdapter.format(value, this._dateFormats.display.dateInput) : ''; } /** Assigns a value to the model. */ _assignValue(value) { // We may get some incoming values before the model was // assigned. Save the value so that we can assign it later. if (this._model) { this._assignValueToModel(value); this._pendingValue = null; } else { this._pendingValue = value; } } /** Whether a value is considered valid. */ _isValidValue(value) { return !value || this._dateAdapter.isValid(value); } /** * Checks whether a parent control is disabled. This is in place so that it can be overridden * by inputs extending this one which can be placed inside of a group that can be disabled. */ _parentDisabled() { return false; } /** Programmatically assigns a value to the input. */ _assignValueProgrammatically(value) { value = this._dateAdapter.deserialize(value); this._lastValueValid = this._isValidValue(value); value = this._dateAdapter.getValidDateOrNull(value); this._assignValue(value); this._formatValue(value); } /** Gets whether a value matches the current date filter. */ _matchesFilter(value) { const filter = this._getDateFilter(); return !filter || filter(value); } } MatDatepickerInputBase.decorators = [ { type: Directive } ]; MatDatepickerInputBase.ctorParameters = () => [ { type: ElementRef }, { type: DateAdapter, decorators: [{ type: Optional }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_DATE_FORMATS,] }] } ]; MatDatepickerInputBase.propDecorators = { value: [{ type: Input }], disabled: [{ type: Input }], dateChange: [{ type: Output }], dateInput: [{ type: Output }] }; /** * Checks whether the `SimpleChanges` object from an `ngOnChanges` * callback has any changes, accounting for date objects. */ function dateInputsHaveChanged(changes, adapter) { const keys = Object.keys(changes); for (let key of keys) { const { previousValue, currentValue } = changes[key]; if (adapter.isDateInstance(previousValue) && adapter.isDateInstance(currentValue)) { if (!adapter.sameDate(previousValue, currentValue)) { return true; } } else { return true; } } return false; } /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ /** @docs-private */ const MAT_DATEPICKER_VALUE_ACCESSOR = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MatDatepickerInput), multi: true }; /** @docs-private */ const MAT_DATEPICKER_VALIDATORS = { provide: NG_VALIDATORS, useExisting: forwardRef(() => MatDatepickerInput), multi: true }; /** Directive used to connect an input to a MatDatepicker. */ class MatDatepickerInput extends MatDatepickerInputBase { constructor(elementRef, dateAdapter, dateFormats, _formField) { super(elementRef, dateAdapter, dateFormats); this._formField = _formField; this._closedSubscription = Subscription.EMPTY; this._validator = Validators.compose(super._getValidators()); } /** The datepicker that this input is associated with. */ set matDatepicker(datepicker) { if (datepicker) { this._datepicker = datepicker; this._closedSubscription = datepicker.closedStream.subscribe(() => this._onTouched()); this._registerModel(datepicker.registerInput(this)); } } /** The minimum valid date. */ get min() { return this._min; } set min(value) { const validValue = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); if (!this._dateAdapter.sameDate(validValue, this._min)) { this._min = validValue; this._validatorOnChange(); } } /** The maximum valid date. */ get max() { return this._max; } set max(value) { const validValue = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); if (!this._dateAdapter.sameDate(validValue, this._max)) { this._max = validValue; this._validatorOnChange(); } } /** Function that can be used to filter out dates within the datepicker. */ get dateFilter() { return this._dateFilter; } set dateFilter(value) { const wasMatchingValue = this._matchesFilter(this.value); this._dateFilter = value; if (this._matchesFilter(this.value) !== wasMatchingValue) { this._validatorOnChange(); } } /** * Gets the element that the datepicker popup should be connected to. * @return The element to connect the popup to. */ getConnectedOverlayOrigin() { return this._formField ? this._formField.getConnectedOverlayOrigin() : this._elementRef; } /** Gets the ID of an element that should be used a description for the calendar overlay. */ getOverlayLabelId() { if (this._formField) { return this._formField.getLabelId(); } return this._elementRef.nativeElement.getAttribute('aria-labelledby'); } /** Returns the palette used by the input's form field, if any. */ getThemePalette() { return this._formField ? this._formField.color : undefined; } /** Gets the value at which the calendar should start. */ getStartValue() { return this.value; } ngOnDestroy() { super.ngOnDestroy(); this._closedSubscription.unsubscribe(); } /** Opens the associated datepicker. */ _openPopup() { if (this._datepicker) { this._datepicker.open(); } } _getValueFromModel(modelValue) { return modelValue; } _assignValueToModel(value) { if (this._model) { this._model.updateSelection(value, this); } } /** Gets the input's minimum date. */ _getMinDate() { return this._min; } /** Gets the input's maximum date. */ _getMaxDate() { return this._max; } /** Gets the input's date filtering function. */ _getDateFilter() { return this._dateFilter; } _shouldHandleChangeEvent(event) { return event.source !== this; } } MatDatepickerInput.decorators = [ { type: Directive, args: [{ selector: 'input[matDatepicker]', providers: [ MAT_DATEPICKER_VALUE_ACCESSOR, MAT_DATEPICKER_VALIDATORS, { provide: MAT_INPUT_VALUE_ACCESSOR, useExisting: MatDatepickerInput }, ], host: { 'class': 'mat-datepicker-input', '[attr.aria-haspopup]': '_datepicker ? "dialog" : null', '[attr.aria-owns]': '(_datepicker?.opened && _datepicker.id) || null', '[attr.min]': 'min ? _dateAdapter.toIso8601(min) : null', '[attr.max]': 'max ? _dateAdapter.toIso8601(max) : null', // Used by the test harness to tie this input to its calendar. We can't depend on // `aria-owns` for this, because it's only defined while the calendar is open. '[attr.data-mat-calendar]': '_datepicker ? _datepicker.id : null', '[disabled]': 'disabled', '(input)': '_onInput($event.target.value)', '(change)': '_onChange()', '(blur)': '_onBlur()', '(keydown)': '_onKeydown($event)', }, exportAs: 'matDatepickerInput', },] } ]; MatDatepickerInput.ctorParameters = () => [ { type: ElementRef }, { type: DateAdapter, decorators: [{ type: Optional }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_DATE_FORMATS,] }] }, { type: MatFormField, decorators: [{ type: Optional }, { type: Inject, args: [MAT_FORM_FIELD,] }] } ]; MatDatepickerInput.propDecorators = { matDatepicker: [{ type: Input }], min: [{ type: Input }], max: [{ type: Input }], dateFilter: [{ type: Input, args: ['matDatepickerFilter',] }] }; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ /** Can be used to override the icon of a `matDatepickerToggle`. */ class MatDatepickerToggleIcon { } MatDatepickerToggleIcon.decorators = [ { type: Directive, args: [{ selector: '[matDatepickerToggleIcon]' },] } ]; class MatDatepickerToggle { constructor(_intl, _changeDetectorRef, defaultTabIndex) { this._intl = _intl; this._changeDetectorRef = _changeDetectorRef; this._stateChanges = Subscription.EMPTY; const parsedTabIndex = Number(defaultTabIndex); this.tabIndex = (parsedTabIndex || parsedTabIndex === 0) ? parsedTabIndex : null; } /** Whether the toggle button is disabled. */ get disabled() { if (this._disabled === undefined && this.datepicker) { return this.datepicker.disabled; } return !!this._disabled; } set disabled(value) { this._disabled = coerceBooleanProperty(value); } ngOnChanges(changes) { if (changes['datepicker']) { this._watchStateChanges(); } } ngOnDestroy() { this._stateChanges.unsubscribe(); } ngAfterContentInit() { this._watchStateChanges(); } _open(event) { if (this.datepicker && !this.disabled) { this.datepicker.open(); event.stopPropagation(); } } _watchStateChanges() { const datepickerStateChanged = this.datepicker ? this.datepicker.stateChanges : of(); const inputStateChanged = this.datepicker && this.datepicker.datepickerInput ? this.datepicker.datepickerInput.stateChanges : of(); const datepickerToggled = this.datepicker ? merge(this.datepicker.openedStream, this.datepicker.closedStream) : of(); this._stateChanges.unsubscribe(); this._stateChanges = merge(this._intl.changes, datepickerStateChanged, inputStateChanged, datepickerToggled).subscribe(() => this._changeDetectorRef.markForCheck()); } } MatDatepickerToggle.decorators = [ { type: Component, args: [{ selector: 'mat-datepicker-toggle', template: "\n\n \n \n \n\n \n\n", host: { 'class': 'mat-datepicker-toggle', '[attr.tabindex]': 'null', '[class.mat-datepicker-toggle-active]': 'datepicker && datepicker.opened', '[class.mat-accent]': 'datepicker && datepicker.color === "accent"', '[class.mat-warn]': 'datepicker && datepicker.color === "warn"', // Used by the test harness to tie this toggle to its datepicker. '[attr.data-mat-calendar]': 'datepicker ? datepicker.id : null', // Bind the `click` on the host, rather than the inner `button`, so that we can call // `stopPropagation` on it without affecting the user's `click` handlers. We need to stop // it so that the input doesn't get focused automatically by the form field (See #21836). '(click)': '_open($event)', }, exportAs: 'matDatepickerToggle', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, 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"] },] } ]; MatDatepickerToggle.ctorParameters = () => [ { type: MatDatepickerIntl }, { type: ChangeDetectorRef }, { type: String, decorators: [{ type: Attribute, args: ['tabindex',] }] } ]; MatDatepickerToggle.propDecorators = { datepicker: [{ type: Input, args: ['for',] }], tabIndex: [{ type: Input }], ariaLabel: [{ type: Input, args: ['aria-label',] }], disabled: [{ type: Input }], disableRipple: [{ type: Input }], _customIcon: [{ type: ContentChild, args: [MatDatepickerToggleIcon,] }], _button: [{ type: ViewChild, args: ['button',] }] }; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ /** * Used to provide the date range input wrapper component * to the parts without circular dependencies. */ const MAT_DATE_RANGE_INPUT_PARENT = new InjectionToken('MAT_DATE_RANGE_INPUT_PARENT'); /** * Base class for the individual inputs that can be projected inside a `mat-date-range-input`. */ class MatDateRangeInputPartBase extends MatDatepickerInputBase { constructor(_rangeInput, elementRef, _defaultErrorStateMatcher, _injector, _parentForm, _parentFormGroup, dateAdapter, dateFormats) { super(elementRef, dateAdapter, dateFormats); this._rangeInput = _rangeInput; this._defaultErrorStateMatcher = _defaultErrorStateMatcher; this._injector = _injector; this._parentForm = _parentForm; this._parentFormGroup = _parentFormGroup; } ngOnInit() { // We need the date input to provide itself as a `ControlValueAccessor` and a `Validator`, while // injecting its `NgControl` so that the error state is handled correctly. This introduces a // circular dependency, because both `ControlValueAccessor` and `Validator` depend on the input // itself. Usually we can work around it for the CVA, but there's no API to do it for the // validator. We work around it here by injecting the `NgControl` in `ngOnInit`, after // everything has been resolved. // tslint:disable-next-line:no-bitwise const ngControl = this._injector.get(NgControl, null, InjectFlags.Self | InjectFlags.Optional); if (ngControl) { this.ngControl = ngControl; } } ngDoCheck() { if (this.ngControl) { // We need to re-evaluate this on every change detection cycle, because there are some // error triggers that we can't subscribe to (e.g. parent form submissions). This means // that whatever logic is in here has to be super lean or we risk destroying the performance. this.updateErrorState(); } } /** Gets whether the input is empty. */ isEmpty() { return this._elementRef.nativeElement.value.length === 0; } /** Gets the placeholder of the input. */ _getPlaceholder() { return this._elementRef.nativeElement.placeholder; } /** Focuses the input. */ focus() { this._elementRef.nativeElement.focus(); } /** Handles `input` events on the input element. */ _onInput(value) { super._onInput(value); this._rangeInput._handleChildValueChange(); } /** Opens the datepicker associated with the input. */ _openPopup() { this._rangeInput._openDatepicker(); } /** Gets the minimum date from the range input. */ _getMinDate() { return this._rangeInput.min; } /** Gets the maximum date from the range input. */ _getMaxDate() { return this._rangeInput.max; } /** Gets the date filter function from the range input. */ _getDateFilter() { return this._rangeInput.dateFilter; } _parentDisabled() { return this._rangeInput._groupDisabled; } _shouldHandleChangeEvent({ source }) { return source !== this._rangeInput._startInput && source !== this._rangeInput._endInput; } _assignValueProgrammatically(value) { super._assignValueProgrammatically(value); const opposite = (this === this._rangeInput._startInput ? this._rangeInput._endInput : this._rangeInput._startInput); opposite === null || opposite === void 0 ? void 0 : opposite._validatorOnChange(); } } MatDateRangeInputPartBase.decorators = [ { type: Directive } ]; MatDateRangeInputPartBase.ctorParameters = () => [ { type: undefined, decorators: [{ type: Inject, args: [MAT_DATE_RANGE_INPUT_PARENT,] }] }, { type: ElementRef }, { type: ErrorStateMatcher }, { type: Injector }, { type: NgForm, decorators: [{ type: Optional }] }, { type: FormGroupDirective, decorators: [{ type: Optional }] }, { type: DateAdapter, decorators: [{ type: Optional }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_DATE_FORMATS,] }] } ]; const _MatDateRangeInputBase = mixinErrorState(MatDateRangeInputPartBase); /** Input for entering the start date in a `mat-date-range-input`. */ class MatStartDate extends _MatDateRangeInputBase { constructor(rangeInput, elementRef, defaultErrorStateMatcher, injector, parentForm, parentFormGroup, dateAdapter, dateFormats) { // TODO(crisbeto): this constructor shouldn't be necessary, but ViewEngine doesn't seem to // handle DI correctly when it is inherited from `MatDateRangeInputPartBase`. We can drop this // constructor once ViewEngine is removed. super(rangeInput, elementRef, defaultErrorStateMatcher, injector, parentForm, parentFormGroup, dateAdapter, dateFormats); /** Validator that checks that the start date isn't after the end date. */ this._startValidator = (control) => { const start = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(control.value)); const end = this._model ? this._model.selection.end : null; return (!start || !end || this._dateAdapter.compareDate(start, end) <= 0) ? null : { 'matStartDateInvalid': { 'end': end, 'actual': start } }; }; this._validator = Validators.compose([...super._getValidators(), this._startValidator]); } ngOnInit() { // Normally this happens automatically, but it seems to break if not added explicitly when all // of the criteria below are met: // 1) The class extends a TS mixin. // 2) The application is running in ViewEngine. // 3) The application is being transpiled through tsickle. // This can be removed once google3 is completely migrated to Ivy. super.ngOnInit(); } ngDoCheck() { // Normally this happens automatically, but it seems to break if not added explicitly when all // of the criteria below are met: // 1) The class extends a TS mixin. // 2) The application is running in ViewEngine. // 3) The application is being transpiled through tsickle. // This can be removed once google3 is completely migrated to Ivy. super.ngDoCheck(); } _getValueFromModel(modelValue) { return modelValue.start; } _shouldHandleChangeEvent(change) { var _a; if (!super._shouldHandleChangeEvent(change)) { return false; } else { return !((_a = change.oldValue) === null || _a === void 0 ? void 0 : _a.start) ? !!change.selection.start : !change.selection.start || !!this._dateAdapter.compareDate(change.oldValue.start, change.selection.start); } } _assignValueToModel(value) { if (this._model) { const range = new DateRange(value, this._model.selection.end); this._model.updateSelection(range, this); } } _formatValue(value) { super._formatValue(value); // Any time the input value is reformatted we need to tell the parent. this._rangeInput._handleChildValueChange(); } /** Gets the value that should be used when mirroring the input's size. */ getMirrorValue() { const element = this._elementRef.nativeElement; const value = element.value; return value.length > 0 ? value : element.placeholder; } } MatStartDate.decorators = [ { type: Directive, args: [{ selector: 'input[matStartDate]', host: { 'class': 'mat-start-date mat-date-range-input-inner', '[disabled]': 'disabled', '(input)': '_onInput($event.target.value)', '(change)': '_onChange()', '(keydown)': '_onKeydown($event)', '[attr.id]': '_rangeInput.id', '[attr.aria-haspopup]': '_rangeInput.rangePicker ? "dialog" : null', '[attr.aria-owns]': '(_rangeInput.rangePicker?.opened && _rangeInput.rangePicker.id) || null', '[attr.min]': '_getMinDate() ? _dateAdapter.toIso8601(_getMinDate()) : null', '[attr.max]': '_getMaxDate() ? _dateAdapter.toIso8601(_getMaxDate()) : null', '(blur)': '_onBlur()', 'type': 'text', }, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: MatStartDate, multi: true }, { provide: NG_VALIDATORS, useExisting: MatStartDate, multi: true } ], // These need to be specified explicitly, because some tooling doesn't // seem to pick them up from the base class. See #20932. outputs: ['dateChange', 'dateInput'], inputs: ['errorStateMatcher'] },] } ]; MatStartDate.ctorParameters = () => [ { type: undefined, decorators: [{ type: Inject, args: [MAT_DATE_RANGE_INPUT_PARENT,] }] }, { type: ElementRef }, { type: ErrorStateMatcher }, { type: Injector }, { type: NgForm, decorators: [{ type: Optional }] }, { type: FormGroupDirective, decorators: [{ type: Optional }] }, { type: DateAdapter, decorators: [{ type: Optional }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_DATE_FORMATS,] }] } ]; /** Input for entering the end date in a `mat-date-range-input`. */ class MatEndDate extends _MatDateRangeInputBase { constructor(rangeInput, elementRef, defaultErrorStateMatcher, injector, parentForm, parentFormGroup, dateAdapter, dateFormats) { // TODO(crisbeto): this constructor shouldn't be necessary, but ViewEngine doesn't seem to // handle DI correctly when it is inherited from `MatDateRangeInputPartBase`. We can drop this // constructor once ViewEngine is removed. super(rangeInput, elementRef, defaultErrorStateMatcher, injector, parentForm, parentFormGroup, dateAdapter, dateFormats); /** Validator that checks that the end date isn't before the start date. */ this._endValidator = (control) => { const end = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(control.value)); const start = this._model ? this._model.selection.start : null; return (!end || !start || this._dateAdapter.compareDate(end, start) >= 0) ? null : { 'matEndDateInvalid': { 'start': start, 'actual': end } }; }; this._validator = Validators.compose([...super._getValidators(), this._endValidator]); } ngOnInit() { // Normally this happens automatically, but it seems to break if not added explicitly when all // of the criteria below are met: // 1) The class extends a TS mixin. // 2) The application is running in ViewEngine. // 3) The application is being transpiled through tsickle. // This can be removed once google3 is completely migrated to Ivy. super.ngOnInit(); } ngDoCheck() { // Normally this happens automatically, but it seems to break if not added explicitly when all // of the criteria below are met: // 1) The class extends a TS mixin. // 2) The application is running in ViewEngine. // 3) The application is being transpiled through tsickle. // This can be removed once google3 is completely migrated to Ivy. super.ngDoCheck(); } _getValueFromModel(modelValue) { return modelValue.end; } _shouldHandleChangeEvent(change) { var _a; if (!super._shouldHandleChangeEvent(change)) { return false; } else { return !((_a = change.oldValue) === null || _a === void 0 ? void 0 : _a.end) ? !!change.selection.end : !change.selection.end || !!this._dateAdapter.compareDate(change.oldValue.end, change.selection.end); } } _assignValueToModel(value) { if (this._model) { const range = new DateRange(this._model.selection.start, value); this._model.updateSelection(range, this); } } _onKeydown(event) { // If the user is pressing backspace on an empty end input, move focus back to the start. if (event.keyCode === BACKSPACE && !this._elementRef.nativeElement.value) { this._rangeInput._startInput.focus(); } super._onKeydown(event); } } MatEndDate.decorators = [ { type: Directive, args: [{ selector: 'input[matEndDate]', host: { 'class': 'mat-end-date mat-date-range-input-inner', '[disabled]': 'disabled', '(input)': '_onInput($event.target.value)', '(change)': '_onChange()', '(keydown)': '_onKeydown($event)', '[attr.aria-haspopup]': '_rangeInput.rangePicker ? "dialog" : null', '[attr.aria-owns]': '(_rangeInput.rangePicker?.opened && _rangeInput.rangePicker.id) || null', '[attr.min]': '_getMinDate() ? _dateAdapter.toIso8601(_getMinDate()) : null', '[attr.max]': '_getMaxDate() ? _dateAdapter.toIso8601(_getMaxDate()) : null', '(blur)': '_onBlur()', 'type': 'text', }, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: MatEndDate, multi: true }, { provide: NG_VALIDATORS, useExisting: MatEndDate, multi: true } ], // These need to be specified explicitly, because some tooling doesn't // seem to pick them up from the base class. See #20932. outputs: ['dateChange', 'dateInput'], inputs: ['errorStateMatcher'] },] } ]; MatEndDate.ctorParameters = () => [ { type: undefined, decorators: [{ type: Inject, args: [MAT_DATE_RANGE_INPUT_PARENT,] }] }, { type: ElementRef }, { type: ErrorStateMatcher }, { type: Injector }, { type: NgForm, decorators: [{ type: Optional }] }, { type: FormGroupDirective, decorators: [{ type: Optional }] }, { type: DateAdapter, decorators: [{ type: Optional }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_DATE_FORMATS,] }] } ]; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ let nextUniqueId = 0; class MatDateRangeInput { constructor(_changeDetectorRef, _elementRef, control, _dateAdapter, _formField) { this._changeDetectorRef = _changeDetectorRef; this._elementRef = _elementRef; this._dateAdapter = _dateAdapter; this._formField = _formField; this._closedSubscription = Subscription.EMPTY; /** Unique ID for the input. */ this.id = `mat-date-range-input-${nextUniqueId++}`; /** Whether the control is focused. */ this.focused = false; /** Name of the form control. */ this.controlType = 'mat-date-range-input'; this._groupDisabled = false; /** Value for the `aria-describedby` attribute of the inputs. */ this._ariaDescribedBy = null; /** Separator text to be shown between the inputs. */ this.separator = '–'; /** Start of the comparison range that should be shown in the calendar. */ this.comparisonStart = null; /** End of the comparison range that should be shown in the calendar. */ this.comparisonEnd = null; /** Emits when the input's state has changed. */ this.stateChanges = new Subject(); if (!_dateAdapter && (typeof ngDevMode === 'undefined' || ngDevMode)) { throw createMissingDateImplError('DateAdapter'); } // The datepicker module can be used both with MDC and non-MDC form fields. We have // to conditionally add the MDC input class so that the range picker looks correctly. if (_formField === null || _formField === void 0 ? void 0 : _formField._elementRef.nativeElement.classList.contains('mat-mdc-form-field')) { const classList = _elementRef.nativeElement.classList; classList.add('mat-mdc-input-element'); classList.add('mat-mdc-form-field-input-control'); } // TODO(crisbeto): remove `as any` after #18206 lands. this.ngControl = control; } /** Current value of the range input. */ get value() { return this._model ? this._model.selection : null; } /** Whether the control's label should float. */ get shouldLabelFloat() { return this.focused || !this.empty; } /** * Implemented as a part of `MatFormFieldControl`. * Set the placeholder attribute on `matStartDate` and `matEndDate`. * @docs-private */ get placeholder() { var _a, _b; const start = ((_a = this._startInput) === null || _a === void 0 ? void 0 : _a._getPlaceholder()) || ''; const end = ((_b = this._endInput) === null || _b === void 0 ? void 0 : _b._getPlaceholder()) || ''; return (start || end) ? `${start} ${this.separator} ${end}` : ''; } /** The range picker that this input is associated with. */ get rangePicker() { return this._rangePicker; } set rangePicker(rangePicker) { if (rangePicker) { this._model = rangePicker.registerInput(this); this._rangePicker = rangePicker; this._closedSubscription.unsubscribe(); this._closedSubscription = rangePicker.closedStream.subscribe(() => { var _a, _b; (_a = this._startInput) === null || _a === void 0 ? void 0 : _a._onTouched(); (_b = this._endInput) === null || _b === void 0 ? void 0 : _b._onTouched(); }); this._registerModel(this._model); } } /** Whether the input is required. */ get required() { return !!this._required; } set required(value) { this._required = coerceBooleanProperty(value); } /** Function that can be used to filter out dates within the date range picker. */ get dateFilter() { return this._dateFilter; } set dateFilter(value) { const start = this._startInput; const end = this._endInput; const wasMatchingStart = start && start._matchesFilter(start.value); const wasMatchingEnd = end && end._matchesFilter(start.value); this._dateFilter = value; if (start && start._matchesFilter(start.value) !== wasMatchingStart) { start._validatorOnChange(); } if (end && end._matchesFilter(end.value) !== wasMatchingEnd) { end._validatorOnChange(); } } /** The minimum valid date. */ get min() { return this._min; } set min(value) { const validValue = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); if (!this._dateAdapter.sameDate(validValue, this._min)) { this._min = validValue; this._revalidate(); } } /** The maximum valid date. */ get max() { return this._max; } set max(value) { const validValue = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); if (!this._dateAdapter.sameDate(validValue, this._max)) { this._max = validValue; this._revalidate(); } } /** Whether the input is disabled. */ get disabled() { return (this._startInput && this._endInput) ? (this._startInput.disabled && this._endInput.disabled) : this._groupDisabled; } set disabled(value) { const newValue = coerceBooleanProperty(value); if (newValue !== this._groupDisabled) { this._groupDisabled = newValue; this.stateChanges.next(undefined); } } /** Whether the input is in an error state. */ get errorState() { if (this._startInput && this._endInput) { return this._startInput.errorState || this._endInput.errorState; } return false; } /** Whether the datepicker input is empty. */ get empty() { const startEmpty = this._startInput ? this._startInput.isEmpty() : false; const endEmpty = this._endInput ? this._endInput.isEmpty() : false; return startEmpty && endEmpty; } /** * Implemented as a part of `MatFormFieldControl`. * @docs-private */ setDescribedByIds(ids) { this._ariaDescribedBy = ids.length ? ids.join(' ') : null; } /** * Implemented as a part of `MatFormFieldControl`. * @docs-private */ onContainerClick() { if (!this.focused && !this.disabled) { if (!this._model || !this._model.selection.start) { this._startInput.focus(); } else { this._endInput.focus(); } } } ngAfterContentInit() { if (typeof ngDevMode === 'undefined' || ngDevMode) { if (!this._startInput) { throw Error('mat-date-range-input must contain a matStartDate input'); } if (!this._endInput) { throw Error('mat-date-range-input must contain a matEndDate input'); } } if (this._model) { this._registerModel(this._model); } // We don't need to unsubscribe from this, because we // know that the input streams will be completed on destroy. merge(this._startInput.stateChanges, this._endInput.stateChanges).subscribe(() => { this.stateChanges.next(undefined); }); } ngOnChanges(changes) { if (dateInputsHaveChanged(changes, this._dateAdapter)) { this.stateChanges.next(undefined); } } ngOnDestroy() { this._closedSubscription.unsubscribe(); this.stateChanges.complete(); } /** Gets the date at which the calendar should start. */ getStartValue() { return this.value ? this.value.start : null; } /** Gets the input's theme palette. */ getThemePalette() { return this._formField ? this._formField.color : undefined; } /** Gets the element to which the calendar overlay should be attached. */ getConnectedOverlayOrigin() { return this._formField ? this._formField.getConnectedOverlayOrigin() : this._elementRef; } /** Gets the ID of an element that should be used a description for the calendar overlay. */ getOverlayLabelId() { return this._formField ? this._formField.getLabelId() : null; } /** Gets the value that is used to mirror the state input. */ _getInputMirrorValue() { return this._startInput ? this._startInput.getMirrorValue() : ''; } /** Whether the input placeholders should be hidden. */ _shouldHidePlaceholders() { return this._startInput ? !this._startInput.isEmpty() : false; } /** Handles the value in one of the child inputs changing. */ _handleChildValueChange() { this.stateChanges.next(undefined); this._changeDetectorRef.markForCheck(); } /** Opens the date range picker associated with the input. */ _openDatepicker() { if (this._rangePicker) { this._rangePicker.open(); } } /** Whether the separate text should be hidden. */ _shouldHideSeparator() { return (!this._formField || (this._formField.getLabelId() && !this._formField._shouldLabelFloat())) && this.empty; } /** Gets the value for the `aria-labelledby` attribute of the inputs. */ _getAriaLabelledby() { const formField = this._formField; return formField && formField._hasFloatingLabel() ? formField._labelId : null; } /** Updates the focused state of the range input. */ _updateFocus(origin) { this.focused = origin !== null; this.stateChanges.next(); } /** Re-runs the validators on the start/end inputs. */ _revalidate() { if (this._startInput) { this._startInput._validatorOnChange(); } if (this._endInput) { this._endInput._validatorOnChange(); } } /** Registers the current date selection model with the start/end inputs. */ _registerModel(model) { if (this._startInput) { this._startInput._registerModel(model); } if (this._endInput) { this._endInput._registerModel(model); } } } MatDateRangeInput.decorators = [ { type: Component, args: [{ selector: 'mat-date-range-input', template: "\n
\n \n {{_getInputMirrorValue()}}\n
\n\n {{separator}}\n\n
\n \n
\n\n\n", exportAs: 'matDateRangeInput', host: { 'class': 'mat-date-range-input', '[class.mat-date-range-input-hide-placeholders]': '_shouldHidePlaceholders()', '[class.mat-date-range-input-required]': 'required', '[attr.id]': 'null', 'role': 'group', '[attr.aria-labelledby]': '_getAriaLabelledby()', '[attr.aria-describedby]': '_ariaDescribedBy', // Used by the test harness to tie this input to its calendar. We can't depend on // `aria-owns` for this, because it's only defined while the calendar is open. '[attr.data-mat-calendar]': 'rangePicker ? rangePicker.id : null', }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [ { provide: MatFormFieldControl, useExisting: MatDateRangeInput }, { provide: MAT_DATE_RANGE_INPUT_PARENT, useExisting: MatDateRangeInput }, ], 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"] },] } ]; MatDateRangeInput.ctorParameters = () => [ { type: ChangeDetectorRef }, { type: ElementRef }, { type: ControlContainer, decorators: [{ type: Optional }, { type: Self }] }, { type: DateAdapter, decorators: [{ type: Optional }] }, { type: MatFormField, decorators: [{ type: Optional }, { type: Inject, args: [MAT_FORM_FIELD,] }] } ]; MatDateRangeInput.propDecorators = { rangePicker: [{ type: Input }], required: [{ type: Input }], dateFilter: [{ type: Input }], min: [{ type: Input }], max: [{ type: Input }], disabled: [{ type: Input }], separator: [{ type: Input }], comparisonStart: [{ type: Input }], comparisonEnd: [{ type: Input }], _startInput: [{ type: ContentChild, args: [MatStartDate,] }], _endInput: [{ type: ContentChild, args: [MatEndDate,] }] }; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ // TODO(mmalerba): We use a component instead of a directive here so the user can use implicit // template reference variables (e.g. #d vs #d="matDateRangePicker"). We can change this to a // directive if angular adds support for `exportAs: '$implicit'` on directives. /** Component responsible for managing the date range picker popup/dialog. */ class MatDateRangePicker extends MatDatepickerBase { _forwardContentValues(instance) { super._forwardContentValues(instance); const input = this.datepickerInput; if (input) { instance.comparisonStart = input.comparisonStart; instance.comparisonEnd = input.comparisonEnd; } } } MatDateRangePicker.decorators = [ { type: Component, args: [{ selector: 'mat-date-range-picker', template: '', exportAs: 'matDateRangePicker', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [ MAT_RANGE_DATE_SELECTION_MODEL_PROVIDER, MAT_CALENDAR_RANGE_STRATEGY_PROVIDER, { provide: MatDatepickerBase, useExisting: MatDateRangePicker }, ] },] } ]; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ /** Button that will close the datepicker and assign the current selection to the data model. */ class MatDatepickerApply { constructor(_datepicker) { this._datepicker = _datepicker; } _applySelection() { this._datepicker._applyPendingSelection(); this._datepicker.close(); } } MatDatepickerApply.decorators = [ { type: Directive, args: [{ selector: '[matDatepickerApply], [matDateRangePickerApply]', host: { '(click)': '_applySelection()' } },] } ]; MatDatepickerApply.ctorParameters = () => [ { type: MatDatepickerBase } ]; /** Button that will close the datepicker and discard the current selection. */ class MatDatepickerCancel { constructor(_datepicker) { this._datepicker = _datepicker; } } MatDatepickerCancel.decorators = [ { type: Directive, args: [{ selector: '[matDatepickerCancel], [matDateRangePickerCancel]', host: { '(click)': '_datepicker.close()' } },] } ]; MatDatepickerCancel.ctorParameters = () => [ { type: MatDatepickerBase } ]; /** * Container that can be used to project a row of action buttons * to the bottom of a datepicker or date range picker. */ class MatDatepickerActions { constructor(_datepicker, _viewContainerRef) { this._datepicker = _datepicker; this._viewContainerRef = _viewContainerRef; } ngAfterViewInit() { this._portal = new TemplatePortal(this._template, this._viewContainerRef); this._datepicker.registerActions(this._portal); } ngOnDestroy() { var _a; this._datepicker.removeActions(this._portal); // Needs to be null checked since we initialize it in `ngAfterViewInit`. if (this._portal && this._portal.isAttached) { (_a = this._portal) === null || _a === void 0 ? void 0 : _a.detach(); } } } MatDatepickerActions.decorators = [ { type: Component, args: [{ selector: 'mat-datepicker-actions, mat-date-range-picker-actions', template: `
`, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, 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"] },] } ]; MatDatepickerActions.ctorParameters = () => [ { type: MatDatepickerBase }, { type: ViewContainerRef } ]; MatDatepickerActions.propDecorators = { _template: [{ type: ViewChild, args: [TemplateRef,] }] }; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ class MatDatepickerModule { } MatDatepickerModule.decorators = [ { type: NgModule, args: [{ imports: [ CommonModule, MatButtonModule, OverlayModule, A11yModule, PortalModule, MatCommonModule, ], exports: [ CdkScrollableModule, MatCalendar, MatCalendarBody, MatDatepicker, MatDatepickerContent, MatDatepickerInput, MatDatepickerToggle, MatDatepickerToggleIcon, MatMonthView, MatYearView, MatMultiYearView, MatCalendarHeader, MatDateRangeInput, MatStartDate, MatEndDate, MatDateRangePicker, MatDatepickerActions, MatDatepickerCancel, MatDatepickerApply ], declarations: [ MatCalendar, MatCalendarBody, MatDatepicker, MatDatepickerContent, MatDatepickerInput, MatDatepickerToggle, MatDatepickerToggleIcon, MatMonthView, MatYearView, MatMultiYearView, MatCalendarHeader, MatDateRangeInput, MatStartDate, MatEndDate, MatDateRangePicker, MatDatepickerActions, MatDatepickerCancel, MatDatepickerApply ], providers: [ MatDatepickerIntl, MAT_DATEPICKER_SCROLL_STRATEGY_FACTORY_PROVIDER ], entryComponents: [ MatDatepickerContent, MatCalendarHeader, ] },] } ]; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ /** * Generated bundle index. Do not edit. */ export { 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 }; //# sourceMappingURL=datepicker.js.map