Skip to content

Commit

Permalink
feat(datepicker): add 'aria-label' attribute for days
Browse files Browse the repository at this point in the history
Closes #2319 

BREAKING CHANGE: if you're using a custom `NgbDatepickerI18n` implementation, you'll have to implement an additional method: `getDayAriaLabel(date: NgbDateStruct): string`. It returns the string that will be set for the `aria-label` attribute for each displayed day. If you're not using the custom service, the `aria-label` will default to the value returned by the angular `DatePipe` with `'fullDate'` format.
  • Loading branch information
fbasso authored and maxokorokov committed Apr 27, 2018
1 parent 6961cb3 commit d52059b
Show file tree
Hide file tree
Showing 13 changed files with 157 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ export class IslamicI18n extends NgbDatepickerI18n {
getMonthFullName(month: number) {
return this.getMonthShortName(month);
}

getDayAriaLabel(date: NgbDateStruct): string {
return `${date.day}-${date.month}-${date.year}`;
}
}

@Component({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Component, Injectable} from '@angular/core';
import {NgbDatepickerI18n} from '@ng-bootstrap/ng-bootstrap';
import { NgbDatepickerI18n, NgbDateStruct } from '@ng-bootstrap/ng-bootstrap';

const I18N_VALUES = {
'fr': {
Expand Down Expand Up @@ -33,6 +33,10 @@ export class CustomDatepickerI18n extends NgbDatepickerI18n {
getMonthFullName(month: number): string {
return this.getMonthShortName(month);
}

getDayAriaLabel(date: NgbDateStruct): string {
return `${date.day}-${date.month}-${date.year}`;
}
}

@Component({
Expand Down
6 changes: 5 additions & 1 deletion src/datepicker/datepicker-i18n.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import {NgbDatepickerI18nDefault} from './datepicker-i18n';
import {TestBed} from '@angular/core/testing';
import {LOCALE_ID} from '@angular/core';
import {DatePipe} from '@angular/common';

describe('ngb-datepicker-i18n-default', () => {

let i18n: NgbDatepickerI18nDefault;

beforeEach(() => {
TestBed.configureTestingModule({providers: [DatePipe]});

const locale: string = TestBed.get(LOCALE_ID);
i18n = new NgbDatepickerI18nDefault(locale);
const datePipe: DatePipe = TestBed.get(DatePipe);
i18n = new NgbDatepickerI18nDefault(locale, datePipe);
});

it('should return abbreviated month name', () => {
Expand Down
20 changes: 16 additions & 4 deletions src/datepicker/datepicker-i18n.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {Inject, Injectable, LOCALE_ID} from '@angular/core';
import {FormStyle, getLocaleDayNames, getLocaleMonthNames, TranslationWidth} from '@angular/common';
import {DatePipe} from '@angular/common';
import {NgbDateStruct} from './ngb-date-struct';

/**
* Type of the service supplying month and weekday names to to NgbDatepicker component.
Expand All @@ -26,6 +28,11 @@ export abstract class NgbDatepickerI18n {
* With default calendar we use ISO 8601: 'month' is 1=January ... 12=December
*/
abstract getMonthFullName(month: number): string;

/**
* Returns the aria-label string for a day
*/
abstract getDayAriaLabel(date: NgbDateStruct): string;
}

@Injectable()
Expand All @@ -34,19 +41,24 @@ export class NgbDatepickerI18nDefault extends NgbDatepickerI18n {
private _monthsShort: Array<string>;
private _monthsFull: Array<string>;

constructor(@Inject(LOCALE_ID) locale: string) {
constructor(@Inject(LOCALE_ID) private _locale: string, private _datePipe: DatePipe) {
super();

const weekdaysStartingOnSunday = getLocaleDayNames(locale, FormStyle.Standalone, TranslationWidth.Short);
const weekdaysStartingOnSunday = getLocaleDayNames(_locale, FormStyle.Standalone, TranslationWidth.Short);
this._weekdaysShort = weekdaysStartingOnSunday.map((day, index) => weekdaysStartingOnSunday[(index + 1) % 7]);

this._monthsShort = getLocaleMonthNames(locale, FormStyle.Standalone, TranslationWidth.Abbreviated);
this._monthsFull = getLocaleMonthNames(locale, FormStyle.Standalone, TranslationWidth.Wide);
this._monthsShort = getLocaleMonthNames(_locale, FormStyle.Standalone, TranslationWidth.Abbreviated);
this._monthsFull = getLocaleMonthNames(_locale, FormStyle.Standalone, TranslationWidth.Wide);
}

getWeekdayShortName(weekday: number): string { return this._weekdaysShort[weekday - 1]; }

getMonthShortName(month: number): string { return this._monthsShort[month - 1]; }

getMonthFullName(month: number): string { return this._monthsFull[month - 1]; }

getDayAriaLabel(date: NgbDateStruct): string {
const jsDate = new Date(date.year, date.month - 1, date.day);
return this._datePipe.transform(jsDate, 'fullDate', null, this._locale);
}
}
30 changes: 20 additions & 10 deletions src/datepicker/datepicker-month-view.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,8 @@ class TestComponent {
focused: false,
selected: false
},
tabindex: -1
tabindex: -1,
ariaLabel: 'Monday'
},
{
date: new NgbDate(2016, 8, 23),
Expand All @@ -241,7 +242,8 @@ class TestComponent {
focused: false,
selected: false
},
tabindex: -1
tabindex: -1,
ariaLabel: 'Tuesday'
}
]
}]
Expand All @@ -267,7 +269,8 @@ class TestComponent {
focused: false,
selected: false
},
tabindex: -1
tabindex: -1,
ariaLabel: 'Monday'
},
{
date: new NgbDate(2016, 8, 1),
Expand All @@ -278,7 +281,8 @@ class TestComponent {
focused: false,
selected: false
},
tabindex: -1
tabindex: -1,
ariaLabel: 'Monday'
}
]
},
Expand All @@ -295,7 +299,8 @@ class TestComponent {
focused: false,
selected: false
},
tabindex: -1
tabindex: -1,
ariaLabel: 'Friday'
},
{
date: new NgbDate(2016, 8, 3),
Expand All @@ -306,7 +311,8 @@ class TestComponent {
focused: false,
selected: false
},
tabindex: -1
tabindex: -1,
ariaLabel: 'Saturday'
}
]
},
Expand All @@ -323,7 +329,8 @@ class TestComponent {
focused: false,
selected: false
},
tabindex: -1
tabindex: -1,
ariaLabel: 'Sunday'
},
{
date: new NgbDate(2016, 9, 1),
Expand All @@ -334,7 +341,8 @@ class TestComponent {
focused: false,
selected: false
},
tabindex: -1
tabindex: -1,
ariaLabel: 'Saturday'
}
]
},
Expand All @@ -351,7 +359,8 @@ class TestComponent {
focused: false,
selected: false
},
tabindex: -1
tabindex: -1,
ariaLabel: 'Sunday'
},
{
date: new NgbDate(2016, 9, 3),
Expand All @@ -362,7 +371,8 @@ class TestComponent {
focused: false,
selected: false
},
tabindex: -1
tabindex: -1,
ariaLabel: 'Monday'
}
]
}
Expand Down
3 changes: 2 additions & 1 deletion src/datepicker/datepicker-month-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ import {DayTemplateContext} from './datepicker-day-template-context';
<div *ngFor="let day of week.days" (click)="doSelect(day)" class="ngb-dp-day" role="gridcell"
[class.disabled]="day.context.disabled"
[tabindex]="day.tabindex"
[class.hidden]="isHidden(day)">
[class.hidden]="isHidden(day)"
[attr.aria-label]="day.ariaLabel">
<ng-template [ngIf]="!isHidden(day)">
<ng-template [ngTemplateOutlet]="dayTemplate" [ngTemplateOutletContext]="day.context"></ng-template>
</ng-template>
Expand Down
29 changes: 27 additions & 2 deletions src/datepicker/datepicker-service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {NgbDate} from './ngb-date';
import {Subscription} from 'rxjs';
import {DatepickerViewModel} from './datepicker-view-model';
import {NgbDateStruct} from './ngb-date-struct';
import {NgbDatepickerI18n, NgbDatepickerI18nDefault} from './datepicker-i18n';
import {DatePipe} from '@angular/common';

