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

fix(datepicker): calendar opening on window focus with mdOpenOnFocus #9186

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions src/components/datepicker/js/datepickerDirective.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,24 +304,28 @@
*/
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).
if (!$attrs.tabindex) {
$element.attr('tabindex', '-1');
}

$mdTheming($element);
$mdTheming(angular.element(this.calendarPane));

this.installPropertyInterceptors();
this.attachChangeListeners();
this.attachInteractionListeners();
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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);
}
};
Expand All @@ -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;

Expand Down Expand Up @@ -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;
};
})();
41 changes: 37 additions & 4 deletions src/components/datepicker/js/datepickerDirective.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -586,10 +586,43 @@ describe('md-datepicker', function() {
});
});

it('should be able open the calendar when the input is focused', function() {
createDatepickerInstance('<md-datepicker ng-model="myDate" md-open-on-focus></md-datepicker>');
controller.ngInputElement.triggerHandler('focus');
expect(document.querySelector('md-calendar')).toBeTruthy();
describe('mdOpenOnFocus attribute', function() {
beforeEach(function() {
createDatepickerInstance('<md-datepicker ng-model="myDate" md-open-on-focus></md-datepicker>');
});

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() {
Expand Down