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.
This commit is contained in:
Kristiyan Kostadinov
2018-11-07 18:22:43 +00:00
committed by vivian-hu
parent 69ffd3306e
commit 4be1b06edd
13 changed files with 106 additions and 9 deletions
+1
View File
@@ -30,6 +30,7 @@
<td *ngFor="let item of row; let colIndex = index"
role="gridcell"
class="mat-calendar-body-cell"
[ngClass]="item.cssClasses"
[tabindex]="_isActiveCell(rowIndex, colIndex) ? 0 : -1"
[class.mat-calendar-body-disabled]="!item.enabled"
[class.mat-calendar-body-active]="_isActiveCell(rowIndex, colIndex)"
+14 -4
View File
@@ -1,6 +1,6 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {Component} from '@angular/core';
import {MatCalendarBody, MatCalendarCell} from './calendar-body';
import {MatCalendarBody, MatCalendarCell, MatCalendarCellCssClasses} from './calendar-body';
import {By} from '@angular/platform-browser';
@@ -98,6 +98,14 @@ describe('MatCalendarBody', () => {
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);
}
+7 -1
View File
@@ -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<string> | {[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) {}
}
+1
View File
@@ -9,6 +9,7 @@
[dateFilter]="dateFilter"
[maxDate]="maxDate"
[minDate]="minDate"
[dateClass]="dateClass"
(selectedChange)="_dateSelected($event)"
(_userSelection)="_userSelected()">
</mat-month-view>
+5 -1
View File
@@ -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<D> 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<D> = new EventEmitter<D>();
@@ -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)"
+8
View File
@@ -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`.
<!-- example(datepicker-date-class) -->
### Accessibility
The `MatDatepickerInput` and `MatDatepickerToggle` directives add the `aria-haspopup` attribute to
+4
View File
@@ -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<D> 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<void> = new EventEmitter<void>();
+31
View File
@@ -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<MonthViewWithDateClass>;
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: `<mat-month-view [activeDate]="activeDate" [dateClass]="dateClass"></mat-month-view>`
})
class MonthViewWithDateClass {
activeDate = new Date(2017, JAN, 1);
dateClass(date: Date) {
return date.getDate() % 2 == 0 ? 'even' : undefined;
}
}
+8 -3
View File
@@ -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<D> 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<D | null> = new EventEmitter<D | null>();
@@ -273,8 +276,10 @@ export class MatMonthView<D> 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));
}
}
@@ -0,0 +1,4 @@
.example-custom-date-class {
background: orange;
border-radius: 100%;
}
@@ -0,0 +1,5 @@
<mat-form-field class="example-full-width">
<input matInput [matDatepicker]="picker" placeholder="Choose a date">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker [dateClass]="dateClass" #picker></mat-datepicker>
</mat-form-field>
@@ -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;
}
}