Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit c3bf840

Browse files
committed
feat(calendar): more fine-tuned a11y
1 parent 91bcb48 commit c3bf840

File tree

6 files changed

+68
-23
lines changed

6 files changed

+68
-23
lines changed

src/components/calendar/calendar.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
// PRE RELEASE
1717
// TODO(mchen): Date "isComplete" logic
18-
// TODO(jelbourn): Fix NVDA stealing key presses (IE) ???
1918

2019
// POST RELEASE
2120
// TODO(jelbourn): Animated month transition on ng-model change (virtual-repeat)
@@ -41,7 +40,6 @@
4140
function calendarDirective() {
4241
return {
4342
template:
44-
//'<input class="md-visually-hidden md-dummy" aria-label="Select a date">' +
4543
'<table aria-hidden="true" class="md-calendar-day-header"><thead></thead></table>' +
4644
'<div class="md-calendar-scroll-mask">' +
4745
'<md-virtual-repeat-container class="md-calendar-scroll-container">' +
@@ -52,8 +50,7 @@
5250
'md-item-size="' + TBODY_HEIGHT + '"></tbody>' +
5351
'</table>' +
5452
'</md-virtual-repeat-container>' +
55-
'</div>' +
56-
'<div aria-live="assertive" aria-atomic="true" class="md-visually-hidden"></div>',
53+
'</div>',
5754
scope: {},
5855
require: ['ngModel', 'mdCalendar'],
5956
controller: CalendarCtrl,
@@ -269,8 +266,13 @@
269266
this.$scope.$apply(function() {
270267
// Capture escape and emit back up so that a wrapping component
271268
// (such as a date-picker) can decide to close.
272-
if (event.which == self.keyCode.ESCAPE) {
273-
self.$scope.$emit('md-calendar-escape');
269+
if (event.which == self.keyCode.ESCAPE || event.which == self.keyCode.TAB) {
270+
self.$scope.$emit('md-calendar-close');
271+
272+
if (event.which == self.keyCode.TAB) {
273+
event.preventDefault();
274+
}
275+
274276
return;
275277
}
276278

@@ -285,7 +287,6 @@
285287
// Selection isn't occuring, so the key event is either navigation or nothing.
286288
var date = self.getFocusDateFromKeyEvent(event);
287289
if (date) {
288-
console.log('key event');
289290
event.preventDefault();
290291
event.stopPropagation();
291292

@@ -410,6 +411,7 @@
410411
self.calendarElement.querySelector('#' + self.getDateId(previousSelectedDate));
411412
if (prevDateCell) {
412413
prevDateCell.classList.remove(SELECTED_DATE_CLASS);
414+
prevDateCell.setAttribute('aria-selected', 'false');
413415
}
414416
}
415417

@@ -418,6 +420,7 @@
418420
var dateCell = self.calendarElement.querySelector('#' + self.getDateId(date));
419421
if (dateCell) {
420422
dateCell.classList.add(SELECTED_DATE_CLASS);
423+
dateCell.setAttribute('aria-selected', 'true');
421424
}
422425
}
423426
});

src/components/calendar/calendar.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ describe('md-calendar', function() {
275275

276276
it('should fire an event when escape is pressed', function() {
277277
var escapeHandler = jasmine.createSpy('escapeHandler');
278-
pageScope.$on('md-calendar-escape', escapeHandler);
278+
pageScope.$on('md-calendar-close', escapeHandler);
279279

280280
pageScope.myDate = new Date(2014, FEB, 11);
281281
applyDateChange();

src/components/calendar/calendarMonth.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@
127127
if (this.dateUtil.isValidDate(calendarCtrl.selectedDate) &&
128128
this.dateUtil.isSameDay(opt_date, calendarCtrl.selectedDate)) {
129129
cell.classList.add(SELECTED_DATE_CLASS);
130+
cell.setAttribute('aria-selected', 'true');
130131
}
131132

132133
if (calendarCtrl.focusDate && this.dateUtil.isSameDay(opt_date, calendarCtrl.focusDate)) {
@@ -136,10 +137,21 @@
136137

137138
return cell;
138139
};
139-
140-
CalendarMonthCtrl.prototype.buildDateRow = function() {
140+
141+
/**
142+
* Builds a `tr` element for the calendar grid.
143+
* @param rowNumber The week number within the month.
144+
* @returns {HTMLElement}
145+
*/
146+
CalendarMonthCtrl.prototype.buildDateRow = function(rowNumber) {
141147
var row = document.createElement('tr');
142148
row.setAttribute('role', 'row');
149+
150+
// Because of an NVDA bug (with Firefox), the row needs an aria-label in order
151+
// to prevent the entire row being read aloud when the user moves between rows.
152+
// See http://community.nvda-project.org/ticket/4643.
153+
//row.setAttribute('aria-label', this.dateLocale.weekNumberFormatter(rowNumber));
154+
143155
return row;
144156
};
145157

@@ -158,7 +170,8 @@
158170
// Store rows for the month in a document fragment so that we can append them all at once.
159171
var monthBody = document.createDocumentFragment();
160172

161-
var row = this.buildDateRow();
173+
var rowNumber = 1;
174+
var row = this.buildDateRow(rowNumber);
162175
monthBody.appendChild(row);
163176

164177
// Add a label for the month. If the month starts on a Sun/Mon/Tues, the month label
@@ -196,7 +209,8 @@
196209
// If we've reached the end of the week, start a new row.
197210
if (dayOfWeek === 7) {
198211
dayOfWeek = 0;
199-
row = this.buildDateRow();
212+
rowNumber++;
213+
row = this.buildDateRow(rowNumber);
200214
monthBody.appendChild(row);
201215
}
202216

src/components/calendar/dateLocaleProvider.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@
4343
*/
4444
this.monthHeaderFormatter = null;
4545

46+
/**
47+
* Function that formats a week number into a label for the week.
48+
* @type {function(number): string}
49+
*/
50+
this.weekNumberFormatter = null;
51+
4652
/**
4753
* Function that formats a date into a short aria-live announcement that is read when
4854
* the focused date changes within the same month.
@@ -104,6 +110,15 @@
104110
return service.shortMonths[date.getMonth()] + ' ' + date.getFullYear();
105111
}
106112

113+
/**
114+
* Default week number formatter.
115+
* @param number
116+
* @returns {string}
117+
*/
118+
function defaultWeekNumberFormatter(number) {
119+
return 'Week ' + number;
120+
}
121+
107122
/**
108123
* Default formatter for short aria-live announcements.
109124
* @param {!Date} date
@@ -154,6 +169,7 @@
154169
formatDate: this.formatDate || defaultFormatDate,
155170
parseDate: this.parseDate || defaultParseDate,
156171
monthHeaderFormatter: this.monthHeaderFormatter || defaultMonthHeaderFormatter,
172+
weekNumberFormatter: this.weekNumberFormatter || defaultWeekNumberFormatter,
157173
shortAnnounceFormatter: this.shortAnnounceFormatter || defaultShortAnnounceFormatter,
158174
longAnnounceFormatter: this.longAnnounceFormatter || defaultLongAnnounceFormatter,
159175
msgCalendar: this.msgCalendar || defaultMsgCalendar,

src/components/calendar/datePicker.js

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,16 @@
2626
// interaction on the text input, and multiple tab stops for one component (picker)
2727
// may be confusing.
2828
'<md-button class="md-datepicker-button md-icon-button" type="button" ' +
29-
'tabindex="-1" aria-label="{{::ctrl.dateLocale.msgOpenCalendar}}" ' +
30-
'ng-click="ctrl.openCalendarPane()">' +
29+
'tabindex="-1" aria-hidden="true" ' +
30+
'ng-click="ctrl.openCalendarPane($event)">' +
3131
'<md-icon class="md-datepicker-calendar-icon" md-svg-icon="md-calendar"></md-icon>' +
3232
'</md-button>' +
3333
'<div class="md-datepicker-input-container">' +
34-
'<input class="md-datepicker-input" aria-haspopup="true" aria-describedby="xxx">' +
35-
'<span id="xxx" class="md-visually-hidden">Press Alt + Down to open the calendar</span>' +
34+
'<input class="md-datepicker-input" aria-haspopup="true">' +
35+
//'<span id="xxx" class="md-visually-hidden">Press Alt + Down to open the calendar</span>' +
3636

3737
'<md-button md-no-ink class="md-datepicker-triangle-button md-icon-button" ' +
38-
'ng-click="ctrl.openCalendarPane()" tabindex="-1" ' +
38+
'ng-click="ctrl.openCalendarPane($event)" ' +
3939
'aria-label="{{::ctrl.dateLocale.msgOpenCalendar}}">' +
4040
'<div class="md-datepicker-expand-triangle"></div>' +
4141
'</md-button>' +
@@ -127,6 +127,13 @@
127127
/** @type {boolean} Whether the date-picker's calendar pane is open. */
128128
this.isCalendarOpen = false;
129129

130+
/**
131+
* Element from which the calendar pane was opened. Keep track of this so that we can return
132+
* focus to it when the pane is closed.
133+
* @type {HTMLElement}
134+
*/
135+
this.calendarPaneOpenedFrom = null;
136+
130137
this.calendarPane.id = 'md-date-pane' + $mdUtil.nextUid();
131138

132139
/** Pre-bound click handler is saved so that the event listener can be removed. */
@@ -204,12 +211,12 @@
204211
angular.element(self.inputElement).on('keydown', function(event) {
205212
$scope.$apply(function() {
206213
if (event.altKey && event.keyCode == keyCodes.DOWN_ARROW) {
207-
self.openCalendarPane();
214+
self.openCalendarPane(event);
208215
}
209216
});
210217
});
211218

212-
self.$scope.$on('md-calendar-escape', function() {
219+
self.$scope.$on('md-calendar-close', function() {
213220
self.closeCalendarPane();
214221
});
215222
};
@@ -272,10 +279,14 @@
272279
this.calendarPane.parentNode.removeChild(this.calendarPane);
273280
};
274281

275-
/** Open the floating calendar pane. */
276-
DatePickerCtrl.prototype.openCalendarPane = function() {
282+
/**
283+
* Open the floating calendar pane.
284+
* @param {Event} event
285+
*/
286+
DatePickerCtrl.prototype.openCalendarPane = function(event) {
277287
if (!this.isCalendarOpen && !this.isDisabled) {
278288
this.isCalendarOpen = true;
289+
this.calendarPaneOpenedFrom = event.target;
279290
this.attachCalendarPane();
280291
this.focusCalendar();
281292

@@ -292,7 +303,8 @@
292303
DatePickerCtrl.prototype.closeCalendarPane = function() {
293304
this.isCalendarOpen = false;
294305
this.detachCalendarPane();
295-
this.inputElement.focus();
306+
this.calendarPaneOpenedFrom.focus();
307+
this.calendarPaneOpenedFrom = null;
296308
document.body.removeEventListener('click', this.bodyClickHandler);
297309
};
298310

src/components/calendar/datePicker.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ describe('md-date-picker', function() {
6262
expect(controller.calendarPane.offsetHeight).toBeGreaterThan(0);
6363

6464
// Fake an escape event coming the the calendar.
65-
pageScope.$broadcast('md-calendar-escape');
65+
pageScope.$broadcast('md-calendar-close');
6666

6767
});
6868

0 commit comments

Comments
 (0)