Skip to content

Commit 5b1660e

Browse files
feat(material/datepicker): allow custom pickers with date inputs (#20864)
1 parent 5575673 commit 5b1660e

File tree

6 files changed

+67
-42
lines changed

6 files changed

+67
-42
lines changed

src/material/datepicker/date-range-input.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ import {
3333
MatDateRangeInputParent,
3434
MAT_DATE_RANGE_INPUT_PARENT,
3535
} from './date-range-input-parts';
36-
import {MatDatepickerControl} from './datepicker-base';
36+
import {MatDatepickerControl, MatDatepickerPanel} from './datepicker-base';
3737
import {createMissingDateImplError} from './datepicker-errors';
3838
import {DateFilterFn, dateInputsHaveChanged} from './datepicker-input-base';
39-
import {MatDateRangePicker, MatDateRangePickerInput} from './date-range-picker';
39+
import {MatDateRangePickerInput} from './date-range-picker';
4040
import {DateRange, MatDateSelectionModel} from './date-selection-model';
4141

4242
let nextUniqueId = 0;
@@ -101,14 +101,14 @@ export class MatDateRangeInput<D> implements MatFormFieldControl<DateRange<D>>,
101101
/** The range picker that this input is associated with. */
102102
@Input()
103103
get rangePicker() { return this._rangePicker; }
104-
set rangePicker(rangePicker: MatDateRangePicker<D>) {
104+
set rangePicker(rangePicker: MatDatepickerPanel<MatDatepickerControl<D>, DateRange<D>, D>) {
105105
if (rangePicker) {
106-
this._model = rangePicker._registerInput(this);
106+
this._model = rangePicker.registerInput(this);
107107
this._rangePicker = rangePicker;
108108
this._registerModel(this._model!);
109109
}
110110
}
111-
private _rangePicker: MatDateRangePicker<D>;
111+
private _rangePicker: MatDatepickerPanel<MatDatepickerControl<D>, DateRange<D>, D>;
112112

113113
/** Whether the input is required. */
114114
@Input()

src/material/datepicker/date-range-picker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export class MatDateRangePicker<D> extends MatDatepickerBase<MatDateRangePickerI
4040
protected _forwardContentValues(instance: MatDatepickerContent<DateRange<D>, D>) {
4141
super._forwardContentValues(instance);
4242

43-
const input = this._datepickerInput;
43+
const input = this.datepickerInput;
4444

4545
if (input) {
4646
instance.comparisonStart = input.comparisonStart;

src/material/datepicker/datepicker-base.ts

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ export class MatDatepickerContent<S, D = ExtractDateTypeFromSelection<S>>
174174
}
175175

176176
ngAfterViewInit() {
177-
this._subscriptions.add(this.datepicker._stateChanges.subscribe(() => {
177+
this._subscriptions.add(this.datepicker.stateChanges.subscribe(() => {
178178
this._changeDetectorRef.markForCheck();
179179
}));
180180

@@ -232,10 +232,36 @@ export interface MatDatepickerControl<D> {
232232
stateChanges: Observable<void>;
233233
}
234234

235+
/** A datepicker that can be attached to a {@link MatDatepickerControl}. */
236+
export interface MatDatepickerPanel<C extends MatDatepickerControl<D>, S,
237+
D = ExtractDateTypeFromSelection<S>> {
238+
/** Stream that emits whenever the date picker is closed. */
239+
closedStream: EventEmitter<void>;
240+
/** Color palette to use on the datepicker's calendar. */
241+
color: ThemePalette;
242+
/** The input element the datepicker is associated with. */
243+
datepickerInput: C;
244+
/** Whether the datepicker pop-up should be disabled. */
245+
disabled: boolean;
246+
/** The id for the datepicker's calendar. */
247+
id: string;
248+
/** Whether the datepicker is open. */
249+
opened: boolean;
250+
/** Stream that emits whenever the date picker is opened. */
251+
openedStream: EventEmitter<void>;
252+
/** Emits when the datepicker's state changes. */
253+
stateChanges: Subject<void>;
254+
/** Opens the datepicker. */
255+
open(): void;
256+
/** Register an input with the datepicker. */
257+
registerInput(input: C): MatDateSelectionModel<S, D>;
258+
}
259+
235260
/** Base class for a datepicker. */
236261
@Directive()
237262
export abstract class MatDatepickerBase<C extends MatDatepickerControl<D>, S,
238-
D = ExtractDateTypeFromSelection<S>> implements OnDestroy, OnChanges {
263+
D = ExtractDateTypeFromSelection<S>> implements MatDatepickerPanel<C, S, D>, OnDestroy,
264+
OnChanges {
239265
private _scrollStrategy: () => ScrollStrategy;
240266
private _inputStateChanges = Subscription.EMPTY;
241267

@@ -247,7 +273,7 @@ export abstract class MatDatepickerBase<C extends MatDatepickerControl<D>, S,
247273
get startAt(): D | null {
248274
// If an explicit startAt is set we start there, otherwise we start at whatever the currently
249275
// selected value is.
250-
return this._startAt || (this._datepickerInput ? this._datepickerInput.getStartValue() : null);
276+
return this._startAt || (this.datepickerInput ? this.datepickerInput.getStartValue() : null);
251277
}
252278
set startAt(value: D | null) {
253279
this._startAt = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
@@ -261,7 +287,7 @@ export abstract class MatDatepickerBase<C extends MatDatepickerControl<D>, S,
261287
@Input()
262288
get color(): ThemePalette {
263289
return this._color ||
264-
(this._datepickerInput ? this._datepickerInput.getThemePalette() : undefined);
290+
(this.datepickerInput ? this.datepickerInput.getThemePalette() : undefined);
265291
}
266292
set color(value: ThemePalette) {
267293
this._color = value;
@@ -282,15 +308,15 @@ export abstract class MatDatepickerBase<C extends MatDatepickerControl<D>, S,
282308
/** Whether the datepicker pop-up should be disabled. */
283309
@Input()
284310
get disabled(): boolean {
285-
return this._disabled === undefined && this._datepickerInput ?
286-
this._datepickerInput.disabled : !!this._disabled;
311+
return this._disabled === undefined && this.datepickerInput ?
312+
this.datepickerInput.disabled : !!this._disabled;
287313
}
288314
set disabled(value: boolean) {
289315
const newValue = coerceBooleanProperty(value);
290316

291317
if (newValue !== this._disabled) {
292318
this._disabled = newValue;
293-
this._stateChanges.next(undefined);
319+
this.stateChanges.next(undefined);
294320
}
295321
}
296322
private _disabled: boolean;
@@ -354,16 +380,16 @@ export abstract class MatDatepickerBase<C extends MatDatepickerControl<D>, S,
354380

355381
/** The minimum selectable date. */
356382
_getMinDate(): D | null {
357-
return this._datepickerInput && this._datepickerInput.min;
383+
return this.datepickerInput && this.datepickerInput.min;
358384
}
359385

360386
/** The maximum selectable date. */
361387
_getMaxDate(): D | null {
362-
return this._datepickerInput && this._datepickerInput.max;
388+
return this.datepickerInput && this.datepickerInput.max;
363389
}
364390

365391
_getDateFilter(): DateFilterFn<D> {
366-
return this._datepickerInput && this._datepickerInput.dateFilter;
392+
return this.datepickerInput && this.datepickerInput.dateFilter;
367393
}
368394

369395
/** A reference to the overlay when the calendar is opened as a popup. */
@@ -382,10 +408,10 @@ export abstract class MatDatepickerBase<C extends MatDatepickerControl<D>, S,
382408
private _backdropHarnessClass = `${this.id}-backdrop`;
383409

384410
/** The input element this datepicker is associated with. */
385-
_datepickerInput: C;
411+
datepickerInput: C;
386412

387413
/** Emits when the datepicker's state changes. */
388-
readonly _stateChanges = new Subject<void>();
414+
readonly stateChanges = new Subject<void>();
389415

390416
constructor(private _dialog: MatDialog,
391417
private _overlay: Overlay,
@@ -415,14 +441,14 @@ export abstract class MatDatepickerBase<C extends MatDatepickerControl<D>, S,
415441
}
416442
}
417443

418-
this._stateChanges.next(undefined);
444+
this.stateChanges.next(undefined);
419445
}
420446

421447
ngOnDestroy() {
422448
this._destroyPopup();
423449
this.close();
424450
this._inputStateChanges.unsubscribe();
425-
this._stateChanges.complete();
451+
this.stateChanges.complete();
426452
}
427453

428454
/** Selects the given date */
@@ -450,14 +476,14 @@ export abstract class MatDatepickerBase<C extends MatDatepickerControl<D>, S,
450476
* @param input The datepicker input to register with this datepicker.
451477
* @returns Selection model that the input should hook itself up to.
452478
*/
453-
_registerInput(input: C): MatDateSelectionModel<S, D> {
454-
if (this._datepickerInput && (typeof ngDevMode === 'undefined' || ngDevMode)) {
479+
registerInput(input: C): MatDateSelectionModel<S, D> {
480+
if (this.datepickerInput && (typeof ngDevMode === 'undefined' || ngDevMode)) {
455481
throw Error('A MatDatepicker can only be associated with a single input.');
456482
}
457483
this._inputStateChanges.unsubscribe();
458-
this._datepickerInput = input;
484+
this.datepickerInput = input;
459485
this._inputStateChanges =
460-
input.stateChanges.subscribe(() => this._stateChanges.next(undefined));
486+
input.stateChanges.subscribe(() => this.stateChanges.next(undefined));
461487
return this._model;
462488
}
463489

@@ -466,7 +492,7 @@ export abstract class MatDatepickerBase<C extends MatDatepickerControl<D>, S,
466492
if (this._opened || this.disabled) {
467493
return;
468494
}
469-
if (!this._datepickerInput && (typeof ngDevMode === 'undefined' || ngDevMode)) {
495+
if (!this.datepickerInput && (typeof ngDevMode === 'undefined' || ngDevMode)) {
470496
throw Error('Attempted to open an MatDatepicker with no associated input.');
471497
}
472498
if (this._document) {
@@ -584,7 +610,7 @@ export abstract class MatDatepickerBase<C extends MatDatepickerControl<D>, S,
584610
/** Create the popup. */
585611
private _createPopup(): void {
586612
const positionStrategy = this._overlay.position()
587-
.flexibleConnectedTo(this._datepickerInput.getConnectedOverlayOrigin())
613+
.flexibleConnectedTo(this.datepickerInput.getConnectedOverlayOrigin())
588614
.withTransformOriginOn('.mat-datepicker-content')
589615
.withFlexibleDimensions(false)
590616
.withViewportMargin(8)
@@ -607,7 +633,7 @@ export abstract class MatDatepickerBase<C extends MatDatepickerControl<D>, S,
607633
this._popupRef.detachments(),
608634
this._popupRef.keydownEvents().pipe(filter(event => {
609635
// Closing on alt + up is only valid when there's an input associated with the datepicker.
610-
return (event.keyCode === ESCAPE && !hasModifierKey(event)) || (this._datepickerInput &&
636+
return (event.keyCode === ESCAPE && !hasModifierKey(event)) || (this.datepickerInput &&
611637
hasModifierKey(event, 'altKey') && event.keyCode === UP_ARROW);
612638
}))
613639
).subscribe(event => {

src/material/datepicker/datepicker-input.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,8 @@ import {
2828
} from '@angular/material/core';
2929
import {MatFormField, MAT_FORM_FIELD} from '@angular/material/form-field';
3030
import {MAT_INPUT_VALUE_ACCESSOR} from '@angular/material/input';
31-
import {MatDatepicker} from './datepicker';
3231
import {MatDatepickerInputBase, DateFilterFn} from './datepicker-input-base';
33-
import {MatDatepickerControl} from './datepicker-base';
32+
import {MatDatepickerControl, MatDatepickerPanel} from './datepicker-base';
3433

3534
/** @docs-private */
3635
export const MAT_DATEPICKER_VALUE_ACCESSOR: any = {
@@ -75,13 +74,13 @@ export class MatDatepickerInput<D> extends MatDatepickerInputBase<D | null, D>
7574
implements MatDatepickerControl<D | null> {
7675
/** The datepicker that this input is associated with. */
7776
@Input()
78-
set matDatepicker(datepicker: MatDatepicker<D>) {
77+
set matDatepicker(datepicker: MatDatepickerPanel<MatDatepickerControl<D>, D | null, D>) {
7978
if (datepicker) {
8079
this._datepicker = datepicker;
81-
this._registerModel(datepicker._registerInput(this));
80+
this._registerModel(datepicker.registerInput(this));
8281
}
8382
}
84-
_datepicker: MatDatepicker<D>;
83+
_datepicker: MatDatepickerPanel<MatDatepickerControl<D>, D | null, D>;
8584

8685
/** The minimum valid date. */
8786
@Input()

src/material/datepicker/datepicker-toggle.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
import {MatButton} from '@angular/material/button';
2626
import {merge, of as observableOf, Subscription} from 'rxjs';
2727
import {MatDatepickerIntl} from './datepicker-intl';
28-
import {MatDatepickerBase, MatDatepickerControl} from './datepicker-base';
28+
import {MatDatepickerControl, MatDatepickerPanel} from './datepicker-base';
2929

3030

3131
/** Can be used to override the icon of a `matDatepickerToggle`. */
@@ -59,7 +59,7 @@ export class MatDatepickerToggle<D> implements AfterContentInit, OnChanges, OnDe
5959
private _stateChanges = Subscription.EMPTY;
6060

6161
/** Datepicker instance that the button will toggle. */
62-
@Input('for') datepicker: MatDatepickerBase<MatDatepickerControl<any>, D>;
62+
@Input('for') datepicker: MatDatepickerPanel<MatDatepickerControl<any>, D>;
6363

6464
/** Tabindex for the toggle. */
6565
@Input() tabIndex: number | null;
@@ -118,9 +118,9 @@ export class MatDatepickerToggle<D> implements AfterContentInit, OnChanges, OnDe
118118
}
119119

120120
private _watchStateChanges() {
121-
const datepickerStateChanged = this.datepicker ? this.datepicker._stateChanges : observableOf();
122-
const inputStateChanged = this.datepicker && this.datepicker._datepickerInput ?
123-
this.datepicker._datepickerInput.stateChanges : observableOf();
121+
const datepickerStateChanged = this.datepicker ? this.datepicker.stateChanges : observableOf();
122+
const inputStateChanged = this.datepicker && this.datepicker.datepickerInput ?
123+
this.datepicker.datepickerInput.stateChanges : observableOf();
124124
const datepickerToggled = this.datepicker ?
125125
merge(this.datepicker.openedStream, this.datepicker.closedStream) :
126126
observableOf();

tools/public_api_guard/material/datepicker.d.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -210,12 +210,12 @@ export declare class MatDatepickerContent<S, D = ExtractDateTypeFromSelection<S>
210210
}
211211

212212
export declare class MatDatepickerInput<D> extends MatDatepickerInputBase<D | null, D> implements MatDatepickerControl<D | null> {
213-
_datepicker: MatDatepicker<D>;
213+
_datepicker: MatDatepickerPanel<MatDatepickerControl<D>, D | null, D>;
214214
protected _outsideValueChanged: undefined;
215215
protected _validator: ValidatorFn | null;
216216
get dateFilter(): DateFilterFn<D | null>;
217217
set dateFilter(value: DateFilterFn<D | null>);
218-
set matDatepicker(datepicker: MatDatepicker<D>);
218+
set matDatepicker(datepicker: MatDatepickerPanel<MatDatepickerControl<D>, D | null, D>);
219219
get max(): D | null;
220220
set max(value: D | null);
221221
get min(): D | null;
@@ -272,7 +272,7 @@ export declare class MatDatepickerToggle<D> implements AfterContentInit, OnChang
272272
_button: MatButton;
273273
_customIcon: MatDatepickerToggleIcon;
274274
_intl: MatDatepickerIntl;
275-
datepicker: MatDatepickerBase<MatDatepickerControl<any>, D>;
275+
datepicker: MatDatepickerPanel<MatDatepickerControl<any>, D>;
276276
disableRipple: boolean;
277277
get disabled(): boolean;
278278
set disabled(value: boolean);
@@ -314,8 +314,8 @@ export declare class MatDateRangeInput<D> implements MatFormFieldControl<DateRan
314314
set min(value: D | null);
315315
ngControl: NgControl | null;
316316
get placeholder(): string;
317-
get rangePicker(): MatDateRangePicker<D>;
318-
set rangePicker(rangePicker: MatDateRangePicker<D>);
317+
get rangePicker(): MatDatepickerPanel<MatDatepickerControl<D>, DateRange<D>, D>;
318+
set rangePicker(rangePicker: MatDatepickerPanel<MatDatepickerControl<D>, DateRange<D>, D>);
319319
get required(): boolean;
320320
set required(value: boolean);
321321
separator: string;

0 commit comments

Comments
 (0)