diff --git a/src/demo-app/datepicker/datepicker-demo.html b/src/demo-app/datepicker/datepicker-demo.html
index f259e3dae6b5..efa35dcf189c 100644
--- a/src/demo-app/datepicker/datepicker-demo.html
+++ b/src/demo-app/datepicker/datepicker-demo.html
@@ -5,6 +5,13 @@
Options
Start in year view
Disable datepicker
Disable input
+
+
+ Primary
+ Accent
+ Warn
+
+
@@ -53,7 +60,8 @@ Result
[touchUi]="touch"
[disabled]="datepickerDisabled"
[startAt]="startAt"
- [startView]="yearView ? 'year' : 'month'">
+ [startView]="yearView ? 'year' : 'month'"
+ [color]="color">
"{{resultPickerModel.getError('matDatepickerParse').text}}" is not a valid date!
diff --git a/src/demo-app/datepicker/datepicker-demo.ts b/src/demo-app/datepicker/datepicker-demo.ts
index ae4c40758458..61d6a5146883 100644
--- a/src/demo-app/datepicker/datepicker-demo.ts
+++ b/src/demo-app/datepicker/datepicker-demo.ts
@@ -8,7 +8,9 @@
import {ChangeDetectionStrategy, Component} from '@angular/core';
import {FormControl} from '@angular/forms';
-import {MatDatepickerInputEvent} from '@angular/material';
+import {MatDatepickerInputEvent} from '@angular/material/datepicker';
+import {ThemePalette} from '@angular/material/core';
+
@Component({
moduleId: module.id,
@@ -29,6 +31,7 @@ export class DatepickerDemo {
date: Date;
lastDateInput: Date | null;
lastDateChange: Date | null;
+ color: ThemePalette;
dateCtrl = new FormControl();
diff --git a/src/lib/datepicker/_datepicker-theme.scss b/src/lib/datepicker/_datepicker-theme.scss
index c448e4b83fab..120cd711d205 100644
--- a/src/lib/datepicker/_datepicker-theme.scss
+++ b/src/lib/datepicker/_datepicker-theme.scss
@@ -3,24 +3,32 @@
@import '../core/typography/typography-utils';
+$mat-datepicker-selected-today-box-shadow-width: 1px;
+$mat-datepicker-selected-fade-amount: 0.6;
+$mat-datepicker-today-fade-amount: 0.2;
$mat-calendar-body-font-size: 13px !default;
$mat-calendar-weekday-table-font-size: 11px !default;
+@mixin _mat-datepicker-color($palette) {
+ .mat-calendar-body-selected {
+ background-color: mat-color($palette);
+ color: mat-color($palette, default-contrast);
+ }
+
+ .mat-calendar-body-disabled > .mat-calendar-body-selected {
+ background-color: fade-out(mat-color($palette), $mat-datepicker-selected-fade-amount);
+ }
+
+ .mat-calendar-body-today.mat-calendar-body-selected {
+ box-shadow: inset 0 0 0 $mat-datepicker-selected-today-box-shadow-width
+ mat-color($palette, default-contrast);
+ }
+}
@mixin mat-datepicker-theme($theme) {
- $primary: map-get($theme, primary);
$foreground: map-get($theme, foreground);
$background: map-get($theme, background);
- $mat-datepicker-selected-today-box-shadow-width: 1px;
- $mat-datepicker-selected-fade-amount: 0.6;
- $mat-datepicker-today-fade-amount: 0.2;
-
- .mat-datepicker-content {
- background-color: mat-color($background, card);
- color: mat-color($foreground, text);
- }
-
.mat-calendar-arrow {
border-top-color: mat-color($foreground, icon);
}
@@ -59,34 +67,41 @@ $mat-calendar-weekday-table-font-size: 11px !default;
}
}
- .mat-calendar-body-selected {
- background-color: mat-color($primary);
- color: mat-color($primary, default-contrast);
+ .mat-calendar-body-today:not(.mat-calendar-body-selected) {
+ // Note: though it's not text, the border is a hint about the fact that this is today's date,
+ // so we use the hint color.
+ border-color: mat-color($foreground, hint-text);
}
- .mat-calendar-body-disabled > .mat-calendar-body-selected {
- background-color: fade-out(mat-color($primary), $mat-datepicker-selected-fade-amount);
+ .mat-calendar-body-disabled > .mat-calendar-body-today:not(.mat-calendar-body-selected) {
+ border-color: fade-out(mat-color($foreground, hint-text), $mat-datepicker-today-fade-amount);
}
- .mat-calendar-body-today {
- &:not(.mat-calendar-body-selected) {
- // Note: though it's not text, the border is a hint about the fact that this is today's date,
- // so we use the hint color.
- border-color: mat-color($foreground, hint-text);
- }
+ @include _mat-datepicker-color(map-get($theme, primary));
+
+ .mat-datepicker-content {
+ background-color: mat-color($background, card);
+ color: mat-color($foreground, text);
- &.mat-calendar-body-selected {
- box-shadow: inset 0 0 0 $mat-datepicker-selected-today-box-shadow-width
- mat-color($primary, default-contrast);
+ &.mat-accent {
+ @include _mat-datepicker-color(map-get($theme, accent));
}
- }
- .mat-calendar-body-disabled > .mat-calendar-body-today:not(.mat-calendar-body-selected) {
- border-color: fade-out(mat-color($foreground, hint-text), $mat-datepicker-today-fade-amount);
+ &.mat-warn {
+ @include _mat-datepicker-color(map-get($theme, warn));
+ }
}
.mat-datepicker-toggle-active {
- color: mat-color($primary);
+ color: mat-color(map-get($theme, primary));
+
+ &.mat-accent {
+ color: mat-color(map-get($theme, accent));
+ }
+
+ &.mat-warn {
+ color: mat-color(map-get($theme, warn));
+ }
}
}
diff --git a/src/lib/datepicker/datepicker-input.ts b/src/lib/datepicker/datepicker-input.ts
index fad809625c6a..98ee4ca8db8c 100644
--- a/src/lib/datepicker/datepicker-input.ts
+++ b/src/lib/datepicker/datepicker-input.ts
@@ -337,6 +337,11 @@ export class MatDatepickerInput implements AfterContentInit, ControlValueAcce
this.dateChange.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement));
}
+ /** Returns the palette used by the input's form field, if any. */
+ _getThemePalette() {
+ return this._formField ? this._formField.color : undefined;
+ }
+
/**
* @param obj The object to check.
* @returns The given object if it is both a date instance and valid, otherwise null.
diff --git a/src/lib/datepicker/datepicker-toggle.ts b/src/lib/datepicker/datepicker-toggle.ts
index 52a3415af47c..ac50c391f232 100644
--- a/src/lib/datepicker/datepicker-toggle.ts
+++ b/src/lib/datepicker/datepicker-toggle.ts
@@ -42,6 +42,8 @@ export class MatDatepickerToggleIcon {}
host: {
'class': 'mat-datepicker-toggle',
'[class.mat-datepicker-toggle-active]': 'datepicker && datepicker.opened',
+ '[class.mat-accent]': 'datepicker && datepicker.color === "accent"',
+ '[class.mat-warn]': 'datepicker && datepicker.color === "warn"',
},
exportAs: 'matDatepickerToggle',
encapsulation: ViewEncapsulation.None,
diff --git a/src/lib/datepicker/datepicker.spec.ts b/src/lib/datepicker/datepicker.spec.ts
index 3cb8e7af1d27..701160870064 100644
--- a/src/lib/datepicker/datepicker.spec.ts
+++ b/src/lib/datepicker/datepicker.spec.ts
@@ -14,7 +14,7 @@ import {
NativeDateModule,
SEP,
} from '@angular/material/core';
-import {MatFormFieldModule} from '@angular/material/form-field';
+import {MatFormFieldModule, MatFormField} from '@angular/material/form-field';
import {By} from '@angular/platform-browser';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {MatInputModule} from '../input/index';
@@ -98,6 +98,29 @@ describe('MatDatepicker', () => {
.not.toBeNull();
});
+ it('should pass the datepicker theme color to the overlay', fakeAsync(() => {
+ testComponent.datepicker.color = 'primary';
+ testComponent.datepicker.open();
+ fixture.detectChanges();
+
+ let contentEl = document.querySelector('.mat-datepicker-content')!;
+
+ expect(contentEl.classList).toContain('mat-primary');
+
+ testComponent.datepicker.close();
+ fixture.detectChanges();
+ flush();
+
+ testComponent.datepicker.color = 'warn';
+ testComponent.datepicker.open();
+
+ contentEl = document.querySelector('.mat-datepicker-content')!;
+ fixture.detectChanges();
+
+ expect(contentEl.classList).toContain('mat-warn');
+ expect(contentEl.classList).not.toContain('mat-primary');
+ }));
+
it('should open datepicker if opened input is set to true', () => {
testComponent.opened = true;
fixture.detectChanges();
@@ -137,9 +160,10 @@ describe('MatDatepicker', () => {
expect(document.querySelector('.cdk-overlay-pane')).not.toBeNull();
});
- it('close should close popup', () => {
+ it('close should close popup', fakeAsync(() => {
testComponent.datepicker.open();
fixture.detectChanges();
+ flush();
let popup = document.querySelector('.cdk-overlay-pane')!;
expect(popup).not.toBeNull();
@@ -147,9 +171,10 @@ describe('MatDatepicker', () => {
testComponent.datepicker.close();
fixture.detectChanges();
+ flush();
expect(parseInt(getComputedStyle(popup).height as string)).toBe(0);
- });
+ }));
it('should close the popup when pressing ESCAPE', fakeAsync(() => {
testComponent.datepicker.open();
@@ -848,13 +873,13 @@ describe('MatDatepicker', () => {
beforeEach(fakeAsync(() => {
fixture = createComponent(FormFieldDatepicker, [MatNativeDateModule]);
fixture.detectChanges();
-
testComponent = fixture.componentInstance;
}));
afterEach(fakeAsync(() => {
testComponent.datepicker.close();
fixture.detectChanges();
+ flush();
}));
it('should float the placeholder when an invalid value is entered', () => {
@@ -865,6 +890,41 @@ describe('MatDatepicker', () => {
expect(fixture.debugElement.nativeElement.querySelector('mat-form-field').classList)
.toContain('mat-form-field-should-float');
});
+
+ it('should pass the form field theme color to the overlay', fakeAsync(() => {
+ testComponent.formField.color = 'primary';
+ testComponent.datepicker.open();
+ fixture.detectChanges();
+
+ let contentEl = document.querySelector('.mat-datepicker-content')!;
+
+ expect(contentEl.classList).toContain('mat-primary');
+
+ testComponent.datepicker.close();
+ fixture.detectChanges();
+ flush();
+
+ testComponent.formField.color = 'warn';
+ testComponent.datepicker.open();
+
+ contentEl = document.querySelector('.mat-datepicker-content')!;
+ fixture.detectChanges();
+
+ expect(contentEl.classList).toContain('mat-warn');
+ expect(contentEl.classList).not.toContain('mat-primary');
+ }));
+
+ it('should prefer the datepicker color over the form field one', fakeAsync(() => {
+ testComponent.datepicker.color = 'accent';
+ testComponent.formField.color = 'warn';
+ testComponent.datepicker.open();
+ fixture.detectChanges();
+
+ const contentEl = document.querySelector('.mat-datepicker-content')!;
+
+ expect(contentEl.classList).toContain('mat-accent');
+ expect(contentEl.classList).not.toContain('mat-warn');
+ }));
});
describe('datepicker with min and max dates and validation', () => {
@@ -1423,6 +1483,7 @@ class DatepickerWithCustomIcon {}
class FormFieldDatepicker {
@ViewChild('d') datepicker: MatDatepicker;
@ViewChild(MatDatepickerInput) datepickerInput: MatDatepickerInput;
+ @ViewChild(MatFormField) formField: MatFormField;
}
diff --git a/src/lib/datepicker/datepicker.ts b/src/lib/datepicker/datepicker.ts
index 780311d32a10..7eb04517d190 100644
--- a/src/lib/datepicker/datepicker.ts
+++ b/src/lib/datepicker/datepicker.ts
@@ -36,6 +36,7 @@ import {
ViewChild,
ViewContainerRef,
ViewEncapsulation,
+ ElementRef,
} from '@angular/core';
import {DateAdapter} from '@angular/material/core';
import {MatDialog, MatDialogRef} from '@angular/material/dialog';
@@ -46,6 +47,7 @@ import {merge} from 'rxjs/observable/merge';
import {MatCalendar} from './calendar';
import {createMissingDateImplError} from './datepicker-errors';
import {MatDatepickerInput} from './datepicker-input';
+import {CanColor, mixinColor, ThemePalette} from '@angular/material/core';
/** Used to generate a unique ID for each datepicker instance. */
@@ -68,6 +70,12 @@ export const MAT_DATEPICKER_SCROLL_STRATEGY_PROVIDER = {
useFactory: MAT_DATEPICKER_SCROLL_STRATEGY_PROVIDER_FACTORY,
};
+// Boilerplate for applying mixins to MatDatepickerContent.
+/** @docs-private */
+export class MatDatepickerContentBase {
+ constructor(public _elementRef: ElementRef) {}
+}
+export const _MatDatepickerContentMixinBase = mixinColor(MatDatepickerContentBase);
/**
* Component used as the content for the datepicker dialog and popup. We use this instead of using
@@ -89,12 +97,18 @@ export const MAT_DATEPICKER_SCROLL_STRATEGY_PROVIDER = {
encapsulation: ViewEncapsulation.None,
preserveWhitespaces: false,
changeDetection: ChangeDetectionStrategy.OnPush,
+ inputs: ['color'],
})
-export class MatDatepickerContent implements AfterContentInit {
+export class MatDatepickerContent extends _MatDatepickerContentMixinBase
+ implements AfterContentInit, CanColor {
datepicker: MatDatepicker;
@ViewChild(MatCalendar) _calendar: MatCalendar;
+ constructor(elementRef: ElementRef) {
+ super(elementRef);
+ }
+
ngAfterContentInit() {
this._calendar._focusActiveCell();
}
@@ -114,7 +128,7 @@ export class MatDatepickerContent implements AfterContentInit {
encapsulation: ViewEncapsulation.None,
preserveWhitespaces: false,
})
-export class MatDatepicker implements OnDestroy {
+export class MatDatepicker implements OnDestroy, CanColor {
/** The date to open the calendar to initially. */
@Input()
get startAt(): D | null {
@@ -130,6 +144,9 @@ export class MatDatepicker implements OnDestroy {
/** The view that the calendar should start in. */
@Input() startView: 'month' | 'year' = 'month';
+ /** Color palette to use on the datepicker's calendar. */
+ @Input() color: ThemePalette;
+
/**
* Whether the calendar UI is in touch mode. In touch mode the calendar opens in a dialog rather
* than a popup and elements have more padding to allow for bigger touch targets.
@@ -218,14 +235,18 @@ export class MatDatepicker implements OnDestroy {
private _popupRef: OverlayRef;
/** A reference to the dialog when the calendar is opened as a dialog. */
- private _dialogRef: MatDialogRef | null;
+ private _dialogRef: MatDialogRef> | null;
/** A portal containing the calendar for this datepicker. */
private _calendarPortal: ComponentPortal>;
+ /** Reference to the component instantiated in popup mode. */
+ private _popupComponentRef: ComponentRef> | null;
+
/** The element that was focused before the datepicker was opened. */
private _focusedElementBeforeOpen: HTMLElement | null = null;
+ /** Subscription to value changes in the associated input element. */
private _inputSubscription = Subscription.EMPTY;
/** The input element this datepicker is associated with. */
@@ -254,6 +275,7 @@ export class MatDatepicker implements OnDestroy {
if (this._popupRef) {
this._popupRef.dispose();
+ this._popupComponentRef = null;
}
}
@@ -355,6 +377,7 @@ export class MatDatepicker implements OnDestroy {
});
this._dialogRef.afterClosed().subscribe(() => this.close());
this._dialogRef.componentInstance.datepicker = this;
+ this._setColor();
}
/** Open the calendar as a popup. */
@@ -368,9 +391,9 @@ export class MatDatepicker implements OnDestroy {
}
if (!this._popupRef.hasAttached()) {
- let componentRef: ComponentRef> =
- this._popupRef.attach(this._calendarPortal);
- componentRef.instance.datepicker = this;
+ this._popupComponentRef = this._popupRef.attach(this._calendarPortal);
+ this._popupComponentRef.instance.datepicker = this;
+ this._setColor();
// Update the position once the calendar has rendered.
this._ngZone.onStable.asObservable().pipe(take(1)).subscribe(() => {
@@ -427,4 +450,18 @@ export class MatDatepicker implements OnDestroy {
private _getValidDateOrNull(obj: any): D | null {
return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null;
}
+
+ /** Passes the current theme color along to the calendar overlay. */
+ private _setColor(): void {
+ const input = this._datepickerInput;
+ const color = this.color || (input ? input._getThemePalette() : undefined);
+
+ if (this._popupComponentRef) {
+ this._popupComponentRef.instance.color = color;
+ }
+
+ if (this._dialogRef) {
+ this._dialogRef.componentInstance.color = color;
+ }
+ }
}