Skip to content

Commit 4be1b06

Browse files
crisbetovivian-hu-zz
authored andcommitted
feat(datepicker): allow custom classes to be applied to individual dates (#13971)
Adds the `dateClass` function which allows consumers to apply custom CSS classes to specific dates. This is useful for highlighting specific dates like a holiday. Fixes #13943.
1 parent 69ffd33 commit 4be1b06

13 files changed

+106
-9
lines changed

src/lib/datepicker/calendar-body.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
<td *ngFor="let item of row; let colIndex = index"
3131
role="gridcell"
3232
class="mat-calendar-body-cell"
33+
[ngClass]="item.cssClasses"
3334
[tabindex]="_isActiveCell(rowIndex, colIndex) ? 0 : -1"
3435
[class.mat-calendar-body-disabled]="!item.enabled"
3536
[class.mat-calendar-body-active]="_isActiveCell(rowIndex, colIndex)"

src/lib/datepicker/calendar-body.spec.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
22
import {Component} from '@angular/core';
3-
import {MatCalendarBody, MatCalendarCell} from './calendar-body';
3+
import {MatCalendarBody, MatCalendarCell, MatCalendarCellCssClasses} from './calendar-body';
44
import {By} from '@angular/platform-browser';
55

66

@@ -98,6 +98,14 @@ describe('MatCalendarBody', () => {
9898
expect((cellEls[10] as HTMLElement).innerText.trim()).toBe('11');
9999
expect(cellEls[10].classList).toContain('mat-calendar-body-active');
100100
});
101+
102+
it('should set a class on even dates', () => {
103+
expect((cellEls[0] as HTMLElement).innerText.trim()).toBe('1');
104+
expect((cellEls[1] as HTMLElement).innerText.trim()).toBe('2');
105+
expect(cellEls[0].classList).not.toContain('even');
106+
expect(cellEls[1].classList).toContain('even');
107+
});
108+
101109
});
102110

103111
});
@@ -117,7 +125,9 @@ describe('MatCalendarBody', () => {
117125
})
118126
class StandardCalendarBody {
119127
label = 'Jan 2017';
120-
rows = [[1, 2, 3, 4, 5, 6, 7], [8, 9, 10, 11, 12, 13, 14]].map(r => r.map(createCell));
128+
rows = [[1, 2, 3, 4, 5, 6, 7], [8, 9, 10, 11, 12, 13, 14]].map(row => {
129+
return row.map(cell => createCell(cell, cell % 2 === 0 ? 'even' : undefined));
130+
});
121131
todayValue = 3;
122132
selectedValue = 4;
123133
labelMinRequiredCells = 3;
@@ -128,6 +138,6 @@ class StandardCalendarBody {
128138
}
129139
}
130140

