From 6ce96772741f42b6222b30ec5ce8ed97a46f9693 Mon Sep 17 00:00:00 2001 From: Wesley Cho Date: Mon, 14 Mar 2016 13:49:15 -0700 Subject: [PATCH] fix(modal): dynamically fetch elements - Dynamically fetch elements for determination of which is the first/last to tab/shift+tab to --- src/modal/modal.js | 55 +++++++++++++++--------------------- src/modal/test/modal.spec.js | 46 ++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 32 deletions(-) diff --git a/src/modal/modal.js b/src/modal/modal.js index ddd924ff47..4e97d28b89 100644 --- a/src/modal/modal.js +++ b/src/modal/modal.js @@ -259,8 +259,6 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap']) }; //Modal focus behavior - var focusableElementList; - var focusIndex = 0; var tababbleSelector = 'a[href], area[href], input:not([disabled]), ' + 'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' + 'iframe, object, embed, *[tabindex], *[contenteditable=true]'; @@ -391,15 +389,15 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap']) break; } case 9: { - $modalStack.loadFocusElementList(modal); + var list = $modalStack.loadFocusElementList(modal); var focusChanged = false; if (evt.shiftKey) { - if ($modalStack.isFocusInFirstItem(evt) || $modalStack.isModalFocused(evt, modal)) { - focusChanged = $modalStack.focusLastFocusableElement(); + if ($modalStack.isFocusInFirstItem(evt, list) || $modalStack.isModalFocused(evt, modal)) { + focusChanged = $modalStack.focusLastFocusableElement(list); } } else { - if ($modalStack.isFocusInLastItem(evt)) { - focusChanged = $modalStack.focusFirstFocusableElement(); + if ($modalStack.isFocusInLastItem(evt, list)) { + focusChanged = $modalStack.focusFirstFocusableElement(list); } } @@ -407,6 +405,7 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap']) evt.preventDefault(); evt.stopPropagation(); } + break; } } @@ -476,8 +475,6 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap']) openedWindows.top().value.modalDomEl = angularDomEl; openedWindows.top().value.modalOpener = modalOpener; - - $modalStack.clearFocusListCache(); }; function broadcastClosing(modalWindow, resultOrReason, closing) { @@ -524,16 +521,17 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap']) } }; - $modalStack.focusFirstFocusableElement = function() { - if (focusableElementList.length > 0) { - focusableElementList[0].focus(); + $modalStack.focusFirstFocusableElement = function(list) { + if (list.length > 0) { + list[0].focus(); return true; } return false; }; - $modalStack.focusLastFocusableElement = function() { - if (focusableElementList.length > 0) { - focusableElementList[focusableElementList.length - 1].focus(); + + $modalStack.focusLastFocusableElement = function(list) { + if (list.length > 0) { + list[list.length - 1].focus(); return true; } return false; @@ -549,32 +547,25 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap']) return false; }; - $modalStack.isFocusInFirstItem = function(evt) { - if (focusableElementList.length > 0) { - return (evt.target || evt.srcElement) === focusableElementList[0]; + $modalStack.isFocusInFirstItem = function(evt, list) { + if (list.length > 0) { + return (evt.target || evt.srcElement) === list[0]; } return false; }; - $modalStack.isFocusInLastItem = function(evt) { - if (focusableElementList.length > 0) { - return (evt.target || evt.srcElement) === focusableElementList[focusableElementList.length - 1]; + $modalStack.isFocusInLastItem = function(evt, list) { + if (list.length > 0) { + return (evt.target || evt.srcElement) === list[list.length - 1]; } return false; }; - $modalStack.clearFocusListCache = function() { - focusableElementList = []; - focusIndex = 0; - }; - $modalStack.loadFocusElementList = function(modalWindow) { - if (focusableElementList === undefined || !focusableElementList.length) { - if (modalWindow) { - var modalDomE1 = modalWindow.value.modalDomEl; - if (modalDomE1 && modalDomE1.length) { - focusableElementList = modalDomE1[0].querySelectorAll(tababbleSelector); - } + if (modalWindow) { + var modalDomE1 = modalWindow.value.modalDomEl; + if (modalDomE1 && modalDomE1.length) { + return modalDomE1[0].querySelectorAll(tababbleSelector); } } }; diff --git a/src/modal/test/modal.spec.js b/src/modal/test/modal.spec.js index 43775634dc..929ce5241f 100644 --- a/src/modal/test/modal.spec.js +++ b/src/modal/test/modal.spec.js @@ -715,6 +715,52 @@ describe('$uibModal', function() { initialPage.remove(); }); + + it('should change focus to next proper element when DOM changes and tab is pressed', function() { + var initialPage = angular.element('Outland link'); + angular.element(document.body).append(initialPage); + initialPage.focus(); + + open({ + template:'abc' + + '', + keyboard: false + }); + $rootScope.$digest(); + expect($document).toHaveModalsOpen(1); + + $('#tab-focus-link3').focus(); + expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3'); + + $('#tab-focus-button').remove(); + triggerKeyDown(angular.element(document.activeElement), 9, false); + expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1'); + + initialPage.remove(); + }); + + it('should change focus to next proper element when DOM changes and shift+tab is pressed', function() { + var initialPage = angular.element('Outland link'); + angular.element(document.body).append(initialPage); + initialPage.focus(); + + open({ + template:'abc' + + '', + keyboard: false + }); + $rootScope.$digest(); + expect($document).toHaveModalsOpen(1); + + $('#tab-focus-link1').focus(); + expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1'); + + $('#tab-focus-button').remove(); + triggerKeyDown(angular.element(document.activeElement), 9, true); + expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3'); + + initialPage.remove(); + }); }); describe('default options can be changed in a provider', function() {