diff --git a/src/lib/datepicker/calendar-body.html b/src/lib/datepicker/calendar-body.html index 50b784bb8b5..5a4bff85047 100644 --- a/src/lib/datepicker/calendar-body.html +++ b/src/lib/datepicker/calendar-body.html @@ -30,6 +30,7 @@ { expect((cellEls[10] as HTMLElement).innerText.trim()).toBe('11'); expect(cellEls[10].classList).toContain('mat-calendar-body-active'); }); + + it('should set a class on even dates', () => { + expect((cellEls[0] as HTMLElement).innerText.trim()).toBe('1'); + expect((cellEls[1] as HTMLElement).innerText.trim()).toBe('2'); + expect(cellEls[0].classList).not.toContain('even'); + expect(cellEls[1].classList).toContain('even'); + }); + }); }); @@ -117,7 +125,9 @@ describe('MatCalendarBody', () => { }) class StandardCalendarBody { label = 'Jan 2017'; - rows = [[1, 2, 3, 4, 5, 6, 7], [8, 9, 10, 11, 12, 13, 14]].map(r => r.map(createCell)); + rows = [[1, 2, 3, 4, 5, 6, 7], [8, 9, 10, 11, 12, 13, 14]].map(row => { + return row.map(cell => createCell(cell, cell % 2 === 0 ? 'even' : undefined)); + }); todayValue = 3; selectedValue = 4; labelMinRequiredCells = 3; @@ -128,6 +138,6 @@ class StandardCalendarBody { } } -function createCell(value: number) { - return new MatCalendarCell(value, `${value}`, `${value}-label`, true); +function createCell(value: number, cellClasses?: MatCalendarCellCssClasses) { + return new MatCalendarCell(value, `${value}`, `${value}-label`, true, cellClasses); } diff --git a/src/lib/datepicker/calendar-body.ts b/src/lib/datepicker/calendar-body.ts index b8d4df62014..82eb3b5c0db 100644 --- a/src/lib/datepicker/calendar-body.ts +++ b/src/lib/datepicker/calendar-body.ts @@ -18,6 +18,11 @@ import { } from '@angular/core'; import {take} from 'rxjs/operators'; +/** + * Extra CSS classes that can be associated with a calendar cell. + */ +export type MatCalendarCellCssClasses = string | string[] | Set | {[key: string]: any}; + /** * An internal class that represents the data corresponding to a single calendar cell. * @docs-private @@ -26,7 +31,8 @@ export class MatCalendarCell { constructor(public value: number, public displayValue: string, public ariaLabel: string, - public enabled: boolean) {} + public enabled: boolean, + public cssClasses?: MatCalendarCellCssClasses) {} } diff --git a/src/lib/datepicker/calendar.html b/src/lib/datepicker/calendar.html index 974f7127ce6..1d64737c75b 100644 --- a/src/lib/datepicker/calendar.html +++ b/src/lib/datepicker/calendar.html @@ -9,6 +9,7 @@ [dateFilter]="dateFilter" [maxDate]="maxDate" [minDate]="minDate" + [dateClass]="dateClass" (selectedChange)="_dateSelected($event)" (_userSelection)="_userSelected()"> diff --git a/src/lib/datepicker/calendar.ts b/src/lib/datepicker/calendar.ts index 9eaa9dfe924..dbb1241aaf4 100644 --- a/src/lib/datepicker/calendar.ts +++ b/src/lib/datepicker/calendar.ts @@ -32,6 +32,7 @@ import {MatDatepickerIntl} from './datepicker-intl'; import {MatMonthView} from './month-view'; import {MatMultiYearView, yearsPerPage} from './multi-year-view'; import {MatYearView} from './year-view'; +import {MatCalendarCellCssClasses} from './calendar-body'; /** * Possible views for the calendar. @@ -220,9 +221,12 @@ export class MatCalendar implements AfterContentInit, AfterViewChecked, OnDes } private _maxDate: D | null; - /** A function used to filter which dates are selectable. */ + /** Function used to filter which dates are selectable. */ @Input() dateFilter: (date: D) => boolean; + /** Function that can be used to add custom CSS classes to dates. */ + @Input() dateClass: (date: D) => MatCalendarCellCssClasses; + /** Emits when the currently selected date changes. */ @Output() readonly selectedChange: EventEmitter = new EventEmitter(); diff --git a/src/lib/datepicker/datepicker-content.html b/src/lib/datepicker/datepicker-content.html index 7284d499d92..fbca010906e 100644 --- a/src/lib/datepicker/datepicker-content.html +++ b/src/lib/datepicker/datepicker-content.html @@ -8,6 +8,7 @@ [dateFilter]="datepicker._dateFilter" [headerComponent]="datepicker.calendarHeaderComponent" [selected]="datepicker._selected" + [dateClass]="datepicker.dateClass" [@fadeInCalendar]="'enter'" (selectedChange)="datepicker.select($event)" (yearSelected)="datepicker._selectYear($event)" diff --git a/src/lib/datepicker/datepicker.md b/src/lib/datepicker/datepicker.md index 12b9666a2ca..afbf6b25027 100644 --- a/src/lib/datepicker/datepicker.md +++ b/src/lib/datepicker/datepicker.md @@ -328,6 +328,14 @@ application root module. export class MyApp {} ``` +#### Highlighting specific dates +If you want to apply one or more CSS classes to some dates in the calendar (e.g. to highlight a +holiday), you can do so with the `dateClass` input. It accepts a function which will be called +with each of the dates in the calendar and will apply any classes that are returned. The return +value can be anything that is accepted by `ngClass`. + + + ### Accessibility The `MatDatepickerInput` and `MatDatepickerToggle` directives add the `aria-haspopup` attribute to diff --git a/src/lib/datepicker/datepicker.ts b/src/lib/datepicker/datepicker.ts index 88973e51e23..d952a7d8f91 100644 --- a/src/lib/datepicker/datepicker.ts +++ b/src/lib/datepicker/datepicker.ts @@ -50,6 +50,7 @@ import {MatCalendar} from './calendar'; import {matDatepickerAnimations} from './datepicker-animations'; import {createMissingDateImplError} from './datepicker-errors'; import {MatDatepickerInput} from './datepicker-input'; +import {MatCalendarCellCssClasses} from './calendar-body'; /** Used to generate a unique ID for each datepicker instance. */ let datepickerUid = 0; @@ -212,6 +213,9 @@ export class MatDatepicker implements OnDestroy, CanColor { /** Classes to be passed to the date picker panel. Supports the same syntax as `ngClass`. */ @Input() panelClass: string | string[]; + /** Function that can be used to add custom CSS classes to dates. */ + @Input() dateClass: (date: D) => MatCalendarCellCssClasses; + /** Emits when the datepicker has been opened. */ @Output('opened') openedStream: EventEmitter = new EventEmitter(); diff --git a/src/lib/datepicker/month-view.spec.ts b/src/lib/datepicker/month-view.spec.ts index d789b1ef260..c531d208a2c 100644 --- a/src/lib/datepicker/month-view.spec.ts +++ b/src/lib/datepicker/month-view.spec.ts @@ -35,6 +35,7 @@ describe('MatMonthView', () => { // Test components. StandardMonthView, MonthViewWithDateFilter, + MonthViewWithDateClass, ], providers: [ {provide: Directionality, useFactory: () => dir = {value: 'ltr'}} @@ -291,6 +292,26 @@ describe('MatMonthView', () => { expect(cells[1].classList).not.toContain('mat-calendar-body-disabled'); }); }); + + describe('month view with custom date classes', () => { + let fixture: ComponentFixture; + let monthViewNativeElement: Element; + + beforeEach(() => { + fixture = TestBed.createComponent(MonthViewWithDateClass); + fixture.detectChanges(); + + let monthViewDebugElement = fixture.debugElement.query(By.directive(MatMonthView)); + monthViewNativeElement = monthViewDebugElement.nativeElement; + }); + + it('should be able to add a custom class to some dates', () => { + let cells = monthViewNativeElement.querySelectorAll('.mat-calendar-body-cell'); + expect(cells[0].classList).not.toContain('even'); + expect(cells[1].classList).toContain('even'); + }); + }); + }); @@ -312,3 +333,13 @@ class MonthViewWithDateFilter { return date.getDate() % 2 == 0; } } + +@Component({ + template: `` +}) +class MonthViewWithDateClass { + activeDate = new Date(2017, JAN, 1); + dateClass(date: Date) { + return date.getDate() % 2 == 0 ? 'even' : undefined; + } +} diff --git a/src/lib/datepicker/month-view.ts b/src/lib/datepicker/month-view.ts index 8c190132fff..3f4c198d24b 100644 --- a/src/lib/datepicker/month-view.ts +++ b/src/lib/datepicker/month-view.ts @@ -33,7 +33,7 @@ import { } from '@angular/core'; import {DateAdapter, MAT_DATE_FORMATS, MatDateFormats} from '@angular/material/core'; import {Directionality} from '@angular/cdk/bidi'; -import {MatCalendarBody, MatCalendarCell} from './calendar-body'; +import {MatCalendarBody, MatCalendarCell, MatCalendarCellCssClasses} from './calendar-body'; import {createMissingDateImplError} from './datepicker-errors'; @@ -94,9 +94,12 @@ export class MatMonthView implements AfterContentInit { } private _maxDate: D | null; - /** A function used to filter which dates are selectable. */ + /** Function used to filter which dates are selectable. */ @Input() dateFilter: (date: D) => boolean; + /** Function that can be used to add custom CSS classes to dates. */ + @Input() dateClass: (date: D) => MatCalendarCellCssClasses; + /** Emits when a new date is selected. */ @Output() readonly selectedChange: EventEmitter = new EventEmitter(); @@ -273,8 +276,10 @@ export class MatMonthView implements AfterContentInit { this._dateAdapter.getMonth(this.activeDate), i + 1); const enabled = this._shouldEnableDate(date); const ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.dateA11yLabel); + const cellClasses = this.dateClass ? this.dateClass(date) : undefined; + this._weeks[this._weeks.length - 1] - .push(new MatCalendarCell(i + 1, dateNames[i], ariaLabel, enabled)); + .push(new MatCalendarCell(i + 1, dateNames[i], ariaLabel, enabled, cellClasses)); } } diff --git a/src/material-examples/datepicker-date-class/datepicker-date-class-example.css b/src/material-examples/datepicker-date-class/datepicker-date-class-example.css new file mode 100644 index 00000000000..3a0cabf713d --- /dev/null +++ b/src/material-examples/datepicker-date-class/datepicker-date-class-example.css @@ -0,0 +1,4 @@ +.example-custom-date-class { + background: orange; + border-radius: 100%; +} diff --git a/src/material-examples/datepicker-date-class/datepicker-date-class-example.html b/src/material-examples/datepicker-date-class/datepicker-date-class-example.html new file mode 100644 index 00000000000..fbba9f6a169 --- /dev/null +++ b/src/material-examples/datepicker-date-class/datepicker-date-class-example.html @@ -0,0 +1,5 @@ + + + + + diff --git a/src/material-examples/datepicker-date-class/datepicker-date-class-example.ts b/src/material-examples/datepicker-date-class/datepicker-date-class-example.ts new file mode 100644 index 00000000000..ab92b1eea69 --- /dev/null +++ b/src/material-examples/datepicker-date-class/datepicker-date-class-example.ts @@ -0,0 +1,17 @@ +import {Component, ViewEncapsulation} from '@angular/core'; + +/** @title Datepicker with custom date classes */ +@Component({ + selector: 'datepicker-date-class-example', + templateUrl: 'datepicker-date-class-example.html', + styleUrls: ['datepicker-date-class-example.css'], + encapsulation: ViewEncapsulation.None, +}) +export class DatepickerDateClassExample { + dateClass = (d: Date) => { + const date = d.getDate(); + + // Highlight the 1st and 20th day of each month. + return (date === 1 || date === 20) ? 'example-custom-date-class' : undefined; + } +}