131-
function createCell(value: number) {
132-
return new MatCalendarCell(value, `${value}`, `${value}-label`, true);
141+
function createCell(value: number, cellClasses?: MatCalendarCellCssClasses) {
142+
return new MatCalendarCell(value, `${value}`, `${value}-label`, true, cellClasses);
133143
}

src/lib/datepicker/calendar-body.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ import {
1818
} from '@angular/core';
1919
import {take} from 'rxjs/operators';
2020

21+
/**
22+
* Extra CSS classes that can be associated with a calendar cell.
23+
*/
24+
export type MatCalendarCellCssClasses = string | string[] | Set<string> | {[key: string]: any};
25+
2126
/**
2227
* An internal class that represents the data corresponding to a single calendar cell.
2328
* @docs-private
@@ -26,7 +31,8 @@ export class MatCalendarCell {
2631
constructor(public value: number,
2732
public displayValue: string,
2833
public ariaLabel: string,
29-
public enabled: boolean) {}
34+
public enabled: boolean,
35+
public cssClasses?: MatCalendarCellCssClasses) {}
3036
}
3137

3238

src/lib/datepicker/calendar.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
[dateFilter]="dateFilter"
1010
[maxDate]="maxDate"
1111
[minDate]="minDate"
12+
[dateClass]="dateClass"
1213
(selectedChange)="_dateSelected($event)"
1314
(_userSelection)="_userSelected()">
1415
</mat-month-view>

src/lib/datepicker/calendar.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {MatDatepickerIntl} from './datepicker-intl';
3232
import {MatMonthView} from './month-view';
3333
import {MatMultiYearView, yearsPerPage} from './multi-year-view';
3434
import {MatYearView} from './year-view';
35+
import {MatCalendarCellCssClasses} from './calendar-body';
3536

3637
/**
3738
* Possible views for the calendar.
@@ -220,9 +221,12 @@ export class MatCalendar<D> implements AfterContentInit, AfterViewChecked, OnDes
220221
}
221222
private _maxDate: D | null;
222223

223-
/** A function used to filter which dates are selectable. */
224+
/** Function used to filter which dates are selectable. */
224225
@Input() dateFilter: (date: D) => boolean;
225226

227+
/** Function that can be used to add custom CSS classes to dates. */
228+
@Input() dateClass: (date: D) => MatCalendarCellCssClasses;
229+
226230
/** Emits when the currently selected date changes. */
227231
@Output() readonly selectedChange: EventEmitter<D> = new EventEmitter<D>();
228232

src/lib/datepicker/datepicker-content.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
[dateFilter]="datepicker._dateFilter"
99
[headerComponent]="datepicker.calendarHeaderComponent"
1010
[selected]="datepicker._selected"
11+
[dateClass]="datepicker.dateClass"
1112
[@fadeInCalendar]="'enter'"
1213
(selectedChange)="datepicker.select($event)"
1314
(yearSelected)="datepicker._selectYear($event)"

src/lib/datepicker/datepicker.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,14 @@ application root module.
328328
export class MyApp {}
329329
```
330330

331+
#### Highlighting specific dates
332+
If you want to apply one or more CSS classes to some dates in the calendar (e.g. to highlight a
333+
holiday), you can do so with the `dateClass` input. It accepts a function which will be called
334+
with each of the dates in the calendar and will apply any classes that are returned. The return
335+
value can be anything that is accepted by `ngClass`.
336+
337+
<!-- example(datepicker-date-class) -->
338+
331339
### Accessibility
332340

333341
The `MatDatepickerInput` and `MatDatepickerToggle` directives add the `aria-haspopup` attribute to

src/lib/datepicker/datepicker.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import {MatCalendar} from './calendar';
5050
import {matDatepickerAnimations} from './datepicker-animations';
5151
import {createMissingDateImplError} from './datepicker-errors';
5252
import {MatDatepickerInput} from './datepicker-input';
53+
import {MatCalendarCellCssClasses} from './calendar-body';
5354

5455
/** Used to generate a unique ID for each datepicker instance. */
5556
let datepickerUid = 0;
@@ -212,6 +213,9 @@ export class MatDatepicker<D> implements OnDestroy, CanColor {
212213
/** Classes to be passed to the date picker panel. Supports the same syntax as `ngClass`. */
213214
@Input() panelClass: string | string[];
214215

216+
/** Function that can be used to add custom CSS classes to dates. */
217+
@Input() dateClass: (date: D) => MatCalendarCellCssClasses;
218+
215219
/** Emits when the datepicker has been opened. */
216220
@Output('opened') openedStream: EventEmitter<void> = new EventEmitter<void>();
217221

src/lib/datepicker/month-view.spec.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ describe('MatMonthView', () => {
3535
// Test components.
3636
StandardMonthView,
3737
MonthViewWithDateFilter,
38+
MonthViewWithDateClass,
3839
],
3940
providers: [
4041
{provide: Directionality, useFactory: () => dir = {value: 'ltr'}}
@@ -291,6 +292,26 @@ describe('MatMonthView', () => {
291292
expect(cells[1].classList).not.toContain('mat-calendar-body-disabled');
292293
});
293294
});
295+
296+
describe('month view with custom date classes', () => {
297+
let fixture: ComponentFixture<MonthViewWithDateClass>;
298+
let monthViewNativeElement: Element;
299+
300+
beforeEach(() => {
301+
fixture = TestBed.createComponent(MonthViewWithDateClass);
302+
fixture.detectChanges();
303+
304+
let monthViewDebugElement = fixture.debugElement.query(By.directive(MatMonthView));
305+
monthViewNativeElement = monthViewDebugElement.nativeElement;
306+
});
307+
308+
it('should be able to add a custom class to some dates', () => {
309+
let cells = monthViewNativeElement.querySelectorAll('.mat-calendar-body-cell');
310+
expect(cells[0].classList).not.toContain('even');
311+
expect(cells[1].classList).toContain('even');
312+
});
313+
});
314+
294315
});
295316

296317

@@ -312,3 +333,13 @@ class MonthViewWithDateFilter {
312333
return date.getDate() % 2 == 0;
313334
}
314335
}
336+
337+
@Component({
338+
template: `<mat-month-view [activeDate]="activeDate" [dateClass]="dateClass"></mat-month-view>`
339+
})
340+
class MonthViewWithDateClass {
341+
activeDate = new Date(2017, JAN, 1);
342+
dateClass(date: Date) {
343+
return date.getDate() % 2 == 0 ? 'even' : undefined;
344+
}
345+
}

src/lib/datepicker/month-view.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import {
3333
} from '@angular/core';
3434
import {DateAdapter, MAT_DATE_FORMATS, MatDateFormats} from '@angular/material/core';
3535
import {Directionality} from '@angular/cdk/bidi';
36-
import {MatCalendarBody, MatCalendarCell} from './calendar-body';
36+
import {MatCalendarBody, MatCalendarCell, MatCalendarCellCssClasses} from './calendar-body';
3737
import {createMissingDateImplError} from './datepicker-errors';
3838

3939

@@ -94,9 +94,12 @@ export class MatMonthView<D> implements AfterContentInit {
9494
}
9595
private _maxDate: D | null;
9696

97-
/** A function used to filter which dates are selectable. */
97+
/** Function used to filter which dates are selectable. */
9898
@Input() dateFilter: (date: D) => boolean;
9999

100+
/** Function that can be used to add custom CSS classes to dates. */
101+
@Input() dateClass: (date: D) => MatCalendarCellCssClasses;
102+
100103
/** Emits when a new date is selected. */
101104
@Output() readonly selectedChange: EventEmitter<D | null> = new EventEmitter<D | null>();
102105

@@ -273,8 +276,10 @@ export class MatMonthView<D> implements AfterContentInit {
273276
this._dateAdapter.getMonth(this.activeDate), i + 1);
274277
const enabled = this._shouldEnableDate(date);
275278
const ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.dateA11yLabel);
279+
const cellClasses = this.dateClass ? this.dateClass(date) : undefined;
280+
276281
this._weeks[this._weeks.length - 1]
277-
.push(new MatCalendarCell(i + 1, dateNames[i], ariaLabel, enabled));
282+
.push(new MatCalendarCell(i + 1, dateNames[i], ariaLabel, enabled, cellClasses));
278283
}
279284
}
280285

0 commit comments

Comments
 (0)