/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ import {FactoryProvider, Injectable, Optional, SkipSelf, OnDestroy} from '@angular/core'; import {DateAdapter} from '@angular/material/core'; import {Observable, Subject} from 'rxjs'; /** A class representing a range of dates. */ export class DateRange { /** * Ensures that objects with a `start` and `end` property can't be assigned to a variable that * expects a `DateRange` */ // tslint:disable-next-line:no-unused-variable private _disableStructuralEquivalency: never; constructor( /** The start date of the range. */ readonly start: D | null, /** The end date of the range. */ readonly end: D | null, ) {} } /** * Conditionally picks the date type, if a DateRange is passed in. * @docs-private */ export type ExtractDateTypeFromSelection = T extends DateRange ? D : NonNullable; /** * Event emitted by the date selection model when its selection changes. * @docs-private */ export interface DateSelectionModelChange { /** New value for the selection. */ selection: S; /** Object that triggered the change. */ source: unknown; /** Previous value */ oldValue?: S; } /** * A selection model containing a date selection. * @docs-private */ @Injectable() export abstract class MatDateSelectionModel> implements OnDestroy { private readonly _selectionChanged = new Subject>(); /** Emits when the selection has changed. */ selectionChanged: Observable> = this._selectionChanged; protected constructor( /** The current selection. */ readonly selection: S, protected _adapter: DateAdapter, ) { this.selection = selection; } /** * Updates the current selection in the model. * @param value New selection that should be assigned. * @param source Object that triggered the selection change. */ updateSelection(value: S, source: unknown) { const oldValue = (this as {selection: S}).selection; (this as {selection: S}).selection = value; this._selectionChanged.next({selection: value, source, oldValue}); } ngOnDestroy() { this._selectionChanged.complete(); } protected _isValidDateInstance(date: D): boolean { return this._adapter.isDateInstance(date) && this._adapter.isValid(date); } /** Adds a date to the current selection. */ abstract add(date: D | null): void; /** Checks whether the current selection is valid. */ abstract isValid(): boolean; /** Checks whether the current selection is complete. */ abstract isComplete(): boolean; /** Clones the selection model. */ abstract clone(): MatDateSelectionModel; } /** * A selection model that contains a single date. * @docs-private */ @Injectable() export class MatSingleDateSelectionModel extends MatDateSelectionModel { constructor(adapter: DateAdapter) { super(null, adapter); } /** * Adds a date to the current selection. In the case of a single date selection, the added date * simply overwrites the previous selection */ add(date: D | null) { super.updateSelection(date, this); } /** Checks whether the current selection is valid. */ isValid(): boolean { return this.selection != null && this._isValidDateInstance(this.selection); } /** * Checks whether the current selection is complete. In the case of a single date selection, this * is true if the current selection is not null. */ isComplete() { return this.selection != null; } /** Clones the selection model. */ clone() { const clone = new MatSingleDateSelectionModel(this._adapter); clone.updateSelection(this.selection, this); return clone; } } /** * A selection model that contains a date range. * @docs-private */ @Injectable() export class MatRangeDateSelectionModel extends MatDateSelectionModel, D> { constructor(adapter: DateAdapter) { super(new DateRange(null, null), adapter); } /** * Adds a date to the current selection. In the case of a date range selection, the added date * fills in the next `null` value in the range. If both the start and the end already have a date, * the selection is reset so that the given date is the new `start` and the `end` is null. */ add(date: D | null): void { let {start, end} = this.selection; if (start == null) { start = date; } else if (end == null) { end = date; } else { start = date; end = null; } super.updateSelection(new DateRange(start, end), this); } /** Checks whether the current selection is valid. */ isValid(): boolean { const {start, end} = this.selection; // Empty ranges are valid. if (start == null && end == null) { return true; } // Complete ranges are only valid if both dates are valid and the start is before the end. if (start != null && end != null) { return ( this._isValidDateInstance(start) && this._isValidDateInstance(end) && this._adapter.compareDate(start, end) <= 0 ); } // Partial ranges are valid if the start/end is valid. return ( (start == null || this._isValidDateInstance(start)) && (end == null || this._isValidDateInstance(end)) ); } /** * Checks whether the current selection is complete. In the case of a date range selection, this * is true if the current selection has a non-null `start` and `end`. */ isComplete(): boolean { return this.selection.start != null && this.selection.end != null; } /** Clones the selection model. */ clone() { const clone = new MatRangeDateSelectionModel(this._adapter); clone.updateSelection(this.selection, this); return clone; } } /** @docs-private */ export function MAT_SINGLE_DATE_SELECTION_MODEL_FACTORY( parent: MatSingleDateSelectionModel, adapter: DateAdapter, ) { return parent || new MatSingleDateSelectionModel(adapter); } /** * Used to provide a single selection model to a component. * @docs-private */ export const MAT_SINGLE_DATE_SELECTION_MODEL_PROVIDER: FactoryProvider = { provide: MatDateSelectionModel, deps: [[new Optional(), new SkipSelf(), MatDateSelectionModel], DateAdapter], useFactory: MAT_SINGLE_DATE_SELECTION_MODEL_FACTORY, }; /** @docs-private */ export function MAT_RANGE_DATE_SELECTION_MODEL_FACTORY( parent: MatSingleDateSelectionModel, adapter: DateAdapter, ) { return parent || new MatRangeDateSelectionModel(adapter); } /** * Used to provide a range selection model to a component. * @docs-private */ export const MAT_RANGE_DATE_SELECTION_MODEL_PROVIDER: FactoryProvider = { provide: MatDateSelectionModel, deps: [[new Optional(), new SkipSelf(), MatDateSelectionModel], DateAdapter], useFactory: MAT_RANGE_DATE_SELECTION_MODEL_FACTORY, };