diff --git a/src/components/datepicker/js/datepickerDirective.js b/src/components/datepicker/js/datepickerDirective.js
index b71fcc80c44..b9b6bfc3e43 100644
--- a/src/components/datepicker/js/datepickerDirective.js
+++ b/src/components/datepicker/js/datepickerDirective.js
@@ -304,17 +304,18 @@
*/
this.calendarPaneOpenedFrom = null;
+ /** @type {String} Unique id for the calendar pane. */
this.calendarPane.id = 'md-date-pane' + $mdUtil.nextUid();
- $mdTheming($element);
- $mdTheming(angular.element(this.calendarPane));
-
/** Pre-bound click handler is saved so that the event listener can be removed. */
this.bodyClickHandler = angular.bind(this, this.handleBodyClick);
/** Pre-bound resize handler so that the event listener can be removed. */
this.windowResizeHandler = $mdUtil.debounce(angular.bind(this, this.closeCalendarPane), 100);
+ /** Pre-bound handler for the window blur event. Allows for it to be removed later. */
+ this.windowBlurHandler = angular.bind(this, this.handleWindowBlur);
+
// Unless the user specifies so, the datepicker should not be a tab stop.
// This is necessary because ngAria might add a tabindex to anything with an ng-model
// (based on whether or not the user has turned that particular feature on/off).
@@ -322,6 +323,9 @@
$element.attr('tabindex', '-1');
}
+ $mdTheming($element);
+ $mdTheming(angular.element(this.calendarPane));
+
this.installPropertyInterceptors();
this.attachChangeListeners();
this.attachInteractionListeners();
@@ -415,6 +419,11 @@
if (self.openOnFocus) {
self.ngInputElement.on('focus', angular.bind(self, self.openCalendarPane));
+ angular.element(self.$window).on('blur', self.windowBlurHandler);
+
+ $scope.$on('$destroy', function() {
+ angular.element(self.$window).off('blur', self.windowBlurHandler);
+ });
}
$scope.$on('md-calendar-close', function() {
@@ -643,8 +652,8 @@
}
if (this.calendarPane.parentNode) {
- // Use native DOM removal because we do not want any of the angular state of this element
- // to be disposed.
+ // Use native DOM removal because we do not want any of the
+ // angular state of this element to be disposed.
this.calendarPane.parentNode.removeChild(this.calendarPane);
}
};
@@ -654,7 +663,7 @@
* @param {Event} event
*/
DatePickerCtrl.prototype.openCalendarPane = function(event) {
- if (!this.isCalendarOpen && !this.isDisabled) {
+ if (!this.isCalendarOpen && !this.isDisabled && !this.inputFocusedOnWindowBlur) {
this.isCalendarOpen = this.isOpen = true;
this.calendarPaneOpenedFrom = event.target;
@@ -754,4 +763,13 @@
this.$scope.$digest();
}
};
+
+ /**
+ * Handles the event when the user navigates away from the current tab. Keeps track of
+ * whether the input was focused when the event happened, in order to prevent the calendar
+ * from re-opening.
+ */
+ DatePickerCtrl.prototype.handleWindowBlur = function() {
+ this.inputFocusedOnWindowBlur = document.activeElement === this.inputElement;
+ };
})();
diff --git a/src/components/datepicker/js/datepickerDirective.spec.js b/src/components/datepicker/js/datepickerDirective.spec.js
index 39f04bda038..5957259fcd2 100644
--- a/src/components/datepicker/js/datepickerDirective.spec.js
+++ b/src/components/datepicker/js/datepickerDirective.spec.js
@@ -586,10 +586,43 @@ describe('md-datepicker', function() {
});
});
- it('should be able open the calendar when the input is focused', function() {
- createDatepickerInstance('');
- controller.ngInputElement.triggerHandler('focus');
- expect(document.querySelector('md-calendar')).toBeTruthy();
+ describe('mdOpenOnFocus attribute', function() {
+ beforeEach(function() {
+ createDatepickerInstance('');
+ });
+
+ it('should be able open the calendar when the input is focused', function() {
+ controller.ngInputElement.triggerHandler('focus');
+ expect(controller.isCalendarOpen).toBe(true);
+ });
+
+ it('should not reopen a closed calendar when the window is refocused', inject(function($timeout) {
+ // Focus the input initially to open the calendar.
+ // Note that the element needs to be appended to the DOM so it can be set as the activeElement.
+ document.body.appendChild(element);
+ controller.inputElement.focus();
+ controller.ngInputElement.triggerHandler('focus');
+
+ expect(document.activeElement).toBe(controller.inputElement);
+ expect(controller.isCalendarOpen).toBe(true);
+
+ // Close the calendar, but make sure that the input is still focused.
+ controller.closeCalendarPane();
+ $timeout.flush();
+ expect(document.activeElement).toBe(controller.inputElement);
+ expect(controller.isCalendarOpen).toBe(false);
+
+ // Simulate the user tabbing away.
+ angular.element(window).triggerHandler('blur');
+ expect(controller.inputFocusedOnWindowBlur).toBe(true);
+
+ // Try opening the calendar again.
+ controller.ngInputElement.triggerHandler('focus');
+ expect(controller.isCalendarOpen).toBe(false);
+
+ // Clean up.
+ document.body.removeChild(element);
+ }));
});
describe('hiding the icons', function() {