describe('ngb-datepicker-service', () => {

Expand All @@ -23,8 +25,12 @@ describe('ngb-datepicker-service', () => {
const getDayCtx = (n: number) => getDay(n).context;

beforeEach(() => {
TestBed.configureTestingModule(
{providers: [NgbDatepickerService, {provide: NgbCalendar, useClass: NgbCalendarGregorian}]});
TestBed.configureTestingModule({
providers: [
NgbDatepickerService, {provide: NgbCalendar, useClass: NgbCalendarGregorian},
{provide: NgbDatepickerI18n, useClass: NgbDatepickerI18nDefault}, DatePipe
]
});

calendar = TestBed.get(NgbCalendar);
service = TestBed.get(NgbDatepickerService);
Expand Down Expand Up @@ -320,6 +326,25 @@ describe('ngb-datepicker-service', () => {
expect(getDayInMonth(1, 0, 6).tabindex).toEqual(0); // 1st april in the second month block

});

it(`should set the aria-label when changing the current month`, () => {
service.displayMonths = 2;
service.focus(new NgbDate(2018, 3, 31));

expect(getDayInMonth(0, 4, 5).ariaLabel)
.toEqual('Saturday, March 31, 2018'); // 31 march in the first month block
expect(getDayInMonth(1, 0, 5).ariaLabel)
.toEqual('Saturday, March 31, 2018'); // 31 march in the second month block

service.focusMove('d', 1);
expect(getDayInMonth(0, 4, 5).ariaLabel)
.toEqual('Saturday, March 31, 2018'); // 31 march in the first month block
expect(getDayInMonth(1, 0, 5).ariaLabel)
.toEqual('Saturday, March 31, 2018'); // 31 march in the second month block
expect(getDayInMonth(0, 4, 6).ariaLabel).toEqual('Sunday, April 1, 2018'); // 1st april in the first month block
expect(getDayInMonth(1, 0, 6).ariaLabel).toEqual('Sunday, April 1, 2018'); // 1st april in the second month block

});
});

describe(`disabled`, () => {
Expand Down
6 changes: 3 additions & 3 deletions src/datepicker/datepicker-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from './datepicker-tools';

import {filter} from 'rxjs/operators';
import {NgbDatepickerI18n} from './datepicker-i18n';

@Injectable()
export class NgbDatepickerService {
Expand Down Expand Up @@ -94,7 +95,7 @@ export class NgbDatepickerService {
}
}

constructor(private _calendar: NgbCalendar) {}
constructor(private _calendar: NgbCalendar, private _i18n: NgbDatepickerI18n) {}

focus(date: NgbDate) {
if (!this._state.disabled && this._calendar.isValid(date) && isChangedDate(this._state.focusDate, date)) {
Expand Down Expand Up @@ -159,7 +160,6 @@ export class NgbDatepickerService {

day.tabindex =
(!state.disabled && day.date.equals(state.focusDate) && state.focusDate.month === month.number) ? 0 : -1;

// override context disabled
if (state.disabled === true) {
day.context.disabled = true;
Expand Down Expand Up @@ -221,7 +221,7 @@ export class NgbDatepickerService {
const forceRebuild = 'firstDayOfWeek' in patch || 'markDisabled' in patch || 'minDate' in patch ||
'maxDate' in patch || 'disabled' in patch;

const months = buildMonths(this._calendar, startDate, state, forceRebuild);
const months = buildMonths(this._calendar, startDate, state, this._i18n, forceRebuild);

// updating months and boundary dates
state.months = months;
Expand Down
Loading

0 comments on commit d52059b

Please sign in to comment.