' +
- '',
+ '',
scope: {},
require: ['ngModel', 'mdCalendar'],
controller: CalendarCtrl,
@@ -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;
}
@@ -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();
@@ -410,6 +411,7 @@
self.calendarElement.querySelector('#' + self.getDateId(previousSelectedDate));
if (prevDateCell) {
prevDateCell.classList.remove(SELECTED_DATE_CLASS);
+ prevDateCell.setAttribute('aria-selected', 'false');
}
}
@@ -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');
}
}
});
diff --git a/src/components/calendar/calendar.spec.js b/src/components/calendar/calendar.spec.js
index 5405ab1f8c4..40c60b93fca 100644
--- a/src/components/calendar/calendar.spec.js
+++ b/src/components/calendar/calendar.spec.js
@@ -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();
diff --git a/src/components/calendar/calendarMonth.js b/src/components/calendar/calendarMonth.js
index 44aaf62fdaf..af7f5b5114b 100644
--- a/src/components/calendar/calendarMonth.js
+++ b/src/components/calendar/calendarMonth.js
@@ -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)) {
@@ -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;
};
@@ -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
@@ -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);
}
diff --git a/src/components/calendar/dateLocaleProvider.js b/src/components/calendar/dateLocaleProvider.js
index 59f8e6565ec..1411d4738eb 100644
--- a/src/components/calendar/dateLocaleProvider.js
+++ b/src/components/calendar/dateLocaleProvider.js
@@ -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.
@@ -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
@@ -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,
diff --git a/src/components/calendar/datePicker.js b/src/components/calendar/datePicker.js
index 7a15e4302d2..af1a3976a87 100644
--- a/src/components/calendar/datePicker.js
+++ b/src/components/calendar/datePicker.js
@@ -26,16 +26,16 @@
// interaction on the text input, and multiple tab stops for one component (picker)
// may be confusing.
'' +
+ 'tabindex="-1" aria-hidden="true" ' +
+ 'ng-click="ctrl.openCalendarPane($event)">' +
'' +
'' +
'
' +
- '' +
- 'Press Alt + Down to open the calendar' +
+ '' +
+ //'Press Alt + Down to open the calendar' +
'' +
'' +
'' +
@@ -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. */
@@ -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();
});
};
@@ -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();
@@ -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);
};
diff --git a/src/components/calendar/datePicker.spec.js b/src/components/calendar/datePicker.spec.js
index 8bcabe1746a..600313dc72d 100644
--- a/src/components/calendar/datePicker.spec.js
+++ b/src/components/calendar/datePicker.spec.js
@@ -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');
});