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

Commit e15a22a

Browse files
committed
fix(modal): dynamically fetch elements
- Dynamically fetch elements for determination of which is the first/last to tab/shift+tab to Closes #5630 Fixes #5050 Fixes #5421
1 parent bb36e40 commit e15a22a

File tree

2 files changed

+69
-32
lines changed

2 files changed

+69
-32
lines changed

src/modal/modal.js

+23-32
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,6 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
259259
};
260260

261261
//Modal focus behavior
262-
var focusableElementList;
263-
var focusIndex = 0;
264262
var tababbleSelector = 'a[href], area[href], input:not([disabled]), ' +
265263
'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' +
266264
'iframe, object, embed, *[tabindex], *[contenteditable=true]';
@@ -391,22 +389,23 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
391389
break;
392390
}
393391
case 9: {
394-
$modalStack.loadFocusElementList(modal);
392+
var list = $modalStack.loadFocusElementList(modal);
395393
var focusChanged = false;
396394
if (evt.shiftKey) {
397-
if ($modalStack.isFocusInFirstItem(evt) || $modalStack.isModalFocused(evt, modal)) {
398-
focusChanged = $modalStack.focusLastFocusableElement();
395+
if ($modalStack.isFocusInFirstItem(evt, list) || $modalStack.isModalFocused(evt, modal)) {
396+
focusChanged = $modalStack.focusLastFocusableElement(list);
399397
}
400398
} else {
401-
if ($modalStack.isFocusInLastItem(evt)) {
402-
focusChanged = $modalStack.focusFirstFocusableElement();
399+
if ($modalStack.isFocusInLastItem(evt, list)) {
400+
focusChanged = $modalStack.focusFirstFocusableElement(list);
403401
}
404402
}
405403

406404
if (focusChanged) {
407405
evt.preventDefault();
408406
evt.stopPropagation();
409407
}
408+
410409
break;
411410
}
412411
}
@@ -476,8 +475,6 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
476475

477476
openedWindows.top().value.modalDomEl = angularDomEl;
478477
openedWindows.top().value.modalOpener = modalOpener;
479-
480-
$modalStack.clearFocusListCache();
481478
};
482479

483480
function broadcastClosing(modalWindow, resultOrReason, closing) {
@@ -524,16 +521,17 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
524521
}
525522
};
526523

527-
$modalStack.focusFirstFocusableElement = function() {
528-
if (focusableElementList.length > 0) {
529-
focusableElementList[0].focus();
524+
$modalStack.focusFirstFocusableElement = function(list) {
525+
if (list.length > 0) {
526+
list[0].focus();
530527
return true;
531528
}
532529
return false;
533530
};
534-
$modalStack.focusLastFocusableElement = function() {
535-
if (focusableElementList.length > 0) {
536-
focusableElementList[focusableElementList.length - 1].focus();
531+
532+
$modalStack.focusLastFocusableElement = function(list) {
533+
if (list.length > 0) {
534+
list[list.length - 1].focus();
537535
return true;
538536
}
539537
return false;
@@ -549,32 +547,25 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
549547
return false;
550548
};
551549

552-
$modalStack.isFocusInFirstItem = function(evt) {
553-
if (focusableElementList.length > 0) {
554-
return (evt.target || evt.srcElement) === focusableElementList[0];
550+
$modalStack.isFocusInFirstItem = function(evt, list) {
551+
if (list.length > 0) {
552+
return (evt.target || evt.srcElement) === list[0];
555553
}
556554
return false;
557555
};
558556

559-
$modalStack.isFocusInLastItem = function(evt) {
560-
if (focusableElementList.length > 0) {
561-
return (evt.target || evt.srcElement) === focusableElementList[focusableElementList.length - 1];
557+
$modalStack.isFocusInLastItem = function(evt, list) {
558+
if (list.length > 0) {
559+
return (evt.target || evt.srcElement) === list[list.length - 1];
562560
}
563561
return false;
564562
};
565563

566-
$modalStack.clearFocusListCache = function() {
567-
focusableElementList = [];
568-
focusIndex = 0;
569-
};
570-
571564
$modalStack.loadFocusElementList = function(modalWindow) {
572-
if (focusableElementList === undefined || !focusableElementList.length) {
573-
if (modalWindow) {
574-
var modalDomE1 = modalWindow.value.modalDomEl;
575-
if (modalDomE1 && modalDomE1.length) {
576-
focusableElementList = modalDomE1[0].querySelectorAll(tababbleSelector);
577-
}
565+
if (modalWindow) {
566+
var modalDomE1 = modalWindow.value.modalDomEl;
567+
if (modalDomE1 && modalDomE1.length) {
568+
return modalDomE1[0].querySelectorAll(tababbleSelector);
578569
}
579570
}
580571
};

src/modal/test/modal.spec.js

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

716716
initialPage.remove();
717717
});
718+
719+
it('should change focus to next proper element when DOM changes and tab is pressed', function() {
720+
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
721+
angular.element(document.body).append(initialPage);
722+
initialPage.focus();
723+
724+
open({
725+
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>' +
726+
'<button id="tab-focus-button">Open me!</button>',
727+
keyboard: false
728+
});
729+
$rootScope.$digest();
730+
expect($document).toHaveModalsOpen(1);
731+
732+
$('#tab-focus-link3').focus();
733+
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');
734+
735+
$('#tab-focus-button').remove();
736+
triggerKeyDown(angular.element(document.activeElement), 9, false);
737+
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');
738+
739+
initialPage.remove();
740+
});
741+
742+
it('should change focus to next proper element when DOM changes and shift+tab is pressed', function() {
743+
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
744+
angular.element(document.body).append(initialPage);
745+
initialPage.focus();
746+
747+
open({
748+
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>' +
749+
'<button id="tab-focus-button">Open me!</button>',
750+
keyboard: false
751+
});
752+
$rootScope.$digest();
753+
expect($document).toHaveModalsOpen(1);
754+
755+
$('#tab-focus-link1').focus();
756+
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');
757+
758+
$('#tab-focus-button').remove();
759+
triggerKeyDown(angular.element(document.activeElement), 9, true);
760+
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');
761+
762+
initialPage.remove();
763+
});
718764
});
719765

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

0 commit comments

Comments
 (0)