sass-references/angular-material/material/datepicker/date-selection-model.ts

249 lines
7.0 KiB
TypeScript
Raw Permalink Normal View History

2024-12-06 10:42:08 +08:00
/**
* @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<D> {
/**
* 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> = T extends DateRange<infer D> ? D : NonNullable<T>;
/**
* Event emitted by the date selection model when its selection changes.
* @docs-private
*/
export interface DateSelectionModelChange<S> {
/** 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<S, D = ExtractDateTypeFromSelection<S>>
implements OnDestroy
{
private readonly _selectionChanged = new Subject<DateSelectionModelChange<S>>();
/** Emits when the selection has changed. */
selectionChanged: Observable<DateSelectionModelChange<S>> = this._selectionChanged;
protected constructor(
/** The current selection. */
readonly selection: S,
protected _adapter: DateAdapter<D>,
) {
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<S, D>;
}
/**
* A selection model that contains a single date.
* @docs-private
*/
@Injectable()
export class MatSingleDateSelectionModel<D> extends MatDateSelectionModel<D | null, D> {
constructor(adapter: DateAdapter<D>) {
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<D>(this._adapter);
clone.updateSelection(this.selection, this);
return clone;
}
}
/**
* A selection model that contains a date range.
* @docs-private
*/
@Injectable()
export class MatRangeDateSelectionModel<D> extends MatDateSelectionModel<DateRange<D>, D> {
constructor(adapter: DateAdapter<D>) {
super(new DateRange<D>(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<D>(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<D>(this._adapter);
clone.updateSelection(this.selection, this);
return clone;
}
}
/** @docs-private */
export function MAT_SINGLE_DATE_SELECTION_MODEL_FACTORY(
parent: MatSingleDateSelectionModel<unknown>,
adapter: DateAdapter<unknown>,
) {
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<unknown>,
adapter: DateAdapter<unknown>,
) {
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,
};