Skip to content

Commit 37b43ff

Browse files
xlou978mmalerba
andcommitted
Trying to incorporate the new type definition (#17942)
* incoporate new type definition * fix test and clean up the types a bit * update api gold * fix some nits * fix lint Co-authored-by: mmalerba <mmalerba@google.com>
1 parent 47e1b01 commit 37b43ff

File tree

3 files changed

+164
-141
lines changed

3 files changed

+164
-141
lines changed

src/material/core/datetime/date-selection-model.ts

Lines changed: 123 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -6,169 +6,191 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import {FactoryProvider, Injectable, OnDestroy, Optional, SkipSelf} from '@angular/core';
910
import {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

src/material/datepicker/datepicker-input.ts

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ import {
3232
import {
3333
DateAdapter,
3434
MAT_DATE_FORMATS,
35-
MatSingleDateSelectionModel,
3635
MatDateFormats,
36+
MatSingleDateSelectionModel,
3737
ThemePalette,
3838
} from '@angular/material/core';
3939
import {MatFormField} from '@angular/material/form-field';
@@ -67,10 +67,10 @@ export class MatDatepickerInputEvent<D> {
6767
value: D | null;
6868

6969
constructor(
70-
/** Reference to the datepicker input component that emitted the event. */
71-
public target: MatDatepickerInput<D>,
72-
/** Reference to the native input element associated with the datepicker input. */
73-
public targetElement: HTMLElement) {
70+
/** Reference to the datepicker input component that emitted the event. */
71+
public target: MatDatepickerInput<D>,
72+
/** Reference to the native input element associated with the datepicker input. */
73+
public targetElement: HTMLElement) {
7474
this.value = this.target.value;
7575
}
7676
}
@@ -129,22 +129,20 @@ export class MatDatepickerInput<D> implements ControlValueAccessor, OnDestroy, V
129129

130130
/** The value of the input. */
131131
@Input()
132-
get value(): D | null { return this._selection.asDate(); }
132+
get value(): D | null { return this._selectionModel.selection; }
133133
set value(value: D | null) {
134-
value = this._dateAdapter.deserialize(value);
135-
const oldDate = this._selection.asDate();
136-
const isDifferent = !this._dateAdapter.sameDate(oldDate, value);
137-
this._selection.setDate(value);
138-
139-
this._lastValueValid = this._selection.isValid();
140-
141-
this._formatValue(this._selection.asDate());
142-
143-
if (isDifferent) {
144-
this._valueChange.emit(this.value);
134+
value = this._dateAdapter.deserialize(value);
135+
this._lastValueValid = !value || this._dateAdapter.isValid(value);
136+
value = this._getValidDateOrNull(value);
137+
const oldDate = this._selectionModel.selection;
138+
this._selectionModel.selection = value;
139+
this._formatValue(value);
140+
141+
if (!this._dateAdapter.sameDate(oldDate, value)) {
142+
this._valueChange.emit(value);
145143
}
146144
}
147-
private _selection: MatSingleDateSelectionModel<D>;
145+
private _selectionModel: MatSingleDateSelectionModel<D>;
148146

149147
/** The minimum valid date. */
150148
@Input()
@@ -259,7 +257,7 @@ export class MatDatepickerInput<D> implements ControlValueAccessor, OnDestroy, V
259257
throw createMissingDateImplError('MAT_DATE_FORMATS');
260258
}
261259

262-
this._selection = new MatSingleDateSelectionModel(this._dateAdapter, null);
260+
this._selectionModel = new MatSingleDateSelectionModel(this._dateAdapter);
263261

264262
// Update the displayed date when the locale changes.
265263
this._localeSubscription = _dateAdapter.localeChanges.subscribe(() => {
@@ -334,8 +332,8 @@ export class MatDatepickerInput<D> implements ControlValueAccessor, OnDestroy, V
334332
this._lastValueValid = !date || this._dateAdapter.isValid(date);
335333
date = this._getValidDateOrNull(date);
336334

337-
if (!this._dateAdapter.sameDate(date, this._selection.asDate())) {
338-
this._selection.setDate(date);
335+
if (!this._dateAdapter.sameDate(date, this._selectionModel.selection)) {
336+
this._selectionModel.selection = date;
339337
this._cvaOnChange(date);
340338
this._valueChange.emit(date);
341339
this.dateInput.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement));

0 commit comments

Comments
 (0)