angular-material-components-zigzag-datetime-picker
Version:
Angular Material Datetime Picker
1 lines • 273 kB
Source Map (JSON)
{"version":3,"file":"angular-material-components-zigzag-datetime-picker.mjs","sources":["../../../../projects/datetime-picker/src/lib/core/date-formats.ts","../../../../projects/datetime-picker/src/lib/calendar-body.ts","../../../../projects/datetime-picker/src/lib/calendar-body.html","../../../../projects/datetime-picker/src/lib/core/date-adapter.ts","../../../../projects/datetime-picker/src/lib/date-range-selection-strategy.ts","../../../../projects/datetime-picker/src/lib/utils/date-utils.ts","../../../../projects/datetime-picker/src/lib/month-view.ts","../../../../projects/datetime-picker/src/lib/month-view.html","../../../../projects/datetime-picker/src/lib/multi-year-view.ts","../../../../projects/datetime-picker/src/lib/multi-year-view.html","../../../../projects/datetime-picker/src/lib/year-view.ts","../../../../projects/datetime-picker/src/lib/year-view.html","../../../../projects/datetime-picker/src/lib/calendar.ts","../../../../projects/datetime-picker/src/lib/calendar-header.html","../../../../projects/datetime-picker/src/lib/calendar.html","../../../../projects/datetime-picker/src/lib/timepicker.component.ts","../../../../projects/datetime-picker/src/lib/timepicker.component.html","../../../../projects/datetime-picker/src/lib/datetime-picker.component.ts","../../../../projects/datetime-picker/src/lib/datetime-content.component.html","../../../../projects/datetime-picker/src/lib/datetime-input.ts","../../../../projects/datetime-picker/src/lib/timepicker.module.ts","../../../../projects/datetime-picker/src/lib/datetime-picker.module.ts","../../../../projects/datetime-picker/src/lib/core/native-date-adapter.ts","../../../../projects/datetime-picker/src/lib/core/native-date-formats.ts","../../../../projects/datetime-picker/src/lib/core/native-date.module.ts","../../../../projects/datetime-picker/src/public-api.ts","../../../../projects/datetime-picker/src/angular-material-components-zigzag-datetime-picker.ts"],"sourcesContent":["/**\r\n * @license\r\n * Copyright Google LLC All Rights Reserved.\r\n *\r\n * Use of this source code is governed by an MIT-style license that can be\r\n * found in the LICENSE file at https://angular.io/license\r\n */\r\n\r\nimport { InjectionToken } from '@angular/core';\r\n\r\n\r\nexport type NgxMatDateFormats = {\r\n parse: {\r\n dateInput: any\r\n },\r\n display: {\r\n dateInput: any,\r\n monthYearLabel: any,\r\n dateA11yLabel: any,\r\n monthYearA11yLabel: any,\r\n }\r\n};\r\n\r\n\r\nexport const NGX_MAT_DATE_FORMATS = new InjectionToken<NgxMatDateFormats>('ngx-mat-date-formats');\r\n","/**\r\n * @license\r\n * Copyright Google LLC All Rights Reserved.\r\n *\r\n * Use of this source code is governed by an MIT-style license that can be\r\n * found in the LICENSE file at https://angular.io/license\r\n */\r\n\r\nimport {\r\n ChangeDetectionStrategy,\r\n Component,\r\n ElementRef,\r\n EventEmitter,\r\n Input,\r\n Output,\r\n ViewEncapsulation,\r\n NgZone,\r\n OnChanges,\r\n SimpleChanges,\r\n OnDestroy,\r\n} from '@angular/core';\r\nimport {take} from 'rxjs/operators';\r\n\r\n/**\r\n * Extra CSS classes that can be associated with a calendar cell.\r\n */\r\nexport type NgxMatCalendarCellCssClasses = string | string[] | Set<string> | {[key: string]: any};\r\n\r\n/**\r\n * An internal class that represents the data corresponding to a single calendar cell.\r\n * @docs-private\r\n */\r\nexport class NgxMatCalendarCell<D = any> {\r\n constructor(public value: number,\r\n public displayValue: string,\r\n public ariaLabel: string,\r\n public enabled: boolean,\r\n public cssClasses: NgxMatCalendarCellCssClasses = {},\r\n public compareValue = value,\r\n public rawValue?: D) {}\r\n}\r\n\r\n/** Event emitted when a date inside the calendar is triggered as a result of a user action. */\r\nexport interface NgxMatCalendarUserEvent<D> {\r\n value: D;\r\n event: Event;\r\n}\r\n\r\n/**\r\n * An internal component used to display calendar data in a table.\r\n * @docs-private\r\n */\r\n@Component({\r\n selector: '[ngx-mat-calendar-body]',\r\n templateUrl: 'calendar-body.html',\r\n styleUrls: ['calendar-body.scss'],\r\n host: {\r\n 'class': 'ngx-mat-calendar-body',\r\n 'role': 'grid',\r\n 'aria-readonly': 'true'\r\n },\r\n exportAs: 'NgxMatCalendarBody',\r\n encapsulation: ViewEncapsulation.None,\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n})\r\nexport class NgxMatCalendarBody implements OnChanges, OnDestroy {\r\n /**\r\n * Used to skip the next focus event when rendering the preview range.\r\n * We need a flag like this, because some browsers fire focus events asynchronously.\r\n */\r\n private _skipNextFocus: boolean;\r\n\r\n /** The label for the table. (e.g. \"Jan 2017\"). */\r\n @Input() label: string;\r\n\r\n /** The cells to display in the table. */\r\n @Input() rows: NgxMatCalendarCell[][];\r\n\r\n /** The value in the table that corresponds to today. */\r\n @Input() todayValue: number;\r\n\r\n /** Start value of the selected date range. */\r\n @Input() startValue: number;\r\n\r\n /** End value of the selected date range. */\r\n @Input() endValue: number;\r\n\r\n /** The minimum number of free cells needed to fit the label in the first row. */\r\n @Input() labelMinRequiredCells: number;\r\n\r\n /** The number of columns in the table. */\r\n @Input() numCols: number = 7;\r\n\r\n /** The cell number of the active cell in the table. */\r\n @Input() activeCell: number = 0;\r\n\r\n /** Whether a range is being selected. */\r\n @Input() isRange: boolean = false;\r\n\r\n /**\r\n * The aspect ratio (width / height) to use for the cells in the table. This aspect ratio will be\r\n * maintained even as the table resizes.\r\n */\r\n @Input() cellAspectRatio: number = 1;\r\n\r\n /** Start of the comparison range. */\r\n @Input() comparisonStart: number | null;\r\n\r\n /** End of the comparison range. */\r\n @Input() comparisonEnd: number | null;\r\n\r\n /** Start of the preview range. */\r\n @Input() previewStart: number | null = null;\r\n\r\n /** End of the preview range. */\r\n @Input() previewEnd: number | null = null;\r\n\r\n /** Emits when a new value is selected. */\r\n @Output() readonly selectedValueChange: EventEmitter<NgxMatCalendarUserEvent<number>> =\r\n new EventEmitter<NgxMatCalendarUserEvent<number>>();\r\n\r\n /** Emits when the preview has changed as a result of a user action. */\r\n @Output() previewChange = new EventEmitter<NgxMatCalendarUserEvent<NgxMatCalendarCell | null>>();\r\n\r\n /** The number of blank cells to put at the beginning for the first row. */\r\n _firstRowOffset: number;\r\n\r\n /** Padding for the individual date cells. */\r\n _cellPadding: string;\r\n\r\n /** Width of an individual cell. */\r\n _cellWidth: string;\r\n\r\n constructor(private _elementRef: ElementRef<HTMLElement>, private _ngZone: NgZone) {\r\n _ngZone.runOutsideAngular(() => {\r\n const element = _elementRef.nativeElement;\r\n element.addEventListener('mouseenter', this._enterHandler, true);\r\n element.addEventListener('focus', this._enterHandler, true);\r\n element.addEventListener('mouseleave', this._leaveHandler, true);\r\n element.addEventListener('blur', this._leaveHandler, true);\r\n });\r\n }\r\n\r\n /** Called when a cell is clicked. */\r\n _cellClicked(cell: NgxMatCalendarCell, event: MouseEvent): void {\r\n if (cell.enabled) {\r\n this.selectedValueChange.emit({value: cell.value, event});\r\n }\r\n }\r\n\r\n /** Returns whether a cell should be marked as selected. */\r\n _isSelected(cell: NgxMatCalendarCell) {\r\n return this.startValue === cell.compareValue || this.endValue === cell.compareValue;\r\n }\r\n\r\n ngOnChanges(changes: SimpleChanges) {\r\n const columnChanges = changes['numCols'];\r\n const {rows, numCols} = this;\r\n\r\n if (changes['rows'] || columnChanges) {\r\n this._firstRowOffset = rows && rows.length && rows[0].length ? numCols - rows[0].length : 0;\r\n }\r\n\r\n if (changes['cellAspectRatio'] || columnChanges || !this._cellPadding) {\r\n this._cellPadding = `${50 * this.cellAspectRatio / numCols}%`;\r\n }\r\n\r\n if (columnChanges || !this._cellWidth) {\r\n this._cellWidth = `${100 / numCols}%`;\r\n }\r\n }\r\n\r\n ngOnDestroy() {\r\n const element = this._elementRef.nativeElement;\r\n element.removeEventListener('mouseenter', this._enterHandler, true);\r\n element.removeEventListener('focus', this._enterHandler, true);\r\n element.removeEventListener('mouseleave', this._leaveHandler, true);\r\n element.removeEventListener('blur', this._leaveHandler, true);\r\n }\r\n\r\n /** Returns whether a cell is active. */\r\n _isActiveCell(rowIndex: number, colIndex: number): boolean {\r\n let cellNumber = rowIndex * this.numCols + colIndex;\r\n\r\n // Account for the fact that the first row may not have as many cells.\r\n if (rowIndex) {\r\n cellNumber -= this._firstRowOffset;\r\n }\r\n\r\n return cellNumber == this.activeCell;\r\n }\r\n\r\n /** Focuses the active cell after the microtask queue is empty. */\r\n _focusActiveCell(movePreview = true) {\r\n this._ngZone.runOutsideAngular(() => {\r\n this._ngZone.onStable.asObservable().pipe(take(1)).subscribe(() => {\r\n const activeCell: HTMLElement | null =\r\n this._elementRef.nativeElement.querySelector('.ngx-mat-calendar-body-active');\r\n\r\n if (activeCell) {\r\n if (!movePreview) {\r\n this._skipNextFocus = true;\r\n }\r\n\r\n activeCell.focus();\r\n }\r\n });\r\n });\r\n }\r\n\r\n /** Gets whether a value is the start of the main range. */\r\n _isRangeStart(value: number) {\r\n return isStart(value, this.startValue, this.endValue);\r\n }\r\n\r\n /** Gets whether a value is the end of the main range. */\r\n _isRangeEnd(value: number) {\r\n return isEnd(value, this.startValue, this.endValue);\r\n }\r\n\r\n /** Gets whether a value is within the currently-selected range. */\r\n _isInRange(value: number): boolean {\r\n return isInRange(value, this.startValue, this.endValue, this.isRange);\r\n }\r\n\r\n /** Gets whether a value is the start of the comparison range. */\r\n _isComparisonStart(value: number) {\r\n return isStart(value, this.comparisonStart, this.comparisonEnd);\r\n }\r\n\r\n /** Whether the cell is a start bridge cell between the main and comparison ranges. */\r\n _isComparisonBridgeStart(value: number, rowIndex: number, colIndex: number) {\r\n if (!this._isComparisonStart(value) || this._isRangeStart(value) || !this._isInRange(value)) {\r\n return false;\r\n }\r\n\r\n let previousCell: NgxMatCalendarCell | undefined = this.rows[rowIndex][colIndex - 1];\r\n\r\n if (!previousCell) {\r\n const previousRow = this.rows[rowIndex - 1];\r\n previousCell = previousRow && previousRow[previousRow.length - 1];\r\n }\r\n\r\n return previousCell && !this._isRangeEnd(previousCell.compareValue);\r\n }\r\n\r\n /** Whether the cell is an end bridge cell between the main and comparison ranges. */\r\n _isComparisonBridgeEnd(value: number, rowIndex: number, colIndex: number) {\r\n if (!this._isComparisonEnd(value) || this._isRangeEnd(value) || !this._isInRange(value)) {\r\n return false;\r\n }\r\n\r\n let nextCell: NgxMatCalendarCell | undefined = this.rows[rowIndex][colIndex + 1];\r\n\r\n if (!nextCell) {\r\n const nextRow = this.rows[rowIndex + 1];\r\n nextCell = nextRow && nextRow[0];\r\n }\r\n\r\n return nextCell && !this._isRangeStart(nextCell.compareValue);\r\n }\r\n\r\n /** Gets whether a value is the end of the comparison range. */\r\n _isComparisonEnd(value: number) {\r\n return isEnd(value, this.comparisonStart, this.comparisonEnd);\r\n }\r\n\r\n /** Gets whether a value is within the current comparison range. */\r\n _isInComparisonRange(value: number) {\r\n return isInRange(value, this.comparisonStart, this.comparisonEnd, this.isRange);\r\n }\r\n\r\n /** Gets whether a value is the start of the preview range. */\r\n _isPreviewStart(value: number) {\r\n return isStart(value, this.previewStart, this.previewEnd);\r\n }\r\n\r\n /** Gets whether a value is the end of the preview range. */\r\n _isPreviewEnd(value: number) {\r\n return isEnd(value, this.previewStart, this.previewEnd);\r\n }\r\n\r\n /** Gets whether a value is inside the preview range. */\r\n _isInPreview(value: number) {\r\n return isInRange(value, this.previewStart, this.previewEnd, this.isRange);\r\n }\r\n\r\n /**\r\n * Event handler for when the user enters an element\r\n * inside the calendar body (e.g. by hovering in or focus).\r\n */\r\n private _enterHandler = (event: Event) => {\r\n if (this._skipNextFocus && event.type === 'focus') {\r\n this._skipNextFocus = false;\r\n return;\r\n }\r\n\r\n // We only need to hit the zone when we're selecting a range.\r\n if (event.target && this.isRange) {\r\n const cell = this._getCellFromElement(event.target as HTMLElement);\r\n\r\n if (cell) {\r\n this._ngZone.run(() => this.previewChange.emit({value: cell.enabled ? cell : null, event}));\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Event handler for when the user's pointer leaves an element\r\n * inside the calendar body (e.g. by hovering out or blurring).\r\n */\r\n private _leaveHandler = (event: Event) => {\r\n // We only need to hit the zone when we're selecting a range.\r\n if (this.previewEnd !== null && this.isRange) {\r\n // Only reset the preview end value when leaving cells. This looks better, because\r\n // we have a gap between the cells and the rows and we don't want to remove the\r\n // range just for it to show up again when the user moves a few pixels to the side.\r\n if (event.target && isTableCell(event.target as HTMLElement)) {\r\n this._ngZone.run(() => this.previewChange.emit({value: null, event}));\r\n }\r\n }\r\n }\r\n\r\n /** Finds the NgxMatCalendarCell that corresponds to a DOM node. */\r\n private _getCellFromElement(element: HTMLElement): NgxMatCalendarCell | null {\r\n let cell: HTMLElement | undefined;\r\n\r\n if (isTableCell(element)) {\r\n cell = element;\r\n } else if (isTableCell(element.parentNode!)) {\r\n cell = element.parentNode as HTMLElement;\r\n }\r\n\r\n if (cell) {\r\n const row = cell.getAttribute('data-ngx-mat-row');\r\n const col = cell.getAttribute('data-ngx-mat-col');\r\n\r\n if (row && col) {\r\n return this.rows[parseInt(row)][parseInt(col)];\r\n }\r\n }\r\n\r\n return null;\r\n }\r\n\r\n}\r\n\r\n/** Checks whether a node is a table cell element. */\r\nfunction isTableCell(node: Node): node is HTMLTableCellElement {\r\n return node.nodeName === 'TD';\r\n}\r\n\r\n/** Checks whether a value is the start of a range. */\r\nfunction isStart(value: number, start: number | null, end: number | null): boolean {\r\n return end !== null && start !== end && value < end && value === start;\r\n}\r\n\r\n/** Checks whether a value is the end of a range. */\r\nfunction isEnd(value: number, start: number | null, end: number | null): boolean {\r\n return start !== null && start !== end && value >= start && value === end;\r\n}\r\n\r\n/** Checks whether a value is inside of a range. */\r\nfunction isInRange(value: number,\r\n start: number | null,\r\n end: number | null,\r\n rangeEnabled: boolean): boolean {\r\n return rangeEnabled && start !== null && end !== null && start !== end &&\r\n value >= start && value <= end;\r\n}\r\n","<!--\r\n If there's not enough space in the first row, create a separate label row. We mark this row as\r\n aria-hidden because we don't want it to be read out as one of the weeks in the month.\r\n-->\r\n<tr *ngIf=\"_firstRowOffset < labelMinRequiredCells\" aria-hidden=\"true\">\r\n <td class=\"mat-calendar-body-label\"\r\n [attr.colspan]=\"numCols\"\r\n [style.paddingTop]=\"_cellPadding\"\r\n [style.paddingBottom]=\"_cellPadding\">\r\n {{label}}\r\n </td>\r\n</tr>\r\n\r\n<!-- Create the first row separately so we can include a special spacer cell. -->\r\n<tr *ngFor=\"let row of rows; let rowIndex = index\" role=\"row\">\r\n <!--\r\n We mark this cell as aria-hidden so it doesn't get read out as one of the days in the week.\r\n The aspect ratio of the table cells is maintained by setting the top and bottom padding as a\r\n percentage of the width (a variant of the trick described here:\r\n https://www.w3schools.com/howto/howto_css_aspect_ratio.asp).\r\n -->\r\n <td *ngIf=\"rowIndex === 0 && _firstRowOffset\"\r\n aria-hidden=\"true\"\r\n class=\"mat-calendar-body-label\"\r\n [attr.colspan]=\"_firstRowOffset\"\r\n [style.paddingTop]=\"_cellPadding\"\r\n [style.paddingBottom]=\"_cellPadding\">\r\n {{_firstRowOffset >= labelMinRequiredCells ? label : ''}}\r\n </td>\r\n <td *ngFor=\"let item of row; let colIndex = index\"\r\n role=\"gridcell\"\r\n class=\"mat-calendar-body-cell\"\r\n [ngClass]=\"item.cssClasses\"\r\n [tabindex]=\"_isActiveCell(rowIndex, colIndex) ? 0 : -1\"\r\n [attr.data-mat-row]=\"rowIndex\"\r\n [attr.data-mat-col]=\"colIndex\"\r\n [class.mat-calendar-body-disabled]=\"!item.enabled\"\r\n [class.mat-calendar-body-active]=\"_isActiveCell(rowIndex, colIndex)\"\r\n [class.mat-calendar-body-range-start]=\"_isRangeStart(item.compareValue)\"\r\n [class.mat-calendar-body-range-end]=\"_isRangeEnd(item.compareValue)\"\r\n [class.mat-calendar-body-in-range]=\"_isInRange(item.compareValue)\"\r\n [class.mat-calendar-body-comparison-bridge-start]=\"_isComparisonBridgeStart(item.compareValue, rowIndex, colIndex)\"\r\n [class.mat-calendar-body-comparison-bridge-end]=\"_isComparisonBridgeEnd(item.compareValue, rowIndex, colIndex)\"\r\n [class.mat-calendar-body-comparison-start]=\"_isComparisonStart(item.compareValue)\"\r\n [class.mat-calendar-body-comparison-end]=\"_isComparisonEnd(item.compareValue)\"\r\n [class.mat-calendar-body-in-comparison-range]=\"_isInComparisonRange(item.compareValue)\"\r\n [class.mat-calendar-body-preview-start]=\"_isPreviewStart(item.compareValue)\"\r\n [class.mat-calendar-body-preview-end]=\"_isPreviewEnd(item.compareValue)\"\r\n [class.mat-calendar-body-in-preview]=\"_isInPreview(item.compareValue)\"\r\n [attr.aria-label]=\"item.ariaLabel\"\r\n [attr.aria-disabled]=\"!item.enabled || null\"\r\n [attr.aria-selected]=\"_isSelected(item)\"\r\n (click)=\"_cellClicked(item, $event)\"\r\n [style.width]=\"_cellWidth\"\r\n [style.paddingTop]=\"_cellPadding\"\r\n [style.paddingBottom]=\"_cellPadding\">\r\n <div class=\"mat-calendar-body-cell-content mat-focus-indicator\"\r\n [class.mat-calendar-body-selected]=\"_isSelected(item)\"\r\n [class.mat-calendar-body-today]=\"todayValue === item.compareValue\">\r\n {{item.displayValue}}\r\n </div>\r\n <div class=\"mat-calendar-body-cell-preview\"></div>\r\n </td>\r\n</tr>\r\n","import { DateAdapter } from '@angular/material/core';\r\n\r\nexport abstract class NgxMatDateAdapter<D> extends DateAdapter<D> {\r\n /**\r\n * Gets the hour component of the given date.\r\n * @param date The date to extract the month from.\r\n * @returns The hour component.\r\n */\r\n abstract getHour(date: D): number;\r\n\r\n /**\r\n* Gets the minute component of the given date.\r\n* @param date The date to extract the month from.\r\n* @returns The minute component.\r\n*/\r\n abstract getMinute(date: D): number;\r\n\r\n /**\r\n * Gets the second component of the given date.\r\n * @param date The date to extract the month from.\r\n * @returns The second component.\r\n */\r\n abstract getSecond(date: D): number;\r\n\r\n /**\r\n * Set the hour component of the given date.\r\n * @param date The date to extract the month from.\r\n * @param value The value to set.\r\n */\r\n abstract setHour(date: D, value: number): void;\r\n\r\n /**\r\n * Set the second component of the given date.\r\n * @param date The date to extract the month from.\r\n * @param value The value to set.\r\n */\r\n abstract setMinute(date: D, value: number): void;\r\n\r\n /**\r\n * Set the second component of the given date.\r\n * @param date The date to extract the month from.\r\n * @param value The value to set.\r\n */\r\n abstract setSecond(date: D, value: number): void;\r\n\r\n /**\r\n * Check if two date have same time\r\n * @param a Date 1\r\n * @param b Date 2\r\n */\r\n isSameTime(a: D, b: D): boolean {\r\n if (a == null || b == null) return true;\r\n return this.getHour(a) === this.getHour(b)\r\n && this.getMinute(a) === this.getMinute(b)\r\n && this.getSecond(a) === this.getSecond(b);\r\n }\r\n\r\n /**\r\n * Copy time from a date to a another date\r\n * @param toDate \r\n * @param fromDate \r\n */\r\n copyTime(toDate: D, fromDate: D) {\r\n this.setHour(toDate, this.getHour(fromDate));\r\n this.setMinute(toDate, this.getMinute(fromDate));\r\n this.setSecond(toDate, this.getSecond(fromDate));\r\n }\r\n\r\n /**\r\n * Compares two dates.\r\n * @param first The first date to compare.\r\n * @param second The second date to compare.\r\n * @returns 0 if the dates are equal, a number less than 0 if the first date is earlier,\r\n * a number greater than 0 if the first date is later.\r\n */\r\n compareDateWithTime(first: D, second: D, showSeconds?: boolean): number {\r\n let res = super.compareDate(first, second) ||\r\n this.getHour(first) - this.getHour(second) ||\r\n this.getMinute(first) - this.getMinute(second);\r\n if (showSeconds) {\r\n res = res || this.getSecond(first) - this.getSecond(second);\r\n }\r\n return res;\r\n }\r\n\r\n /**\r\n * Set time by using default values\r\n * @param defaultTime List default values [hour, minute, second]\r\n */\r\n setTimeByDefaultValues(date: D, defaultTime: number[]) {\r\n if (!Array.isArray(defaultTime)) {\r\n throw Error('@Input DefaultTime should be an array');\r\n }\r\n this.setHour(date, defaultTime[0] || 0);\r\n this.setMinute(date, defaultTime[1] || 0);\r\n this.setSecond(date, defaultTime[2] || 0);\r\n }\r\n\r\n}\r\n","/**\r\n * @license\r\n * Copyright Google LLC All Rights Reserved.\r\n *\r\n * Use of this source code is governed by an MIT-style license that can be\r\n * found in the LICENSE file at https://angular.io/license\r\n */\r\n\r\nimport {Injectable, InjectionToken} from '@angular/core';\r\nimport { DateRange } from '@angular/material/datepicker';\r\nimport { NgxMatDateAdapter } from './core/date-adapter';\r\n\r\n/** Injection token used to customize the date range selection behavior. */\r\nexport const NGX_MAT_DATE_RANGE_SELECTION_STRATEGY =\r\n new InjectionToken<NgxMatDateRangeSelectionStrategy<any>>('NGX_MAT_DATE_RANGE_SELECTION_STRATEGY');\r\n\r\n/** Object that can be provided in order to customize the date range selection behavior. */\r\nexport interface NgxMatDateRangeSelectionStrategy<D> {\r\n /**\r\n * Called when the user has finished selecting a value.\r\n * @param date Date that was selected. Will be null if the user cleared the selection.\r\n * @param currentRange Range that is currently show in the calendar.\r\n * @param event DOM event that triggered the selection. Currently only corresponds to a `click`\r\n * event, but it may get expanded in the future.\r\n */\r\n selectionFinished(date: D | null, currentRange: DateRange<D>, event: Event): DateRange<D>;\r\n\r\n /**\r\n * Called when the user has activated a new date (e.g. by hovering over\r\n * it or moving focus) and the calendar tries to display a date range.\r\n *\r\n * @param activeDate Date that the user has activated. Will be null if the user moved\r\n * focus to an element that's no a calendar cell.\r\n * @param currentRange Range that is currently shown in the calendar.\r\n * @param event DOM event that caused the preview to be changed. Will be either a\r\n * `mouseenter`/`mouseleave` or `focus`/`blur` depending on how the user is navigating.\r\n */\r\n createPreview(activeDate: D | null, currentRange: DateRange<D>, event: Event): DateRange<D>;\r\n}\r\n\r\n/** Provides the default date range selection behavior. */\r\n@Injectable()\r\nexport class DefaultNgxMatCalendarRangeStrategy<D> implements NgxMatDateRangeSelectionStrategy<D> {\r\n constructor(private _dateAdapter: NgxMatDateAdapter<D>) {}\r\n\r\n selectionFinished(date: D, currentRange: DateRange<D>) {\r\n let {start, end} = currentRange;\r\n\r\n if (start == null) {\r\n start = date;\r\n } else if (end == null && date && this._dateAdapter.compareDate(date, start) >= 0) {\r\n end = date;\r\n } else {\r\n start = date;\r\n end = null;\r\n }\r\n\r\n return new DateRange<D>(start, end);\r\n }\r\n\r\n createPreview(activeDate: D | null, currentRange: DateRange<D>) {\r\n let start: D | null = null;\r\n let end: D | null = null;\r\n\r\n if (currentRange.start && !currentRange.end && activeDate) {\r\n start = currentRange.start;\r\n end = activeDate;\r\n }\r\n\r\n return new DateRange<D>(start, end);\r\n }\r\n}\r\n","export const LIMIT_TIMES = {\r\n minHour: 0,\r\n maxHour: 24,\r\n minMinute: 0,\r\n maxMinute: 60,\r\n minSecond: 0,\r\n maxSecond: 60,\r\n meridian: 12\r\n}\r\n\r\nexport const MERIDIANS = {\r\n AM: 'AM',\r\n PM: 'PM'\r\n}\r\n\r\nexport const DEFAULT_STEP = 1;\r\nexport const NUMERIC_REGEX = /[^0-9]/g;\r\n\r\nexport const PATTERN_INPUT_HOUR = /^(2[0-3]|[0-1][0-9]|[0-9])$/;\r\nexport const PATTERN_INPUT_MINUTE = /^([0-5][0-9]|[0-9])$/;\r\nexport const PATTERN_INPUT_SECOND = /^([0-5][0-9]|[0-9])$/;\r\n\r\nexport function formatTwoDigitTimeValue(val: number) {\r\n const txt = val.toString();\r\n return txt.length > 1 ? txt : `0${txt}`;\r\n}\r\n\r\nexport function createMissingDateImplError(provider: string) {\r\n return Error(\r\n `NgxMatDatepicker: No provider found for ${provider}. You must import one of the following ` +\r\n `modules at your application root: NgxMatNativeDateModule, NgxMatMomentModule, or provide a ` +\r\n `custom implementation.`);\r\n}\r\n\r\n/** Formats a range of years. */\r\nexport function formatYearRange(start: string, end: string): string {\r\n return `${start} \\u2013 ${end}`;\r\n}\r\n\r\n","/**\r\n * @license\r\n * Copyright Google LLC All Rights Reserved.\r\n *\r\n * Use of this source code is governed by an MIT-style license that can be\r\n * found in the LICENSE file at https://angular.io/license\r\n */\r\n\r\nimport {\r\n DOWN_ARROW,\r\n END,\r\n ENTER,\r\n HOME,\r\n LEFT_ARROW,\r\n PAGE_DOWN,\r\n PAGE_UP,\r\n RIGHT_ARROW,\r\n UP_ARROW,\r\n SPACE,\r\n ESCAPE,\r\n} from '@angular/cdk/keycodes';\r\nimport {\r\n AfterContentInit,\r\n ChangeDetectionStrategy,\r\n ChangeDetectorRef,\r\n Component,\r\n EventEmitter,\r\n Inject,\r\n Input,\r\n Optional,\r\n Output,\r\n ViewEncapsulation,\r\n ViewChild,\r\n OnDestroy,\r\n} from '@angular/core';\r\nimport {Directionality} from '@angular/cdk/bidi';\r\n\r\nimport {Subscription} from 'rxjs';\r\nimport {startWith} from 'rxjs/operators';\r\nimport { DateRange } from '@angular/material/datepicker';\r\nimport { NgxMatCalendarCellCssClasses, NgxMatCalendarUserEvent, NgxMatCalendarBody, NgxMatCalendarCell } from './calendar-body';\r\nimport { NGX_MAT_DATE_FORMATS, NgxMatDateFormats } from './core/date-formats';\r\nimport { NgxMatDateAdapter } from './core/date-adapter';\r\nimport { NGX_MAT_DATE_RANGE_SELECTION_STRATEGY, NgxMatDateRangeSelectionStrategy } from './date-range-selection-strategy';\r\nimport { createMissingDateImplError } from './utils/date-utils';\r\n\r\n\r\n\r\nconst DAYS_PER_WEEK = 7;\r\n\r\n\r\n/**\r\n * An internal component used to display a single month in the datepicker.\r\n * @docs-private\r\n */\r\n@Component({\r\n selector: 'ngx-mat-month-view',\r\n templateUrl: 'month-view.html',\r\n exportAs: 'ngxMatMonthView',\r\n encapsulation: ViewEncapsulation.None,\r\n changeDetection: ChangeDetectionStrategy.OnPush\r\n})\r\nexport class NgxMatMonthView<D> implements AfterContentInit, OnDestroy {\r\n private _rerenderSubscription = Subscription.EMPTY;\r\n\r\n /**\r\n * The date to display in this month view (everything other than the month and year is ignored).\r\n */\r\n @Input()\r\n get activeDate(): D { return this._activeDate; }\r\n set activeDate(value: D) {\r\n const oldActiveDate = this._activeDate;\r\n const validDate =\r\n this._getValidDateOrNull(this._dateAdapter.deserialize(value)) || this._dateAdapter.today();\r\n this._activeDate = this._dateAdapter.clampDate(validDate, this.minDate, this.maxDate);\r\n if (!this._hasSameMonthAndYear(oldActiveDate, this._activeDate)) {\r\n this._init();\r\n }\r\n }\r\n private _activeDate: D;\r\n\r\n /** The currently selected date. */\r\n @Input()\r\n get selected(): DateRange<D> | D | null { return this._selected; }\r\n set selected(value: DateRange<D> | D | null) {\r\n if (value instanceof DateRange) {\r\n this._selected = value;\r\n } else {\r\n this._selected = this._getValidDateOrNull(this._dateAdapter.deserialize(value));\r\n }\r\n\r\n this._setRanges(this._selected);\r\n }\r\n private _selected: DateRange<D> | D | null;\r\n\r\n /** The minimum selectable date. */\r\n @Input()\r\n get minDate(): D | null { return this._minDate; }\r\n set minDate(value: D | null) {\r\n this._minDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));\r\n }\r\n private _minDate: D | null;\r\n\r\n /** The maximum selectable date. */\r\n @Input()\r\n get maxDate(): D | null { return this._maxDate; }\r\n set maxDate(value: D | null) {\r\n this._maxDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));\r\n }\r\n private _maxDate: D | null;\r\n\r\n /** Function used to filter which dates are selectable. */\r\n @Input() dateFilter: (date: D) => boolean;\r\n\r\n /** Function that can be used to add custom CSS classes to dates. */\r\n @Input() dateClass: (date: D) => NgxMatCalendarCellCssClasses;\r\n\r\n /** Start of the comparison range. */\r\n @Input() comparisonStart: D | null;\r\n\r\n /** End of the comparison range. */\r\n @Input() comparisonEnd: D | null;\r\n\r\n /** Emits when a new date is selected. */\r\n @Output() readonly selectedChange: EventEmitter<D | null> = new EventEmitter<D | null>();\r\n\r\n /** Emits when any date is selected. */\r\n @Output() readonly _userSelection: EventEmitter<NgxMatCalendarUserEvent<D | null>> =\r\n new EventEmitter<NgxMatCalendarUserEvent<D | null>>();\r\n\r\n /** Emits when any date is activated. */\r\n @Output() readonly activeDateChange: EventEmitter<D> = new EventEmitter<D>();\r\n\r\n /** The body of calendar table */\r\n @ViewChild(NgxMatCalendarBody) _matCalendarBody: NgxMatCalendarBody;\r\n\r\n /** The label for this month (e.g. \"January 2017\"). */\r\n _monthLabel: string;\r\n\r\n /** Grid of calendar cells representing the dates of the month. */\r\n _weeks: NgxMatCalendarCell[][];\r\n\r\n /** The number of blank cells in the first row before the 1st of the month. */\r\n _firstWeekOffset: number;\r\n\r\n /** Start value of the currently-shown date range. */\r\n _rangeStart: number | null;\r\n\r\n /** End value of the currently-shown date range. */\r\n _rangeEnd: number | null;\r\n\r\n /** Start value of the currently-shown comparison date range. */\r\n _comparisonRangeStart: number | null;\r\n\r\n /** End value of the currently-shown comparison date range. */\r\n _comparisonRangeEnd: number | null;\r\n\r\n /** Start of the preview range. */\r\n _previewStart: number | null;\r\n\r\n /** End of the preview range. */\r\n _previewEnd: number | null;\r\n\r\n /** Whether the user is currently selecting a range of dates. */\r\n _isRange: boolean;\r\n\r\n /** The date of the month that today falls on. Null if today is in another month. */\r\n _todayDate: number | null;\r\n\r\n /** The names of the weekdays. */\r\n _weekdays: {long: string, narrow: string}[];\r\n\r\n constructor(private _changeDetectorRef: ChangeDetectorRef,\r\n @Optional() @Inject(NGX_MAT_DATE_FORMATS) private _dateFormats: NgxMatDateFormats,\r\n @Optional() public _dateAdapter: NgxMatDateAdapter<D>,\r\n @Optional() private _dir?: Directionality,\r\n @Inject(NGX_MAT_DATE_RANGE_SELECTION_STRATEGY) @Optional()\r\n private _rangeStrategy?: NgxMatDateRangeSelectionStrategy<D>) {\r\n if (!this._dateAdapter) {\r\n throw createMissingDateImplError('NgxMatDateAdapter');\r\n }\r\n if (!this._dateFormats) {\r\n throw createMissingDateImplError('NGX_MAT_DATE_FORMATS');\r\n }\r\n\r\n this._activeDate = this._dateAdapter.today();\r\n }\r\n\r\n ngAfterContentInit() {\r\n this._rerenderSubscription = this._dateAdapter.localeChanges\r\n .pipe(startWith(null))\r\n .subscribe(() => this._init());\r\n }\r\n\r\n ngOnDestroy() {\r\n this._rerenderSubscription.unsubscribe();\r\n }\r\n\r\n /** Handles when a new date is selected. */\r\n _dateSelected(event: NgxMatCalendarUserEvent<number>) {\r\n const date = event.value;\r\n const selectedYear = this._dateAdapter.getYear(this.activeDate);\r\n const selectedMonth = this._dateAdapter.getMonth(this.activeDate);\r\n const selectedDate = this._dateAdapter.createDate(selectedYear, selectedMonth, date);\r\n let rangeStartDate: number | null;\r\n let rangeEndDate: number | null;\r\n\r\n if (this._selected instanceof DateRange) {\r\n rangeStartDate = this._getDateInCurrentMonth(this._selected.start);\r\n rangeEndDate = this._getDateInCurrentMonth(this._selected.end);\r\n } else {\r\n rangeStartDate = rangeEndDate = this._getDateInCurrentMonth(this._selected);\r\n }\r\n\r\n if (rangeStartDate !== date || rangeEndDate !== date) {\r\n this.selectedChange.emit(selectedDate);\r\n }\r\n\r\n this._userSelection.emit({value: selectedDate, event: event.event});\r\n }\r\n\r\n /** Handles keydown events on the calendar body when calendar is in month view. */\r\n _handleCalendarBodyKeydown(event: KeyboardEvent): void {\r\n // TODO(mmalerba): We currently allow keyboard navigation to disabled dates, but just prevent\r\n // disabled ones from being selected. This may not be ideal, we should look into whether\r\n // navigation should skip over disabled dates, and if so, how to implement that efficiently.\r\n\r\n const oldActiveDate = this._activeDate;\r\n const isRtl = this._isRtl();\r\n\r\n switch (event.keyCode) {\r\n case LEFT_ARROW:\r\n this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, isRtl ? 1 : -1);\r\n break;\r\n case RIGHT_ARROW:\r\n this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, isRtl ? -1 : 1);\r\n break;\r\n case UP_ARROW:\r\n this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, -7);\r\n break;\r\n case DOWN_ARROW:\r\n this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, 7);\r\n break;\r\n case HOME:\r\n this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate,\r\n 1 - this._dateAdapter.getDate(this._activeDate));\r\n break;\r\n case END:\r\n this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate,\r\n (this._dateAdapter.getNumDaysInMonth(this._activeDate) -\r\n this._dateAdapter.getDate(this._activeDate)));\r\n break;\r\n case PAGE_UP:\r\n this.activeDate = event.altKey ?\r\n this._dateAdapter.addCalendarYears(this._activeDate, -1) :\r\n this._dateAdapter.addCalendarMonths(this._activeDate, -1);\r\n break;\r\n case PAGE_DOWN:\r\n this.activeDate = event.altKey ?\r\n this._dateAdapter.addCalendarYears(this._activeDate, 1) :\r\n this._dateAdapter.addCalendarMonths(this._activeDate, 1);\r\n break;\r\n case ENTER:\r\n case SPACE:\r\n if (!this.dateFilter || this.dateFilter(this._activeDate)) {\r\n this._dateSelected({value: this._dateAdapter.getDate(this._activeDate), event});\r\n // Prevent unexpected default actions such as form submission.\r\n event.preventDefault();\r\n }\r\n return;\r\n case ESCAPE:\r\n // Abort the current range selection if the user presses escape mid-selection.\r\n if (this._previewEnd != null) {\r\n this._previewStart = this._previewEnd = null;\r\n this.selectedChange.emit(null);\r\n this._userSelection.emit({value: null, event});\r\n event.preventDefault();\r\n event.stopPropagation(); // Prevents the overlay from closing.\r\n }\r\n return;\r\n default:\r\n // Don't prevent default or focus active cell on keys that we don't explicitly handle.\r\n return;\r\n }\r\n\r\n if (this._dateAdapter.compareDate(oldActiveDate, this.activeDate)) {\r\n this.activeDateChange.emit(this.activeDate);\r\n }\r\n\r\n this._focusActiveCell();\r\n // Prevent unexpected default actions such as form submission.\r\n event.preventDefault();\r\n }\r\n\r\n /** Initializes this month view. */\r\n _init() {\r\n this._setRanges(this.selected);\r\n this._todayDate = this._getCellCompareValue(this._dateAdapter.today());\r\n this._monthLabel =\r\n this._dateAdapter.getMonthNames('short')[this._dateAdapter.getMonth(this.activeDate)]\r\n .toLocaleUpperCase();\r\n\r\n let firstOfMonth = this._dateAdapter.createDate(this._dateAdapter.getYear(this.activeDate),\r\n this._dateAdapter.getMonth(this.activeDate), 1);\r\n this._firstWeekOffset =\r\n (DAYS_PER_WEEK + this._dateAdapter.getDayOfWeek(firstOfMonth) -\r\n this._dateAdapter.getFirstDayOfWeek()) % DAYS_PER_WEEK;\r\n\r\n this._initWeekdays();\r\n this._createWeekCells();\r\n this._changeDetectorRef.markForCheck();\r\n }\r\n\r\n /** Focuses the active cell after the microtask queue is empty. */\r\n _focusActiveCell(movePreview?: boolean) {\r\n this._matCalendarBody._focusActiveCell(movePreview);\r\n }\r\n\r\n /** Called when the user has activated a new cell and the preview needs to be updated. */\r\n _previewChanged({event, value: cell}: NgxMatCalendarUserEvent<NgxMatCalendarCell<D> | null>) {\r\n if (this._rangeStrategy) {\r\n // We can assume that this will be a range, because preview\r\n // events aren't fired for single date selections.\r\n const value = cell ? cell.rawValue! : null;\r\n const previewRange =\r\n this._rangeStrategy.createPreview(value, this.selected as DateRange<D>, event);\r\n this._previewStart = this._getCellCompareValue(previewRange.start);\r\n this._previewEnd = this._getCellCompareValue(previewRange.end);\r\n\r\n // Note that here we need to use `detectChanges`, rather than `markForCheck`, because\r\n // the way `_focusActiveCell` is set up at the moment makes it fire at the wrong time\r\n // when navigating one month back using the keyboard which will cause this handler\r\n // to throw a \"changed after checked\" error when updating the preview state.\r\n this._changeDetectorRef.detectChanges();\r\n }\r\n }\r\n\r\n /** Initializes the weekdays. */\r\n private _initWeekdays() {\r\n const firstDayOfWeek = this._dateAdapter.getFirstDayOfWeek();\r\n const narrowWeekdays = this._dateAdapter.getDayOfWeekNames('narrow');\r\n const longWeekdays = this._dateAdapter.getDayOfWeekNames('long');\r\n\r\n // Rotate the labels for days of the week based on the configured first day of the week.\r\n let weekdays = longWeekdays.map((long, i) => {\r\n return {long, narrow: narrowWeekdays[i]};\r\n });\r\n this._weekdays = weekdays.slice(firstDayOfWeek).concat(weekdays.slice(0, firstDayOfWeek));\r\n }\r\n\r\n /** Creates MatCalendarCells for the dates in this month. */\r\n private _createWeekCells() {\r\n const daysInMonth = this._dateAdapter.getNumDaysInMonth(this.activeDate);\r\n const dateNames = this._dateAdapter.getDateNames();\r\n this._weeks = [[]];\r\n for (let i = 0, cell = this._firstWeekOffset; i < daysInMonth; i++, cell++) {\r\n if (cell == DAYS_PER_WEEK) {\r\n this._weeks.push([]);\r\n cell = 0;\r\n }\r\n const date = this._dateAdapter.createDate(\r\n this._dateAdapter.getYear(this.activeDate),\r\n this._dateAdapter.getMonth(this.activeDate), i + 1);\r\n const enabled = this._shouldEnableDate(date);\r\n const ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.dateA11yLabel);\r\n const cellClasses = this.dateClass ? this.dateClass(date) : undefined;\r\n\r\n this._weeks[this._weeks.length - 1].push(new NgxMatCalendarCell<D>(i + 1, dateNames[i],\r\n ariaLabel, enabled, cellClasses, this._getCellCompareValue(date)!, date));\r\n }\r\n }\r\n\r\n /** Date filter for the month */\r\n private _shouldEnableDate(date: D): boolean {\r\n return !!date &&\r\n (!this.minDate || this._dateAdapter.compareDate(date, this.minDate) >= 0) &&\r\n (!this.maxDate || this._dateAdapter.compareDate(date, this.maxDate) <= 0) &&\r\n (!this.dateFilter || this.dateFilter(date));\r\n }\r\n\r\n /**\r\n * Gets the date in this month that the given Date falls on.\r\n * Returns null if the given Date is in another month.\r\n */\r\n private _getDateInCurrentMonth(date: D | null): number | null {\r\n return date && this._hasSameMonthAndYear(date, this.activeDate) ?\r\n this._dateAdapter.getDate(date) : null;\r\n }\r\n\r\n /** Checks whether the 2 dates are non-null and fall within the same month of the same year. */\r\n private _hasSameMonthAndYear(d1: D | null, d2: D | null): boolean {\r\n return !!(d1 && d2 && this._dateAdapter.getMonth(d1) == this._dateAdapter.getMonth(d2) &&\r\n this._dateAdapter.getYear(d1) == this._dateAdapter.getYear(d2));\r\n }\r\n\r\n /** Gets the value that will be used to one cell to another. */\r\n private _getCellCompareValue(date: D | null): number | null {\r\n if (date) {\r\n // We use the time since the Unix epoch to compare dates in this view, rather than the\r\n // cell values, because we need to support ranges that span across multiple months/years.\r\n const year = this._dateAdapter.getYear(date);\r\n const month = this._dateAdapter.getMonth(date);\r\n const day = this._dateAdapter.getDate(date);\r\n return new Date(year, month, day).getTime();\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * @param obj The object to check.\r\n * @returns The given object if it is both a date instance and valid, otherwise null.\r\n */\r\n private _getValidDateOrNull(obj: any): D | null {\r\n return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null;\r\n }\r\n\r\n /** Determines whether the user has the RTL layout direction. */\r\n private _isRtl() {\r\n return this._dir && this._dir.value === 'rtl';\r\n }\r\n\r\n /** Sets the current range based on a model value. */\r\n private _setRanges(selectedValue: DateRange<D> | D | null) {\r\n if (selectedValue instanceof DateRange) {\r\n this._rangeStart = this._getCellCompareValue(selectedValue.start);\r\n this._rangeEnd = this._getCellCompareValue(selectedValue.end);\r\n this._isRange = true;\r\n } else {\r\n this._rangeStart = this._rangeEnd = this._getCellCompareValue(selectedValue);\r\n this._isRange = false;\r\n }\r\n\r\n this._comparisonRangeStart = this._getCellCompareValue(this.comparisonStart);\r\n this._comparisonRangeEnd = this._getCellCompareValue(this.comparisonEnd);\r\n }\r\n}\r\n","<table class=\"mat-calendar-table\" role=\"presentation\">\r\n <thead class=\"mat-calendar-table-header\">\r\n <tr>\r\n <th scope=\"col\" *ngFor=\"let day of _weekdays\" [attr.aria-label]=\"day.long\">{{day.narrow}}</th>\r\n </tr>\r\n <tr><th class=\"mat-calendar-table-header-divider\" colspan=\"7\" aria-hidden=\"true\"></th></tr>\r\n </thead>\r\n <tbody ngx-mat-calendar-body\r\n [label]=\"_monthLabel\"\r\n [rows]=\"_weeks\"\r\n [todayValue]=\"_todayDate!\"\r\n [startValue]=\"_rangeStart!\"\r\n [endValue]=\"_rangeEnd!\"\r\n [comparisonStart]=\"_comparisonRangeStart\"\r\n [comparisonEnd]=\"_comparisonRangeEnd\"\r\n [previewStart]=\"_previewStart\"\r\n [previewEnd]=\"_previewEnd\"\r\n [isRange]=\"_isRange\"\r\n [labelMinRequiredCells]=\"3\"\r\n [activeCell]=\"_dateAdapter.getDate(activeDate) - 1\"\r\n (selectedValueChange)=\"_dateSelected($event)\"\r\n (previewChange)=\"_previewChanged($event)\"\r\n (keydown)=\"_handleCalendarBodyKeydown($event)\">\r\n </tbody>\r\n</table>\r\n","/**\r\n * @license\r\n * Copyright Google LLC All Rights Reserved.\r\n *\r\n * Use of this source code is governed by an MIT-style license that can be\r\n * found in the LICENSE file at https://angular.io/license\r\n */\r\n\r\nimport {\r\n DOWN_ARROW,\r\n END,\r\n ENTER,\r\n HOME,\r\n LEFT_ARROW,\r\n PAGE_DOWN,\r\n PAGE_UP,\r\n RIGHT_ARROW,\r\n UP_ARROW,\r\n SPACE,\r\n} from '@angular/cdk/keycodes';\r\nimport {\r\n AfterContentInit,\r\n ChangeDetectionStrategy,\r\n ChangeDetectorRef,\r\n Component,\r\n EventEmitter,\r\n Input,\r\n Optional,\r\n Output,\r\n ViewChild,\r\n ViewEncapsulation,\r\n OnDestroy,\r\n} from '@angular/core';\r\nimport {Directionality} from '@angular/cdk/bidi';\r\nimport {Subscription} from 'rxjs';\r\nimport {startWith} from 'rxjs/operators';\r\nimport { DateRange } from '@angular/material/datepicker';\r\nimport { NgxMatCalendarCell, NgxMatCalendarBody, NgxMatCalendarUserEvent } from './calendar-body';\r\nimport { NgxMatDateAdapter } from './core/date-adapter';\r\nimport { createMissingDateImplError } from './utils/date-utils';\r\n\r\nexport const yearsPerPage = 24;\r\n\r\nexport const yearsPerRow = 4;\r\n\r\n/**\r\n * An internal component used to display a year selector in the datepicker.\r\n * @docs-private\r\n */\r\n@Component({\r\n selector: 'ngx-mat-multi-year-view',\r\n templateUrl: 'multi-year-view.html',\r\n exportAs: 'ngxMatMultiYearView',\r\n encapsulation: ViewEncapsulation.None,\r\n changeDetection: ChangeDetectionStrategy.OnPush\r\n})\r\nexport class NgxMatMultiYearView<D> implements AfterContentInit, OnDestroy {\r\n private _rerenderSubscription = Subscription.EMPTY;\r\n\r\n /** The date to display in this multi-year view (everything other than the year is ignored). */\r\n @Input()\r\n get activeDate(): D { return this._activeDate; }\r\n set activeDate(value: D) {\r\n let oldActiveDate = this._activeDate;\r\n const validDate =\r\n this._getValidDateOrNull(this._dateAdapter.deserialize(value)) || this._dateAdapter.today();\r\n this._activeDate = this._dateAdapter.clampDate(validDate, this.minDate, this.maxDate);\r\n\r\n if (!isSameMultiYearView(\r\n this._dateAdapter, oldActiveDate, this._activeDate, this.minDate, this.maxDate)) {\r\n this._init();\r\n }\r\n }\r\n private _activeDate: D;\r\n\r\n /** The currently selected date. */\r\n @Input()\r\n get selected(): DateRange<D> | D | null { return this._selected; }\r\n set selected(value: DateRange<D> | D | null) {\r\n if (value instanceof DateRange) {\r\n this._selected = value;\r\n } else {\r\n this._selected = this._getValidDateOrNull(this._dateAdapter.deserialize(value));\r\n }\r\n\r\n this._setSelectedYear(value);\r\n }\r\n private _selected: DateRange<D> | D | null;\r\n\r\n\r\n /** The minimum selectable date. */\r\n @Input()\r\n get minDate(): D | null { return this._minDate; }\r\n set minDate(value: D | null) {\r\n this._minDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));\r\n }\r\n private _minDate: D | null;\r\n\r\n /** The maximum selectable date. */\r\n @Input()\r\n get maxDate(): D | null { return this._maxDate; }\r\n set maxDate(value: D | null) {\r\n this._maxDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));\r\n }\r\n private _maxDate: D | null;\r\n\r\n /** A function used to filter which dates are selectable. */\r\n @Input() dateFilter: (date: D) => boolean;\r\n\r\n /** Emits when a new year is selected. */\r\n @Output() readonly selectedChange: EventEmitter<D> = new EventEmitter<D>();\r\n\r\n /** Emits the selected year. This doesn't imply a change on the selected date */\r\n @Output() readonly yearSelected: EventEmitter<D> = new EventEmitter<D>();\r\n\r\n /** Emits when any date is activated. */\r\n @Output() readonly activeDateChange: EventEmitter<D> = new EventEmitter<D>();\r\n\r\n /** The body of calendar table */\r\n @ViewChild(NgxMatCalendarBody) _matCalendarBody: NgxMatCalendarBody;\r\n\r\n /** Grid of calendar cells representing the currently displayed years. */\r\n _years: NgxMatCalendarCell[][];\r\n\r\n /** The year that today falls on. */\r\n _todayYear: number;\r\n\r\n /** The year of the selected date. Null if the selected date is null. */\r\n _selecte