66 * found in the LICENSE file at https://angular.io/license
77 */
88
9+ import { FactoryProvider , Injectable , OnDestroy , Optional , SkipSelf } from '@angular/core' ;
910import { DateAdapter } from '@angular/material/core' ;
10- import { FactoryProvider , Optional , SkipSelf , Injectable } from '@angular/core' ;
11- import { Subject , Observable } from 'rxjs' ;
11+ import { Observable , Subject } from 'rxjs' ;
1212
13- export abstract class MatDateSelectionModel < D > {
14- protected _valueChangesSubject = new Subject < void > ( ) ;
13+ /** A class representing a range of dates. */
14+ export class DateRange < D > {
15+ /**
16+ * Ensures that objects with a `start` and `end` property can't be assigned to a variable that
17+ * expects a `DateRange`
18+ */
19+ // tslint:disable-next-line:no-unused-variable
20+ private _disableStructuralEquivalency : never ;
21+
22+ /** The start date of the range. */
23+ readonly start : D | null ;
24+
25+ /** The end date of the range. */
26+ readonly end : D | null ;
27+
28+ constructor ( range ?: { start ?: D | null , end ?: D | null } | null ) {
29+ this . start = range && range . start || null ;
30+ this . end = range && range . end || null ;
31+ }
32+ }
33+
34+ type ExtractDateTypeFromSelection < T > = T extends DateRange < infer D > ? D : NonNullable < T > ;
35+
36+ /** A selection model containing a date selection. */
37+ export abstract class MatDateSelectionModel < S , D = ExtractDateTypeFromSelection < S > >
38+ implements OnDestroy {
39+ /** Subject used to emit value change events. */
40+ private _valueChangesSubject = new Subject < void > ( ) ;
41+
42+ /** Observable of value change events. */
1543 valueChanges : Observable < void > = this . _valueChangesSubject . asObservable ( ) ;
1644
17- constructor ( protected readonly adapter : DateAdapter < D > ) { }
45+ /** The current selection. */
46+ get selection ( ) : S { return this . _selection ; }
47+ set selection ( s : S ) {
48+ this . _selection = s ;
49+ this . _valueChangesSubject . next ( ) ;
50+ }
1851
19- destroy ( ) {
52+ protected constructor ( protected readonly adapter : DateAdapter < D > , private _selection : S ) { }
53+
54+ ngOnDestroy ( ) {
2055 this . _valueChangesSubject . complete ( ) ;
2156 }
2257
58+ /** Adds a date to the current selection. */
2359 abstract add ( date : D | null ) : void ;
60+
61+ /** Checks whether the current selection is complete. */
2462 abstract isComplete ( ) : boolean ;
63+
64+ /**
65+ * Checks whether the current selection is the same as the selection in the given selection model.
66+ */
2567 abstract isSame ( other : MatDateSelectionModel < D > ) : boolean ;
68+
69+ /** Checks whether the current selection is valid. */
2670 abstract isValid ( ) : boolean ;
27- abstract overlaps ( range : DateRange < D > ) : boolean ;
28- }
2971
30- export interface DateRange < D > {
31- start : D | null ;
32- end : D | null ;
72+ /** Checks whether the current selection overlaps with the given range. */
73+ abstract overlaps ( range : DateRange < D > ) : boolean ;
3374}
3475
35- /**
36- * Concrete implementation of a MatDateSelectionModel that holds a single date.
37- */
76+ /** A selection model that contains a single date. */
3877@Injectable ( )
39- export class MatSingleDateSelectionModel < D > extends MatDateSelectionModel < D > {
40- private _date : D | null = null ;
41-
78+ export class MatSingleDateSelectionModel < D > extends MatDateSelectionModel < D | null , D > {
4279 constructor ( adapter : DateAdapter < D > , date ?: D | null ) {
43- super ( adapter ) ;
44- this . _date = date === undefined ? null : date ;
80+ super ( adapter , date || null ) ;
4581 }
4682
83+ /**
84+ * Adds a date to the current selection. In the case of a single date selection, the added date
85+ * simply overwrites the previous selection
86+ */
4787 add ( date : D | null ) {
48- this . _date = date ;
49- this . _valueChangesSubject . next ( ) ;
88+ this . selection = date ;
5089 }
5190
52- compareDate ( other : MatSingleDateSelectionModel < D > ) {
53- const date = this . asDate ( ) ;
54- const otherDate = other . asDate ( ) ;
55- if ( date != null && otherDate != null ) {
56- return this . adapter . compareDate ( date , otherDate ) ;
57- }
58- return date === otherDate ;
59- }
60-
61- isComplete ( ) { return this . _date != null ; }
91+ /**
92+ * Checks whether the current selection is complete. In the case of a single date selection, this
93+ * is true if the current selection is not null.
94+ */
95+ isComplete ( ) { return this . selection != null ; }
6296
63- isSame ( other : MatDateSelectionModel < D > ) : boolean {
97+ /**
98+ * Checks whether the current selection is the same as the selection in the given selection model.
99+ */
100+ isSame ( other : MatDateSelectionModel < any > ) : boolean {
64101 return other instanceof MatSingleDateSelectionModel &&
65- this . adapter . sameDate ( other . asDate ( ) , this . _date ) ;
102+ this . adapter . sameDate ( other . selection , this . selection ) ;
66103 }
67104
105+ /**
106+ * Checks whether the current selection is valid. In the case of a single date selection, this
107+ * means that the current selection is not null and is a valid date.
108+ */
68109 isValid ( ) : boolean {
69- return this . _date != null && this . adapter . isDateInstance ( this . _date ) &&
70- this . adapter . isValid ( this . _date ) ;
110+ return this . selection != null && this . adapter . isDateInstance ( this . selection ) &&
111+ this . adapter . isValid ( this . selection ) ;
71112 }
72113
73- asDate ( ) : D | null {
74- return this . isValid ( ) ? this . _date : null ;
75- }
76-
77- setDate ( date : D | null ) {
78- this . _date = date ;
79- this . _valueChangesSubject . next ( ) ;
80- }
81-
82- /**
83- * Determines if the single date is within a given date range. Retuns false if either dates of
84- * the range is null or if the selection is undefined.
85- */
114+ /** Checks whether the current selection overlaps with the given range. */
86115 overlaps ( range : DateRange < D > ) : boolean {
87- return ! ! ( this . _date && range . start && range . end &&
88- this . adapter . compareDate ( range . start , this . _date ) <= 0 &&
89- this . adapter . compareDate ( this . _date , range . end ) <= 0 ) ;
116+ return ! ! ( this . selection && range . start && range . end &&
117+ this . adapter . compareDate ( range . start , this . selection ) <= 0 &&
118+ this . adapter . compareDate ( this . selection , range . end ) <= 0 ) ;
90119 }
91120}
92121
93- /**
94- * Concrete implementation of a MatDateSelectionModel that holds a date range, represented by
95- * a start date and an end date.
96- */
122+ /** A selection model that contains a date range. */
97123@Injectable ( )
98- export class MatRangeDateSelectionModel < D > extends MatDateSelectionModel < D > {
99- private _start : D | null = null ;
100- private _end : D | null = null ;
101-
102- constructor ( adapter : DateAdapter < D > , start ?: D | null , end ?: D | null ) {
103- super ( adapter ) ;
104- this . _start = start === undefined ? null : start ;
105- this . _end = end === undefined ? null : end ;
124+ export class MatRangeDateSelectionModel < D > extends MatDateSelectionModel < DateRange < D > , D > {
125+ constructor ( adapter : DateAdapter < D > , range ?: { start ?: D | null , end ?: D | null } | null ) {
126+ super ( adapter , new DateRange ( range ) ) ;
106127 }
107128
108129 /**
109- * Adds an additional date to the range. If no date is set thus far, it will set it to the
110- * beginning. If the beginning is set, it will set it to the end.
111- * If add is called on a complete selection, it will empty the selection and set it as the start .
130+ * Adds a date to the current selection. In the case of a date range selection, the added date
131+ * fills in the next `null` value in the range. If both the start and the end already have a date,
132+ * the selection is reset so that the given date is the new `start` and the `end` is null .
112133 */
113134 add ( date : D | null ) : void {
114- if ( this . _start == null ) {
115- this . _start = date ;
116- } else if ( this . _end == null ) {
117- this . _end = date ;
135+ let { start, end} = this . selection ;
136+
137+ if ( start == null ) {
138+ start = date ;
139+ } else if ( end == null ) {
140+ end = date ;
118141 } else {
119- this . _start = date ;
120- this . _end = null ;
142+ start = date ;
143+ end = null ;
121144 }
122145
123- this . _valueChangesSubject . next ( ) ;
124- }
125-
126- setRange ( start : D | null , end : D | null ) {
127- this . _start = start ;
128- this . _end = end ;
146+ this . selection = new DateRange < D > ( { start, end} ) ;
129147 }
130148
149+ /**
150+ * Checks whether the current selection is complete. In the case of a date range selection, this
151+ * is true if the current selection has a non-null `start` and `end`.
152+ */
131153 isComplete ( ) : boolean {
132- return this . _start != null && this . _end != null ;
154+ return this . selection . start != null && this . selection . end != null ;
133155 }
134156
135- isSame ( other : MatDateSelectionModel < D > ) : boolean {
157+ /**
158+ * Checks whether the current selection is the same as the selection in the given selection model.
159+ */
160+ isSame ( other : MatDateSelectionModel < any > ) : boolean {
136161 if ( other instanceof MatRangeDateSelectionModel ) {
137- const otherRange = other . asRange ( ) ;
138- return this . adapter . sameDate ( this . _start , otherRange . start ) &&
139- this . adapter . sameDate ( this . _end , otherRange . end ) ;
162+ return this . adapter . sameDate ( this . selection . start , other . selection . start ) &&
163+ this . adapter . sameDate ( this . selection . end , other . selection . end ) ;
140164 }
141165 return false ;
142166 }
143167
168+ /**
169+ * Checks whether the current selection is valid. In the case of a date range selection, this
170+ * means that the current selection has a `start` and `end` that are both non-null and valid
171+ * dates.
172+ */
144173 isValid ( ) : boolean {
145- return this . _start != null && this . _end != null &&
146- this . adapter . isValid ( this . _start ! ) && this . adapter . isValid ( this . _end ! ) ;
147- }
148-
149- asRange ( ) : DateRange < D > {
150- return {
151- start : this . _start ,
152- end : this . _end ,
153- } ;
174+ return this . selection . start != null && this . selection . end != null &&
175+ this . adapter . isValid ( this . selection . start ! ) && this . adapter . isValid ( this . selection . end ! ) ;
154176 }
155177
156178 /**
157179 * Returns true if the given range and the selection overlap in any way. False if otherwise, that
158180 * includes incomplete selections or ranges.
159181 */
160182 overlaps ( range : DateRange < D > ) : boolean {
161- if ( ! ( this . _start && this . _end && range . start && range . end ) ) {
183+ if ( ! ( this . selection . start && this . selection . end && range . start && range . end ) ) {
162184 return false ;
163185 }
164186
165187 return (
166- this . _isBetween ( range . start , this . _start , this . _end ) ||
167- this . _isBetween ( range . end , this . _start , this . _end ) ||
168- (
169- this . adapter . compareDate ( range . start , this . _start ) <= 0 &&
170- this . adapter . compareDate ( this . _end , range . end ) <= 0
171- )
188+ this . _isBetween ( range . start , this . selection . start , this . selection . end ) ||
189+ this . _isBetween ( range . end , this . selection . start , this . selection . end ) ||
190+ (
191+ this . adapter . compareDate ( range . start , this . selection . start ) <= 0 &&
192+ this . adapter . compareDate ( this . selection . end , range . end ) <= 0
193+ )
172194 ) ;
173195 }
174196
@@ -177,9 +199,8 @@ export class MatRangeDateSelectionModel<D> extends MatDateSelectionModel<D> {
177199 }
178200}
179201
180- export function MAT_SINGLE_DATE_SELECTION_MODEL_FACTORY < D > ( parent :
181- MatSingleDateSelectionModel < D > ,
182- adapter : DateAdapter < D > ) {
202+ export function MAT_SINGLE_DATE_SELECTION_MODEL_FACTORY (
203+ parent : MatSingleDateSelectionModel < unknown > , adapter : DateAdapter < unknown > ) {
183204 return parent || new MatSingleDateSelectionModel ( adapter ) ;
184205}
185206
0 commit comments