Skip to content
This repository has been archived by the owner on Aug 29, 2023. It is now read-only.

Commit

Permalink
feat(calendar): more fine-tuned a11y
Browse files Browse the repository at this point in the history
  • Loading branch information
jelbourn committed Aug 13, 2015
1 parent 91bcb48 commit c3bf840
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 23 deletions.
17 changes: 10 additions & 7 deletions src/components/calendar/calendar.js
Expand Up @@ -15,7 +15,6 @@

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

// POST RELEASE
// TODO(jelbourn): Animated month transition on ng-model change (virtual-repeat)
Expand All @@ -41,7 +40,6 @@
function calendarDirective() {
return {
template:
//'<input class="md-visually-hidden md-dummy" aria-label="Select a date">' +
'<table aria-hidden="true" class="md-calendar-day-header"><thead></thead></table>' +
'<div class="md-calendar-scroll-mask">' +
'<md-virtual-repeat-container class="md-calendar-scroll-container">' +
Expand All @@ -52,8 +50,7 @@
'md-item-size="' + TBODY_HEIGHT + '"></tbody>' +
'</table>' +
'</md-virtual-repeat-container>' +
'</div>' +
'<div aria-live="assertive" aria-atomic="true" class="md-visually-hidden"></div>',
'</div>',
scope: {},
require: ['ngModel', 'mdCalendar'],
controller: CalendarCtrl,
Expand Down Expand Up @@ -269,8 +266,13 @@
this.$scope.$apply(function() {
// Capture escape and emit back up so that a wrapping component
// (such as a date-picker) can decide to close.
if (event.which == self.keyCode.ESCAPE) {
self.$scope.$emit('md-calendar-escape');
if (event.which == self.keyCode.ESCAPE || event.which == self.keyCode.TAB) {
self.$scope.$emit('md-calendar-close');

if (event.which == self.keyCode.TAB) {
event.preventDefault();
}

return;
}

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

Expand Down Expand Up @@ -410,6 +411,7 @@
self.calendarElement.querySelector('#' + self.getDateId(previousSelectedDate));
if (prevDateCell) {
prevDateCell.classList.remove(SELECTED_DATE_CLASS);
prevDateCell.setAttribute('aria-selected', 'false');
}
}

Expand All @@ -418,6 +420,7 @@
var dateCell = self.calendarElement.querySelector('#' + self.getDateId(date));
if (dateCell) {
dateCell.classList.add(SELECTED_DATE_CLASS);
dateCell.setAttribute('aria-selected', 'true');
}
}
});
Expand Down
2 changes: 1 addition & 1 deletion src/components/calendar/calendar.spec.js
Expand Up @@ -275,7 +275,7 @@ describe('md-calendar', function() {

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

pageScope.myDate = new Date(2014, FEB, 11);
applyDateChange();
Expand Down
22 changes: 18 additions & 4 deletions src/components/calendar/calendarMonth.js
Expand Up @@ -127,6 +127,7 @@
if (this.dateUtil.isValidDate(calendarCtrl.selectedDate) &&
this.dateUtil.isSameDay(opt_date, calendarCtrl.selectedDate)) {
cell.classList.add(SELECTED_DATE_CLASS);
cell.setAttribute('aria-selected', 'true');
}

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

return cell;
};

CalendarMonthCtrl.prototype.buildDateRow = function() {

/**
* Builds a `tr` element for the calendar grid.
* @param rowNumber The week number within the month.
* @returns {HTMLElement}
*/
CalendarMonthCtrl.prototype.buildDateRow = function(rowNumber) {
var row = document.createElement('tr');
row.setAttribute('role', 'row');

// Because of an NVDA bug (with Firefox), the row needs an aria-label in order
// to prevent the entire row being read aloud when the user moves between rows.
// See http://community.nvda-project.org/ticket/4643.
//row.setAttribute('aria-label', this.dateLocale.weekNumberFormatter(rowNumber));

return row;
};

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

var row = this.buildDateRow();
var rowNumber = 1;
var row = this.buildDateRow(rowNumber);
monthBody.appendChild(row);

// Add a label for the month. If the month starts on a Sun/Mon/Tues, the month label
Expand Down Expand Up @@ -196,7 +209,8 @@
// If we've reached the end of the week, start a new row.
if (dayOfWeek === 7) {
dayOfWeek = 0;
row = this.buildDateRow();
rowNumber++;
row = this.buildDateRow(rowNumber);
monthBody.appendChild(row);
}

Expand Down
16 changes: 16 additions & 0 deletions src/components/calendar/dateLocaleProvider.js
Expand Up @@ -43,6 +43,12 @@
*/
this.monthHeaderFormatter = null;

/**
* Function that formats a week number into a label for the week.
* @type {function(number): string}
*/
this.weekNumberFormatter = null;

/**
* Function that formats a date into a short aria-live announcement that is read when
* the focused date changes within the same month.
Expand Down Expand Up @@ -104,6 +110,15 @@
return service.shortMonths[date.getMonth()] + ' ' + date.getFullYear();
}

/**
* Default week number formatter.
* @param number
* @returns {string}
*/
function defaultWeekNumberFormatter(number) {
return 'Week ' + number;
}

/**
* Default formatter for short aria-live announcements.
* @param {!Date} date
Expand Down Expand Up @@ -154,6 +169,7 @@
formatDate: this.formatDate || defaultFormatDate,
parseDate: this.parseDate || defaultParseDate,
monthHeaderFormatter: this.monthHeaderFormatter || defaultMonthHeaderFormatter,
weekNumberFormatter: this.weekNumberFormatter || defaultWeekNumberFormatter,
shortAnnounceFormatter: this.shortAnnounceFormatter || defaultShortAnnounceFormatter,
longAnnounceFormatter: this.longAnnounceFormatter || defaultLongAnnounceFormatter,
msgCalendar: this.msgCalendar || defaultMsgCalendar,
Expand Down
32 changes: 22 additions & 10 deletions src/components/calendar/datePicker.js
Expand Up @@ -26,16 +26,16 @@
// interaction on the text input, and multiple tab stops for one component (picker)
// may be confusing.
'<md-button class="md-datepicker-button md-icon-button" type="button" ' +
'tabindex="-1" aria-label="{{::ctrl.dateLocale.msgOpenCalendar}}" ' +
'ng-click="ctrl.openCalendarPane()">' +
'tabindex="-1" aria-hidden="true" ' +
'ng-click="ctrl.openCalendarPane($event)">' +
'<md-icon class="md-datepicker-calendar-icon" md-svg-icon="md-calendar"></md-icon>' +
'</md-button>' +
'<div class="md-datepicker-input-container">' +
'<input class="md-datepicker-input" aria-haspopup="true" aria-describedby="xxx">' +
'<span id="xxx" class="md-visually-hidden">Press Alt + Down to open the calendar</span>' +
'<input class="md-datepicker-input" aria-haspopup="true">' +
//'<span id="xxx" class="md-visually-hidden">Press Alt + Down to open the calendar</span>' +

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

/**
* Element from which the calendar pane was opened. Keep track of this so that we can return
* focus to it when the pane is closed.
* @type {HTMLElement}
*/
this.calendarPaneOpenedFrom = null;

this.calendarPane.id = 'md-date-pane' + $mdUtil.nextUid();

/** Pre-bound click handler is saved so that the event listener can be removed. */
Expand Down Expand Up @@ -204,12 +211,12 @@
angular.element(self.inputElement).on('keydown', function(event) {
$scope.$apply(function() {
if (event.altKey && event.keyCode == keyCodes.DOWN_ARROW) {
self.openCalendarPane();
self.openCalendarPane(event);
}
});
});

self.$scope.$on('md-calendar-escape', function() {
self.$scope.$on('md-calendar-close', function() {
self.closeCalendarPane();
});
};
Expand Down Expand Up @@ -272,10 +279,14 @@
this.calendarPane.parentNode.removeChild(this.calendarPane);
};

/** Open the floating calendar pane. */
DatePickerCtrl.prototype.openCalendarPane = function() {
/**
* Open the floating calendar pane.
* @param {Event} event
*/
DatePickerCtrl.prototype.openCalendarPane = function(event) {
if (!this.isCalendarOpen && !this.isDisabled) {
this.isCalendarOpen = true;
this.calendarPaneOpenedFrom = event.target;
this.attachCalendarPane();
this.focusCalendar();

Expand All @@ -292,7 +303,8 @@
DatePickerCtrl.prototype.closeCalendarPane = function() {
this.isCalendarOpen = false;
this.detachCalendarPane();
this.inputElement.focus();
this.calendarPaneOpenedFrom.focus();
this.calendarPaneOpenedFrom = null;
document.body.removeEventListener('click', this.bodyClickHandler);
};

Expand Down
2 changes: 1 addition & 1 deletion src/components/calendar/datePicker.spec.js
Expand Up @@ -62,7 +62,7 @@ describe('md-date-picker', function() {
expect(controller.calendarPane.offsetHeight).toBeGreaterThan(0);

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

});

Expand Down

0 comments on commit c3bf840

Please sign in to comment.