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

Commit 215dce9

Browse files
crisbetoThomasBurleson
authored andcommitted
fix(datepicker): calendar opening on window focus with mdOpenOnFocus
Fixes cases where a closed calendar gets reopened when the user tabs away, on a datepicker with `md-open-on-focus`. Closes #9186
1 parent 09e2eb6 commit 215dce9

File tree

2 files changed

+61
-10
lines changed

2 files changed

+61
-10
lines changed

src/components/datepicker/js/datepickerDirective.js

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -304,24 +304,28 @@
304304
*/
305305
this.calendarPaneOpenedFrom = null;
306306

307+
/** @type {String} Unique id for the calendar pane. */
307308
this.calendarPane.id = 'md-date-pane' + $mdUtil.nextUid();
308309

309-
$mdTheming($element);
310-
$mdTheming(angular.element(this.calendarPane));
311-
312310
/** Pre-bound click handler is saved so that the event listener can be removed. */
313311
this.bodyClickHandler = angular.bind(this, this.handleBodyClick);
314312

315313
/** Pre-bound resize handler so that the event listener can be removed. */
316314
this.windowResizeHandler = $mdUtil.debounce(angular.bind(this, this.closeCalendarPane), 100);
317315

316+
/** Pre-bound handler for the window blur event. Allows for it to be removed later. */
317+
this.windowBlurHandler = angular.bind(this, this.handleWindowBlur);
318+
318319
// Unless the user specifies so, the datepicker should not be a tab stop.
319320
// This is necessary because ngAria might add a tabindex to anything with an ng-model
320321
// (based on whether or not the user has turned that particular feature on/off).
321322
if (!$attrs.tabindex) {
322323
$element.attr('tabindex', '-1');
323324
}
324325

326+
$mdTheming($element);
327+
$mdTheming(angular.element(this.calendarPane));
328+
325329
this.installPropertyInterceptors();
326330
this.attachChangeListeners();
327331
this.attachInteractionListeners();
@@ -415,6 +419,11 @@
415419

416420
if (self.openOnFocus) {
417421
self.ngInputElement.on('focus', angular.bind(self, self.openCalendarPane));
422+
angular.element(self.$window).on('blur', self.windowBlurHandler);
423+
424+
$scope.$on('$destroy', function() {
425+
angular.element(self.$window).off('blur', self.windowBlurHandler);
426+
});
418427
}
419428

420429
$scope.$on('md-calendar-close', function() {
@@ -643,8 +652,8 @@
643652
}
644653

645654
if (this.calendarPane.parentNode) {
646-
// Use native DOM removal because we do not want any of the angular state of this element
647-
// to be disposed.
655+
// Use native DOM removal because we do not want any of the
656+
// angular state of this element to be disposed.
648657
this.calendarPane.parentNode.removeChild(this.calendarPane);
649658
}
650659
};
@@ -654,7 +663,7 @@
654663
* @param {Event} event
655664
*/
656665
DatePickerCtrl.prototype.openCalendarPane = function(event) {
657-
if (!this.isCalendarOpen && !this.isDisabled) {
666+
if (!this.isCalendarOpen && !this.isDisabled && !this.inputFocusedOnWindowBlur) {
658667
this.isCalendarOpen = this.isOpen = true;
659668
this.calendarPaneOpenedFrom = event.target;
660669

@@ -754,4 +763,13 @@
754763
this.$scope.$digest();
755764
}
756765
};
766+
767+
/**
768+
* Handles the event when the user navigates away from the current tab. Keeps track of
769+
* whether the input was focused when the event happened, in order to prevent the calendar
770+
* from re-opening.
771+
*/
772+
DatePickerCtrl.prototype.handleWindowBlur = function() {
773+
this.inputFocusedOnWindowBlur = document.activeElement === this.inputElement;
774+
};
757775
})();

src/components/datepicker/js/datepickerDirective.spec.js

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -586,10 +586,43 @@ describe('md-datepicker', function() {
586586
});
587587
});
588588

589-
it('should be able open the calendar when the input is focused', function() {
590-
createDatepickerInstance('<md-datepicker ng-model="myDate" md-open-on-focus></md-datepicker>');
591-
controller.ngInputElement.triggerHandler('focus');
592-
expect(document.querySelector('md-calendar')).toBeTruthy();
589+
describe('mdOpenOnFocus attribute', function() {
590+
beforeEach(function() {
591+
createDatepickerInstance('<md-datepicker ng-model="myDate" md-open-on-focus></md-datepicker>');
592+
});
593+
594+
it('should be able open the calendar when the input is focused', function() {
595+
controller.ngInputElement.triggerHandler('focus');
596+
expect(controller.isCalendarOpen).toBe(true);
597+
});
598+
599+
it('should not reopen a closed calendar when the window is refocused', inject(function($timeout) {
600+
// Focus the input initially to open the calendar.
601+
// Note that the element needs to be appended to the DOM so it can be set as the activeElement.
602+
document.body.appendChild(element);
603+
controller.inputElement.focus();
604+
controller.ngInputElement.triggerHandler('focus');
605+
606+
expect(document.activeElement).toBe(controller.inputElement);
607+
expect(controller.isCalendarOpen).toBe(true);
608+
609+
// Close the calendar, but make sure that the input is still focused.
610+
controller.closeCalendarPane();
611+
$timeout.flush();
612+
expect(document.activeElement).toBe(controller.inputElement);
613+
expect(controller.isCalendarOpen).toBe(false);
614+
615+
// Simulate the user tabbing away.
616+
angular.element(window).triggerHandler('blur');
617+
expect(controller.inputFocusedOnWindowBlur).toBe(true);
618+
619+
// Try opening the calendar again.
620+
controller.ngInputElement.triggerHandler('focus');
621+
expect(controller.isCalendarOpen).toBe(false);
622+
623+
// Clean up.
624+
document.body.removeChild(element);
625+
}));
593626
});
594627

595628
describe('hiding the icons', function() {

0 commit comments

Comments
 (0)