Skip to content

fix(datepicker): support dateClass on year and multi-year views #20256

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Component, ViewEncapsulation} from '@angular/core';
import {MatCalendarCellCssClasses} from '@angular/material/datepicker';
import {MatCalendarCellClassFunction} from '@angular/material/datepicker';

/** @title Datepicker with custom date classes */
@Component({
Expand All @@ -9,10 +9,15 @@ import {MatCalendarCellCssClasses} from '@angular/material/datepicker';
encapsulation: ViewEncapsulation.None,
})
export class DatepickerDateClassExample {
dateClass = (d: Date): MatCalendarCellCssClasses => {
const date = d.getDate();
dateClass: MatCalendarCellClassFunction<Date> = (cellDate, view) => {
// Only highligh dates inside the month view.
if (view === 'month') {
const date = cellDate.getDate();

// Highlight the 1st and 20th day of each month.
return (date === 1 || date === 20) ? 'example-custom-date-class' : '';
// Highlight the 1st and 20th day of each month.
return (date === 1 || date === 20) ? 'example-custom-date-class' : '';
}

return '';
}
}
8 changes: 5 additions & 3 deletions src/material/datepicker/calendar-body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ import {
} from '@angular/core';
import {take} from 'rxjs/operators';

/**
* Extra CSS classes that can be associated with a calendar cell.
*/
/** Extra CSS classes that can be associated with a calendar cell. */
export type MatCalendarCellCssClasses = string | string[] | Set<string> | {[key: string]: any};

/** Function that can generate the extra classes that should be added to a calendar cell. */
export type MatCalendarCellClassFunction<D> =
(date: D, view: 'month' | 'year' | 'multi-year') => MatCalendarCellCssClasses;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a type alias for 'month' | 'year' | 'multi-year' somewhere? I thought we did, but I could be crazy

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we've just been doing it inline.


/**
* An internal class that represents the data corresponding to a single calendar cell.
* @docs-private
Expand Down
2 changes: 2 additions & 0 deletions src/material/datepicker/calendar.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
[dateFilter]="dateFilter"
[maxDate]="maxDate"
[minDate]="minDate"
[dateClass]="dateClass"
(monthSelected)="_monthSelectedInYearView($event)"
(selectedChange)="_goToDateInView($event, 'month')">
</mat-year-view>
Expand All @@ -32,6 +33,7 @@
[dateFilter]="dateFilter"
[maxDate]="maxDate"
[minDate]="minDate"
[dateClass]="dateClass"
(yearSelected)="_yearSelectedInMultiYearView($event)"
(selectedChange)="_goToDateInView($event, 'year')">
</mat-multi-year-view>
Expand Down
4 changes: 2 additions & 2 deletions src/material/datepicker/calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
MatDateFormats,
} from '@angular/material/core';
import {Subject, Subscription} from 'rxjs';
import {MatCalendarCellCssClasses, MatCalendarUserEvent} from './calendar-body';
import {MatCalendarUserEvent, MatCalendarCellClassFunction} from './calendar-body';
import {createMissingDateImplError} from './datepicker-errors';
import {MatDatepickerIntl} from './datepicker-intl';
import {MatMonthView} from './month-view';
Expand Down Expand Up @@ -245,7 +245,7 @@ export class MatCalendar<D> implements AfterContentInit, AfterViewChecked, OnDes
@Input() dateFilter: (date: D) => boolean;

/** Function that can be used to add custom CSS classes to dates. */
@Input() dateClass: (date: D) => MatCalendarCellCssClasses;
@Input() dateClass: MatCalendarCellClassFunction<D>;

/** Start of the comparison range. */
@Input() comparisonStart: D | null;
Expand Down
4 changes: 2 additions & 2 deletions src/material/datepicker/datepicker-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ import {filter, take} from 'rxjs/operators';
import {MatCalendar} from './calendar';
import {matDatepickerAnimations} from './datepicker-animations';
import {createMissingDateImplError} from './datepicker-errors';
import {MatCalendarCellCssClasses, MatCalendarUserEvent} from './calendar-body';
import {MatCalendarUserEvent, MatCalendarCellClassFunction} from './calendar-body';
import {DateFilterFn} from './datepicker-input-base';
import {
ExtractDateTypeFromSelection,
Expand Down Expand Up @@ -322,7 +322,7 @@ export abstract class MatDatepickerBase<C extends MatDatepickerControl<D>, S,
@Input() panelClass: string | string[];

/** Function that can be used to add custom CSS classes to dates. */
@Input() dateClass: (date: D) => MatCalendarCellCssClasses;
@Input() dateClass: MatCalendarCellClassFunction<D>;

/** Emits when the datepicker has been opened. */
@Output('opened') openedStream: EventEmitter<void> = new EventEmitter<void>();
Expand Down
6 changes: 6 additions & 0 deletions src/material/datepicker/month-view.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -441,9 +441,11 @@ describe('MatMonthView', () => {
describe('month view with custom date classes', () => {
let fixture: ComponentFixture<MonthViewWithDateClass>;
let monthViewNativeElement: Element;
let dateClassSpy: jasmine.Spy;

beforeEach(() => {
fixture = TestBed.createComponent(MonthViewWithDateClass);
dateClassSpy = spyOn(fixture.componentInstance, 'dateClass').and.callThrough();
fixture.detectChanges();

let monthViewDebugElement = fixture.debugElement.query(By.directive(MatMonthView))!;
Expand All @@ -455,6 +457,10 @@ describe('MatMonthView', () => {
expect(cells[0].classList).not.toContain('even');
expect(cells[1].classList).toContain('even');
});

it('should call dateClass with the correct view name', () => {
expect(dateClassSpy).toHaveBeenCalledWith(jasmine.any(Date), 'month');
});
});

});
Expand Down
6 changes: 3 additions & 3 deletions src/material/datepicker/month-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ import {Directionality} from '@angular/cdk/bidi';
import {
MatCalendarBody,
MatCalendarCell,
MatCalendarCellCssClasses,
MatCalendarUserEvent,
MatCalendarCellClassFunction,
} from './calendar-body';
import {createMissingDateImplError} from './datepicker-errors';
import {Subscription} from 'rxjs';
Expand Down Expand Up @@ -120,7 +120,7 @@ export class MatMonthView<D> implements AfterContentInit, OnDestroy {
@Input() dateFilter: (date: D) => boolean;

/** Function that can be used to add custom CSS classes to dates. */
@Input() dateClass: (date: D) => MatCalendarCellCssClasses;
@Input() dateClass: MatCalendarCellClassFunction<D>;

/** Start of the comparison range. */
@Input() comparisonStart: D | null;
Expand Down Expand Up @@ -370,7 +370,7 @@ export class MatMonthView<D> implements AfterContentInit, OnDestroy {
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;
const cellClasses = this.dateClass ? this.dateClass(date, 'month') : undefined;

this._weeks[this._weeks.length - 1].push(new MatCalendarCell<D>(i + 1, dateNames[i],
ariaLabel, enabled, cellClasses, this._getCellCompareValue(date)!, date));
Expand Down
40 changes: 40 additions & 0 deletions src/material/datepicker/multi-year-view.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ describe('MatMultiYearView', () => {
StandardMultiYearView,
MultiYearViewWithDateFilter,
MultiYearViewWithMinMaxDate,
MultiYearViewWithDateClass,
],
providers: [
{provide: Directionality, useFactory: () => dir = {value: 'ltr'}}
Expand Down Expand Up @@ -343,6 +344,32 @@ describe('MatMultiYearView', () => {
expect(cells[9].classList).not.toContain('mat-calendar-body-disabled');
});
});

describe('multi-year view with custom date classes', () => {
let fixture: ComponentFixture<MultiYearViewWithDateClass>;
let multiYearViewNativeElement: Element;
let dateClassSpy: jasmine.Spy;

beforeEach(() => {
fixture = TestBed.createComponent(MultiYearViewWithDateClass);
dateClassSpy = spyOn(fixture.componentInstance, 'dateClass').and.callThrough();
fixture.detectChanges();

let multiYearViewDebugElement = fixture.debugElement.query(By.directive(MatMultiYearView))!;
multiYearViewNativeElement = multiYearViewDebugElement.nativeElement;
});

it('should be able to add a custom class to some dates', () => {
let cells = multiYearViewNativeElement.querySelectorAll('.mat-calendar-body-cell');
expect(cells[0].classList).toContain('even');
expect(cells[1].classList).not.toContain('even');
});

it('should call dateClass with the correct view name', () => {
expect(dateClassSpy).toHaveBeenCalledWith(jasmine.any(Date), 'multi-year');
});
});

});

@Component({
Expand Down Expand Up @@ -387,3 +414,16 @@ class MultiYearViewWithMinMaxDate {
minDate: Date | null;
maxDate: Date | null;
}


@Component({
template: `
<mat-multi-year-view [activeDate]="activeDate" [dateClass]="dateClass"></mat-multi-year-view>
`
})
class MultiYearViewWithDateClass {
activeDate = new Date(2017, JAN, 1);
dateClass(date: Date) {
return date.getFullYear() % 2 == 0 ? 'even' : undefined;
}
}
17 changes: 14 additions & 3 deletions src/material/datepicker/multi-year-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ import {
} from '@angular/core';
import {DateAdapter} from '@angular/material/core';
import {Directionality} from '@angular/cdk/bidi';
import {MatCalendarBody, MatCalendarCell, MatCalendarUserEvent} from './calendar-body';
import {
MatCalendarBody,
MatCalendarCell,
MatCalendarUserEvent,
MatCalendarCellClassFunction,
} from './calendar-body';
import {createMissingDateImplError} from './datepicker-errors';
import {Subscription} from 'rxjs';
import {startWith} from 'rxjs/operators';
Expand Down Expand Up @@ -109,6 +114,9 @@ export class MatMultiYearView<D> implements AfterContentInit, OnDestroy {
/** A function used to filter which dates are selectable. */
@Input() dateFilter: (date: D) => boolean;

/** Function that can be used to add custom CSS classes to date cells. */
@Input() dateClass: MatCalendarCellClassFunction<D>;

/** Emits when a new year is selected. */
@Output() readonly selectedChange: EventEmitter<D> = new EventEmitter<D>();

Expand Down Expand Up @@ -251,8 +259,11 @@ export class MatMultiYearView<D> implements AfterContentInit, OnDestroy {

/** Creates an MatCalendarCell for the given year. */
private _createCellForYear(year: number) {
let yearName = this._dateAdapter.getYearName(this._dateAdapter.createDate(year, 0, 1));
return new MatCalendarCell(year, yearName, yearName, this._shouldEnableYear(year));
const date = this._dateAdapter.createDate(year, 0, 1);
const yearName = this._dateAdapter.getYearName(date);
const cellClasses = this.dateClass ? this.dateClass(date, 'multi-year') : undefined;

return new MatCalendarCell(year, yearName, yearName, this._shouldEnableYear(year), cellClasses);
}

/** Whether the given year is enabled. */
Expand Down
38 changes: 38 additions & 0 deletions src/material/datepicker/year-view.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ describe('MatYearView', () => {
// Test components.
StandardYearView,
YearViewWithDateFilter,
YearViewWithDateClass,
],
providers: [
{provide: Directionality, useFactory: () => dir = {value: 'ltr'}}
Expand Down Expand Up @@ -329,6 +330,32 @@ describe('MatYearView', () => {
});

});

describe('year view with custom date classes', () => {
let fixture: ComponentFixture<YearViewWithDateClass>;
let yearViewNativeElement: Element;
let dateClassSpy: jasmine.Spy;

beforeEach(() => {
fixture = TestBed.createComponent(YearViewWithDateClass);
dateClassSpy = spyOn(fixture.componentInstance, 'dateClass').and.callThrough();
fixture.detectChanges();

let yearViewDebugElement = fixture.debugElement.query(By.directive(MatYearView))!;
yearViewNativeElement = yearViewDebugElement.nativeElement;
});

it('should be able to add a custom class to some month cells', () => {
let cells = yearViewNativeElement.querySelectorAll('.mat-calendar-body-cell');
expect(cells[0].classList).toContain('even');
expect(cells[1].classList).not.toContain('even');
});

it('should call dateClass with the correct view name', () => {
expect(dateClassSpy).toHaveBeenCalledWith(jasmine.any(Date), 'year');
});
});

});


Expand Down Expand Up @@ -368,3 +395,14 @@ class YearViewWithDateFilter {
return true;
}
}


@Component({
template: `<mat-year-view [activeDate]="activeDate" [dateClass]="dateClass"></mat-year-view>`
})
class YearViewWithDateClass {
activeDate = new Date(2017, JAN, 1);
dateClass(date: Date) {
return date.getMonth() % 2 == 0 ? 'even' : undefined;
}
}
21 changes: 15 additions & 6 deletions src/material/datepicker/year-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@ import {
} from '@angular/core';
import {DateAdapter, MAT_DATE_FORMATS, MatDateFormats} from '@angular/material/core';
import {Directionality} from '@angular/cdk/bidi';
import {MatCalendarBody, MatCalendarCell, MatCalendarUserEvent} from './calendar-body';
import {
MatCalendarBody,
MatCalendarCell,
MatCalendarUserEvent,
MatCalendarCellClassFunction,
} from './calendar-body';
import {createMissingDateImplError} from './datepicker-errors';
import {Subscription} from 'rxjs';
import {startWith} from 'rxjs/operators';
Expand Down Expand Up @@ -103,6 +108,9 @@ export class MatYearView<D> implements AfterContentInit, OnDestroy {
/** A function used to filter which dates are selectable. */
@Input() dateFilter: (date: D) => boolean;

/** Function that can be used to add custom CSS classes to date cells. */
@Input() dateClass: MatCalendarCellClassFunction<D>;

/** Emits when a new month is selected. */
@Output() readonly selectedChange: EventEmitter<D> = new EventEmitter<D>();

Expand Down Expand Up @@ -254,11 +262,12 @@ export class MatYearView<D> implements AfterContentInit, OnDestroy {

/** Creates an MatCalendarCell for the given month. */
private _createCellForMonth(month: number, monthName: string) {
let ariaLabel = this._dateAdapter.format(
this._dateAdapter.createDate(this._dateAdapter.getYear(this.activeDate), month, 1),
this._dateFormats.display.monthYearA11yLabel);
return new MatCalendarCell(
month, monthName.toLocaleUpperCase(), ariaLabel, this._shouldEnableMonth(month));
const date = this._dateAdapter.createDate(this._dateAdapter.getYear(this.activeDate), month, 1);
const ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.monthYearA11yLabel);
const cellClasses = this.dateClass ? this.dateClass(date, 'year') : undefined;

return new MatCalendarCell(month, monthName.toLocaleUpperCase(), ariaLabel,
this._shouldEnableMonth(month), cellClasses);
}

/** Whether the given month is enabled. */
Expand Down
Loading