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

Commit

Permalink
fix(modal): Wait for animation before focus.
Browse files Browse the repository at this point in the history
- focus modal only after animation completes

Closes #4300
Fixes #4274
  • Loading branch information
wawyed authored and wesleycho committed Aug 28, 2015
1 parent 5a44eb2 commit 937a1f3
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 18 deletions.
37 changes: 21 additions & 16 deletions src/modal/modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,15 @@ angular.module('ui.bootstrap.modal', [])
});

modalRenderDeferObj.promise.then(function() {
var animationPromise = null;

if (attrs.modalInClass) {
if ($animateCss) {
$animateCss(element, {
animationPromise = $animateCss(element, {
addClass: attrs.modalInClass
}).start();
} else {
$animate.addClass(element, attrs.modalInClass);
animationPromise = $animate.addClass(element, attrs.modalInClass);
}

scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
Expand All @@ -224,20 +226,23 @@ angular.module('ui.bootstrap.modal', [])
});
}

var inputsWithAutofocus = element[0].querySelectorAll('[autofocus]');
/**
* Auto-focusing of a freshly-opened modal element causes any child elements
* with the autofocus attribute to lose focus. This is an issue on touch
* based devices which will show and then hide the onscreen keyboard.
* Attempts to refocus the autofocus element via JavaScript will not reopen
* the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
* the modal element if the modal does not contain an autofocus element.
*/
if (inputsWithAutofocus.length) {
inputsWithAutofocus[0].focus();
} else {
element[0].focus();
}

$q.when(animationPromise).then(function() {
var inputsWithAutofocus = element[0].querySelectorAll('[autofocus]');
/**
* Auto-focusing of a freshly-opened modal element causes any child elements
* with the autofocus attribute to lose focus. This is an issue on touch
* based devices which will show and then hide the onscreen keyboard.
* Attempts to refocus the autofocus element via JavaScript will not reopen
* the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
* the modal element if the modal does not contain an autofocus element.
*/
if (inputsWithAutofocus.length) {
inputsWithAutofocus[0].focus();
} else {
element[0].focus();
}
});

// Notify {@link $modalStack} that modal is rendered.
var modal = $modalStack.getTop();
Expand Down
52 changes: 50 additions & 2 deletions src/modal/test/modal.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,8 @@ describe('$modal', function () {
expect(document.activeElement.tagName).toBe('A');

var modal = open({template: '<div>Content<button>inside modal</button></div>'});
$animate.flush();
$rootScope.$digest();
expect(document.activeElement.tagName).toBe('DIV');
expect($document).toHaveModalsOpen(1);

Expand All @@ -315,6 +317,8 @@ describe('$modal', function () {
expect(document.activeElement.tagName).toBe('A');

var modal = open({template: '<div>Content</div>'});
$animate.flush();
$rootScope.$digest();
expect(document.activeElement.tagName).toBe('DIV');
expect($document).toHaveModalsOpen(1);

Expand Down Expand Up @@ -377,10 +381,11 @@ describe('$modal', function () {
expect(modal.opened).toBeRejectedWith('ko');
});

it('should focus on the element that has autofocus attribute when the modal is open/reopen', function() {
it('should focus on the element that has autofocus attribute when the modal is open/reopen and the animations have finished', function() {
function openAndCloseModalWithAutofocusElement() {
var modal = open({template: '<div><input type="text" id="auto-focus-element" autofocus></div>'});

$animate.flush();
$rootScope.$digest();
expect(angular.element('#auto-focus-element')).toHaveFocus();

close(modal, 'closed ok');
Expand All @@ -392,6 +397,45 @@ describe('$modal', function () {
openAndCloseModalWithAutofocusElement();
});

it('should wait until the in animation is finished before attempting to focus the modal or autofocus element', function() {
function openAndCloseModalWithAutofocusElement() {
var modal = open({template: '<div><input type="text" id="auto-focus-element" autofocus></div>'});
expect(angular.element('#auto-focus-element')).not.toHaveFocus();

$animate.flush();
$rootScope.$digest();

expect(angular.element('#auto-focus-element')).toHaveFocus();

close(modal, 'closed ok');

expect(modal.result).toBeResolvedWith('closed ok');
}

function openAndCloseModalWithOutAutofocusElement() {
var link = '<a href>Link</a>';
var element = angular.element(link);
angular.element(document.body).append(element);
element.focus();
expect(document.activeElement.tagName).toBe('A');

var modal = open({template: '<div><input type="text"></div>'});
expect(document.activeElement.tagName).toBe('A');

$animate.flush();
$rootScope.$digest();

expect(document.activeElement.tagName).toBe('DIV');

close(modal, 'closed ok');

expect(modal.result).toBeResolvedWith('closed ok');
}

openAndCloseModalWithAutofocusElement();
openAndCloseModalWithOutAutofocusElement();
});

it('should change focus to first element when tab key was pressed', function() {
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
angular.element(document.body).append(initialPage);
Expand Down Expand Up @@ -918,11 +962,15 @@ describe('$modal', function () {
expect(document.activeElement.tagName).toBe('A');

var modal1 = open({template: '<div>Modal1<button id="focus">inside modal1</button></div>'});
$animate.flush();
$rootScope.$digest();
document.getElementById('focus').focus();
expect(document.activeElement.tagName).toBe('BUTTON');
expect($document).toHaveModalsOpen(1);

var modal2 = open({template: '<div>Modal2</div>'});
$animate.flush();
$rootScope.$digest();
expect(document.activeElement.tagName).toBe('DIV');
expect($document).toHaveModalsOpen(2);

Expand Down

0 comments on commit 937a1f3

Please sign in to comment.