{"version":3,"file":"datepicker.mjs","sources":["../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/datepicker-errors.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/datepicker-intl.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/calendar-body.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/calendar-body.html","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/date-selection-model.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/date-range-selection-strategy.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/month-view.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/month-view.html","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/multi-year-view.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/multi-year-view.html","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/year-view.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/year-view.html","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/calendar.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/calendar-header.html","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/calendar.html","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/datepicker-base.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/datepicker-content.html","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/datepicker.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/datepicker-input-base.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/datepicker-input.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/datepicker-toggle.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/datepicker-toggle.html","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/date-range-input.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/date-range-input.html","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/aria-accessible-name.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/date-range-input-parts.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/date-range-picker.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/datepicker-actions.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/datepicker-module.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/material/datepicker/datepicker-animations.ts"],"sourcesContent":["/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\n/** @docs-private */\nexport function createMissingDateImplError(provider: string) {\n return Error(\n `MatDatepicker: No provider found for ${provider}. You must add one of the following ` +\n `to your app config: provideNativeDateAdapter, provideDateFnsAdapter, ` +\n `provideLuxonDateAdapter, provideMomentDateAdapter, or provide a custom implementation.`,\n );\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {Injectable} from '@angular/core';\nimport {Subject} from 'rxjs';\n\n/** Datepicker data that requires internationalization. */\n@Injectable({providedIn: 'root'})\nexport class MatDatepickerIntl {\n /**\n * Stream that emits whenever the labels here are changed. Use this to notify\n * components if the labels have changed after initialization.\n */\n readonly changes: Subject = new Subject();\n\n /** A label for the calendar popup (used by screen readers). */\n calendarLabel = 'Calendar';\n\n /** A label for the button used to open the calendar popup (used by screen readers). */\n openCalendarLabel = 'Open calendar';\n\n /** Label for the button used to close the calendar popup. */\n closeCalendarLabel = 'Close calendar';\n\n /** A label for the previous month button (used by screen readers). */\n prevMonthLabel = 'Previous month';\n\n /** A label for the next month button (used by screen readers). */\n nextMonthLabel = 'Next month';\n\n /** A label for the previous year button (used by screen readers). */\n prevYearLabel = 'Previous year';\n\n /** A label for the next year button (used by screen readers). */\n nextYearLabel = 'Next year';\n\n /** A label for the previous multi-year button (used by screen readers). */\n prevMultiYearLabel = 'Previous 24 years';\n\n /** A label for the next multi-year button (used by screen readers). */\n nextMultiYearLabel = 'Next 24 years';\n\n /** A label for the 'switch to month view' button (used by screen readers). */\n switchToMonthViewLabel = 'Choose date';\n\n /** A label for the 'switch to year view' button (used by screen readers). */\n switchToMultiYearViewLabel = 'Choose month and year';\n\n /**\n * A label for the first date of a range of dates (used by screen readers).\n * @deprecated Provide your own internationalization string.\n * @breaking-change 17.0.0\n */\n startDateLabel = 'Start date';\n\n /**\n * A label for the last date of a range of dates (used by screen readers).\n * @deprecated Provide your own internationalization string.\n * @breaking-change 17.0.0\n */\n endDateLabel = 'End date';\n\n /**\n * A label for the Comparison date of a range of dates (used by screen readers).\n */\n comparisonDateLabel = 'Comparison range';\n\n /** Formats a range of years (used for visuals). */\n formatYearRange(start: string, end: string): string {\n return `${start} \\u2013 ${end}`;\n }\n\n /** Formats a label for a range of years (used by screen readers). */\n formatYearRangeLabel(start: string, end: string): string {\n return `${start} to ${end}`;\n }\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {Platform, _bindEventWithOptions} from '@angular/cdk/platform';\nimport {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n EventEmitter,\n Input,\n Output,\n ViewEncapsulation,\n NgZone,\n OnChanges,\n SimpleChanges,\n OnDestroy,\n AfterViewChecked,\n inject,\n afterNextRender,\n Injector,\n Renderer2,\n} from '@angular/core';\nimport {_IdGenerator} from '@angular/cdk/a11y';\nimport {NgClass} from '@angular/common';\nimport {_CdkPrivateStyleLoader} from '@angular/cdk/private';\nimport {_StructuralStylesLoader} from '../core';\nimport {MatDatepickerIntl} from './datepicker-intl';\n\n/** Extra CSS classes that can be associated with a calendar cell. */\nexport type MatCalendarCellCssClasses = string | string[] | Set | {[key: string]: any};\n\n/** Function that can generate the extra classes that should be added to a calendar cell. */\nexport type MatCalendarCellClassFunction = (\n date: D,\n view: 'month' | 'year' | 'multi-year',\n) => MatCalendarCellCssClasses;\n\nlet uniqueIdCounter = 0;\n\n/**\n * An internal class that represents the data corresponding to a single calendar cell.\n * @docs-private\n */\nexport class MatCalendarCell {\n readonly id = uniqueIdCounter++;\n\n constructor(\n public value: number,\n public displayValue: string,\n public ariaLabel: string,\n public enabled: boolean,\n public cssClasses: MatCalendarCellCssClasses = {},\n public compareValue = value,\n public rawValue?: D,\n ) {}\n}\n\n/** Event emitted when a date inside the calendar is triggered as a result of a user action. */\nexport interface MatCalendarUserEvent {\n value: D;\n event: Event;\n}\n\n/** Event options that can be used to bind an active, capturing event. */\nconst activeCapturingEventOptions = {\n passive: false,\n capture: true,\n};\n\n/** Event options that can be used to bind a passive, capturing event. */\nconst passiveCapturingEventOptions = {\n passive: true,\n capture: true,\n};\n\n/** Event options that can be used to bind a passive, non-capturing event. */\nconst passiveEventOptions = {passive: true};\n\n/**\n * An internal component used to display calendar data in a table.\n * @docs-private\n */\n@Component({\n selector: '[mat-calendar-body]',\n templateUrl: 'calendar-body.html',\n styleUrl: 'calendar-body.css',\n host: {\n 'class': 'mat-calendar-body',\n },\n exportAs: 'matCalendarBody',\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [NgClass],\n})\nexport class MatCalendarBody implements OnChanges, OnDestroy, AfterViewChecked {\n private _elementRef = inject>(ElementRef);\n private _ngZone = inject(NgZone);\n private _platform = inject(Platform);\n private _intl = inject(MatDatepickerIntl);\n private _eventCleanups: (() => void)[];\n\n /**\n * Used to skip the next focus event when rendering the preview range.\n * We need a flag like this, because some browsers fire focus events asynchronously.\n */\n private _skipNextFocus: boolean;\n\n /**\n * Used to focus the active cell after change detection has run.\n */\n private _focusActiveCellAfterViewChecked = false;\n\n /** The label for the table. (e.g. \"Jan 2017\"). */\n @Input() label: string;\n\n /** The cells to display in the table. */\n @Input() rows: MatCalendarCell[][];\n\n /** The value in the table that corresponds to today. */\n @Input() todayValue: number;\n\n /** Start value of the selected date range. */\n @Input() startValue: number;\n\n /** End value of the selected date range. */\n @Input() endValue: number;\n\n /** The minimum number of free cells needed to fit the label in the first row. */\n @Input() labelMinRequiredCells: number;\n\n /** The number of columns in the table. */\n @Input() numCols: number = 7;\n\n /** The cell number of the active cell in the table. */\n @Input() activeCell: number = 0;\n\n ngAfterViewChecked() {\n if (this._focusActiveCellAfterViewChecked) {\n this._focusActiveCell();\n this._focusActiveCellAfterViewChecked = false;\n }\n }\n\n /** Whether a range is being selected. */\n @Input() isRange: boolean = false;\n\n /**\n * The aspect ratio (width / height) to use for the cells in the table. This aspect ratio will be\n * maintained even as the table resizes.\n */\n @Input() cellAspectRatio: number = 1;\n\n /** Start of the comparison range. */\n @Input() comparisonStart: number | null;\n\n /** End of the comparison range. */\n @Input() comparisonEnd: number | null;\n\n /** Start of the preview range. */\n @Input() previewStart: number | null = null;\n\n /** End of the preview range. */\n @Input() previewEnd: number | null = null;\n\n /** ARIA Accessible name of the `` */\n @Input() startDateAccessibleName: string | null;\n\n /** ARIA Accessible name of the `` */\n @Input() endDateAccessibleName: string | null;\n\n /** Emits when a new value is selected. */\n @Output() readonly selectedValueChange = new EventEmitter>();\n\n /** Emits when the preview has changed as a result of a user action. */\n @Output() readonly previewChange = new EventEmitter<\n MatCalendarUserEvent\n >();\n\n @Output() readonly activeDateChange = new EventEmitter>();\n\n /** Emits the date at the possible start of a drag event. */\n @Output() readonly dragStarted = new EventEmitter>();\n\n /** Emits the date at the conclusion of a drag, or null if mouse was not released on a date. */\n @Output() readonly dragEnded = new EventEmitter>();\n\n /** The number of blank cells to put at the beginning for the first row. */\n _firstRowOffset: number;\n\n /** Padding for the individual date cells. */\n _cellPadding: string;\n\n /** Width of an individual cell. */\n _cellWidth: string;\n\n /** ID for the start date label. */\n _startDateLabelId: string;\n\n /** ID for the end date label. */\n _endDateLabelId: string;\n\n /** ID for the comparison start date label. */\n _comparisonStartDateLabelId: string;\n\n /** ID for the comparison end date label. */\n _comparisonEndDateLabelId: string;\n\n private _didDragSinceMouseDown = false;\n\n private _injector = inject(Injector);\n\n comparisonDateAccessibleName = this._intl.comparisonDateLabel;\n\n /**\n * Tracking function for rows based on their identity. Ideally we would use some sort of\n * key on the row, but that would require a breaking change for the `rows` input. We don't\n * use the built-in identity tracking, because it logs warnings.\n */\n _trackRow = (row: MatCalendarCell[]) => row;\n\n constructor(...args: unknown[]);\n\n constructor() {\n const renderer = inject(Renderer2);\n const idGenerator = inject(_IdGenerator);\n this._startDateLabelId = idGenerator.getId('mat-calendar-body-start-');\n this._endDateLabelId = idGenerator.getId('mat-calendar-body-end-');\n this._comparisonStartDateLabelId = idGenerator.getId('mat-calendar-body-comparison-start-');\n this._comparisonEndDateLabelId = idGenerator.getId('mat-calendar-body-comparison-end-');\n\n inject(_CdkPrivateStyleLoader).load(_StructuralStylesLoader);\n\n this._ngZone.runOutsideAngular(() => {\n const element = this._elementRef.nativeElement;\n const cleanups = [\n // `touchmove` is active since we need to call `preventDefault`.\n _bindEventWithOptions(\n renderer,\n element,\n 'touchmove',\n this._touchmoveHandler,\n activeCapturingEventOptions,\n ),\n _bindEventWithOptions(\n renderer,\n element,\n 'mouseenter',\n this._enterHandler,\n passiveCapturingEventOptions,\n ),\n _bindEventWithOptions(\n renderer,\n element,\n 'focus',\n this._enterHandler,\n passiveCapturingEventOptions,\n ),\n _bindEventWithOptions(\n renderer,\n element,\n 'mouseleave',\n this._leaveHandler,\n passiveCapturingEventOptions,\n ),\n _bindEventWithOptions(\n renderer,\n element,\n 'blur',\n this._leaveHandler,\n passiveCapturingEventOptions,\n ),\n _bindEventWithOptions(\n renderer,\n element,\n 'mousedown',\n this._mousedownHandler,\n passiveEventOptions,\n ),\n _bindEventWithOptions(\n renderer,\n element,\n 'touchstart',\n this._mousedownHandler,\n passiveEventOptions,\n ),\n ];\n\n if (this._platform.isBrowser) {\n cleanups.push(\n renderer.listen('window', 'mouseup', this._mouseupHandler),\n renderer.listen('window', 'touchend', this._touchendHandler),\n );\n }\n\n this._eventCleanups = cleanups;\n });\n }\n\n /** Called when a cell is clicked. */\n _cellClicked(cell: MatCalendarCell, event: MouseEvent): void {\n // Ignore \"clicks\" that are actually canceled drags (eg the user dragged\n // off and then went back to this cell to undo).\n if (this._didDragSinceMouseDown) {\n return;\n }\n\n if (cell.enabled) {\n this.selectedValueChange.emit({value: cell.value, event});\n }\n }\n\n _emitActiveDateChange(cell: MatCalendarCell, event: FocusEvent): void {\n if (cell.enabled) {\n this.activeDateChange.emit({value: cell.value, event});\n }\n }\n\n /** Returns whether a cell should be marked as selected. */\n _isSelected(value: number) {\n return this.startValue === value || this.endValue === value;\n }\n\n ngOnChanges(changes: SimpleChanges) {\n const columnChanges = changes['numCols'];\n const {rows, numCols} = this;\n\n if (changes['rows'] || columnChanges) {\n this._firstRowOffset = rows && rows.length && rows[0].length ? numCols - rows[0].length : 0;\n }\n\n if (changes['cellAspectRatio'] || columnChanges || !this._cellPadding) {\n this._cellPadding = `${(50 * this.cellAspectRatio) / numCols}%`;\n }\n\n if (columnChanges || !this._cellWidth) {\n this._cellWidth = `${100 / numCols}%`;\n }\n }\n\n ngOnDestroy() {\n this._eventCleanups.forEach(cleanup => cleanup());\n }\n\n /** Returns whether a cell is active. */\n _isActiveCell(rowIndex: number, colIndex: number): boolean {\n let cellNumber = rowIndex * this.numCols + colIndex;\n\n // Account for the fact that the first row may not have as many cells.\n if (rowIndex) {\n cellNumber -= this._firstRowOffset;\n }\n\n return cellNumber == this.activeCell;\n }\n\n /**\n * Focuses the active cell after the microtask queue is empty.\n *\n * Adding a 0ms setTimeout seems to fix Voiceover losing focus when pressing PageUp/PageDown\n * (issue #24330).\n *\n * Determined a 0ms by gradually increasing duration from 0 and testing two use cases with screen\n * reader enabled:\n *\n * 1. Pressing PageUp/PageDown repeatedly with pausing between each key press.\n * 2. Pressing and holding the PageDown key with repeated keys enabled.\n *\n * Test 1 worked roughly 95-99% of the time with 0ms and got a little bit better as the duration\n * increased. Test 2 got slightly better until the duration was long enough to interfere with\n * repeated keys. If the repeated key speed was faster than the timeout duration, then pressing\n * and holding pagedown caused the entire page to scroll.\n *\n * Since repeated key speed can verify across machines, determined that any duration could\n * potentially interfere with repeated keys. 0ms would be best because it almost entirely\n * eliminates the focus being lost in Voiceover (#24330) without causing unintended side effects.\n * Adding delay also complicates writing tests.\n */\n _focusActiveCell(movePreview = true) {\n afterNextRender(\n () => {\n setTimeout(() => {\n const activeCell: HTMLElement | null = this._elementRef.nativeElement.querySelector(\n '.mat-calendar-body-active',\n );\n\n if (activeCell) {\n if (!movePreview) {\n this._skipNextFocus = true;\n }\n\n activeCell.focus();\n }\n });\n },\n {injector: this._injector},\n );\n }\n\n /** Focuses the active cell after change detection has run and the microtask queue is empty. */\n _scheduleFocusActiveCellAfterViewChecked() {\n this._focusActiveCellAfterViewChecked = true;\n }\n\n /** Gets whether a value is the start of the main range. */\n _isRangeStart(value: number) {\n return isStart(value, this.startValue, this.endValue);\n }\n\n /** Gets whether a value is the end of the main range. */\n _isRangeEnd(value: number) {\n return isEnd(value, this.startValue, this.endValue);\n }\n\n /** Gets whether a value is within the currently-selected range. */\n _isInRange(value: number): boolean {\n return isInRange(value, this.startValue, this.endValue, this.isRange);\n }\n\n /** Gets whether a value is the start of the comparison range. */\n _isComparisonStart(value: number) {\n return isStart(value, this.comparisonStart, this.comparisonEnd);\n }\n\n /** Whether the cell is a start bridge cell between the main and comparison ranges. */\n _isComparisonBridgeStart(value: number, rowIndex: number, colIndex: number) {\n if (!this._isComparisonStart(value) || this._isRangeStart(value) || !this._isInRange(value)) {\n return false;\n }\n\n let previousCell: MatCalendarCell | undefined = this.rows[rowIndex][colIndex - 1];\n\n if (!previousCell) {\n const previousRow = this.rows[rowIndex - 1];\n previousCell = previousRow && previousRow[previousRow.length - 1];\n }\n\n return previousCell && !this._isRangeEnd(previousCell.compareValue);\n }\n\n /** Whether the cell is an end bridge cell between the main and comparison ranges. */\n _isComparisonBridgeEnd(value: number, rowIndex: number, colIndex: number) {\n if (!this._isComparisonEnd(value) || this._isRangeEnd(value) || !this._isInRange(value)) {\n return false;\n }\n\n let nextCell: MatCalendarCell | undefined = this.rows[rowIndex][colIndex + 1];\n\n if (!nextCell) {\n const nextRow = this.rows[rowIndex + 1];\n nextCell = nextRow && nextRow[0];\n }\n\n return nextCell && !this._isRangeStart(nextCell.compareValue);\n }\n\n /** Gets whether a value is the end of the comparison range. */\n _isComparisonEnd(value: number) {\n return isEnd(value, this.comparisonStart, this.comparisonEnd);\n }\n\n /** Gets whether a value is within the current comparison range. */\n _isInComparisonRange(value: number) {\n return isInRange(value, this.comparisonStart, this.comparisonEnd, this.isRange);\n }\n\n /**\n * Gets whether a value is the same as the start and end of the comparison range.\n * For context, the functions that we use to determine whether something is the start/end of\n * a range don't allow for the start and end to be on the same day, because we'd have to use\n * much more specific CSS selectors to style them correctly in all scenarios. This is fine for\n * the regular range, because when it happens, the selected styles take over and still show where\n * the range would've been, however we don't have these selected styles for a comparison range.\n * This function is used to apply a class that serves the same purpose as the one for selected\n * dates, but it only applies in the context of a comparison range.\n */\n _isComparisonIdentical(value: number) {\n // Note that we don't need to null check the start/end\n // here, because the `value` will always be defined.\n return this.comparisonStart === this.comparisonEnd && value === this.comparisonStart;\n }\n\n /** Gets whether a value is the start of the preview range. */\n _isPreviewStart(value: number) {\n return isStart(value, this.previewStart, this.previewEnd);\n }\n\n /** Gets whether a value is the end of the preview range. */\n _isPreviewEnd(value: number) {\n return isEnd(value, this.previewStart, this.previewEnd);\n }\n\n /** Gets whether a value is inside the preview range. */\n _isInPreview(value: number) {\n return isInRange(value, this.previewStart, this.previewEnd, this.isRange);\n }\n\n /** Gets ids of aria descriptions for the start and end of a date range. */\n _getDescribedby(value: number): string | null {\n if (!this.isRange) {\n return null;\n }\n\n if (this.startValue === value && this.endValue === value) {\n return `${this._startDateLabelId} ${this._endDateLabelId}`;\n } else if (this.startValue === value) {\n return this._startDateLabelId;\n } else if (this.endValue === value) {\n return this._endDateLabelId;\n }\n\n if (this.comparisonStart !== null && this.comparisonEnd !== null) {\n if (value === this.comparisonStart && value === this.comparisonEnd) {\n return `${this._comparisonStartDateLabelId} ${this._comparisonEndDateLabelId}`;\n } else if (value === this.comparisonStart) {\n return this._comparisonStartDateLabelId;\n } else if (value === this.comparisonEnd) {\n return this._comparisonEndDateLabelId;\n }\n }\n\n return null;\n }\n\n /**\n * Event handler for when the user enters an element\n * inside the calendar body (e.g. by hovering in or focus).\n */\n private _enterHandler = (event: Event) => {\n if (this._skipNextFocus && event.type === 'focus') {\n this._skipNextFocus = false;\n return;\n }\n\n // We only need to hit the zone when we're selecting a range.\n if (event.target && this.isRange) {\n const cell = this._getCellFromElement(event.target as HTMLElement);\n\n if (cell) {\n this._ngZone.run(() => this.previewChange.emit({value: cell.enabled ? cell : null, event}));\n }\n }\n };\n\n private _touchmoveHandler = (event: TouchEvent) => {\n if (!this.isRange) return;\n\n const target = getActualTouchTarget(event);\n const cell = target ? this._getCellFromElement(target as HTMLElement) : null;\n\n if (target !== event.target) {\n this._didDragSinceMouseDown = true;\n }\n\n // If the initial target of the touch is a date cell, prevent default so\n // that the move is not handled as a scroll.\n if (getCellElement(event.target as HTMLElement)) {\n event.preventDefault();\n }\n\n this._ngZone.run(() => this.previewChange.emit({value: cell?.enabled ? cell : null, event}));\n };\n\n /**\n * Event handler for when the user's pointer leaves an element\n * inside the calendar body (e.g. by hovering out or blurring).\n */\n private _leaveHandler = (event: Event) => {\n // We only need to hit the zone when we're selecting a range.\n if (this.previewEnd !== null && this.isRange) {\n if (event.type !== 'blur') {\n this._didDragSinceMouseDown = true;\n }\n\n // Only reset the preview end value when leaving cells. This looks better, because\n // we have a gap between the cells and the rows and we don't want to remove the\n // range just for it to show up again when the user moves a few pixels to the side.\n if (\n event.target &&\n this._getCellFromElement(event.target as HTMLElement) &&\n !(\n (event as MouseEvent).relatedTarget &&\n this._getCellFromElement((event as MouseEvent).relatedTarget as HTMLElement)\n )\n ) {\n this._ngZone.run(() => this.previewChange.emit({value: null, event}));\n }\n }\n };\n\n /**\n * Triggered on mousedown or touchstart on a date cell.\n * Respsonsible for starting a drag sequence.\n */\n private _mousedownHandler = (event: Event) => {\n if (!this.isRange) return;\n\n this._didDragSinceMouseDown = false;\n // Begin a drag if a cell within the current range was targeted.\n const cell = event.target && this._getCellFromElement(event.target as HTMLElement);\n if (!cell || !this._isInRange(cell.compareValue)) {\n return;\n }\n\n this._ngZone.run(() => {\n this.dragStarted.emit({\n value: cell.rawValue,\n event,\n });\n });\n };\n\n /** Triggered on mouseup anywhere. Respsonsible for ending a drag sequence. */\n private _mouseupHandler = (event: Event) => {\n if (!this.isRange) return;\n\n const cellElement = getCellElement(event.target as HTMLElement);\n if (!cellElement) {\n // Mouseup happened outside of datepicker. Cancel drag.\n this._ngZone.run(() => {\n this.dragEnded.emit({value: null, event});\n });\n return;\n }\n\n if (cellElement.closest('.mat-calendar-body') !== this._elementRef.nativeElement) {\n // Mouseup happened inside a different month instance.\n // Allow it to handle the event.\n return;\n }\n\n this._ngZone.run(() => {\n const cell = this._getCellFromElement(cellElement);\n this.dragEnded.emit({value: cell?.rawValue ?? null, event});\n });\n };\n\n /** Triggered on touchend anywhere. Respsonsible for ending a drag sequence. */\n private _touchendHandler = (event: TouchEvent) => {\n const target = getActualTouchTarget(event);\n\n if (target) {\n this._mouseupHandler({target} as unknown as Event);\n }\n };\n\n /** Finds the MatCalendarCell that corresponds to a DOM node. */\n private _getCellFromElement(element: HTMLElement): MatCalendarCell | null {\n const cell = getCellElement(element);\n\n if (cell) {\n const row = cell.getAttribute('data-mat-row');\n const col = cell.getAttribute('data-mat-col');\n\n if (row && col) {\n return this.rows[parseInt(row)][parseInt(col)];\n }\n }\n\n return null;\n }\n}\n\n/** Checks whether a node is a table cell element. */\nfunction isTableCell(node: Node | undefined | null): node is HTMLTableCellElement {\n return node?.nodeName === 'TD';\n}\n\n/**\n * Gets the date table cell element that is or contains the specified element.\n * Or returns null if element is not part of a date cell.\n */\nfunction getCellElement(element: HTMLElement): HTMLElement | null {\n let cell: HTMLElement | undefined;\n if (isTableCell(element)) {\n cell = element;\n } else if (isTableCell(element.parentNode)) {\n cell = element.parentNode as HTMLElement;\n } else if (isTableCell(element.parentNode?.parentNode)) {\n cell = element.parentNode!.parentNode as HTMLElement;\n }\n\n return cell?.getAttribute('data-mat-row') != null ? cell : null;\n}\n\n/** Checks whether a value is the start of a range. */\nfunction isStart(value: number, start: number | null, end: number | null): boolean {\n return end !== null && start !== end && value < end && value === start;\n}\n\n/** Checks whether a value is the end of a range. */\nfunction isEnd(value: number, start: number | null, end: number | null): boolean {\n return start !== null && start !== end && value >= start && value === end;\n}\n\n/** Checks whether a value is inside of a range. */\nfunction isInRange(\n value: number,\n start: number | null,\n end: number | null,\n rangeEnabled: boolean,\n): boolean {\n return (\n rangeEnabled &&\n start !== null &&\n end !== null &&\n start !== end &&\n value >= start &&\n value <= end\n );\n}\n\n/**\n * Extracts the element that actually corresponds to a touch event's location\n * (rather than the element that initiated the sequence of touch events).\n */\nfunction getActualTouchTarget(event: TouchEvent): Element | null {\n const touchLocation = event.changedTouches[0];\n return document.elementFromPoint(touchLocation.clientX, touchLocation.clientY);\n}\n","\n@if (_firstRowOffset < labelMinRequiredCells) {\n \n \n {{label}}\n \n \n}\n\n\n@for (row of rows; track _trackRow(row); let rowIndex = $index) {\n \n \n @if (rowIndex === 0 && _firstRowOffset) {\n \n {{_firstRowOffset >= labelMinRequiredCells ? label : ''}}\n \n }\n \n @for (item of row; track item.id; let colIndex = $index) {\n \n \n \n {{item.displayValue}}\n \n \n \n \n }\n \n}\n\n\n {{startDateAccessibleName}}\n\n\n {{endDateAccessibleName}}\n\n\n {{comparisonDateAccessibleName}} {{startDateAccessibleName}}\n\n\n {{comparisonDateAccessibleName}} {{endDateAccessibleName}}\n\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {FactoryProvider, Injectable, Optional, SkipSelf, OnDestroy} from '@angular/core';\nimport {DateAdapter} from '../core';\nimport {Observable, Subject} from 'rxjs';\n\n/** A class representing a range of dates. */\nexport class DateRange {\n /**\n * Ensures that objects with a `start` and `end` property can't be assigned to a variable that\n * expects a `DateRange`\n */\n // tslint:disable-next-line:no-unused-variable\n private _disableStructuralEquivalency: never;\n\n constructor(\n /** The start date of the range. */\n readonly start: D | null,\n /** The end date of the range. */\n readonly end: D | null,\n ) {}\n}\n\n/**\n * Conditionally picks the date type, if a DateRange is passed in.\n * @docs-private\n */\nexport type ExtractDateTypeFromSelection = T extends DateRange ? D : NonNullable;\n\n/**\n * Event emitted by the date selection model when its selection changes.\n * @docs-private\n */\nexport interface DateSelectionModelChange {\n /** New value for the selection. */\n selection: S;\n\n /** Object that triggered the change. */\n source: unknown;\n\n /** Previous value */\n oldValue?: S;\n}\n\n/**\n * A selection model containing a date selection.\n * @docs-private\n */\n@Injectable()\nexport abstract class MatDateSelectionModel>\n implements OnDestroy\n{\n private readonly _selectionChanged = new Subject>();\n\n /** Emits when the selection has changed. */\n selectionChanged: Observable> = this._selectionChanged;\n\n protected constructor(\n /** The current selection. */\n readonly selection: S,\n protected _adapter: DateAdapter,\n ) {\n this.selection = selection;\n }\n\n /**\n * Updates the current selection in the model.\n * @param value New selection that should be assigned.\n * @param source Object that triggered the selection change.\n */\n updateSelection(value: S, source: unknown) {\n const oldValue = (this as {selection: S}).selection;\n (this as {selection: S}).selection = value;\n this._selectionChanged.next({selection: value, source, oldValue});\n }\n\n ngOnDestroy() {\n this._selectionChanged.complete();\n }\n\n protected _isValidDateInstance(date: D): boolean {\n return this._adapter.isDateInstance(date) && this._adapter.isValid(date);\n }\n\n /** Adds a date to the current selection. */\n abstract add(date: D | null): void;\n\n /** Checks whether the current selection is valid. */\n abstract isValid(): boolean;\n\n /** Checks whether the current selection is complete. */\n abstract isComplete(): boolean;\n\n /** Clones the selection model. */\n abstract clone(): MatDateSelectionModel;\n}\n\n/**\n * A selection model that contains a single date.\n * @docs-private\n */\n@Injectable()\nexport class MatSingleDateSelectionModel extends MatDateSelectionModel {\n constructor(adapter: DateAdapter) {\n super(null, adapter);\n }\n\n /**\n * Adds a date to the current selection. In the case of a single date selection, the added date\n * simply overwrites the previous selection\n */\n add(date: D | null) {\n super.updateSelection(date, this);\n }\n\n /** Checks whether the current selection is valid. */\n isValid(): boolean {\n return this.selection != null && this._isValidDateInstance(this.selection);\n }\n\n /**\n * Checks whether the current selection is complete. In the case of a single date selection, this\n * is true if the current selection is not null.\n */\n isComplete() {\n return this.selection != null;\n }\n\n /** Clones the selection model. */\n clone() {\n const clone = new MatSingleDateSelectionModel(this._adapter);\n clone.updateSelection(this.selection, this);\n return clone;\n }\n}\n\n/**\n * A selection model that contains a date range.\n * @docs-private\n */\n@Injectable()\nexport class MatRangeDateSelectionModel extends MatDateSelectionModel, D> {\n constructor(adapter: DateAdapter) {\n super(new DateRange(null, null), adapter);\n }\n\n /**\n * Adds a date to the current selection. In the case of a date range selection, the added date\n * fills in the next `null` value in the range. If both the start and the end already have a date,\n * the selection is reset so that the given date is the new `start` and the `end` is null.\n */\n add(date: D | null): void {\n let {start, end} = this.selection;\n\n if (start == null) {\n start = date;\n } else if (end == null) {\n end = date;\n } else {\n start = date;\n end = null;\n }\n\n super.updateSelection(new DateRange(start, end), this);\n }\n\n /** Checks whether the current selection is valid. */\n isValid(): boolean {\n const {start, end} = this.selection;\n\n // Empty ranges are valid.\n if (start == null && end == null) {\n return true;\n }\n\n // Complete ranges are only valid if both dates are valid and the start is before the end.\n if (start != null && end != null) {\n return (\n this._isValidDateInstance(start) &&\n this._isValidDateInstance(end) &&\n this._adapter.compareDate(start, end) <= 0\n );\n }\n\n // Partial ranges are valid if the start/end is valid.\n return (\n (start == null || this._isValidDateInstance(start)) &&\n (end == null || this._isValidDateInstance(end))\n );\n }\n\n /**\n * Checks whether the current selection is complete. In the case of a date range selection, this\n * is true if the current selection has a non-null `start` and `end`.\n */\n isComplete(): boolean {\n return this.selection.start != null && this.selection.end != null;\n }\n\n /** Clones the selection model. */\n clone() {\n const clone = new MatRangeDateSelectionModel(this._adapter);\n clone.updateSelection(this.selection, this);\n return clone;\n }\n}\n\n/**\n * @docs-private\n * @deprecated No longer used, will be removed.\n * @breaking-change 21.0.0\n */\nexport function MAT_SINGLE_DATE_SELECTION_MODEL_FACTORY(\n parent: MatSingleDateSelectionModel,\n adapter: DateAdapter,\n) {\n return parent || new MatSingleDateSelectionModel(adapter);\n}\n\n/**\n * Used to provide a single selection model to a component.\n * @docs-private\n * @deprecated No longer used, will be removed.\n * @breaking-change 21.0.0\n */\nexport const MAT_SINGLE_DATE_SELECTION_MODEL_PROVIDER: FactoryProvider = {\n provide: MatDateSelectionModel,\n deps: [[new Optional(), new SkipSelf(), MatDateSelectionModel], DateAdapter],\n useFactory: MAT_SINGLE_DATE_SELECTION_MODEL_FACTORY,\n};\n\n/**\n * @docs-private\n * @deprecated No longer used, will be removed.\n * @breaking-change 21.0.0\n */\nexport function MAT_RANGE_DATE_SELECTION_MODEL_FACTORY(\n parent: MatSingleDateSelectionModel,\n adapter: DateAdapter,\n) {\n return parent || new MatRangeDateSelectionModel(adapter);\n}\n\n/**\n * Used to provide a range selection model to a component.\n * @docs-private\n * @deprecated No longer used, will be removed.\n * @breaking-change 21.0.0\n */\nexport const MAT_RANGE_DATE_SELECTION_MODEL_PROVIDER: FactoryProvider = {\n provide: MatDateSelectionModel,\n deps: [[new Optional(), new SkipSelf(), MatDateSelectionModel], DateAdapter],\n useFactory: MAT_RANGE_DATE_SELECTION_MODEL_FACTORY,\n};\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {Injectable, InjectionToken, Optional, SkipSelf, FactoryProvider} from '@angular/core';\nimport {DateAdapter} from '../core';\nimport {DateRange} from './date-selection-model';\n\n/** Injection token used to customize the date range selection behavior. */\nexport const MAT_DATE_RANGE_SELECTION_STRATEGY = new InjectionToken<\n MatDateRangeSelectionStrategy\n>('MAT_DATE_RANGE_SELECTION_STRATEGY');\n\n/** Object that can be provided in order to customize the date range selection behavior. */\nexport interface MatDateRangeSelectionStrategy {\n /**\n * Called when the user has finished selecting a value.\n * @param date Date that was selected. Will be null if the user cleared the selection.\n * @param currentRange Range that is currently show in the calendar.\n * @param event DOM event that triggered the selection. Currently only corresponds to a `click`\n * event, but it may get expanded in the future.\n */\n selectionFinished(date: D | null, currentRange: DateRange, event: Event): DateRange;\n\n /**\n * Called when the user has activated a new date (e.g. by hovering over\n * it or moving focus) and the calendar tries to display a date range.\n *\n * @param activeDate Date that the user has activated. Will be null if the user moved\n * focus to an element that's no a calendar cell.\n * @param currentRange Range that is currently shown in the calendar.\n * @param event DOM event that caused the preview to be changed. Will be either a\n * `mouseenter`/`mouseleave` or `focus`/`blur` depending on how the user is navigating.\n */\n createPreview(activeDate: D | null, currentRange: DateRange, event: Event): DateRange;\n\n /**\n * Called when the user has dragged a date in the currently selected range to another\n * date. Returns the date updated range that should result from this interaction.\n *\n * @param dateOrigin The date the user started dragging from.\n * @param originalRange The originally selected date range.\n * @param newDate The currently targeted date in the drag operation.\n * @param event DOM event that triggered the updated drag state. Will be\n * `mouseenter`/`mouseup` or `touchmove`/`touchend` depending on the device type.\n */\n createDrag?(\n dragOrigin: D,\n originalRange: DateRange,\n newDate: D,\n event: Event,\n ): DateRange | null;\n}\n\n/** Provides the default date range selection behavior. */\n@Injectable()\nexport class DefaultMatCalendarRangeStrategy implements MatDateRangeSelectionStrategy {\n constructor(private _dateAdapter: DateAdapter) {}\n\n selectionFinished(date: D, currentRange: DateRange) {\n let {start, end} = currentRange;\n\n if (start == null) {\n start = date;\n } else if (end == null && date && this._dateAdapter.compareDate(date, start) >= 0) {\n end = date;\n } else {\n start = date;\n end = null;\n }\n\n return new DateRange(start, end);\n }\n\n createPreview(activeDate: D | null, currentRange: DateRange) {\n let start: D | null = null;\n let end: D | null = null;\n\n if (currentRange.start && !currentRange.end && activeDate) {\n start = currentRange.start;\n end = activeDate;\n }\n\n return new DateRange(start, end);\n }\n\n createDrag(dragOrigin: D, originalRange: DateRange, newDate: D) {\n let start = originalRange.start;\n let end = originalRange.end;\n\n if (!start || !end) {\n // Can't drag from an incomplete range.\n return null;\n }\n\n const adapter = this._dateAdapter;\n\n const isRange = adapter.compareDate(start, end) !== 0;\n const diffYears = adapter.getYear(newDate) - adapter.getYear(dragOrigin);\n const diffMonths = adapter.getMonth(newDate) - adapter.getMonth(dragOrigin);\n const diffDays = adapter.getDate(newDate) - adapter.getDate(dragOrigin);\n\n if (isRange && adapter.sameDate(dragOrigin, originalRange.start)) {\n start = newDate;\n if (adapter.compareDate(newDate, end) > 0) {\n end = adapter.addCalendarYears(end, diffYears);\n end = adapter.addCalendarMonths(end, diffMonths);\n end = adapter.addCalendarDays(end, diffDays);\n }\n } else if (isRange && adapter.sameDate(dragOrigin, originalRange.end)) {\n end = newDate;\n if (adapter.compareDate(newDate, start) < 0) {\n start = adapter.addCalendarYears(start, diffYears);\n start = adapter.addCalendarMonths(start, diffMonths);\n start = adapter.addCalendarDays(start, diffDays);\n }\n } else {\n start = adapter.addCalendarYears(start, diffYears);\n start = adapter.addCalendarMonths(start, diffMonths);\n start = adapter.addCalendarDays(start, diffDays);\n end = adapter.addCalendarYears(end, diffYears);\n end = adapter.addCalendarMonths(end, diffMonths);\n end = adapter.addCalendarDays(end, diffDays);\n }\n\n return new DateRange(start, end);\n }\n}\n\n/**\n * @docs-private\n * @deprecated No longer used, will be removed.\n * @breaking-change 21.0.0\n */\nexport function MAT_CALENDAR_RANGE_STRATEGY_PROVIDER_FACTORY(\n parent: MatDateRangeSelectionStrategy,\n adapter: DateAdapter,\n) {\n return parent || new DefaultMatCalendarRangeStrategy(adapter);\n}\n\n/**\n * @docs-private\n * @deprecated No longer used, will be removed.\n * @breaking-change 21.0.0\n */\nexport const MAT_CALENDAR_RANGE_STRATEGY_PROVIDER: FactoryProvider = {\n provide: MAT_DATE_RANGE_SELECTION_STRATEGY,\n deps: [[new Optional(), new SkipSelf(), MAT_DATE_RANGE_SELECTION_STRATEGY], DateAdapter],\n useFactory: MAT_CALENDAR_RANGE_STRATEGY_PROVIDER_FACTORY,\n};\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {\n DOWN_ARROW,\n END,\n ENTER,\n HOME,\n LEFT_ARROW,\n PAGE_DOWN,\n PAGE_UP,\n RIGHT_ARROW,\n UP_ARROW,\n SPACE,\n ESCAPE,\n hasModifierKey,\n} from '@angular/cdk/keycodes';\nimport {\n AfterContentInit,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n EventEmitter,\n Input,\n Output,\n ViewEncapsulation,\n ViewChild,\n OnDestroy,\n SimpleChanges,\n OnChanges,\n inject,\n} from '@angular/core';\nimport {DateAdapter, MAT_DATE_FORMATS, MatDateFormats} from '../core';\nimport {Directionality} from '@angular/cdk/bidi';\nimport {\n MatCalendarBody,\n MatCalendarCell,\n MatCalendarUserEvent,\n MatCalendarCellClassFunction,\n} from './calendar-body';\nimport {createMissingDateImplError} from './datepicker-errors';\nimport {Subscription} from 'rxjs';\nimport {startWith} from 'rxjs/operators';\nimport {DateRange} from './date-selection-model';\nimport {\n MatDateRangeSelectionStrategy,\n MAT_DATE_RANGE_SELECTION_STRATEGY,\n} from './date-range-selection-strategy';\nimport {_CdkPrivateStyleLoader, _VisuallyHiddenLoader} from '@angular/cdk/private';\n\nconst DAYS_PER_WEEK = 7;\n\nlet uniqueIdCounter = 0;\n\n/**\n * An internal component used to display a single month in the datepicker.\n * @docs-private\n */\n@Component({\n selector: 'mat-month-view',\n templateUrl: 'month-view.html',\n exportAs: 'matMonthView',\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [MatCalendarBody],\n})\nexport class MatMonthView implements AfterContentInit, OnChanges, OnDestroy {\n readonly _changeDetectorRef = inject(ChangeDetectorRef);\n private _dateFormats = inject(MAT_DATE_FORMATS, {optional: true})!;\n _dateAdapter = inject>(DateAdapter, {optional: true})!;\n private _dir = inject(Directionality, {optional: true});\n private _rangeStrategy = inject>(\n MAT_DATE_RANGE_SELECTION_STRATEGY,\n {optional: true},\n );\n\n private _rerenderSubscription = Subscription.EMPTY;\n\n /** Flag used to filter out space/enter keyup events that originated outside of the view. */\n private _selectionKeyPressed: boolean;\n\n /**\n * The date to display in this month view (everything other than the month and year is ignored).\n */\n @Input()\n get activeDate(): D {\n return this._activeDate;\n }\n set activeDate(value: D) {\n const oldActiveDate = this._activeDate;\n const validDate =\n this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)) ||\n this._dateAdapter.today();\n this._activeDate = this._dateAdapter.clampDate(validDate, this.minDate, this.maxDate);\n if (!this._hasSameMonthAndYear(oldActiveDate, this._activeDate)) {\n this._init();\n }\n }\n private _activeDate: D;\n\n /** The currently selected date. */\n @Input()\n get selected(): DateRange | D | null {\n return this._selected;\n }\n set selected(value: DateRange | D | null) {\n if (value instanceof DateRange) {\n this._selected = value;\n } else {\n this._selected = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));\n }\n\n this._setRanges(this._selected);\n }\n private _selected: DateRange | D | null;\n\n /** The minimum selectable date. */\n @Input()\n get minDate(): D | null {\n return this._minDate;\n }\n set minDate(value: D | null) {\n this._minDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));\n }\n private _minDate: D | null;\n\n /** The maximum selectable date. */\n @Input()\n get maxDate(): D | null {\n return this._maxDate;\n }\n set maxDate(value: D | null) {\n this._maxDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));\n }\n private _maxDate: D | null;\n\n /** Function used to filter which dates are selectable. */\n @Input() dateFilter: (date: D) => boolean;\n\n /** Function that can be used to add custom CSS classes to dates. */\n @Input() dateClass: MatCalendarCellClassFunction;\n\n /** Start of the comparison range. */\n @Input() comparisonStart: D | null;\n\n /** End of the comparison range. */\n @Input() comparisonEnd: D | null;\n\n /** ARIA Accessible name of the `` */\n @Input() startDateAccessibleName: string | null;\n\n /** ARIA Accessible name of the `` */\n @Input() endDateAccessibleName: string | null;\n\n /** Origin of active drag, or null when dragging is not active. */\n @Input() activeDrag: MatCalendarUserEvent | null = null;\n\n /** Emits when a new date is selected. */\n @Output() readonly selectedChange: EventEmitter = new EventEmitter();\n\n /** Emits when any date is selected. */\n @Output() readonly _userSelection: EventEmitter> =\n new EventEmitter>();\n\n /** Emits when the user initiates a date range drag via mouse or touch. */\n @Output() readonly dragStarted = new EventEmitter>();\n\n /**\n * Emits when the user completes or cancels a date range drag.\n * Emits null when the drag was canceled or the newly selected date range if completed.\n */\n @Output() readonly dragEnded = new EventEmitter | null>>();\n\n /** Emits when any date is activated. */\n @Output() readonly activeDateChange: EventEmitter = new EventEmitter();\n\n /** The body of calendar table */\n @ViewChild(MatCalendarBody) _matCalendarBody: MatCalendarBody;\n\n /** The label for this month (e.g. \"January 2017\"). */\n _monthLabel: string;\n\n /** Grid of calendar cells representing the dates of the month. */\n _weeks: MatCalendarCell[][];\n\n /** The number of blank cells in the first row before the 1st of the month. */\n _firstWeekOffset: number;\n\n /** Start value of the currently-shown date range. */\n _rangeStart: number | null;\n\n /** End value of the currently-shown date range. */\n _rangeEnd: number | null;\n\n /** Start value of the currently-shown comparison date range. */\n _comparisonRangeStart: number | null;\n\n /** End value of the currently-shown comparison date range. */\n _comparisonRangeEnd: number | null;\n\n /** Start of the preview range. */\n _previewStart: number | null;\n\n /** End of the preview range. */\n _previewEnd: number | null;\n\n /** Whether the user is currently selecting a range of dates. */\n _isRange: boolean;\n\n /** The date of the month that today falls on. Null if today is in another month. */\n _todayDate: number | null;\n\n /** The names of the weekdays. */\n _weekdays: {long: string; narrow: string; id: number}[];\n\n constructor(...args: unknown[]);\n\n constructor() {\n inject(_CdkPrivateStyleLoader).load(_VisuallyHiddenLoader);\n if (typeof ngDevMode === 'undefined' || ngDevMode) {\n if (!this._dateAdapter) {\n throw createMissingDateImplError('DateAdapter');\n }\n if (!this._dateFormats) {\n throw createMissingDateImplError('MAT_DATE_FORMATS');\n }\n }\n\n this._activeDate = this._dateAdapter.today();\n }\n\n ngAfterContentInit() {\n this._rerenderSubscription = this._dateAdapter.localeChanges\n .pipe(startWith(null))\n .subscribe(() => this._init());\n }\n\n ngOnChanges(changes: SimpleChanges) {\n const comparisonChange = changes['comparisonStart'] || changes['comparisonEnd'];\n\n if (comparisonChange && !comparisonChange.firstChange) {\n this._setRanges(this.selected);\n }\n\n if (changes['activeDrag'] && !this.activeDrag) {\n this._clearPreview();\n }\n }\n\n ngOnDestroy() {\n this._rerenderSubscription.unsubscribe();\n }\n\n /** Handles when a new date is selected. */\n _dateSelected(event: MatCalendarUserEvent) {\n const date = event.value;\n const selectedDate = this._getDateFromDayOfMonth(date);\n let rangeStartDate: number | null;\n let rangeEndDate: number | null;\n\n if (this._selected instanceof DateRange) {\n rangeStartDate = this._getDateInCurrentMonth(this._selected.start);\n rangeEndDate = this._getDateInCurrentMonth(this._selected.end);\n } else {\n rangeStartDate = rangeEndDate = this._getDateInCurrentMonth(this._selected);\n }\n\n if (rangeStartDate !== date || rangeEndDate !== date) {\n this.selectedChange.emit(selectedDate);\n }\n\n this._userSelection.emit({value: selectedDate, event: event.event});\n this._clearPreview();\n this._changeDetectorRef.markForCheck();\n }\n\n /**\n * Takes the index of a calendar body cell wrapped in an event as argument. For the date that\n * corresponds to the given cell, set `activeDate` to that date and fire `activeDateChange` with\n * that date.\n *\n * This function is used to match each component's model of the active date with the calendar\n * body cell that was focused. It updates its value of `activeDate` synchronously and updates the\n * parent's value asynchronously via the `activeDateChange` event. The child component receives an\n * updated value asynchronously via the `activeCell` Input.\n */\n _updateActiveDate(event: MatCalendarUserEvent) {\n const month = event.value;\n const oldActiveDate = this._activeDate;\n this.activeDate = this._getDateFromDayOfMonth(month);\n\n if (this._dateAdapter.compareDate(oldActiveDate, this.activeDate)) {\n this.activeDateChange.emit(this._activeDate);\n }\n }\n\n /** Handles keydown events on the calendar body when calendar is in month view. */\n _handleCalendarBodyKeydown(event: KeyboardEvent): void {\n // TODO(mmalerba): We currently allow keyboard navigation to disabled dates, but just prevent\n // disabled ones from being selected. This may not be ideal, we should look into whether\n // navigation should skip over disabled dates, and if so, how to implement that efficiently.\n\n const oldActiveDate = this._activeDate;\n const isRtl = this._isRtl();\n\n switch (event.keyCode) {\n case LEFT_ARROW:\n this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, isRtl ? 1 : -1);\n break;\n case RIGHT_ARROW:\n this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, isRtl ? -1 : 1);\n break;\n case UP_ARROW:\n this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, -7);\n break;\n case DOWN_ARROW:\n this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, 7);\n break;\n case HOME:\n this.activeDate = this._dateAdapter.addCalendarDays(\n this._activeDate,\n 1 - this._dateAdapter.getDate(this._activeDate),\n );\n break;\n case END:\n this.activeDate = this._dateAdapter.addCalendarDays(\n this._activeDate,\n this._dateAdapter.getNumDaysInMonth(this._activeDate) -\n this._dateAdapter.getDate(this._activeDate),\n );\n break;\n case PAGE_UP:\n this.activeDate = event.altKey\n ? this._dateAdapter.addCalendarYears(this._activeDate, -1)\n : this._dateAdapter.addCalendarMonths(this._activeDate, -1);\n break;\n case PAGE_DOWN:\n this.activeDate = event.altKey\n ? this._dateAdapter.addCalendarYears(this._activeDate, 1)\n : this._dateAdapter.addCalendarMonths(this._activeDate, 1);\n break;\n case ENTER:\n case SPACE:\n this._selectionKeyPressed = true;\n\n if (this._canSelect(this._activeDate)) {\n // Prevent unexpected default actions such as form submission.\n // Note that we only prevent the default action here while the selection happens in\n // `keyup` below. We can't do the selection here, because it can cause the calendar to\n // reopen if focus is restored immediately. We also can't call `preventDefault` on `keyup`\n // because it's too late (see #23305).\n event.preventDefault();\n }\n return;\n case ESCAPE:\n // Abort the current range selection if the user presses escape mid-selection.\n if (this._previewEnd != null && !hasModifierKey(event)) {\n this._clearPreview();\n // If a drag is in progress, cancel the drag without changing the\n // current selection.\n if (this.activeDrag) {\n this.dragEnded.emit({value: null, event});\n } else {\n this.selectedChange.emit(null);\n this._userSelection.emit({value: null, event});\n }\n event.preventDefault();\n event.stopPropagation(); // Prevents the overlay from closing.\n }\n return;\n default:\n // Don't prevent default or focus active cell on keys that we don't explicitly handle.\n return;\n }\n\n if (this._dateAdapter.compareDate(oldActiveDate, this.activeDate)) {\n this.activeDateChange.emit(this.activeDate);\n\n this._focusActiveCellAfterViewChecked();\n }\n\n // Prevent unexpected default actions such as form submission.\n event.preventDefault();\n }\n\n /** Handles keyup events on the calendar body when calendar is in month view. */\n _handleCalendarBodyKeyup(event: KeyboardEvent): void {\n if (event.keyCode === SPACE || event.keyCode === ENTER) {\n if (this._selectionKeyPressed && this._canSelect(this._activeDate)) {\n this._dateSelected({value: this._dateAdapter.getDate(this._activeDate), event});\n }\n\n this._selectionKeyPressed = false;\n }\n }\n\n /** Initializes this month view. */\n _init() {\n this._setRanges(this.selected);\n this._todayDate = this._getCellCompareValue(this._dateAdapter.today());\n this._monthLabel = this._dateFormats.display.monthLabel\n ? this._dateAdapter.format(this.activeDate, this._dateFormats.display.monthLabel)\n : this._dateAdapter\n .getMonthNames('short')\n [this._dateAdapter.getMonth(this.activeDate)].toLocaleUpperCase();\n\n let firstOfMonth = this._dateAdapter.createDate(\n this._dateAdapter.getYear(this.activeDate),\n this._dateAdapter.getMonth(this.activeDate),\n 1,\n );\n this._firstWeekOffset =\n (DAYS_PER_WEEK +\n this._dateAdapter.getDayOfWeek(firstOfMonth) -\n this._dateAdapter.getFirstDayOfWeek()) %\n DAYS_PER_WEEK;\n\n this._initWeekdays();\n this._createWeekCells();\n this._changeDetectorRef.markForCheck();\n }\n\n /** Focuses the active cell after the microtask queue is empty. */\n _focusActiveCell(movePreview?: boolean) {\n this._matCalendarBody._focusActiveCell(movePreview);\n }\n\n /** Focuses the active cell after change detection has run and the microtask queue is empty. */\n _focusActiveCellAfterViewChecked() {\n this._matCalendarBody._scheduleFocusActiveCellAfterViewChecked();\n }\n\n /** Called when the user has activated a new cell and the preview needs to be updated. */\n _previewChanged({event, value: cell}: MatCalendarUserEvent | null>) {\n if (this._rangeStrategy) {\n // We can assume that this will be a range, because preview\n // events aren't fired for single date selections.\n const value = cell ? cell.rawValue! : null;\n const previewRange = this._rangeStrategy.createPreview(\n value,\n this.selected as DateRange,\n event,\n );\n this._previewStart = this._getCellCompareValue(previewRange.start);\n this._previewEnd = this._getCellCompareValue(previewRange.end);\n\n if (this.activeDrag && value) {\n const dragRange = this._rangeStrategy.createDrag?.(\n this.activeDrag.value,\n this.selected as DateRange,\n value,\n event,\n );\n\n if (dragRange) {\n this._previewStart = this._getCellCompareValue(dragRange.start);\n this._previewEnd = this._getCellCompareValue(dragRange.end);\n }\n }\n\n // Note that here we need to use `detectChanges`, rather than `markForCheck`, because\n // the way `_focusActiveCell` is set up at the moment makes it fire at the wrong time\n // when navigating one month back using the keyboard which will cause this handler\n // to throw a \"changed after checked\" error when updating the preview state.\n this._changeDetectorRef.detectChanges();\n }\n }\n\n /**\n * Called when the user has ended a drag. If the drag/drop was successful,\n * computes and emits the new range selection.\n */\n protected _dragEnded(event: MatCalendarUserEvent) {\n if (!this.activeDrag) return;\n\n if (event.value) {\n // Propagate drag effect\n const dragDropResult = this._rangeStrategy?.createDrag?.(\n this.activeDrag.value,\n this.selected as DateRange,\n event.value,\n event.event,\n );\n\n this.dragEnded.emit({value: dragDropResult ?? null, event: event.event});\n } else {\n this.dragEnded.emit({value: null, event: event.event});\n }\n }\n\n /**\n * Takes a day of the month and returns a new date in the same month and year as the currently\n * active date. The returned date will have the same day of the month as the argument date.\n */\n private _getDateFromDayOfMonth(dayOfMonth: number): D {\n return this._dateAdapter.createDate(\n this._dateAdapter.getYear(this.activeDate),\n this._dateAdapter.getMonth(this.activeDate),\n dayOfMonth,\n );\n }\n\n /** Initializes the weekdays. */\n private _initWeekdays() {\n const firstDayOfWeek = this._dateAdapter.getFirstDayOfWeek();\n const narrowWeekdays = this._dateAdapter.getDayOfWeekNames('narrow');\n const longWeekdays = this._dateAdapter.getDayOfWeekNames('long');\n\n // Rotate the labels for days of the week based on the configured first day of the week.\n let weekdays = longWeekdays.map((long, i) => {\n return {long, narrow: narrowWeekdays[i], id: uniqueIdCounter++};\n });\n this._weekdays = weekdays.slice(firstDayOfWeek).concat(weekdays.slice(0, firstDayOfWeek));\n }\n\n /** Creates MatCalendarCells for the dates in this month. */\n private _createWeekCells() {\n const daysInMonth = this._dateAdapter.getNumDaysInMonth(this.activeDate);\n const dateNames = this._dateAdapter.getDateNames();\n this._weeks = [[]];\n for (let i = 0, cell = this._firstWeekOffset; i < daysInMonth; i++, cell++) {\n if (cell == DAYS_PER_WEEK) {\n this._weeks.push([]);\n cell = 0;\n }\n const date = this._dateAdapter.createDate(\n this._dateAdapter.getYear(this.activeDate),\n this._dateAdapter.getMonth(this.activeDate),\n i + 1,\n );\n const enabled = this._shouldEnableDate(date);\n const ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.dateA11yLabel);\n const cellClasses = this.dateClass ? this.dateClass(date, 'month') : undefined;\n\n this._weeks[this._weeks.length - 1].push(\n new MatCalendarCell(\n i + 1,\n dateNames[i],\n ariaLabel,\n enabled,\n cellClasses,\n this._getCellCompareValue(date)!,\n date,\n ),\n );\n }\n }\n\n /** Date filter for the month */\n private _shouldEnableDate(date: D): boolean {\n return (\n !!date &&\n (!this.minDate || this._dateAdapter.compareDate(date, this.minDate) >= 0) &&\n (!this.maxDate || this._dateAdapter.compareDate(date, this.maxDate) <= 0) &&\n (!this.dateFilter || this.dateFilter(date))\n );\n }\n\n /**\n * Gets the date in this month that the given Date falls on.\n * Returns null if the given Date is in another month.\n */\n private _getDateInCurrentMonth(date: D | null): number | null {\n return date && this._hasSameMonthAndYear(date, this.activeDate)\n ? this._dateAdapter.getDate(date)\n : null;\n }\n\n /** Checks whether the 2 dates are non-null and fall within the same month of the same year. */\n private _hasSameMonthAndYear(d1: D | null, d2: D | null): boolean {\n return !!(\n d1 &&\n d2 &&\n this._dateAdapter.getMonth(d1) == this._dateAdapter.getMonth(d2) &&\n this._dateAdapter.getYear(d1) == this._dateAdapter.getYear(d2)\n );\n }\n\n /** Gets the value that will be used to one cell to another. */\n private _getCellCompareValue(date: D | null): number | null {\n if (date) {\n // We use the time since the Unix epoch to compare dates in this view, rather than the\n // cell values, because we need to support ranges that span across multiple months/years.\n const year = this._dateAdapter.getYear(date);\n const month = this._dateAdapter.getMonth(date);\n const day = this._dateAdapter.getDate(date);\n return new Date(year, month, day).getTime();\n }\n\n return null;\n }\n\n /** Determines whether the user has the RTL layout direction. */\n private _isRtl() {\n return this._dir && this._dir.value === 'rtl';\n }\n\n /** Sets the current range based on a model value. */\n private _setRanges(selectedValue: DateRange | D | null) {\n if (selectedValue instanceof DateRange) {\n this._rangeStart = this._getCellCompareValue(selectedValue.start);\n this._rangeEnd = this._getCellCompareValue(selectedValue.end);\n this._isRange = true;\n } else {\n this._rangeStart = this._rangeEnd = this._getCellCompareValue(selectedValue);\n this._isRange = false;\n }\n\n this._comparisonRangeStart = this._getCellCompareValue(this.comparisonStart);\n this._comparisonRangeEnd = this._getCellCompareValue(this.comparisonEnd);\n }\n\n /** Gets whether a date can be selected in the month view. */\n private _canSelect(date: D) {\n return !this.dateFilter || this.dateFilter(date);\n }\n\n /** Clears out preview state. */\n private _clearPreview() {\n this._previewStart = this._previewEnd = null;\n }\n}\n","\n \n \n @for (day of _weekdays; track day.id) {\n \n }\n \n \n \n \n \n
\n {{day.long}}\n {{day.narrow}}\n
\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {\n DOWN_ARROW,\n END,\n ENTER,\n HOME,\n LEFT_ARROW,\n PAGE_DOWN,\n PAGE_UP,\n RIGHT_ARROW,\n UP_ARROW,\n SPACE,\n} from '@angular/cdk/keycodes';\nimport {\n AfterContentInit,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n EventEmitter,\n Input,\n Output,\n ViewChild,\n ViewEncapsulation,\n OnDestroy,\n inject,\n} from '@angular/core';\nimport {DateAdapter} from '../core';\nimport {Directionality} from '@angular/cdk/bidi';\nimport {\n MatCalendarBody,\n MatCalendarCell,\n MatCalendarUserEvent,\n MatCalendarCellClassFunction,\n} from './calendar-body';\nimport {createMissingDateImplError} from './datepicker-errors';\nimport {Subscription} from 'rxjs';\nimport {startWith} from 'rxjs/operators';\nimport {DateRange} from './date-selection-model';\n\nexport const yearsPerPage = 24;\n\nexport const yearsPerRow = 4;\n\n/**\n * An internal component used to display a year selector in the datepicker.\n * @docs-private\n */\n@Component({\n selector: 'mat-multi-year-view',\n templateUrl: 'multi-year-view.html',\n exportAs: 'matMultiYearView',\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [MatCalendarBody],\n})\nexport class MatMultiYearView implements AfterContentInit, OnDestroy {\n private _changeDetectorRef = inject(ChangeDetectorRef);\n _dateAdapter = inject>(DateAdapter, {optional: true})!;\n private _dir = inject(Directionality, {optional: true});\n private _rerenderSubscription = Subscription.EMPTY;\n\n /** Flag used to filter out space/enter keyup events that originated outside of the view. */\n private _selectionKeyPressed: boolean;\n\n /** The date to display in this multi-year view (everything other than the year is ignored). */\n @Input()\n get activeDate(): D {\n return this._activeDate;\n }\n set activeDate(value: D) {\n let oldActiveDate = this._activeDate;\n const validDate =\n this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)) ||\n this._dateAdapter.today();\n this._activeDate = this._dateAdapter.clampDate(validDate, this.minDate, this.maxDate);\n\n if (\n !isSameMultiYearView(\n this._dateAdapter,\n oldActiveDate,\n this._activeDate,\n this.minDate,\n this.maxDate,\n )\n ) {\n this._init();\n }\n }\n private _activeDate: D;\n\n /** The currently selected date. */\n @Input()\n get selected(): DateRange | D | null {\n return this._selected;\n }\n set selected(value: DateRange | D | null) {\n if (value instanceof DateRange) {\n this._selected = value;\n } else {\n this._selected = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));\n }\n\n this._setSelectedYear(value);\n }\n private _selected: DateRange | D | null;\n\n /** The minimum selectable date. */\n @Input()\n get minDate(): D | null {\n return this._minDate;\n }\n set minDate(value: D | null) {\n this._minDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));\n }\n private _minDate: D | null;\n\n /** The maximum selectable date. */\n @Input()\n get maxDate(): D | null {\n return this._maxDate;\n }\n set maxDate(value: D | null) {\n this._maxDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));\n }\n private _maxDate: D | null;\n\n /** A function used to filter which dates are selectable. */\n @Input() dateFilter: (date: D) => boolean;\n\n /** Function that can be used to add custom CSS classes to date cells. */\n @Input() dateClass: MatCalendarCellClassFunction;\n\n /** Emits when a new year is selected. */\n @Output() readonly selectedChange: EventEmitter = new EventEmitter();\n\n /** Emits the selected year. This doesn't imply a change on the selected date */\n @Output() readonly yearSelected: EventEmitter = new EventEmitter();\n\n /** Emits when any date is activated. */\n @Output() readonly activeDateChange: EventEmitter = new EventEmitter();\n\n /** The body of calendar table */\n @ViewChild(MatCalendarBody) _matCalendarBody: MatCalendarBody;\n\n /** Grid of calendar cells representing the currently displayed years. */\n _years: MatCalendarCell[][];\n\n /** The year that today falls on. */\n _todayYear: number;\n\n /** The year of the selected date. Null if the selected date is null. */\n _selectedYear: number | null;\n\n constructor(...args: unknown[]);\n\n constructor() {\n if (!this._dateAdapter && (typeof ngDevMode === 'undefined' || ngDevMode)) {\n throw createMissingDateImplError('DateAdapter');\n }\n\n this._activeDate = this._dateAdapter.today();\n }\n\n ngAfterContentInit() {\n this._rerenderSubscription = this._dateAdapter.localeChanges\n .pipe(startWith(null))\n .subscribe(() => this._init());\n }\n\n ngOnDestroy() {\n this._rerenderSubscription.unsubscribe();\n }\n\n /** Initializes this multi-year view. */\n _init() {\n this._todayYear = this._dateAdapter.getYear(this._dateAdapter.today());\n\n // We want a range years such that we maximize the number of\n // enabled dates visible at once. This prevents issues where the minimum year\n // is the last item of a page OR the maximum year is the first item of a page.\n\n // The offset from the active year to the \"slot\" for the starting year is the\n // *actual* first rendered year in the multi-year view.\n const activeYear = this._dateAdapter.getYear(this._activeDate);\n const minYearOfPage =\n activeYear - getActiveOffset(this._dateAdapter, this.activeDate, this.minDate, this.maxDate);\n\n this._years = [];\n for (let i = 0, row: number[] = []; i < yearsPerPage; i++) {\n row.push(minYearOfPage + i);\n if (row.length == yearsPerRow) {\n this._years.push(row.map(year => this._createCellForYear(year)));\n row = [];\n }\n }\n this._changeDetectorRef.markForCheck();\n }\n\n /** Handles when a new year is selected. */\n _yearSelected(event: MatCalendarUserEvent) {\n const year = event.value;\n const selectedYear = this._dateAdapter.createDate(year, 0, 1);\n const selectedDate = this._getDateFromYear(year);\n\n this.yearSelected.emit(selectedYear);\n this.selectedChange.emit(selectedDate);\n }\n\n /**\n * Takes the index of a calendar body cell wrapped in an event as argument. For the date that\n * corresponds to the given cell, set `activeDate` to that date and fire `activeDateChange` with\n * that date.\n *\n * This function is used to match each component's model of the active date with the calendar\n * body cell that was focused. It updates its value of `activeDate` synchronously and updates the\n * parent's value asynchronously via the `activeDateChange` event. The child component receives an\n * updated value asynchronously via the `activeCell` Input.\n */\n _updateActiveDate(event: MatCalendarUserEvent) {\n const year = event.value;\n const oldActiveDate = this._activeDate;\n\n this.activeDate = this._getDateFromYear(year);\n if (this._dateAdapter.compareDate(oldActiveDate, this.activeDate)) {\n this.activeDateChange.emit(this.activeDate);\n }\n }\n\n /** Handles keydown events on the calendar body when calendar is in multi-year view. */\n _handleCalendarBodyKeydown(event: KeyboardEvent): void {\n const oldActiveDate = this._activeDate;\n const isRtl = this._isRtl();\n\n switch (event.keyCode) {\n case LEFT_ARROW:\n this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, isRtl ? 1 : -1);\n break;\n case RIGHT_ARROW:\n this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, isRtl ? -1 : 1);\n break;\n case UP_ARROW:\n this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, -yearsPerRow);\n break;\n case DOWN_ARROW:\n this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, yearsPerRow);\n break;\n case HOME:\n this.activeDate = this._dateAdapter.addCalendarYears(\n this._activeDate,\n -getActiveOffset(this._dateAdapter, this.activeDate, this.minDate, this.maxDate),\n );\n break;\n case END:\n this.activeDate = this._dateAdapter.addCalendarYears(\n this._activeDate,\n yearsPerPage -\n getActiveOffset(this._dateAdapter, this.activeDate, this.minDate, this.maxDate) -\n 1,\n );\n break;\n case PAGE_UP:\n this.activeDate = this._dateAdapter.addCalendarYears(\n this._activeDate,\n event.altKey ? -yearsPerPage * 10 : -yearsPerPage,\n );\n break;\n case PAGE_DOWN:\n this.activeDate = this._dateAdapter.addCalendarYears(\n this._activeDate,\n event.altKey ? yearsPerPage * 10 : yearsPerPage,\n );\n break;\n case ENTER:\n case SPACE:\n // Note that we only prevent the default action here while the selection happens in\n // `keyup` below. We can't do the selection here, because it can cause the calendar to\n // reopen if focus is restored immediately. We also can't call `preventDefault` on `keyup`\n // because it's too late (see #23305).\n this._selectionKeyPressed = true;\n break;\n default:\n // Don't prevent default or focus active cell on keys that we don't explicitly handle.\n return;\n }\n if (this._dateAdapter.compareDate(oldActiveDate, this.activeDate)) {\n this.activeDateChange.emit(this.activeDate);\n }\n\n this._focusActiveCellAfterViewChecked();\n // Prevent unexpected default actions such as form submission.\n event.preventDefault();\n }\n\n /** Handles keyup events on the calendar body when calendar is in multi-year view. */\n _handleCalendarBodyKeyup(event: KeyboardEvent): void {\n if (event.keyCode === SPACE || event.keyCode === ENTER) {\n if (this._selectionKeyPressed) {\n this._yearSelected({value: this._dateAdapter.getYear(this._activeDate), event});\n }\n\n this._selectionKeyPressed = false;\n }\n }\n\n _getActiveCell(): number {\n return getActiveOffset(this._dateAdapter, this.activeDate, this.minDate, this.maxDate);\n }\n\n /** Focuses the active cell after the microtask queue is empty. */\n _focusActiveCell() {\n this._matCalendarBody._focusActiveCell();\n }\n\n /** Focuses the active cell after change detection has run and the microtask queue is empty. */\n _focusActiveCellAfterViewChecked() {\n this._matCalendarBody._scheduleFocusActiveCellAfterViewChecked();\n }\n\n /**\n * Takes a year and returns a new date on the same day and month as the currently active date\n * The returned date will have the same year as the argument date.\n */\n private _getDateFromYear(year: number) {\n const activeMonth = this._dateAdapter.getMonth(this.activeDate);\n const daysInMonth = this._dateAdapter.getNumDaysInMonth(\n this._dateAdapter.createDate(year, activeMonth, 1),\n );\n const normalizedDate = this._dateAdapter.createDate(\n year,\n activeMonth,\n Math.min(this._dateAdapter.getDate(this.activeDate), daysInMonth),\n );\n return normalizedDate;\n }\n\n /** Creates an MatCalendarCell for the given year. */\n private _createCellForYear(year: number) {\n const date = this._dateAdapter.createDate(year, 0, 1);\n const yearName = this._dateAdapter.getYearName(date);\n const cellClasses = this.dateClass ? this.dateClass(date, 'multi-year') : undefined;\n\n return new MatCalendarCell(year, yearName, yearName, this._shouldEnableYear(year), cellClasses);\n }\n\n /** Whether the given year is enabled. */\n private _shouldEnableYear(year: number) {\n // disable if the year is greater than maxDate lower than minDate\n if (\n year === undefined ||\n year === null ||\n (this.maxDate && year > this._dateAdapter.getYear(this.maxDate)) ||\n (this.minDate && year < this._dateAdapter.getYear(this.minDate))\n ) {\n return false;\n }\n\n // enable if it reaches here and there's no filter defined\n if (!this.dateFilter) {\n return true;\n }\n\n const firstOfYear = this._dateAdapter.createDate(year, 0, 1);\n\n // If any date in the year is enabled count the year as enabled.\n for (\n let date = firstOfYear;\n this._dateAdapter.getYear(date) == year;\n date = this._dateAdapter.addCalendarDays(date, 1)\n ) {\n if (this.dateFilter(date)) {\n return true;\n }\n }\n\n return false;\n }\n\n /** Determines whether the user has the RTL layout direction. */\n private _isRtl() {\n return this._dir && this._dir.value === 'rtl';\n }\n\n /** Sets the currently-highlighted year based on a model value. */\n private _setSelectedYear(value: DateRange | D | null) {\n this._selectedYear = null;\n\n if (value instanceof DateRange) {\n const displayValue = value.start || value.end;\n\n if (displayValue) {\n this._selectedYear = this._dateAdapter.getYear(displayValue);\n }\n } else if (value) {\n this._selectedYear = this._dateAdapter.getYear(value);\n }\n }\n}\n\nexport function isSameMultiYearView(\n dateAdapter: DateAdapter,\n date1: D,\n date2: D,\n minDate: D | null,\n maxDate: D | null,\n): boolean {\n const year1 = dateAdapter.getYear(date1);\n const year2 = dateAdapter.getYear(date2);\n const startingYear = getStartingYear(dateAdapter, minDate, maxDate);\n return (\n Math.floor((year1 - startingYear) / yearsPerPage) ===\n Math.floor((year2 - startingYear) / yearsPerPage)\n );\n}\n\n/**\n * When the multi-year view is first opened, the active year will be in view.\n * So we compute how many years are between the active year and the *slot* where our\n * \"startingYear\" will render when paged into view.\n */\nexport function getActiveOffset(\n dateAdapter: DateAdapter,\n activeDate: D,\n minDate: D | null,\n maxDate: D | null,\n): number {\n const activeYear = dateAdapter.getYear(activeDate);\n return euclideanModulo(activeYear - getStartingYear(dateAdapter, minDate, maxDate), yearsPerPage);\n}\n\n/**\n * We pick a \"starting\" year such that either the maximum year would be at the end\n * or the minimum year would be at the beginning of a page.\n */\nfunction getStartingYear(\n dateAdapter: DateAdapter,\n minDate: D | null,\n maxDate: D | null,\n): number {\n let startingYear = 0;\n if (maxDate) {\n const maxYear = dateAdapter.getYear(maxDate);\n startingYear = maxYear - yearsPerPage + 1;\n } else if (minDate) {\n startingYear = dateAdapter.getYear(minDate);\n }\n return startingYear;\n}\n\n/** Gets remainder that is non-negative, even if first number is negative */\nfunction euclideanModulo(a: number, b: number): number {\n return ((a % b) + b) % b;\n}\n","\n \n \n \n \n \n
\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {\n DOWN_ARROW,\n END,\n ENTER,\n HOME,\n LEFT_ARROW,\n PAGE_DOWN,\n PAGE_UP,\n RIGHT_ARROW,\n UP_ARROW,\n SPACE,\n} from '@angular/cdk/keycodes';\nimport {\n AfterContentInit,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n EventEmitter,\n Input,\n Output,\n ViewChild,\n ViewEncapsulation,\n OnDestroy,\n inject,\n} from '@angular/core';\nimport {DateAdapter, MAT_DATE_FORMATS, MatDateFormats} from '../core';\nimport {Directionality} from '@angular/cdk/bidi';\nimport {\n MatCalendarBody,\n MatCalendarCell,\n MatCalendarUserEvent,\n MatCalendarCellClassFunction,\n} from './calendar-body';\nimport {createMissingDateImplError} from './datepicker-errors';\nimport {Subscription} from 'rxjs';\nimport {startWith} from 'rxjs/operators';\nimport {DateRange} from './date-selection-model';\n\n/**\n * An internal component used to display a single year in the datepicker.\n * @docs-private\n */\n@Component({\n selector: 'mat-year-view',\n templateUrl: 'year-view.html',\n exportAs: 'matYearView',\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [MatCalendarBody],\n})\nexport class MatYearView implements AfterContentInit, OnDestroy {\n readonly _changeDetectorRef = inject(ChangeDetectorRef);\n private _dateFormats = inject(MAT_DATE_FORMATS, {optional: true})!;\n _dateAdapter = inject>(DateAdapter, {optional: true})!;\n private _dir = inject(Directionality, {optional: true});\n\n private _rerenderSubscription = Subscription.EMPTY;\n\n /** Flag used to filter out space/enter keyup events that originated outside of the view. */\n private _selectionKeyPressed: boolean;\n\n /** The date to display in this year view (everything other than the year is ignored). */\n @Input()\n get activeDate(): D {\n return this._activeDate;\n }\n set activeDate(value: D) {\n let oldActiveDate = this._activeDate;\n const validDate =\n this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)) ||\n this._dateAdapter.today();\n this._activeDate = this._dateAdapter.clampDate(validDate, this.minDate, this.maxDate);\n if (this._dateAdapter.getYear(oldActiveDate) !== this._dateAdapter.getYear(this._activeDate)) {\n this._init();\n }\n }\n private _activeDate: D;\n\n /** The currently selected date. */\n @Input()\n get selected(): DateRange | D | null {\n return this._selected;\n }\n set selected(value: DateRange | D | null) {\n if (value instanceof DateRange) {\n this._selected = value;\n } else {\n this._selected = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));\n }\n\n this._setSelectedMonth(value);\n }\n private _selected: DateRange | D | null;\n\n /** The minimum selectable date. */\n @Input()\n get minDate(): D | null {\n return this._minDate;\n }\n set minDate(value: D | null) {\n this._minDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));\n }\n private _minDate: D | null;\n\n /** The maximum selectable date. */\n @Input()\n get maxDate(): D | null {\n return this._maxDate;\n }\n set maxDate(value: D | null) {\n this._maxDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));\n }\n private _maxDate: D | null;\n\n /** A function used to filter which dates are selectable. */\n @Input() dateFilter: (date: D) => boolean;\n\n /** Function that can be used to add custom CSS classes to date cells. */\n @Input() dateClass: MatCalendarCellClassFunction;\n\n /** Emits when a new month is selected. */\n @Output() readonly selectedChange: EventEmitter = new EventEmitter();\n\n /** Emits the selected month. This doesn't imply a change on the selected date */\n @Output() readonly monthSelected: EventEmitter = new EventEmitter();\n\n /** Emits when any date is activated. */\n @Output() readonly activeDateChange: EventEmitter = new EventEmitter();\n\n /** The body of calendar table */\n @ViewChild(MatCalendarBody) _matCalendarBody: MatCalendarBody;\n\n /** Grid of calendar cells representing the months of the year. */\n _months: MatCalendarCell[][];\n\n /** The label for this year (e.g. \"2017\"). */\n _yearLabel: string;\n\n /** The month in this year that today falls on. Null if today is in a different year. */\n _todayMonth: number | null;\n\n /**\n * The month in this year that the selected Date falls on.\n * Null if the selected Date is in a different year.\n */\n _selectedMonth: number | null;\n\n constructor(...args: unknown[]);\n\n constructor() {\n if (typeof ngDevMode === 'undefined' || ngDevMode) {\n if (!this._dateAdapter) {\n throw createMissingDateImplError('DateAdapter');\n }\n if (!this._dateFormats) {\n throw createMissingDateImplError('MAT_DATE_FORMATS');\n }\n }\n\n this._activeDate = this._dateAdapter.today();\n }\n\n ngAfterContentInit() {\n this._rerenderSubscription = this._dateAdapter.localeChanges\n .pipe(startWith(null))\n .subscribe(() => this._init());\n }\n\n ngOnDestroy() {\n this._rerenderSubscription.unsubscribe();\n }\n\n /** Handles when a new month is selected. */\n _monthSelected(event: MatCalendarUserEvent) {\n const month = event.value;\n\n const selectedMonth = this._dateAdapter.createDate(\n this._dateAdapter.getYear(this.activeDate),\n month,\n 1,\n );\n this.monthSelected.emit(selectedMonth);\n\n const selectedDate = this._getDateFromMonth(month);\n this.selectedChange.emit(selectedDate);\n }\n\n /**\n * Takes the index of a calendar body cell wrapped in an event as argument. For the date that\n * corresponds to the given cell, set `activeDate` to that date and fire `activeDateChange` with\n * that date.\n *\n * This function is used to match each component's model of the active date with the calendar\n * body cell that was focused. It updates its value of `activeDate` synchronously and updates the\n * parent's value asynchronously via the `activeDateChange` event. The child component receives an\n * updated value asynchronously via the `activeCell` Input.\n */\n _updateActiveDate(event: MatCalendarUserEvent) {\n const month = event.value;\n const oldActiveDate = this._activeDate;\n\n this.activeDate = this._getDateFromMonth(month);\n\n if (this._dateAdapter.compareDate(oldActiveDate, this.activeDate)) {\n this.activeDateChange.emit(this.activeDate);\n }\n }\n\n /** Handles keydown events on the calendar body when calendar is in year view. */\n _handleCalendarBodyKeydown(event: KeyboardEvent): void {\n // TODO(mmalerba): We currently allow keyboard navigation to disabled dates, but just prevent\n // disabled ones from being selected. This may not be ideal, we should look into whether\n // navigation should skip over disabled dates, and if so, how to implement that efficiently.\n\n const oldActiveDate = this._activeDate;\n const isRtl = this._isRtl();\n\n switch (event.keyCode) {\n case LEFT_ARROW:\n this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, isRtl ? 1 : -1);\n break;\n case RIGHT_ARROW:\n this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, isRtl ? -1 : 1);\n break;\n case UP_ARROW:\n this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, -4);\n break;\n case DOWN_ARROW:\n this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, 4);\n break;\n case HOME:\n this.activeDate = this._dateAdapter.addCalendarMonths(\n this._activeDate,\n -this._dateAdapter.getMonth(this._activeDate),\n );\n break;\n case END:\n this.activeDate = this._dateAdapter.addCalendarMonths(\n this._activeDate,\n 11 - this._dateAdapter.getMonth(this._activeDate),\n );\n break;\n case PAGE_UP:\n this.activeDate = this._dateAdapter.addCalendarYears(\n this._activeDate,\n event.altKey ? -10 : -1,\n );\n break;\n case PAGE_DOWN:\n this.activeDate = this._dateAdapter.addCalendarYears(\n this._activeDate,\n event.altKey ? 10 : 1,\n );\n break;\n case ENTER:\n case SPACE:\n // Note that we only prevent the default action here while the selection happens in\n // `keyup` below. We can't do the selection here, because it can cause the calendar to\n // reopen if focus is restored immediately. We also can't call `preventDefault` on `keyup`\n // because it's too late (see #23305).\n this._selectionKeyPressed = true;\n break;\n default:\n // Don't prevent default or focus active cell on keys that we don't explicitly handle.\n return;\n }\n\n if (this._dateAdapter.compareDate(oldActiveDate, this.activeDate)) {\n this.activeDateChange.emit(this.activeDate);\n this._focusActiveCellAfterViewChecked();\n }\n\n // Prevent unexpected default actions such as form submission.\n event.preventDefault();\n }\n\n /** Handles keyup events on the calendar body when calendar is in year view. */\n _handleCalendarBodyKeyup(event: KeyboardEvent): void {\n if (event.keyCode === SPACE || event.keyCode === ENTER) {\n if (this._selectionKeyPressed) {\n this._monthSelected({value: this._dateAdapter.getMonth(this._activeDate), event});\n }\n\n this._selectionKeyPressed = false;\n }\n }\n\n /** Initializes this year view. */\n _init() {\n this._setSelectedMonth(this.selected);\n this._todayMonth = this._getMonthInCurrentYear(this._dateAdapter.today());\n this._yearLabel = this._dateAdapter.getYearName(this.activeDate);\n\n let monthNames = this._dateAdapter.getMonthNames('short');\n // First row of months only contains 5 elements so we can fit the year label on the same row.\n this._months = [\n [0, 1, 2, 3],\n [4, 5, 6, 7],\n [8, 9, 10, 11],\n ].map(row => row.map(month => this._createCellForMonth(month, monthNames[month])));\n this._changeDetectorRef.markForCheck();\n }\n\n /** Focuses the active cell after the microtask queue is empty. */\n _focusActiveCell() {\n this._matCalendarBody._focusActiveCell();\n }\n\n /** Schedules the matCalendarBody to focus the active cell after change detection has run */\n _focusActiveCellAfterViewChecked() {\n this._matCalendarBody._scheduleFocusActiveCellAfterViewChecked();\n }\n\n /**\n * Gets the month in this year that the given Date falls on.\n * Returns null if the given Date is in another year.\n */\n private _getMonthInCurrentYear(date: D | null) {\n return date && this._dateAdapter.getYear(date) == this._dateAdapter.getYear(this.activeDate)\n ? this._dateAdapter.getMonth(date)\n : null;\n }\n\n /**\n * Takes a month and returns a new date in the same day and year as the currently active date.\n * The returned date will have the same month as the argument date.\n */\n private _getDateFromMonth(month: number) {\n const normalizedDate = this._dateAdapter.createDate(\n this._dateAdapter.getYear(this.activeDate),\n month,\n 1,\n );\n\n const daysInMonth = this._dateAdapter.getNumDaysInMonth(normalizedDate);\n\n return this._dateAdapter.createDate(\n this._dateAdapter.getYear(this.activeDate),\n month,\n Math.min(this._dateAdapter.getDate(this.activeDate), daysInMonth),\n );\n }\n\n /** Creates an MatCalendarCell for the given month. */\n private _createCellForMonth(month: number, monthName: string) {\n const date = this._dateAdapter.createDate(this._dateAdapter.getYear(this.activeDate), month, 1);\n const ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.monthYearA11yLabel);\n const cellClasses = this.dateClass ? this.dateClass(date, 'year') : undefined;\n\n return new MatCalendarCell(\n month,\n monthName.toLocaleUpperCase(),\n ariaLabel,\n this._shouldEnableMonth(month),\n cellClasses,\n );\n }\n\n /** Whether the given month is enabled. */\n private _shouldEnableMonth(month: number) {\n const activeYear = this._dateAdapter.getYear(this.activeDate);\n\n if (\n month === undefined ||\n month === null ||\n this._isYearAndMonthAfterMaxDate(activeYear, month) ||\n this._isYearAndMonthBeforeMinDate(activeYear, month)\n ) {\n return false;\n }\n\n if (!this.dateFilter) {\n return true;\n }\n\n const firstOfMonth = this._dateAdapter.createDate(activeYear, month, 1);\n\n // If any date in the month is enabled count the month as enabled.\n for (\n let date = firstOfMonth;\n this._dateAdapter.getMonth(date) == month;\n date = this._dateAdapter.addCalendarDays(date, 1)\n ) {\n if (this.dateFilter(date)) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Tests whether the combination month/year is after this.maxDate, considering\n * just the month and year of this.maxDate\n */\n private _isYearAndMonthAfterMaxDate(year: number, month: number) {\n if (this.maxDate) {\n const maxYear = this._dateAdapter.getYear(this.maxDate);\n const maxMonth = this._dateAdapter.getMonth(this.maxDate);\n\n return year > maxYear || (year === maxYear && month > maxMonth);\n }\n\n return false;\n }\n\n /**\n * Tests whether the combination month/year is before this.minDate, considering\n * just the month and year of this.minDate\n */\n private _isYearAndMonthBeforeMinDate(year: number, month: number) {\n if (this.minDate) {\n const minYear = this._dateAdapter.getYear(this.minDate);\n const minMonth = this._dateAdapter.getMonth(this.minDate);\n\n return year < minYear || (year === minYear && month < minMonth);\n }\n\n return false;\n }\n\n /** Determines whether the user has the RTL layout direction. */\n private _isRtl() {\n return this._dir && this._dir.value === 'rtl';\n }\n\n /** Sets the currently-selected month based on a model value. */\n private _setSelectedMonth(value: DateRange | D | null) {\n if (value instanceof DateRange) {\n this._selectedMonth =\n this._getMonthInCurrentYear(value.start) || this._getMonthInCurrentYear(value.end);\n } else {\n this._selectedMonth = this._getMonthInCurrentYear(value);\n }\n }\n}\n","\n \n \n \n \n \n
\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {CdkPortalOutlet, ComponentPortal, ComponentType, Portal} from '@angular/cdk/portal';\nimport {\n AfterContentInit,\n AfterViewChecked,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n ElementRef,\n EventEmitter,\n Input,\n OnChanges,\n OnDestroy,\n Output,\n SimpleChange,\n SimpleChanges,\n ViewChild,\n ViewEncapsulation,\n inject,\n} from '@angular/core';\nimport {DateAdapter, MAT_DATE_FORMATS, MatDateFormats} from '../core';\nimport {Subject, Subscription} from 'rxjs';\nimport {MatCalendarUserEvent, MatCalendarCellClassFunction} from './calendar-body';\nimport {createMissingDateImplError} from './datepicker-errors';\nimport {MatDatepickerIntl} from './datepicker-intl';\nimport {MatMonthView} from './month-view';\nimport {\n getActiveOffset,\n isSameMultiYearView,\n MatMultiYearView,\n yearsPerPage,\n} from './multi-year-view';\nimport {MatYearView} from './year-view';\nimport {MAT_SINGLE_DATE_SELECTION_MODEL_PROVIDER, DateRange} from './date-selection-model';\nimport {MatIconButton, MatButton} from '../button';\nimport {_IdGenerator, CdkMonitorFocus} from '@angular/cdk/a11y';\nimport {_CdkPrivateStyleLoader, _VisuallyHiddenLoader} from '@angular/cdk/private';\nimport {_getFocusedElementPierceShadowDom} from '@angular/cdk/platform';\n\n/**\n * Possible views for the calendar.\n * @docs-private\n */\nexport type MatCalendarView = 'month' | 'year' | 'multi-year';\n\n/** Default header for MatCalendar */\n@Component({\n selector: 'mat-calendar-header',\n templateUrl: 'calendar-header.html',\n exportAs: 'matCalendarHeader',\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [MatButton, MatIconButton],\n})\nexport class MatCalendarHeader {\n private _intl = inject(MatDatepickerIntl);\n calendar = inject>(MatCalendar);\n private _dateAdapter = inject>(DateAdapter, {optional: true})!;\n private _dateFormats = inject(MAT_DATE_FORMATS, {optional: true})!;\n\n constructor(...args: unknown[]);\n\n constructor() {\n inject(_CdkPrivateStyleLoader).load(_VisuallyHiddenLoader);\n const changeDetectorRef = inject(ChangeDetectorRef);\n this.calendar.stateChanges.subscribe(() => changeDetectorRef.markForCheck());\n }\n\n /** The display text for the current calendar view. */\n get periodButtonText(): string {\n if (this.calendar.currentView == 'month') {\n return this._dateAdapter\n .format(this.calendar.activeDate, this._dateFormats.display.monthYearLabel)\n .toLocaleUpperCase();\n }\n if (this.calendar.currentView == 'year') {\n return this._dateAdapter.getYearName(this.calendar.activeDate);\n }\n\n return this._intl.formatYearRange(...this._formatMinAndMaxYearLabels());\n }\n\n /** The aria description for the current calendar view. */\n get periodButtonDescription(): string {\n if (this.calendar.currentView == 'month') {\n return this._dateAdapter\n .format(this.calendar.activeDate, this._dateFormats.display.monthYearLabel)\n .toLocaleUpperCase();\n }\n if (this.calendar.currentView == 'year') {\n return this._dateAdapter.getYearName(this.calendar.activeDate);\n }\n\n // Format a label for the window of years displayed in the multi-year calendar view. Use\n // `formatYearRangeLabel` because it is TTS friendly.\n return this._intl.formatYearRangeLabel(...this._formatMinAndMaxYearLabels());\n }\n\n /** The `aria-label` for changing the calendar view. */\n get periodButtonLabel(): string {\n return this.calendar.currentView == 'month'\n ? this._intl.switchToMultiYearViewLabel\n : this._intl.switchToMonthViewLabel;\n }\n\n /** The label for the previous button. */\n get prevButtonLabel(): string {\n return {\n 'month': this._intl.prevMonthLabel,\n 'year': this._intl.prevYearLabel,\n 'multi-year': this._intl.prevMultiYearLabel,\n }[this.calendar.currentView];\n }\n\n /** The label for the next button. */\n get nextButtonLabel(): string {\n return {\n 'month': this._intl.nextMonthLabel,\n 'year': this._intl.nextYearLabel,\n 'multi-year': this._intl.nextMultiYearLabel,\n }[this.calendar.currentView];\n }\n\n /** Handles user clicks on the period label. */\n currentPeriodClicked(): void {\n this.calendar.currentView = this.calendar.currentView == 'month' ? 'multi-year' : 'month';\n }\n\n /** Handles user clicks on the previous button. */\n previousClicked(): void {\n this.calendar.activeDate =\n this.calendar.currentView == 'month'\n ? this._dateAdapter.addCalendarMonths(this.calendar.activeDate, -1)\n : this._dateAdapter.addCalendarYears(\n this.calendar.activeDate,\n this.calendar.currentView == 'year' ? -1 : -yearsPerPage,\n );\n }\n\n /** Handles user clicks on the next button. */\n nextClicked(): void {\n this.calendar.activeDate =\n this.calendar.currentView == 'month'\n ? this._dateAdapter.addCalendarMonths(this.calendar.activeDate, 1)\n : this._dateAdapter.addCalendarYears(\n this.calendar.activeDate,\n this.calendar.currentView == 'year' ? 1 : yearsPerPage,\n );\n }\n\n /** Whether the previous period button is enabled. */\n previousEnabled(): boolean {\n if (!this.calendar.minDate) {\n return true;\n }\n return (\n !this.calendar.minDate || !this._isSameView(this.calendar.activeDate, this.calendar.minDate)\n );\n }\n\n /** Whether the next period button is enabled. */\n nextEnabled(): boolean {\n return (\n !this.calendar.maxDate || !this._isSameView(this.calendar.activeDate, this.calendar.maxDate)\n );\n }\n\n /** Whether the two dates represent the same view in the current view mode (month or year). */\n private _isSameView(date1: D, date2: D): boolean {\n if (this.calendar.currentView == 'month') {\n return (\n this._dateAdapter.getYear(date1) == this._dateAdapter.getYear(date2) &&\n this._dateAdapter.getMonth(date1) == this._dateAdapter.getMonth(date2)\n );\n }\n if (this.calendar.currentView == 'year') {\n return this._dateAdapter.getYear(date1) == this._dateAdapter.getYear(date2);\n }\n // Otherwise we are in 'multi-year' view.\n return isSameMultiYearView(\n this._dateAdapter,\n date1,\n date2,\n this.calendar.minDate,\n this.calendar.maxDate,\n );\n }\n\n /**\n * Format two individual labels for the minimum year and maximum year available in the multi-year\n * calendar view. Returns an array of two strings where the first string is the formatted label\n * for the minimum year, and the second string is the formatted label for the maximum year.\n */\n private _formatMinAndMaxYearLabels(): [minYearLabel: string, maxYearLabel: string] {\n // The offset from the active year to the \"slot\" for the starting year is the\n // *actual* first rendered year in the multi-year view, and the last year is\n // just yearsPerPage - 1 away.\n const activeYear = this._dateAdapter.getYear(this.calendar.activeDate);\n const minYearOfPage =\n activeYear -\n getActiveOffset(\n this._dateAdapter,\n this.calendar.activeDate,\n this.calendar.minDate,\n this.calendar.maxDate,\n );\n const maxYearOfPage = minYearOfPage + yearsPerPage - 1;\n const minYearLabel = this._dateAdapter.getYearName(\n this._dateAdapter.createDate(minYearOfPage, 0, 1),\n );\n const maxYearLabel = this._dateAdapter.getYearName(\n this._dateAdapter.createDate(maxYearOfPage, 0, 1),\n );\n\n return [minYearLabel, maxYearLabel];\n }\n\n _periodButtonLabelId = inject(_IdGenerator).getId('mat-calendar-period-label-');\n}\n\n/** A calendar that is used as part of the datepicker. */\n@Component({\n selector: 'mat-calendar',\n templateUrl: 'calendar.html',\n styleUrl: 'calendar.css',\n host: {\n 'class': 'mat-calendar',\n },\n exportAs: 'matCalendar',\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [MAT_SINGLE_DATE_SELECTION_MODEL_PROVIDER],\n imports: [CdkPortalOutlet, CdkMonitorFocus, MatMonthView, MatYearView, MatMultiYearView],\n})\nexport class MatCalendar implements AfterContentInit, AfterViewChecked, OnDestroy, OnChanges {\n private _dateAdapter = inject>(DateAdapter, {optional: true})!;\n private _dateFormats = inject(MAT_DATE_FORMATS, {optional: true});\n private _changeDetectorRef = inject(ChangeDetectorRef);\n private _elementRef = inject>(ElementRef);\n\n /** An input indicating the type of the header component, if set. */\n @Input() headerComponent: ComponentType;\n\n /** A portal containing the header component type for this calendar. */\n _calendarHeaderPortal: Portal;\n\n private _intlChanges: Subscription;\n\n /**\n * Used for scheduling that focus should be moved to the active cell on the next tick.\n * We need to schedule it, rather than do it immediately, because we have to wait\n * for Angular to re-evaluate the view children.\n */\n private _moveFocusOnNextTick = false;\n\n /** A date representing the period (month or year) to start the calendar in. */\n @Input()\n get startAt(): D | null {\n return this._startAt;\n }\n set startAt(value: D | null) {\n this._startAt = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));\n }\n private _startAt: D | null;\n\n /** Whether the calendar should be started in month or year view. */\n @Input() startView: MatCalendarView = 'month';\n\n /** The currently selected date. */\n @Input()\n get selected(): DateRange | D | null {\n return this._selected;\n }\n set selected(value: DateRange | D | null) {\n if (value instanceof DateRange) {\n this._selected = value;\n } else {\n this._selected = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));\n }\n }\n private _selected: DateRange | D | null;\n\n /** The minimum selectable date. */\n @Input()\n get minDate(): D | null {\n return this._minDate;\n }\n set minDate(value: D | null) {\n this._minDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));\n }\n private _minDate: D | null;\n\n /** The maximum selectable date. */\n @Input()\n get maxDate(): D | null {\n return this._maxDate;\n }\n set maxDate(value: D | null) {\n this._maxDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));\n }\n private _maxDate: D | null;\n\n /** Function used to filter which dates are selectable. */\n @Input() dateFilter: (date: D) => boolean;\n\n /** Function that can be used to add custom CSS classes to dates. */\n @Input() dateClass: MatCalendarCellClassFunction;\n\n /** Start of the comparison range. */\n @Input() comparisonStart: D | null;\n\n /** End of the comparison range. */\n @Input() comparisonEnd: D | null;\n\n /** ARIA Accessible name of the `` */\n @Input() startDateAccessibleName: string | null;\n\n /** ARIA Accessible name of the `` */\n @Input() endDateAccessibleName: string | null;\n\n /** Emits when the currently selected date changes. */\n @Output() readonly selectedChange: EventEmitter = new EventEmitter();\n\n /**\n * Emits the year chosen in multiyear view.\n * This doesn't imply a change on the selected date.\n */\n @Output() readonly yearSelected: EventEmitter = new EventEmitter();\n\n /**\n * Emits the month chosen in year view.\n * This doesn't imply a change on the selected date.\n */\n @Output() readonly monthSelected: EventEmitter = new EventEmitter();\n\n /**\n * Emits when the current view changes.\n */\n @Output() readonly viewChanged: EventEmitter = new EventEmitter(\n true,\n );\n\n /** Emits when any date is selected. */\n @Output() readonly _userSelection: EventEmitter> =\n new EventEmitter>();\n\n /** Emits a new date range value when the user completes a drag drop operation. */\n @Output() readonly _userDragDrop = new EventEmitter>>();\n\n /** Reference to the current month view component. */\n @ViewChild(MatMonthView) monthView: MatMonthView;\n\n /** Reference to the current year view component. */\n @ViewChild(MatYearView) yearView: MatYearView;\n\n /** Reference to the current multi-year view component. */\n @ViewChild(MatMultiYearView) multiYearView: MatMultiYearView;\n\n /**\n * The current active date. This determines which time period is shown and which date is\n * highlighted when using keyboard navigation.\n */\n get activeDate(): D {\n return this._clampedActiveDate;\n }\n set activeDate(value: D) {\n this._clampedActiveDate = this._dateAdapter.clampDate(value, this.minDate, this.maxDate);\n this.stateChanges.next();\n this._changeDetectorRef.markForCheck();\n }\n private _clampedActiveDate: D;\n\n /** Whether the calendar is in month view. */\n get currentView(): MatCalendarView {\n return this._currentView;\n }\n set currentView(value: MatCalendarView) {\n const viewChangedResult = this._currentView !== value ? value : null;\n this._currentView = value;\n this._moveFocusOnNextTick = true;\n this._changeDetectorRef.markForCheck();\n if (viewChangedResult) {\n this.viewChanged.emit(viewChangedResult);\n }\n }\n private _currentView: MatCalendarView;\n\n /** Origin of active drag, or null when dragging is not active. */\n protected _activeDrag: MatCalendarUserEvent | null = null;\n\n /**\n * Emits whenever there is a state change that the header may need to respond to.\n */\n readonly stateChanges = new Subject();\n\n constructor(...args: unknown[]);\n\n constructor() {\n if (typeof ngDevMode === 'undefined' || ngDevMode) {\n if (!this._dateAdapter) {\n throw createMissingDateImplError('DateAdapter');\n }\n\n if (!this._dateFormats) {\n throw createMissingDateImplError('MAT_DATE_FORMATS');\n }\n }\n\n this._intlChanges = inject(MatDatepickerIntl).changes.subscribe(() => {\n this._changeDetectorRef.markForCheck();\n this.stateChanges.next();\n });\n }\n\n ngAfterContentInit() {\n this._calendarHeaderPortal = new ComponentPortal(this.headerComponent || MatCalendarHeader);\n this.activeDate = this.startAt || this._dateAdapter.today();\n\n // Assign to the private property since we don't want to move focus on init.\n this._currentView = this.startView;\n }\n\n ngAfterViewChecked() {\n if (this._moveFocusOnNextTick) {\n this._moveFocusOnNextTick = false;\n this.focusActiveCell();\n }\n }\n\n ngOnDestroy() {\n this._intlChanges.unsubscribe();\n this.stateChanges.complete();\n }\n\n ngOnChanges(changes: SimpleChanges) {\n // Ignore date changes that are at a different time on the same day. This fixes issues where\n // the calendar re-renders when there is no meaningful change to [minDate] or [maxDate]\n // (#24435).\n const minDateChange: SimpleChange | undefined =\n changes['minDate'] &&\n !this._dateAdapter.sameDate(changes['minDate'].previousValue, changes['minDate'].currentValue)\n ? changes['minDate']\n : undefined;\n const maxDateChange: SimpleChange | undefined =\n changes['maxDate'] &&\n !this._dateAdapter.sameDate(changes['maxDate'].previousValue, changes['maxDate'].currentValue)\n ? changes['maxDate']\n : undefined;\n\n const changeRequiringRerender = minDateChange || maxDateChange || changes['dateFilter'];\n\n if (changeRequiringRerender && !changeRequiringRerender.firstChange) {\n const view = this._getCurrentViewComponent();\n\n if (view) {\n // Schedule focus to be moved to the active date since re-rendering can blur the active\n // cell (see #29265), however don't do so if focus is outside of the calendar, because it\n // can steal away the user's attention (see #30635).\n if (this._elementRef.nativeElement.contains(_getFocusedElementPierceShadowDom())) {\n this._moveFocusOnNextTick = true;\n }\n\n // We need to `detectChanges` manually here, because the `minDate`, `maxDate` etc. are\n // passed down to the view via data bindings which won't be up-to-date when we call `_init`.\n this._changeDetectorRef.detectChanges();\n view._init();\n }\n }\n\n this.stateChanges.next();\n }\n\n /** Focuses the active date. */\n focusActiveCell() {\n this._getCurrentViewComponent()._focusActiveCell(false);\n }\n\n /** Updates today's date after an update of the active date */\n updateTodaysDate() {\n this._getCurrentViewComponent()._init();\n }\n\n /** Handles date selection in the month view. */\n _dateSelected(event: MatCalendarUserEvent): void {\n const date = event.value;\n\n if (\n this.selected instanceof DateRange ||\n (date && !this._dateAdapter.sameDate(date, this.selected))\n ) {\n this.selectedChange.emit(date);\n }\n\n this._userSelection.emit(event);\n }\n\n /** Handles year selection in the multiyear view. */\n _yearSelectedInMultiYearView(normalizedYear: D) {\n this.yearSelected.emit(normalizedYear);\n }\n\n /** Handles month selection in the year view. */\n _monthSelectedInYearView(normalizedMonth: D) {\n this.monthSelected.emit(normalizedMonth);\n }\n\n /** Handles year/month selection in the multi-year/year views. */\n _goToDateInView(date: D, view: 'month' | 'year' | 'multi-year'): void {\n this.activeDate = date;\n this.currentView = view;\n }\n\n /** Called when the user starts dragging to change a date range. */\n _dragStarted(event: MatCalendarUserEvent) {\n this._activeDrag = event;\n }\n\n /**\n * Called when a drag completes. It may end in cancelation or in the selection\n * of a new range.\n */\n _dragEnded(event: MatCalendarUserEvent | null>) {\n if (!this._activeDrag) return;\n\n if (event.value) {\n this._userDragDrop.emit(event as MatCalendarUserEvent>);\n }\n\n this._activeDrag = null;\n }\n\n /** Returns the component instance that corresponds to the current calendar view. */\n private _getCurrentViewComponent(): MatMonthView | MatYearView | MatMultiYearView {\n // The return type is explicitly written as a union to ensure that the Closure compiler does\n // not optimize calls to _init(). Without the explicit return type, TypeScript narrows it to\n // only the first component type. See https://github.com/angular/components/issues/22996.\n return this.monthView || this.yearView || this.multiYearView;\n }\n}\n","
\n
\n \n {{periodButtonDescription}}\n \n\n
\n\n \n\n \n\n \n
\n
\n","\n\n
\n @switch (currentView) {\n @case ('month') {\n \n }\n\n @case ('year') {\n \n }\n\n @case ('multi-year') {\n \n }\n }\n
\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {_IdGenerator, CdkTrapFocus} from '@angular/cdk/a11y';\nimport {Directionality} from '@angular/cdk/bidi';\nimport {coerceStringArray} from '@angular/cdk/coercion';\nimport {\n DOWN_ARROW,\n ESCAPE,\n hasModifierKey,\n LEFT_ARROW,\n ModifierKey,\n PAGE_DOWN,\n PAGE_UP,\n RIGHT_ARROW,\n UP_ARROW,\n} from '@angular/cdk/keycodes';\nimport {\n FlexibleConnectedPositionStrategy,\n Overlay,\n OverlayConfig,\n OverlayRef,\n ScrollStrategy,\n} from '@angular/cdk/overlay';\nimport {_getFocusedElementPierceShadowDom} from '@angular/cdk/platform';\nimport {CdkPortalOutlet, ComponentPortal, ComponentType, TemplatePortal} from '@angular/cdk/portal';\nimport {DOCUMENT} from '@angular/common';\nimport {\n afterNextRender,\n AfterViewInit,\n ANIMATION_MODULE_TYPE,\n booleanAttribute,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n ComponentRef,\n Directive,\n ElementRef,\n EventEmitter,\n inject,\n InjectionToken,\n Injector,\n Input,\n NgZone,\n OnChanges,\n OnDestroy,\n Output,\n Renderer2,\n SimpleChanges,\n ViewChild,\n ViewContainerRef,\n ViewEncapsulation,\n} from '@angular/core';\nimport {MatButton} from '../button';\nimport {DateAdapter, ThemePalette} from '../core';\nimport {merge, Observable, Subject, Subscription} from 'rxjs';\nimport {filter, take} from 'rxjs/operators';\nimport {MatCalendar, MatCalendarView} from './calendar';\nimport {MatCalendarCellClassFunction, MatCalendarUserEvent} from './calendar-body';\nimport {\n MAT_DATE_RANGE_SELECTION_STRATEGY,\n MatDateRangeSelectionStrategy,\n} from './date-range-selection-strategy';\nimport {\n DateRange,\n ExtractDateTypeFromSelection,\n MatDateSelectionModel,\n} from './date-selection-model';\nimport {createMissingDateImplError} from './datepicker-errors';\nimport {DateFilterFn} from './datepicker-input-base';\nimport {MatDatepickerIntl} from './datepicker-intl';\nimport {_CdkPrivateStyleLoader, _VisuallyHiddenLoader} from '@angular/cdk/private';\n\n/** Injection token that determines the scroll handling while the calendar is open. */\nexport const MAT_DATEPICKER_SCROLL_STRATEGY = new InjectionToken<() => ScrollStrategy>(\n 'mat-datepicker-scroll-strategy',\n {\n providedIn: 'root',\n factory: () => {\n const overlay = inject(Overlay);\n return () => overlay.scrollStrategies.reposition();\n },\n },\n);\n\n/**\n * @docs-private\n * @deprecated No longer used, will be removed.\n * @breaking-change 21.0.0\n */\nexport function MAT_DATEPICKER_SCROLL_STRATEGY_FACTORY(overlay: Overlay): () => ScrollStrategy {\n return () => overlay.scrollStrategies.reposition();\n}\n\n/** Possible positions for the datepicker dropdown along the X axis. */\nexport type DatepickerDropdownPositionX = 'start' | 'end';\n\n/** Possible positions for the datepicker dropdown along the Y axis. */\nexport type DatepickerDropdownPositionY = 'above' | 'below';\n\n/**\n * @docs-private\n * @deprecated No longer used, will be removed.\n * @breaking-change 21.0.0\n */\nexport const MAT_DATEPICKER_SCROLL_STRATEGY_FACTORY_PROVIDER = {\n provide: MAT_DATEPICKER_SCROLL_STRATEGY,\n deps: [Overlay],\n useFactory: MAT_DATEPICKER_SCROLL_STRATEGY_FACTORY,\n};\n\n/**\n * Component used as the content for the datepicker overlay. We use this instead of using\n * MatCalendar directly as the content so we can control the initial focus. This also gives us a\n * place to put additional features of the overlay that are not part of the calendar itself in the\n * future. (e.g. confirmation buttons).\n * @docs-private\n */\n@Component({\n selector: 'mat-datepicker-content',\n templateUrl: 'datepicker-content.html',\n styleUrl: 'datepicker-content.css',\n host: {\n 'class': 'mat-datepicker-content',\n '[class]': 'color ? \"mat-\" + color : \"\"',\n '[class.mat-datepicker-content-touch]': 'datepicker.touchUi',\n '[class.mat-datepicker-content-animations-enabled]': '!_animationsDisabled',\n },\n exportAs: 'matDatepickerContent',\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [CdkTrapFocus, MatCalendar, CdkPortalOutlet, MatButton],\n})\nexport class MatDatepickerContent>\n implements AfterViewInit, OnDestroy\n{\n protected _elementRef = inject>(ElementRef);\n protected _animationsDisabled =\n inject(ANIMATION_MODULE_TYPE, {optional: true}) === 'NoopAnimations';\n private _changeDetectorRef = inject(ChangeDetectorRef);\n private _globalModel = inject>(MatDateSelectionModel);\n private _dateAdapter = inject>(DateAdapter)!;\n private _ngZone = inject(NgZone);\n private _rangeSelectionStrategy = inject>(\n MAT_DATE_RANGE_SELECTION_STRATEGY,\n {optional: true},\n );\n\n private _stateChanges: Subscription | undefined;\n private _model: MatDateSelectionModel;\n private _eventCleanups: (() => void)[] | undefined;\n private _animationFallback: ReturnType | undefined;\n\n /** Reference to the internal calendar component. */\n @ViewChild(MatCalendar) _calendar: MatCalendar;\n\n /**\n * Theme color of the internal calendar. This API is supported in M2 themes\n * only, it has no effect in M3 themes. For color customization in M3, see https://material.angular.dev/components/datepicker/styling.\n *\n * For information on applying color variants in M3, see\n * https://material.angular.dev/guide/material-2-theming#optional-add-backwards-compatibility-styles-for-color-variants\n */\n @Input() color: ThemePalette;\n\n /** Reference to the datepicker that created the overlay. */\n datepicker: MatDatepickerBase;\n\n /** Start of the comparison range. */\n comparisonStart: D | null;\n\n /** End of the comparison range. */\n comparisonEnd: D | null;\n\n /** ARIA Accessible name of the `` */\n startDateAccessibleName: string | null;\n\n /** ARIA Accessible name of the `` */\n endDateAccessibleName: string | null;\n\n /** Whether the datepicker is above or below the input. */\n _isAbove: boolean;\n\n /** Emits when an animation has finished. */\n readonly _animationDone = new Subject();\n\n /** Whether there is an in-progress animation. */\n _isAnimating = false;\n\n /** Text for the close button. */\n _closeButtonText: string;\n\n /** Whether the close button currently has focus. */\n _closeButtonFocused: boolean;\n\n /** Portal with projected action buttons. */\n _actionsPortal: TemplatePortal | null = null;\n\n /** Id of the label for the `role=\"dialog\"` element. */\n _dialogLabelId: string | null;\n\n constructor(...args: unknown[]);\n\n constructor() {\n inject(_CdkPrivateStyleLoader).load(_VisuallyHiddenLoader);\n this._closeButtonText = inject(MatDatepickerIntl).closeCalendarLabel;\n\n if (!this._animationsDisabled) {\n const element = this._elementRef.nativeElement;\n const renderer = inject(Renderer2);\n\n this._eventCleanups = this._ngZone.runOutsideAngular(() => [\n renderer.listen(element, 'animationstart', this._handleAnimationEvent),\n renderer.listen(element, 'animationend', this._handleAnimationEvent),\n renderer.listen(element, 'animationcancel', this._handleAnimationEvent),\n ]);\n }\n }\n\n ngAfterViewInit() {\n this._stateChanges = this.datepicker.stateChanges.subscribe(() => {\n this._changeDetectorRef.markForCheck();\n });\n this._calendar.focusActiveCell();\n }\n\n ngOnDestroy() {\n clearTimeout(this._animationFallback);\n this._eventCleanups?.forEach(cleanup => cleanup());\n this._stateChanges?.unsubscribe();\n this._animationDone.complete();\n }\n\n _handleUserSelection(event: MatCalendarUserEvent) {\n const selection = this._model.selection;\n const value = event.value;\n const isRange = selection instanceof DateRange;\n\n // If we're selecting a range and we have a selection strategy, always pass the value through\n // there. Otherwise don't assign null values to the model, unless we're selecting a range.\n // A null value when picking a range means that the user cancelled the selection (e.g. by\n // pressing escape), whereas when selecting a single value it means that the value didn't\n // change. This isn't very intuitive, but it's here for backwards-compatibility.\n if (isRange && this._rangeSelectionStrategy) {\n const newSelection = this._rangeSelectionStrategy.selectionFinished(\n value,\n selection as unknown as DateRange,\n event.event,\n );\n this._model.updateSelection(newSelection as unknown as S, this);\n } else if (\n value &&\n (isRange || !this._dateAdapter.sameDate(value, selection as unknown as D))\n ) {\n this._model.add(value);\n }\n\n // Delegate closing the overlay to the actions.\n if ((!this._model || this._model.isComplete()) && !this._actionsPortal) {\n this.datepicker.close();\n }\n }\n\n _handleUserDragDrop(event: MatCalendarUserEvent>) {\n this._model.updateSelection(event.value as unknown as S, this);\n }\n\n _startExitAnimation() {\n this._elementRef.nativeElement.classList.add('mat-datepicker-content-exit');\n\n if (this._animationsDisabled) {\n this._animationDone.next();\n } else {\n // Some internal apps disable animations in tests using `* {animation: none !important}`.\n // If that happens, the animation events won't fire and we'll never clean up the overlay.\n // Add a fallback that will fire if the animation doesn't run in a certain amount of time.\n clearTimeout(this._animationFallback);\n this._animationFallback = setTimeout(() => {\n if (!this._isAnimating) {\n this._animationDone.next();\n }\n }, 200);\n }\n }\n\n private _handleAnimationEvent = (event: AnimationEvent) => {\n const element = this._elementRef.nativeElement;\n\n if (event.target !== element || !event.animationName.startsWith('_mat-datepicker-content')) {\n return;\n }\n\n clearTimeout(this._animationFallback);\n this._isAnimating = event.type === 'animationstart';\n element.classList.toggle('mat-datepicker-content-animating', this._isAnimating);\n\n if (!this._isAnimating) {\n this._animationDone.next();\n }\n };\n\n _getSelected() {\n return this._model.selection as unknown as D | DateRange | null;\n }\n\n /** Applies the current pending selection to the global model. */\n _applyPendingSelection() {\n if (this._model !== this._globalModel) {\n this._globalModel.updateSelection(this._model.selection, this);\n }\n }\n\n /**\n * Assigns a new portal containing the datepicker actions.\n * @param portal Portal with the actions to be assigned.\n * @param forceRerender Whether a re-render of the portal should be triggered. This isn't\n * necessary if the portal is assigned during initialization, but it may be required if it's\n * added at a later point.\n */\n _assignActions(portal: TemplatePortal | null, forceRerender: boolean) {\n // If we have actions, clone the model so that we have the ability to cancel the selection,\n // otherwise update the global model directly. Note that we want to assign this as soon as\n // possible, but `_actionsPortal` isn't available in the constructor so we do it in `ngOnInit`.\n this._model = portal ? this._globalModel.clone() : this._globalModel;\n this._actionsPortal = portal;\n\n if (forceRerender) {\n this._changeDetectorRef.detectChanges();\n }\n }\n}\n\n/** Form control that can be associated with a datepicker. */\nexport interface MatDatepickerControl {\n getStartValue(): D | null;\n getThemePalette(): ThemePalette;\n min: D | null;\n max: D | null;\n disabled: boolean;\n dateFilter: DateFilterFn;\n getConnectedOverlayOrigin(): ElementRef;\n getOverlayLabelId(): string | null;\n stateChanges: Observable;\n}\n\n/** A datepicker that can be attached to a {@link MatDatepickerControl}. */\nexport interface MatDatepickerPanel<\n C extends MatDatepickerControl,\n S,\n D = ExtractDateTypeFromSelection,\n> {\n /** Stream that emits whenever the date picker is closed. */\n closedStream: EventEmitter;\n /**\n * Color palette to use on the datepicker's calendar. This API is supported in M2 themes only, it\n * has no effect in M3 themes. For color customization in M3, see https://material.angular.dev/components/datepicker/styling.\n *\n * For information on applying color variants in M3, see\n * https://material.angular.dev/guide/material-2-theming#optional-add-backwards-compatibility-styles-for-color-variants\n */\n color: ThemePalette;\n /** The input element the datepicker is associated with. */\n datepickerInput: C;\n /** Whether the datepicker pop-up should be disabled. */\n disabled: boolean;\n /** The id for the datepicker's calendar. */\n id: string;\n /** Whether the datepicker is open. */\n opened: boolean;\n /** Stream that emits whenever the date picker is opened. */\n openedStream: EventEmitter;\n /** Emits when the datepicker's state changes. */\n stateChanges: Subject;\n /** Opens the datepicker. */\n open(): void;\n /** Register an input with the datepicker. */\n registerInput(input: C): MatDateSelectionModel;\n}\n\n/** Base class for a datepicker. */\n@Directive()\nexport abstract class MatDatepickerBase<\n C extends MatDatepickerControl,\n S,\n D = ExtractDateTypeFromSelection,\n >\n implements MatDatepickerPanel, OnDestroy, OnChanges\n{\n private _overlay = inject(Overlay);\n private _viewContainerRef = inject(ViewContainerRef);\n private _dateAdapter = inject>(DateAdapter, {optional: true})!;\n private _dir = inject(Directionality, {optional: true});\n private _model = inject>(MatDateSelectionModel);\n\n private _scrollStrategy = inject(MAT_DATEPICKER_SCROLL_STRATEGY);\n private _inputStateChanges = Subscription.EMPTY;\n private _document = inject(DOCUMENT);\n\n /** An input indicating the type of the custom header component for the calendar, if set. */\n @Input() calendarHeaderComponent: ComponentType;\n\n /** The date to open the calendar to initially. */\n @Input()\n get startAt(): D | null {\n // If an explicit startAt is set we start there, otherwise we start at whatever the currently\n // selected value is.\n return this._startAt || (this.datepickerInput ? this.datepickerInput.getStartValue() : null);\n }\n set startAt(value: D | null) {\n this._startAt = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));\n }\n private _startAt: D | null;\n\n /** The view that the calendar should start in. */\n @Input() startView: 'month' | 'year' | 'multi-year' = 'month';\n\n /**\n * Theme color of the datepicker's calendar. This API is supported in M2 themes only, it\n * has no effect in M3 themes. For color customization in M3, see https://material.angular.dev/components/datepicker/styling.\n *\n * For information on applying color variants in M3, see\n * https://material.angular.dev/guide/material-2-theming#optional-add-backwards-compatibility-styles-for-color-variants\n */\n @Input()\n get color(): ThemePalette {\n return (\n this._color || (this.datepickerInput ? this.datepickerInput.getThemePalette() : undefined)\n );\n }\n set color(value: ThemePalette) {\n this._color = value;\n }\n _color: ThemePalette;\n\n /**\n * Whether the calendar UI is in touch mode. In touch mode the calendar opens in a dialog rather\n * than a dropdown and elements have more padding to allow for bigger touch targets.\n */\n @Input({transform: booleanAttribute})\n touchUi: boolean = false;\n\n /** Whether the datepicker pop-up should be disabled. */\n @Input({transform: booleanAttribute})\n get disabled(): boolean {\n return this._disabled === undefined && this.datepickerInput\n ? this.datepickerInput.disabled\n : !!this._disabled;\n }\n set disabled(value: boolean) {\n if (value !== this._disabled) {\n this._disabled = value;\n this.stateChanges.next(undefined);\n }\n }\n private _disabled: boolean;\n\n /** Preferred position of the datepicker in the X axis. */\n @Input()\n xPosition: DatepickerDropdownPositionX = 'start';\n\n /** Preferred position of the datepicker in the Y axis. */\n @Input()\n yPosition: DatepickerDropdownPositionY = 'below';\n\n /**\n * Whether to restore focus to the previously-focused element when the calendar is closed.\n * Note that automatic focus restoration is an accessibility feature and it is recommended that\n * you provide your own equivalent, if you decide to turn it off.\n */\n @Input({transform: booleanAttribute})\n restoreFocus: boolean = true;\n\n /**\n * Emits selected year in multiyear view.\n * This doesn't imply a change on the selected date.\n */\n @Output() readonly yearSelected: EventEmitter = new EventEmitter();\n\n /**\n * Emits selected month in year view.\n * This doesn't imply a change on the selected date.\n */\n @Output() readonly monthSelected: EventEmitter = new EventEmitter();\n\n /**\n * Emits when the current view changes.\n */\n @Output() readonly viewChanged: EventEmitter = new EventEmitter(\n true,\n );\n\n /** Function that can be used to add custom CSS classes to dates. */\n @Input() dateClass: MatCalendarCellClassFunction;\n\n /** Emits when the datepicker has been opened. */\n @Output('opened') readonly openedStream = new EventEmitter();\n\n /** Emits when the datepicker has been closed. */\n @Output('closed') readonly closedStream = new EventEmitter();\n\n /** Classes to be passed to the date picker panel. */\n @Input()\n get panelClass(): string | string[] {\n return this._panelClass;\n }\n set panelClass(value: string | string[]) {\n this._panelClass = coerceStringArray(value);\n }\n private _panelClass: string[];\n\n /** Whether the calendar is open. */\n @Input({transform: booleanAttribute})\n get opened(): boolean {\n return this._opened;\n }\n set opened(value: boolean) {\n if (value) {\n this.open();\n } else {\n this.close();\n }\n }\n private _opened = false;\n\n /** The id for the datepicker calendar. */\n id: string = inject(_IdGenerator).getId('mat-datepicker-');\n\n /** The minimum selectable date. */\n _getMinDate(): D | null {\n return this.datepickerInput && this.datepickerInput.min;\n }\n\n /** The maximum selectable date. */\n _getMaxDate(): D | null {\n return this.datepickerInput && this.datepickerInput.max;\n }\n\n _getDateFilter(): DateFilterFn {\n return this.datepickerInput && this.datepickerInput.dateFilter;\n }\n\n /** A reference to the overlay into which we've rendered the calendar. */\n private _overlayRef: OverlayRef | null;\n\n /** Reference to the component instance rendered in the overlay. */\n private _componentRef: ComponentRef> | null;\n\n /** The element that was focused before the datepicker was opened. */\n private _focusedElementBeforeOpen: HTMLElement | null = null;\n\n /** Unique class that will be added to the backdrop so that the test harnesses can look it up. */\n private _backdropHarnessClass = `${this.id}-backdrop`;\n\n /** Currently-registered actions portal. */\n private _actionsPortal: TemplatePortal | null;\n\n /** The input element this datepicker is associated with. */\n datepickerInput: C;\n\n /** Emits when the datepicker's state changes. */\n readonly stateChanges = new Subject();\n\n private _injector = inject(Injector);\n\n private readonly _changeDetectorRef = inject(ChangeDetectorRef);\n\n constructor(...args: unknown[]);\n\n constructor() {\n if (!this._dateAdapter && (typeof ngDevMode === 'undefined' || ngDevMode)) {\n throw createMissingDateImplError('DateAdapter');\n }\n\n this._model.selectionChanged.subscribe(() => {\n this._changeDetectorRef.markForCheck();\n });\n }\n\n ngOnChanges(changes: SimpleChanges) {\n const positionChange = changes['xPosition'] || changes['yPosition'];\n\n if (positionChange && !positionChange.firstChange && this._overlayRef) {\n const positionStrategy = this._overlayRef.getConfig().positionStrategy;\n\n if (positionStrategy instanceof FlexibleConnectedPositionStrategy) {\n this._setConnectedPositions(positionStrategy);\n\n if (this.opened) {\n this._overlayRef.updatePosition();\n }\n }\n }\n\n this.stateChanges.next(undefined);\n }\n\n ngOnDestroy() {\n this._destroyOverlay();\n this.close();\n this._inputStateChanges.unsubscribe();\n this.stateChanges.complete();\n }\n\n /** Selects the given date */\n select(date: D): void {\n this._model.add(date);\n }\n\n /** Emits the selected year in multiyear view */\n _selectYear(normalizedYear: D): void {\n this.yearSelected.emit(normalizedYear);\n }\n\n /** Emits selected month in year view */\n _selectMonth(normalizedMonth: D): void {\n this.monthSelected.emit(normalizedMonth);\n }\n\n /** Emits changed view */\n _viewChanged(view: MatCalendarView): void {\n this.viewChanged.emit(view);\n }\n\n /**\n * Register an input with this datepicker.\n * @param input The datepicker input to register with this datepicker.\n * @returns Selection model that the input should hook itself up to.\n */\n registerInput(input: C): MatDateSelectionModel {\n if (this.datepickerInput && (typeof ngDevMode === 'undefined' || ngDevMode)) {\n throw Error('A MatDatepicker can only be associated with a single input.');\n }\n this._inputStateChanges.unsubscribe();\n this.datepickerInput = input;\n this._inputStateChanges = input.stateChanges.subscribe(() => this.stateChanges.next(undefined));\n return this._model;\n }\n\n /**\n * Registers a portal containing action buttons with the datepicker.\n * @param portal Portal to be registered.\n */\n registerActions(portal: TemplatePortal): void {\n if (this._actionsPortal && (typeof ngDevMode === 'undefined' || ngDevMode)) {\n throw Error('A MatDatepicker can only be associated with a single actions row.');\n }\n this._actionsPortal = portal;\n this._componentRef?.instance._assignActions(portal, true);\n }\n\n /**\n * Removes a portal containing action buttons from the datepicker.\n * @param portal Portal to be removed.\n */\n removeActions(portal: TemplatePortal): void {\n if (portal === this._actionsPortal) {\n this._actionsPortal = null;\n this._componentRef?.instance._assignActions(null, true);\n }\n }\n\n /** Open the calendar. */\n open(): void {\n // Skip reopening if there's an in-progress animation to avoid overlapping\n // sequences which can cause \"changed after checked\" errors. See #25837.\n if (this._opened || this.disabled || this._componentRef?.instance._isAnimating) {\n return;\n }\n\n if (!this.datepickerInput && (typeof ngDevMode === 'undefined' || ngDevMode)) {\n throw Error('Attempted to open an MatDatepicker with no associated input.');\n }\n\n this._focusedElementBeforeOpen = _getFocusedElementPierceShadowDom();\n this._openOverlay();\n this._opened = true;\n this.openedStream.emit();\n }\n\n /** Close the calendar. */\n close(): void {\n // Skip reopening if there's an in-progress animation to avoid overlapping\n // sequences which can cause \"changed after checked\" errors. See #25837.\n if (!this._opened || this._componentRef?.instance._isAnimating) {\n return;\n }\n\n const canRestoreFocus =\n this.restoreFocus &&\n this._focusedElementBeforeOpen &&\n typeof this._focusedElementBeforeOpen.focus === 'function';\n\n const completeClose = () => {\n // The `_opened` could've been reset already if\n // we got two events in quick succession.\n if (this._opened) {\n this._opened = false;\n this.closedStream.emit();\n }\n };\n\n if (this._componentRef) {\n const {instance, location} = this._componentRef;\n instance._animationDone.pipe(take(1)).subscribe(() => {\n const activeElement = this._document.activeElement;\n\n // Since we restore focus after the exit animation, we have to check that\n // the user didn't move focus themselves inside the `close` handler.\n if (\n canRestoreFocus &&\n (!activeElement ||\n activeElement === this._document.activeElement ||\n location.nativeElement.contains(activeElement))\n ) {\n this._focusedElementBeforeOpen!.focus();\n }\n\n this._focusedElementBeforeOpen = null;\n this._destroyOverlay();\n });\n instance._startExitAnimation();\n }\n\n if (canRestoreFocus) {\n // Because IE moves focus asynchronously, we can't count on it being restored before we've\n // marked the datepicker as closed. If the event fires out of sequence and the element that\n // we're refocusing opens the datepicker on focus, the user could be stuck with not being\n // able to close the calendar at all. We work around it by making the logic, that marks\n // the datepicker as closed, async as well.\n setTimeout(completeClose);\n } else {\n completeClose();\n }\n }\n\n /** Applies the current pending selection on the overlay to the model. */\n _applyPendingSelection() {\n this._componentRef?.instance?._applyPendingSelection();\n }\n\n /** Forwards relevant values from the datepicker to the datepicker content inside the overlay. */\n protected _forwardContentValues(instance: MatDatepickerContent) {\n instance.datepicker = this;\n instance.color = this.color;\n instance._dialogLabelId = this.datepickerInput.getOverlayLabelId();\n instance._assignActions(this._actionsPortal, false);\n }\n\n /** Opens the overlay with the calendar. */\n private _openOverlay(): void {\n this._destroyOverlay();\n\n const isDialog = this.touchUi;\n const portal = new ComponentPortal>(\n MatDatepickerContent,\n this._viewContainerRef,\n );\n const overlayRef = (this._overlayRef = this._overlay.create(\n new OverlayConfig({\n positionStrategy: isDialog ? this._getDialogStrategy() : this._getDropdownStrategy(),\n hasBackdrop: true,\n backdropClass: [\n isDialog ? 'cdk-overlay-dark-backdrop' : 'mat-overlay-transparent-backdrop',\n this._backdropHarnessClass,\n ],\n direction: this._dir || 'ltr',\n scrollStrategy: isDialog ? this._overlay.scrollStrategies.block() : this._scrollStrategy(),\n panelClass: `mat-datepicker-${isDialog ? 'dialog' : 'popup'}`,\n }),\n ));\n\n this._getCloseStream(overlayRef).subscribe(event => {\n if (event) {\n event.preventDefault();\n }\n this.close();\n });\n\n // The `preventDefault` call happens inside the calendar as well, however focus moves into\n // it inside a timeout which can give browsers a chance to fire off a keyboard event in-between\n // that can scroll the page (see #24969). Always block default actions of arrow keys for the\n // entire overlay so the page doesn't get scrolled by accident.\n overlayRef.keydownEvents().subscribe(event => {\n const keyCode = event.keyCode;\n\n if (\n keyCode === UP_ARROW ||\n keyCode === DOWN_ARROW ||\n keyCode === LEFT_ARROW ||\n keyCode === RIGHT_ARROW ||\n keyCode === PAGE_UP ||\n keyCode === PAGE_DOWN\n ) {\n event.preventDefault();\n }\n });\n\n this._componentRef = overlayRef.attach(portal);\n this._forwardContentValues(this._componentRef.instance);\n\n // Update the position once the calendar has rendered. Only relevant in dropdown mode.\n if (!isDialog) {\n afterNextRender(\n () => {\n overlayRef.updatePosition();\n },\n {injector: this._injector},\n );\n }\n }\n\n /** Destroys the current overlay. */\n private _destroyOverlay() {\n if (this._overlayRef) {\n this._overlayRef.dispose();\n this._overlayRef = this._componentRef = null;\n }\n }\n\n /** Gets a position strategy that will open the calendar as a dropdown. */\n private _getDialogStrategy() {\n return this._overlay.position().global().centerHorizontally().centerVertically();\n }\n\n /** Gets a position strategy that will open the calendar as a dropdown. */\n private _getDropdownStrategy() {\n const strategy = this._overlay\n .position()\n .flexibleConnectedTo(this.datepickerInput.getConnectedOverlayOrigin())\n .withTransformOriginOn('.mat-datepicker-content')\n .withFlexibleDimensions(false)\n .withViewportMargin(8)\n .withLockedPosition();\n\n return this._setConnectedPositions(strategy);\n }\n\n /** Sets the positions of the datepicker in dropdown mode based on the current configuration. */\n private _setConnectedPositions(strategy: FlexibleConnectedPositionStrategy) {\n const primaryX = this.xPosition === 'end' ? 'end' : 'start';\n const secondaryX = primaryX === 'start' ? 'end' : 'start';\n const primaryY = this.yPosition === 'above' ? 'bottom' : 'top';\n const secondaryY = primaryY === 'top' ? 'bottom' : 'top';\n\n return strategy.withPositions([\n {\n originX: primaryX,\n originY: secondaryY,\n overlayX: primaryX,\n overlayY: primaryY,\n },\n {\n originX: primaryX,\n originY: primaryY,\n overlayX: primaryX,\n overlayY: secondaryY,\n },\n {\n originX: secondaryX,\n originY: secondaryY,\n overlayX: secondaryX,\n overlayY: primaryY,\n },\n {\n originX: secondaryX,\n originY: primaryY,\n overlayX: secondaryX,\n overlayY: secondaryY,\n },\n ]);\n }\n\n /** Gets an observable that will emit when the overlay is supposed to be closed. */\n private _getCloseStream(overlayRef: OverlayRef) {\n const ctrlShiftMetaModifiers: ModifierKey[] = ['ctrlKey', 'shiftKey', 'metaKey'];\n return merge(\n overlayRef.backdropClick(),\n overlayRef.detachments(),\n overlayRef.keydownEvents().pipe(\n filter(event => {\n // Closing on alt + up is only valid when there's an input associated with the datepicker.\n return (\n (event.keyCode === ESCAPE && !hasModifierKey(event)) ||\n (this.datepickerInput &&\n hasModifierKey(event, 'altKey') &&\n event.keyCode === UP_ARROW &&\n ctrlShiftMetaModifiers.every(\n (modifier: ModifierKey) => !hasModifierKey(event, modifier),\n ))\n );\n }),\n ),\n );\n }\n}\n","\n \n\n \n\n \n {{ _closeButtonText }}\n\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core';\nimport {MatDatepickerBase, MatDatepickerControl} from './datepicker-base';\nimport {MAT_SINGLE_DATE_SELECTION_MODEL_PROVIDER} from './date-selection-model';\n\n// TODO(mmalerba): We use a component instead of a directive here so the user can use implicit\n// template reference variables (e.g. #d vs #d=\"matDatepicker\"). We can change this to a directive\n// if angular adds support for `exportAs: '$implicit'` on directives.\n/** Component responsible for managing the datepicker popup/dialog. */\n@Component({\n selector: 'mat-datepicker',\n template: '',\n exportAs: 'matDatepicker',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n providers: [\n MAT_SINGLE_DATE_SELECTION_MODEL_PROVIDER,\n {provide: MatDatepickerBase, useExisting: MatDatepicker},\n ],\n})\nexport class MatDatepicker extends MatDatepickerBase, D | null, D> {}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {DOWN_ARROW, hasModifierKey, ModifierKey} from '@angular/cdk/keycodes';\nimport {\n Directive,\n ElementRef,\n EventEmitter,\n Input,\n OnDestroy,\n Output,\n AfterViewInit,\n OnChanges,\n SimpleChanges,\n booleanAttribute,\n inject,\n} from '@angular/core';\nimport {\n AbstractControl,\n ControlValueAccessor,\n ValidationErrors,\n Validator,\n ValidatorFn,\n} from '@angular/forms';\nimport {DateAdapter, MAT_DATE_FORMATS, MatDateFormats, ThemePalette} from '../core';\nimport {Subscription, Subject} from 'rxjs';\nimport {createMissingDateImplError} from './datepicker-errors';\nimport {\n ExtractDateTypeFromSelection,\n MatDateSelectionModel,\n DateSelectionModelChange,\n} from './date-selection-model';\n\n/**\n * An event used for datepicker input and change events. We don't always have access to a native\n * input or change event because the event may have been triggered by the user clicking on the\n * calendar popup. For consistency, we always use MatDatepickerInputEvent instead.\n */\nexport class MatDatepickerInputEvent {\n /** The new value for the target datepicker input. */\n value: D | null;\n\n constructor(\n /** Reference to the datepicker input component that emitted the event. */\n public target: MatDatepickerInputBase,\n /** Reference to the native input element associated with the datepicker input. */\n public targetElement: HTMLElement,\n ) {\n this.value = this.target.value;\n }\n}\n\n/**\n * Function that can be used to filter out dates from a calendar.\n * Datepicker can sometimes receive a null value as input for the date argument.\n * This doesn't represent a \"null date\" but rather signifies that no date has been selected yet in the calendar.\n */\nexport type DateFilterFn = (date: D | null) => boolean;\n\n/**\n * Partial representation of `MatFormField` that is used for backwards-compatibility\n * between the legacy and non-legacy variants.\n */\nexport interface _MatFormFieldPartial {\n getConnectedOverlayOrigin(): ElementRef;\n getLabelId(): string | null;\n color: ThemePalette;\n _elementRef: ElementRef;\n _shouldLabelFloat(): boolean;\n _hasFloatingLabel(): boolean;\n _labelId: string;\n}\n\n/** Base class for datepicker inputs. */\n@Directive()\nexport abstract class MatDatepickerInputBase>\n implements ControlValueAccessor, AfterViewInit, OnChanges, OnDestroy, Validator\n{\n protected _elementRef = inject>(ElementRef);\n _dateAdapter = inject>(DateAdapter, {optional: true})!;\n private _dateFormats = inject(MAT_DATE_FORMATS, {optional: true})!;\n\n /** Whether the component has been initialized. */\n private _isInitialized: boolean;\n\n /** The value of the input. */\n @Input()\n get value(): D | null {\n return this._model ? this._getValueFromModel(this._model.selection) : this._pendingValue;\n }\n set value(value: any) {\n this._assignValueProgrammatically(value);\n }\n protected _model: MatDateSelectionModel | undefined;\n\n /** Whether the datepicker-input is disabled. */\n @Input({transform: booleanAttribute})\n get disabled(): boolean {\n return !!this._disabled || this._parentDisabled();\n }\n set disabled(value: boolean) {\n const newValue = value;\n const element = this._elementRef.nativeElement;\n\n if (this._disabled !== newValue) {\n this._disabled = newValue;\n this.stateChanges.next(undefined);\n }\n\n // We need to null check the `blur` method, because it's undefined during SSR.\n // In Ivy static bindings are invoked earlier, before the element is attached to the DOM.\n // This can cause an error to be thrown in some browsers (IE/Edge) which assert that the\n // element has been inserted.\n if (newValue && this._isInitialized && element.blur) {\n // Normally, native input elements automatically blur if they turn disabled. This behavior\n // is problematic, because it would mean that it triggers another change detection cycle,\n // which then causes a changed after checked error if the input element was focused before.\n element.blur();\n }\n }\n private _disabled: boolean;\n\n /** Emits when a `change` event is fired on this ``. */\n @Output() readonly dateChange: EventEmitter> = new EventEmitter<\n MatDatepickerInputEvent\n >();\n\n /** Emits when an `input` event is fired on this ``. */\n @Output() readonly dateInput: EventEmitter> = new EventEmitter<\n MatDatepickerInputEvent\n >();\n\n /** Emits when the internal state has changed */\n readonly stateChanges = new Subject();\n\n _onTouched = () => {};\n _validatorOnChange = () => {};\n\n private _cvaOnChange: (value: any) => void = () => {};\n private _valueChangesSubscription = Subscription.EMPTY;\n private _localeSubscription = Subscription.EMPTY;\n\n /**\n * Since the value is kept on the model which is assigned in an Input,\n * we might get a value before we have a model. This property keeps track\n * of the value until we have somewhere to assign it.\n */\n private _pendingValue: D | null;\n\n /** The form control validator for whether the input parses. */\n private _parseValidator: ValidatorFn = (): ValidationErrors | null => {\n return this._lastValueValid\n ? null\n : {'matDatepickerParse': {'text': this._elementRef.nativeElement.value}};\n };\n\n /** The form control validator for the date filter. */\n private _filterValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {\n const controlValue = this._dateAdapter.getValidDateOrNull(\n this._dateAdapter.deserialize(control.value),\n );\n return !controlValue || this._matchesFilter(controlValue)\n ? null\n : {'matDatepickerFilter': true};\n };\n\n /** The form control validator for the min date. */\n private _minValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {\n const controlValue = this._dateAdapter.getValidDateOrNull(\n this._dateAdapter.deserialize(control.value),\n );\n const min = this._getMinDate();\n return !min || !controlValue || this._dateAdapter.compareDate(min, controlValue) <= 0\n ? null\n : {'matDatepickerMin': {'min': min, 'actual': controlValue}};\n };\n\n /** The form control validator for the max date. */\n private _maxValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {\n const controlValue = this._dateAdapter.getValidDateOrNull(\n this._dateAdapter.deserialize(control.value),\n );\n const max = this._getMaxDate();\n return !max || !controlValue || this._dateAdapter.compareDate(max, controlValue) >= 0\n ? null\n : {'matDatepickerMax': {'max': max, 'actual': controlValue}};\n };\n\n /** Gets the base validator functions. */\n protected _getValidators(): ValidatorFn[] {\n return [this._parseValidator, this._minValidator, this._maxValidator, this._filterValidator];\n }\n\n /** Gets the minimum date for the input. Used for validation. */\n abstract _getMinDate(): D | null;\n\n /** Gets the maximum date for the input. Used for validation. */\n abstract _getMaxDate(): D | null;\n\n /** Gets the date filter function. Used for validation. */\n protected abstract _getDateFilter(): DateFilterFn | undefined;\n\n /** Registers a date selection model with the input. */\n _registerModel(model: MatDateSelectionModel): void {\n this._model = model;\n this._valueChangesSubscription.unsubscribe();\n\n if (this._pendingValue) {\n this._assignValue(this._pendingValue);\n }\n\n this._valueChangesSubscription = this._model.selectionChanged.subscribe(event => {\n if (this._shouldHandleChangeEvent(event)) {\n const value = this._getValueFromModel(event.selection);\n this._lastValueValid = this._isValidValue(value);\n this._cvaOnChange(value);\n this._onTouched();\n this._formatValue(value);\n this.dateInput.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement));\n this.dateChange.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement));\n }\n });\n }\n\n /** Opens the popup associated with the input. */\n protected abstract _openPopup(): void;\n\n /** Assigns a value to the input's model. */\n protected abstract _assignValueToModel(model: D | null): void;\n\n /** Converts a value from the model into a native value for the input. */\n protected abstract _getValueFromModel(modelValue: S): D | null;\n\n /** Combined form control validator for this input. */\n protected abstract _validator: ValidatorFn | null;\n\n /** Predicate that determines whether the input should handle a particular change event. */\n protected abstract _shouldHandleChangeEvent(event: DateSelectionModelChange): boolean;\n\n /** Whether the last value set on the input was valid. */\n protected _lastValueValid = false;\n\n constructor(...args: unknown[]);\n\n constructor() {\n if (typeof ngDevMode === 'undefined' || ngDevMode) {\n if (!this._dateAdapter) {\n throw createMissingDateImplError('DateAdapter');\n }\n if (!this._dateFormats) {\n throw createMissingDateImplError('MAT_DATE_FORMATS');\n }\n }\n\n // Update the displayed date when the locale changes.\n this._localeSubscription = this._dateAdapter.localeChanges.subscribe(() => {\n this._assignValueProgrammatically(this.value);\n });\n }\n\n ngAfterViewInit() {\n this._isInitialized = true;\n }\n\n ngOnChanges(changes: SimpleChanges) {\n if (dateInputsHaveChanged(changes, this._dateAdapter)) {\n this.stateChanges.next(undefined);\n }\n }\n\n ngOnDestroy() {\n this._valueChangesSubscription.unsubscribe();\n this._localeSubscription.unsubscribe();\n this.stateChanges.complete();\n }\n\n /** @docs-private */\n registerOnValidatorChange(fn: () => void): void {\n this._validatorOnChange = fn;\n }\n\n /** @docs-private */\n validate(c: AbstractControl): ValidationErrors | null {\n return this._validator ? this._validator(c) : null;\n }\n\n // Implemented as part of ControlValueAccessor.\n writeValue(value: D): void {\n this._assignValueProgrammatically(value);\n }\n\n // Implemented as part of ControlValueAccessor.\n registerOnChange(fn: (value: any) => void): void {\n this._cvaOnChange = fn;\n }\n\n // Implemented as part of ControlValueAccessor.\n registerOnTouched(fn: () => void): void {\n this._onTouched = fn;\n }\n\n // Implemented as part of ControlValueAccessor.\n setDisabledState(isDisabled: boolean): void {\n this.disabled = isDisabled;\n }\n\n _onKeydown(event: KeyboardEvent) {\n const ctrlShiftMetaModifiers: ModifierKey[] = ['ctrlKey', 'shiftKey', 'metaKey'];\n const isAltDownArrow =\n hasModifierKey(event, 'altKey') &&\n event.keyCode === DOWN_ARROW &&\n ctrlShiftMetaModifiers.every((modifier: ModifierKey) => !hasModifierKey(event, modifier));\n\n if (isAltDownArrow && !this._elementRef.nativeElement.readOnly) {\n this._openPopup();\n event.preventDefault();\n }\n }\n\n _onInput(value: string) {\n const lastValueWasValid = this._lastValueValid;\n let date = this._dateAdapter.parse(value, this._dateFormats.parse.dateInput);\n this._lastValueValid = this._isValidValue(date);\n date = this._dateAdapter.getValidDateOrNull(date);\n const hasChanged = !this._dateAdapter.sameDate(date, this.value);\n\n // We need to fire the CVA change event for all\n // nulls, otherwise the validators won't run.\n if (!date || hasChanged) {\n this._cvaOnChange(date);\n } else {\n // Call the CVA change handler for invalid values\n // since this is what marks the control as dirty.\n if (value && !this.value) {\n this._cvaOnChange(date);\n }\n\n if (lastValueWasValid !== this._lastValueValid) {\n this._validatorOnChange();\n }\n }\n\n if (hasChanged) {\n this._assignValue(date);\n this.dateInput.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement));\n }\n }\n\n _onChange() {\n this.dateChange.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement));\n }\n\n /** Handles blur events on the input. */\n _onBlur() {\n // Reformat the input only if we have a valid value.\n if (this.value) {\n this._formatValue(this.value);\n }\n\n this._onTouched();\n }\n\n /** Formats a value and sets it on the input element. */\n protected _formatValue(value: D | null) {\n this._elementRef.nativeElement.value =\n value != null ? this._dateAdapter.format(value, this._dateFormats.display.dateInput) : '';\n }\n\n /** Assigns a value to the model. */\n private _assignValue(value: D | null) {\n // We may get some incoming values before the model was\n // assigned. Save the value so that we can assign it later.\n if (this._model) {\n this._assignValueToModel(value);\n this._pendingValue = null;\n } else {\n this._pendingValue = value;\n }\n }\n\n /** Whether a value is considered valid. */\n private _isValidValue(value: D | null): boolean {\n return !value || this._dateAdapter.isValid(value);\n }\n\n /**\n * Checks whether a parent control is disabled. This is in place so that it can be overridden\n * by inputs extending this one which can be placed inside of a group that can be disabled.\n */\n protected _parentDisabled() {\n return false;\n }\n\n /** Programmatically assigns a value to the input. */\n protected _assignValueProgrammatically(value: D | null) {\n value = this._dateAdapter.deserialize(value);\n this._lastValueValid = this._isValidValue(value);\n value = this._dateAdapter.getValidDateOrNull(value);\n this._assignValue(value);\n this._formatValue(value);\n }\n\n /** Gets whether a value matches the current date filter. */\n _matchesFilter(value: D | null): boolean {\n const filter = this._getDateFilter();\n return !filter || filter(value);\n }\n}\n\n/**\n * Checks whether the `SimpleChanges` object from an `ngOnChanges`\n * callback has any changes, accounting for date objects.\n */\nexport function dateInputsHaveChanged(\n changes: SimpleChanges,\n adapter: DateAdapter,\n): boolean {\n const keys = Object.keys(changes);\n\n for (let key of keys) {\n const {previousValue, currentValue} = changes[key];\n\n if (adapter.isDateInstance(previousValue) && adapter.isDateInstance(currentValue)) {\n if (!adapter.sameDate(previousValue, currentValue)) {\n return true;\n }\n } else {\n return true;\n }\n }\n\n return false;\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {Directive, ElementRef, forwardRef, Input, OnDestroy, signal, inject} from '@angular/core';\nimport {NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidatorFn, Validators} from '@angular/forms';\nimport {ThemePalette} from '../core';\nimport {MAT_FORM_FIELD} from '../form-field';\nimport {MAT_INPUT_VALUE_ACCESSOR} from '../input';\nimport {Subscription} from 'rxjs';\nimport {DateSelectionModelChange} from './date-selection-model';\nimport {MatDatepickerControl, MatDatepickerPanel} from './datepicker-base';\nimport {_MatFormFieldPartial, DateFilterFn, MatDatepickerInputBase} from './datepicker-input-base';\n\n/** @docs-private */\nexport const MAT_DATEPICKER_VALUE_ACCESSOR: any = {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => MatDatepickerInput),\n multi: true,\n};\n\n/** @docs-private */\nexport const MAT_DATEPICKER_VALIDATORS: any = {\n provide: NG_VALIDATORS,\n useExisting: forwardRef(() => MatDatepickerInput),\n multi: true,\n};\n\n/** Directive used to connect an input to a MatDatepicker. */\n@Directive({\n selector: 'input[matDatepicker]',\n providers: [\n MAT_DATEPICKER_VALUE_ACCESSOR,\n MAT_DATEPICKER_VALIDATORS,\n {provide: MAT_INPUT_VALUE_ACCESSOR, useExisting: MatDatepickerInput},\n ],\n host: {\n 'class': 'mat-datepicker-input',\n '[attr.aria-haspopup]': '_datepicker ? \"dialog\" : null',\n '[attr.aria-owns]': '_ariaOwns()',\n '[attr.min]': 'min ? _dateAdapter.toIso8601(min) : null',\n '[attr.max]': 'max ? _dateAdapter.toIso8601(max) : null',\n // Used by the test harness to tie this input to its calendar. We can't depend on\n // `aria-owns` for this, because it's only defined while the calendar is open.\n '[attr.data-mat-calendar]': '_datepicker ? _datepicker.id : null',\n '[disabled]': 'disabled',\n '(input)': '_onInput($event.target.value)',\n '(change)': '_onChange()',\n '(blur)': '_onBlur()',\n '(keydown)': '_onKeydown($event)',\n },\n exportAs: 'matDatepickerInput',\n})\nexport class MatDatepickerInput\n extends MatDatepickerInputBase\n implements MatDatepickerControl, OnDestroy\n{\n private _formField = inject<_MatFormFieldPartial>(MAT_FORM_FIELD, {optional: true});\n private _closedSubscription = Subscription.EMPTY;\n private _openedSubscription = Subscription.EMPTY;\n\n /** The datepicker that this input is associated with. */\n @Input()\n set matDatepicker(datepicker: MatDatepickerPanel, D | null, D>) {\n if (datepicker) {\n this._datepicker = datepicker;\n this._ariaOwns.set(datepicker.opened ? datepicker.id : null);\n this._closedSubscription = datepicker.closedStream.subscribe(() => {\n this._onTouched();\n this._ariaOwns.set(null);\n });\n this._openedSubscription = datepicker.openedStream.subscribe(() => {\n this._ariaOwns.set(datepicker.id);\n });\n this._registerModel(datepicker.registerInput(this));\n }\n }\n _datepicker: MatDatepickerPanel, D | null, D>;\n\n /** The id of the panel owned by this input. */\n protected _ariaOwns = signal(null);\n\n /** The minimum valid date. */\n @Input()\n get min(): D | null {\n return this._min;\n }\n set min(value: D | null) {\n const validValue = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));\n\n if (!this._dateAdapter.sameDate(validValue, this._min)) {\n this._min = validValue;\n this._validatorOnChange();\n }\n }\n private _min: D | null;\n\n /** The maximum valid date. */\n @Input()\n get max(): D | null {\n return this._max;\n }\n set max(value: D | null) {\n const validValue = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));\n\n if (!this._dateAdapter.sameDate(validValue, this._max)) {\n this._max = validValue;\n this._validatorOnChange();\n }\n }\n private _max: D | null;\n\n /** Function that can be used to filter out dates within the datepicker. */\n @Input('matDatepickerFilter')\n get dateFilter() {\n return this._dateFilter;\n }\n set dateFilter(value: DateFilterFn) {\n const wasMatchingValue = this._matchesFilter(this.value);\n this._dateFilter = value;\n\n if (this._matchesFilter(this.value) !== wasMatchingValue) {\n this._validatorOnChange();\n }\n }\n private _dateFilter: DateFilterFn;\n\n /** The combined form control validator for this input. */\n protected _validator: ValidatorFn | null;\n\n constructor(...args: unknown[]);\n\n constructor() {\n super();\n this._validator = Validators.compose(super._getValidators());\n }\n\n /**\n * Gets the element that the datepicker popup should be connected to.\n * @return The element to connect the popup to.\n */\n getConnectedOverlayOrigin(): ElementRef {\n return this._formField ? this._formField.getConnectedOverlayOrigin() : this._elementRef;\n }\n\n /** Gets the ID of an element that should be used a description for the calendar overlay. */\n getOverlayLabelId(): string | null {\n if (this._formField) {\n return this._formField.getLabelId();\n }\n\n return this._elementRef.nativeElement.getAttribute('aria-labelledby');\n }\n\n /** Returns the palette used by the input's form field, if any. */\n getThemePalette(): ThemePalette {\n return this._formField ? this._formField.color : undefined;\n }\n\n /** Gets the value at which the calendar should start. */\n getStartValue(): D | null {\n return this.value;\n }\n\n override ngOnDestroy() {\n super.ngOnDestroy();\n this._closedSubscription.unsubscribe();\n this._openedSubscription.unsubscribe();\n }\n\n /** Opens the associated datepicker. */\n protected _openPopup(): void {\n if (this._datepicker) {\n this._datepicker.open();\n }\n }\n\n protected _getValueFromModel(modelValue: D | null): D | null {\n return modelValue;\n }\n\n protected _assignValueToModel(value: D | null): void {\n if (this._model) {\n this._model.updateSelection(value, this);\n }\n }\n\n /** Gets the input's minimum date. */\n _getMinDate() {\n return this._min;\n }\n\n /** Gets the input's maximum date. */\n _getMaxDate() {\n return this._max;\n }\n\n /** Gets the input's date filtering function. */\n protected _getDateFilter() {\n return this._dateFilter;\n }\n\n protected _shouldHandleChangeEvent(event: DateSelectionModelChange) {\n return event.source !== this;\n }\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {\n AfterContentInit,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n ContentChild,\n Directive,\n Input,\n OnChanges,\n OnDestroy,\n SimpleChanges,\n ViewEncapsulation,\n ViewChild,\n booleanAttribute,\n inject,\n HostAttributeToken,\n} from '@angular/core';\nimport {MatButton, MatIconButton} from '../button';\nimport {merge, Observable, of as observableOf, Subscription} from 'rxjs';\nimport {MatDatepickerIntl} from './datepicker-intl';\nimport {MatDatepickerControl, MatDatepickerPanel} from './datepicker-base';\n\n/** Can be used to override the icon of a `matDatepickerToggle`. */\n@Directive({\n selector: '[matDatepickerToggleIcon]',\n})\nexport class MatDatepickerToggleIcon {}\n\n@Component({\n selector: 'mat-datepicker-toggle',\n templateUrl: 'datepicker-toggle.html',\n styleUrl: 'datepicker-toggle.css',\n host: {\n 'class': 'mat-datepicker-toggle',\n '[attr.tabindex]': 'null',\n '[class.mat-datepicker-toggle-active]': 'datepicker && datepicker.opened',\n '[class.mat-accent]': 'datepicker && datepicker.color === \"accent\"',\n '[class.mat-warn]': 'datepicker && datepicker.color === \"warn\"',\n // Used by the test harness to tie this toggle to its datepicker.\n '[attr.data-mat-calendar]': 'datepicker ? datepicker.id : null',\n // Bind the `click` on the host, rather than the inner `button`, so that we can call\n // `stopPropagation` on it without affecting the user's `click` handlers. We need to stop\n // it so that the input doesn't get focused automatically by the form field (See #21836).\n '(click)': '_open($event)',\n },\n exportAs: 'matDatepickerToggle',\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [MatIconButton],\n})\nexport class MatDatepickerToggle implements AfterContentInit, OnChanges, OnDestroy {\n _intl = inject(MatDatepickerIntl);\n private _changeDetectorRef = inject(ChangeDetectorRef);\n private _stateChanges = Subscription.EMPTY;\n\n /** Datepicker instance that the button will toggle. */\n @Input('for') datepicker: MatDatepickerPanel, D>;\n\n /** Tabindex for the toggle. */\n @Input() tabIndex: number | null;\n\n /** Screen-reader label for the button. */\n @Input('aria-label') ariaLabel: string;\n\n /** Whether the toggle button is disabled. */\n @Input({transform: booleanAttribute})\n get disabled(): boolean {\n if (this._disabled === undefined && this.datepicker) {\n return this.datepicker.disabled;\n }\n\n return !!this._disabled;\n }\n set disabled(value: boolean) {\n this._disabled = value;\n }\n private _disabled: boolean;\n\n /** Whether ripples on the toggle should be disabled. */\n @Input() disableRipple: boolean;\n\n /** Custom icon set by the consumer. */\n @ContentChild(MatDatepickerToggleIcon) _customIcon: MatDatepickerToggleIcon;\n\n /** Underlying button element. */\n @ViewChild('button') _button: MatButton;\n\n constructor(...args: unknown[]);\n\n constructor() {\n const defaultTabIndex = inject(new HostAttributeToken('tabindex'), {optional: true});\n const parsedTabIndex = Number(defaultTabIndex);\n this.tabIndex = parsedTabIndex || parsedTabIndex === 0 ? parsedTabIndex : null;\n }\n\n ngOnChanges(changes: SimpleChanges) {\n if (changes['datepicker']) {\n this._watchStateChanges();\n }\n }\n\n ngOnDestroy() {\n this._stateChanges.unsubscribe();\n }\n\n ngAfterContentInit() {\n this._watchStateChanges();\n }\n\n _open(event: Event): void {\n if (this.datepicker && !this.disabled) {\n this.datepicker.open();\n event.stopPropagation();\n }\n }\n\n private _watchStateChanges() {\n const datepickerStateChanged = this.datepicker ? this.datepicker.stateChanges : observableOf();\n const inputStateChanged =\n this.datepicker && this.datepicker.datepickerInput\n ? this.datepicker.datepickerInput.stateChanges\n : observableOf();\n const datepickerToggled = this.datepicker\n ? merge(this.datepicker.openedStream, this.datepicker.closedStream)\n : observableOf();\n\n this._stateChanges.unsubscribe();\n this._stateChanges = merge(\n this._intl.changes,\n datepickerStateChanged as Observable,\n inputStateChanged,\n datepickerToggled,\n ).subscribe(() => this._changeDetectorRef.markForCheck());\n }\n}\n","\n\n @if (!_customIcon) {\n \n \n \n }\n\n \n\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {_IdGenerator, CdkMonitorFocus, FocusOrigin} from '@angular/cdk/a11y';\nimport {\n AfterContentInit,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n ElementRef,\n Input,\n OnChanges,\n OnDestroy,\n SimpleChanges,\n ViewEncapsulation,\n booleanAttribute,\n signal,\n inject,\n} from '@angular/core';\nimport {ControlContainer, NgControl, Validators} from '@angular/forms';\nimport {DateAdapter, ThemePalette} from '../core';\nimport {MAT_FORM_FIELD, MatFormFieldControl} from '../form-field';\nimport {Subject, Subscription, merge} from 'rxjs';\nimport type {MatEndDate, MatStartDate} from './date-range-input-parts';\nimport {MatDateRangePickerInput} from './date-range-picker';\nimport {DateRange, MatDateSelectionModel} from './date-selection-model';\nimport {MatDatepickerControl, MatDatepickerPanel} from './datepicker-base';\nimport {createMissingDateImplError} from './datepicker-errors';\nimport {DateFilterFn, _MatFormFieldPartial, dateInputsHaveChanged} from './datepicker-input-base';\n\n@Component({\n selector: 'mat-date-range-input',\n templateUrl: 'date-range-input.html',\n styleUrl: 'date-range-input.css',\n exportAs: 'matDateRangeInput',\n host: {\n 'class': 'mat-date-range-input',\n '[class.mat-date-range-input-hide-placeholders]': '_shouldHidePlaceholders()',\n '[class.mat-date-range-input-required]': 'required',\n '[attr.id]': 'id',\n 'role': 'group',\n '[attr.aria-labelledby]': '_getAriaLabelledby()',\n '[attr.aria-describedby]': '_ariaDescribedBy',\n // Used by the test harness to tie this input to its calendar. We can't depend on\n // `aria-owns` for this, because it's only defined while the calendar is open.\n '[attr.data-mat-calendar]': 'rangePicker ? rangePicker.id : null',\n },\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n providers: [{provide: MatFormFieldControl, useExisting: MatDateRangeInput}],\n imports: [CdkMonitorFocus],\n})\nexport class MatDateRangeInput\n implements\n MatFormFieldControl>,\n MatDatepickerControl,\n MatDateRangePickerInput,\n AfterContentInit,\n OnChanges,\n OnDestroy\n{\n private _changeDetectorRef = inject(ChangeDetectorRef);\n private _elementRef = inject>(ElementRef);\n private _dateAdapter = inject>(DateAdapter, {optional: true})!;\n private _formField = inject<_MatFormFieldPartial>(MAT_FORM_FIELD, {optional: true});\n\n private _closedSubscription = Subscription.EMPTY;\n private _openedSubscription = Subscription.EMPTY;\n\n _startInput: MatStartDate;\n _endInput: MatEndDate;\n\n /** Current value of the range input. */\n get value() {\n return this._model ? this._model.selection : null;\n }\n\n /** Unique ID for the group. */\n id: string = inject(_IdGenerator).getId('mat-date-range-input-');\n\n /** Whether the control is focused. */\n focused = false;\n\n /** Whether the control's label should float. */\n get shouldLabelFloat(): boolean {\n return this.focused || !this.empty;\n }\n\n /** Name of the form control. */\n controlType = 'mat-date-range-input';\n\n /**\n * Implemented as a part of `MatFormFieldControl`.\n * Set the placeholder attribute on `matStartDate` and `matEndDate`.\n * @docs-private\n */\n get placeholder() {\n const start = this._startInput?._getPlaceholder() || '';\n const end = this._endInput?._getPlaceholder() || '';\n return start || end ? `${start} ${this.separator} ${end}` : '';\n }\n\n /** The range picker that this input is associated with. */\n @Input()\n get rangePicker() {\n return this._rangePicker;\n }\n set rangePicker(rangePicker: MatDatepickerPanel, DateRange, D>) {\n if (rangePicker) {\n this._model = rangePicker.registerInput(this);\n this._rangePicker = rangePicker;\n this._closedSubscription.unsubscribe();\n this._openedSubscription.unsubscribe();\n this._ariaOwns.set(this.rangePicker.opened ? rangePicker.id : null);\n this._closedSubscription = rangePicker.closedStream.subscribe(() => {\n this._startInput?._onTouched();\n this._endInput?._onTouched();\n this._ariaOwns.set(null);\n });\n this._openedSubscription = rangePicker.openedStream.subscribe(() => {\n this._ariaOwns.set(rangePicker.id);\n });\n this._registerModel(this._model!);\n }\n }\n private _rangePicker: MatDatepickerPanel, DateRange, D>;\n\n /** The id of the panel owned by this input. */\n _ariaOwns = signal(null);\n\n /** Whether the input is required. */\n @Input({transform: booleanAttribute})\n get required(): boolean {\n return (\n this._required ??\n (this._isTargetRequired(this) ||\n this._isTargetRequired(this._startInput) ||\n this._isTargetRequired(this._endInput)) ??\n false\n );\n }\n set required(value: boolean) {\n this._required = value;\n }\n private _required: boolean | undefined;\n\n /** Function that can be used to filter out dates within the date range picker. */\n @Input()\n get dateFilter() {\n return this._dateFilter;\n }\n set dateFilter(value: DateFilterFn) {\n const start = this._startInput;\n const end = this._endInput;\n const wasMatchingStart = start && start._matchesFilter(start.value);\n const wasMatchingEnd = end && end._matchesFilter(start.value);\n this._dateFilter = value;\n\n if (start && start._matchesFilter(start.value) !== wasMatchingStart) {\n start._validatorOnChange();\n }\n\n if (end && end._matchesFilter(end.value) !== wasMatchingEnd) {\n end._validatorOnChange();\n }\n }\n private _dateFilter: DateFilterFn;\n\n /** The minimum valid date. */\n @Input()\n get min(): D | null {\n return this._min;\n }\n set min(value: D | null) {\n const validValue = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));\n\n if (!this._dateAdapter.sameDate(validValue, this._min)) {\n this._min = validValue;\n this._revalidate();\n }\n }\n private _min: D | null;\n\n /** The maximum valid date. */\n @Input()\n get max(): D | null {\n return this._max;\n }\n set max(value: D | null) {\n const validValue = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));\n\n if (!this._dateAdapter.sameDate(validValue, this._max)) {\n this._max = validValue;\n this._revalidate();\n }\n }\n private _max: D | null;\n\n /** Whether the input is disabled. */\n @Input({transform: booleanAttribute})\n get disabled(): boolean {\n return this._startInput && this._endInput\n ? this._startInput.disabled && this._endInput.disabled\n : this._groupDisabled;\n }\n set disabled(value: boolean) {\n if (value !== this._groupDisabled) {\n this._groupDisabled = value;\n this.stateChanges.next(undefined);\n }\n }\n _groupDisabled = false;\n\n /** Whether the input is in an error state. */\n get errorState(): boolean {\n if (this._startInput && this._endInput) {\n return this._startInput.errorState || this._endInput.errorState;\n }\n\n return false;\n }\n\n /** Whether the datepicker input is empty. */\n get empty(): boolean {\n const startEmpty = this._startInput ? this._startInput.isEmpty() : false;\n const endEmpty = this._endInput ? this._endInput.isEmpty() : false;\n return startEmpty && endEmpty;\n }\n\n /** Value for the `aria-describedby` attribute of the inputs. */\n _ariaDescribedBy: string | null = null;\n\n /** Date selection model currently registered with the input. */\n private _model: MatDateSelectionModel> | undefined;\n\n /** Separator text to be shown between the inputs. */\n @Input() separator = '–';\n\n /** Start of the comparison range that should be shown in the calendar. */\n @Input() comparisonStart: D | null = null;\n\n /** End of the comparison range that should be shown in the calendar. */\n @Input() comparisonEnd: D | null = null;\n\n /**\n * Implemented as a part of `MatFormFieldControl`.\n * TODO(crisbeto): change type to `AbstractControlDirective` after #18206 lands.\n * @docs-private\n */\n ngControl: NgControl | null;\n\n /** Emits when the input's state has changed. */\n readonly stateChanges = new Subject();\n\n /**\n * Disable the automatic labeling to avoid issues like #27241.\n * @docs-private\n */\n readonly disableAutomaticLabeling = true;\n\n constructor(...args: unknown[]);\n\n constructor() {\n if (!this._dateAdapter && (typeof ngDevMode === 'undefined' || ngDevMode)) {\n throw createMissingDateImplError('DateAdapter');\n }\n\n // The datepicker module can be used both with MDC and non-MDC form fields. We have\n // to conditionally add the MDC input class so that the range picker looks correctly.\n if (this._formField?._elementRef.nativeElement.classList.contains('mat-mdc-form-field')) {\n this._elementRef.nativeElement.classList.add(\n 'mat-mdc-input-element',\n 'mat-mdc-form-field-input-control',\n 'mdc-text-field__input',\n );\n }\n\n // TODO(crisbeto): remove `as any` after #18206 lands.\n this.ngControl = inject(ControlContainer, {optional: true, self: true}) as any;\n }\n\n /**\n * Implemented as a part of `MatFormFieldControl`.\n * @docs-private\n */\n setDescribedByIds(ids: string[]): void {\n this._ariaDescribedBy = ids.length ? ids.join(' ') : null;\n }\n\n /**\n * Implemented as a part of `MatFormFieldControl`.\n * @docs-private\n */\n onContainerClick(): void {\n if (!this.focused && !this.disabled) {\n if (!this._model || !this._model.selection.start) {\n this._startInput.focus();\n } else {\n this._endInput.focus();\n }\n }\n }\n\n ngAfterContentInit() {\n if (typeof ngDevMode === 'undefined' || ngDevMode) {\n if (!this._startInput) {\n throw Error('mat-date-range-input must contain a matStartDate input');\n }\n\n if (!this._endInput) {\n throw Error('mat-date-range-input must contain a matEndDate input');\n }\n }\n\n if (this._model) {\n this._registerModel(this._model);\n }\n\n // We don't need to unsubscribe from this, because we\n // know that the input streams will be completed on destroy.\n merge(this._startInput.stateChanges, this._endInput.stateChanges).subscribe(() => {\n this.stateChanges.next(undefined);\n });\n }\n\n ngOnChanges(changes: SimpleChanges) {\n if (dateInputsHaveChanged(changes, this._dateAdapter)) {\n this.stateChanges.next(undefined);\n }\n }\n\n ngOnDestroy() {\n this._closedSubscription.unsubscribe();\n this._openedSubscription.unsubscribe();\n this.stateChanges.complete();\n }\n\n /** Gets the date at which the calendar should start. */\n getStartValue(): D | null {\n return this.value ? this.value.start : null;\n }\n\n /** Gets the input's theme palette. */\n getThemePalette(): ThemePalette {\n return this._formField ? this._formField.color : undefined;\n }\n\n /** Gets the element to which the calendar overlay should be attached. */\n getConnectedOverlayOrigin(): ElementRef {\n return this._formField ? this._formField.getConnectedOverlayOrigin() : this._elementRef;\n }\n\n /** Gets the ID of an element that should be used a description for the calendar overlay. */\n getOverlayLabelId(): string | null {\n return this._formField ? this._formField.getLabelId() : null;\n }\n\n /** Gets the value that is used to mirror the state input. */\n _getInputMirrorValue(part: 'start' | 'end') {\n const input = part === 'start' ? this._startInput : this._endInput;\n return input ? input.getMirrorValue() : '';\n }\n\n /** Whether the input placeholders should be hidden. */\n _shouldHidePlaceholders() {\n return this._startInput ? !this._startInput.isEmpty() : false;\n }\n\n /** Handles the value in one of the child inputs changing. */\n _handleChildValueChange() {\n this.stateChanges.next(undefined);\n this._changeDetectorRef.markForCheck();\n }\n\n /** Opens the date range picker associated with the input. */\n _openDatepicker() {\n if (this._rangePicker) {\n this._rangePicker.open();\n }\n }\n\n /** Whether the separate text should be hidden. */\n _shouldHideSeparator() {\n return (\n (!this._formField ||\n (this._formField.getLabelId() && !this._formField._shouldLabelFloat())) &&\n this.empty\n );\n }\n\n /** Gets the value for the `aria-labelledby` attribute of the inputs. */\n _getAriaLabelledby() {\n const formField = this._formField;\n return formField && formField._hasFloatingLabel() ? formField._labelId : null;\n }\n\n _getStartDateAccessibleName(): string {\n return this._startInput._getAccessibleName();\n }\n\n _getEndDateAccessibleName(): string {\n return this._endInput._getAccessibleName();\n }\n\n /** Updates the focused state of the range input. */\n _updateFocus(origin: FocusOrigin) {\n this.focused = origin !== null;\n this.stateChanges.next();\n }\n\n /** Re-runs the validators on the start/end inputs. */\n private _revalidate() {\n if (this._startInput) {\n this._startInput._validatorOnChange();\n }\n\n if (this._endInput) {\n this._endInput._validatorOnChange();\n }\n }\n\n /** Registers the current date selection model with the start/end inputs. */\n private _registerModel(model: MatDateSelectionModel>) {\n if (this._startInput) {\n this._startInput._registerModel(model);\n }\n\n if (this._endInput) {\n this._endInput._registerModel(model);\n }\n }\n\n /** Checks whether a specific range input directive is required. */\n private _isTargetRequired(target: {ngControl: NgControl | null} | null): boolean | undefined {\n return target?.ngControl?.control?.hasValidator(Validators.required);\n }\n}\n","\n
\n \n {{_getInputMirrorValue('start')}}\n
\n\n {{separator}}\n\n
\n \n {{_getInputMirrorValue('end')}}\n
\n\n\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\n// This file contains the `_computeAriaAccessibleName` function, which computes what the *expected*\n// ARIA accessible name would be for a given element. Implements a subset of ARIA specification\n// [Accessible Name and Description Computation 1.2](https://www.w3.org/TR/accname-1.2/).\n//\n// Specification accname-1.2 can be summarized by returning the result of the first method\n// available.\n//\n// 1. `aria-labelledby` attribute\n// ```\n// \n// \n// \n// ```\n// 2. `aria-label` attribute (e.g. ``)\n// 3. Label with `for`/`id`\n// ```\n// \n// \n// \n// ```\n// 4. `placeholder` attribute (e.g. ``)\n// 5. `title` attribute (e.g. ``)\n// 6. text content\n// ```\n// \n// \n// \n// ```\n\n/**\n * Computes the *expected* ARIA accessible name for argument element based on [accname-1.2\n * specification](https://www.w3.org/TR/accname-1.2/). Implements a subset of accname-1.2,\n * and should only be used for the Datepicker's specific use case.\n *\n * Intended use:\n * This is not a general use implementation. Only implements the parts of accname-1.2 that are\n * required for the Datepicker's specific use case. This function is not intended for any other\n * use.\n *\n * Limitations:\n * - Only covers the needs of `matStartDate` and `matEndDate`. Does not support other use cases.\n * - See NOTES's in implementation for specific details on what parts of the accname-1.2\n * specification are not implemented.\n *\n * @param element {HTMLInputElement} native <input/> element of `matStartDate` or\n * `matEndDate` component. Corresponds to the 'Root Element' from accname-1.2\n *\n * @return expected ARIA accessible name of argument <input/>\n */\nexport function _computeAriaAccessibleName(\n element: HTMLInputElement | HTMLTextAreaElement,\n): string {\n return _computeAriaAccessibleNameInternal(element, true);\n}\n\n/**\n * Determine if argument node is an Element based on `nodeType` property. This function is safe to\n * use with server-side rendering.\n */\nfunction ssrSafeIsElement(node: Node): node is Element {\n return node.nodeType === Node.ELEMENT_NODE;\n}\n\n/**\n * Determine if argument node is an HTMLInputElement based on `nodeName` property. This funciton is\n * safe to use with server-side rendering.\n */\nfunction ssrSafeIsHTMLInputElement(node: Node): node is HTMLInputElement {\n return node.nodeName === 'INPUT';\n}\n\n/**\n * Determine if argument node is an HTMLTextAreaElement based on `nodeName` property. This\n * funciton is safe to use with server-side rendering.\n */\nfunction ssrSafeIsHTMLTextAreaElement(node: Node): node is HTMLTextAreaElement {\n return node.nodeName === 'TEXTAREA';\n}\n\n/**\n * Calculate the expected ARIA accessible name for given DOM Node. Given DOM Node may be either the\n * \"Root node\" passed to `_computeAriaAccessibleName` or \"Current node\" as result of recursion.\n *\n * @return the accessible name of argument DOM Node\n *\n * @param currentNode node to determine accessible name of\n * @param isDirectlyReferenced true if `currentNode` is the root node to calculate ARIA accessible\n * name of. False if it is a result of recursion.\n */\nfunction _computeAriaAccessibleNameInternal(\n currentNode: Node,\n isDirectlyReferenced: boolean,\n): string {\n // NOTE: this differs from accname-1.2 specification.\n // - Does not implement Step 1. of accname-1.2: '''If `currentNode`'s role prohibits naming,\n // return the empty string (\"\")'''.\n // - Does not implement Step 2.A. of accname-1.2: '''if current node is hidden and not directly\n // referenced by aria-labelledby... return the empty string.'''\n\n // acc-name-1.2 Step 2.B.: aria-labelledby\n if (ssrSafeIsElement(currentNode) && isDirectlyReferenced) {\n const labelledbyIds: string[] =\n currentNode.getAttribute?.('aria-labelledby')?.split(/\\s+/g) || [];\n const validIdRefs: HTMLElement[] = labelledbyIds.reduce((validIds, id) => {\n const elem = document.getElementById(id);\n if (elem) {\n validIds.push(elem);\n }\n return validIds;\n }, [] as HTMLElement[]);\n\n if (validIdRefs.length) {\n return validIdRefs\n .map(idRef => {\n return _computeAriaAccessibleNameInternal(idRef, false);\n })\n .join(' ');\n }\n }\n\n // acc-name-1.2 Step 2.C.: aria-label\n if (ssrSafeIsElement(currentNode)) {\n const ariaLabel = currentNode.getAttribute('aria-label')?.trim();\n\n if (ariaLabel) {\n return ariaLabel;\n }\n }\n\n // acc-name-1.2 Step 2.D. attribute or element that defines a text alternative\n //\n // NOTE: this differs from accname-1.2 specification.\n // Only implements Step 2.D. for `