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

Commit 5ef56e2

Browse files
committed
feat(modal): exclude hidden elements from query
- Modify element query to exclude hidden elements Closes #5512 Closes #5644
1 parent bc7c55a commit 5ef56e2

File tree

2 files changed

+58
-2
lines changed

2 files changed

+58
-2
lines changed

src/modal/modal.js

+12-2
Original file line numberDiff line numberDiff line change
@@ -259,10 +259,16 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
259259
};
260260

261261
//Modal focus behavior
262-
var tababbleSelector = 'a[href], area[href], input:not([disabled]), ' +
262+
var tabableSelector = 'a[href], area[href], input:not([disabled]), ' +
263263
'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' +
264264
'iframe, object, embed, *[tabindex], *[contenteditable=true]';
265265

266+
function isVisible(element) {
267+
return !!(element.offsetWidth ||
268+
element.offsetHeight ||
269+
element.getClientRects().length);
270+
}
271+
266272
function backdropIndex() {
267273
var topBackdropIndex = -1;
268274
var opened = openedWindows.keys();
@@ -565,7 +571,11 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
565571
if (modalWindow) {
566572
var modalDomE1 = modalWindow.value.modalDomEl;
567573
if (modalDomE1 && modalDomE1.length) {
568-
return modalDomE1[0].querySelectorAll(tababbleSelector);
574+
var elements = modalDomE1[0].querySelectorAll(tabableSelector);
575+
return elements ?
576+
Array.prototype.filter.call(elements, function(element) {
577+
return isVisible(element);
578+
}) : elements;
569579
}
570580
}
571581
};

src/modal/test/modal.spec.js

+46
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,52 @@ describe('$uibModal', function() {
761761

762762
initialPage.remove();
763763
});
764+
765+
it('should change focus to next non-hidden element when tab is pressed', function() {
766+
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
767+
angular.element(document.body).append(initialPage);
768+
initialPage.focus();
769+
770+
open({
771+
template:'<a href="#" id="tab-focus-link1">a</a><a href="#" id="tab-focus-link2">b</a><a href="#" id="tab-focus-link3">c</a>' +
772+
'<button id="tab-focus-button">Open me!</button>',
773+
keyboard: false
774+
});
775+
$rootScope.$digest();
776+
expect($document).toHaveModalsOpen(1);
777+
778+
$('#tab-focus-link3').focus();
779+
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');
780+
781+
$('#tab-focus-button').css('display', 'none');
782+
triggerKeyDown(angular.element(document.activeElement), 9, false);
783+
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');
784+
785+
initialPage.remove();
786+
});
787+
788+
it('should change focus to previous non-hidden element when shift+tab is pressed', function() {
789+
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
790+
angular.element(document.body).append(initialPage);
791+
initialPage.focus();
792+
793+
open({
794+
template:'<a href="#" id="tab-focus-link1">a</a><a href="#" id="tab-focus-link2">b</a><a href="#" id="tab-focus-link3">c</a>' +
795+
'<button id="tab-focus-button">Open me!</button>',
796+
keyboard: false
797+
});
798+
$rootScope.$digest();
799+
expect($document).toHaveModalsOpen(1);
800+
801+
$('#tab-focus-link1').focus();
802+
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');
803+
804+
$('#tab-focus-button').css('display', 'none');
805+
triggerKeyDown(angular.element(document.activeElement), 9, true);
806+
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');
807+
808+
initialPage.remove();
809+
});
764810
});
765811

766812
describe('default options can be changed in a provider', function() {

0 commit comments

Comments
 (0)