diff --git a/src/modal/modal.js b/src/modal/modal.js index 9470f62d5c..bec1b432fe 100644 --- a/src/modal/modal.js +++ b/src/modal/modal.js @@ -147,6 +147,65 @@ angular.module('ui.bootstrap.modal', []) }; }]) + .directive('enforceFocus',['$document', '$modalStack', function ($document, $modalStack) { + return { + link: function($scope, iElm) { + var body = $document.find('body').eq(0); + var trapFocusDomEl = angular.element('
'); + body.append(trapFocusDomEl); + + + function currentModal(){ + var modal = $modalStack.getTop(); + if (modal && modal.value) { + return modal.value.modalDomEl[0] === iElm[0]; + } + } + + //enforceFocus inside modal + function enforceFocus(evt) { + if (!currentModal()) { + return; + } + if (iElm[0] !== evt.target && !iElm[0].contains(evt.target)) { + iElm[0].focus(); + } + } + $document[0].addEventListener('focus', enforceFocus, true); + + + //return lastFocusable element inside modal + function lastFocusable(domEl) { + var tababbleSelector = 'a[href], area[href], input:not([disabled]), button:not([disabled]),select:not([disabled]), textarea:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]'; + var list = domEl.querySelectorAll(tababbleSelector); + return list[list.length - 1]; + } + + var lastEl = lastFocusable(iElm[0]); + //focus lastElement when shitKey Tab first element + function shiftKeyTabTrap (evt) { + if (!currentModal()) { + return; + } + if(iElm[0] === evt.target && evt.shiftKey && evt.keyCode === 9){ + lastEl.focus(); + evt.preventDefault(); + } + } + $document.bind('keydown', shiftKeyTabTrap); + + + $scope.$on('$destroy',function() { + //Remove trap + trapFocusDomEl.remove(); + //Remove event listener + $document[0].removeEventListener('focus', enforceFocus, true); + $document.unbind('keydown', shiftKeyTabTrap); + }); + } + }; + }]) + .directive('modalAnimationClass', [ function () { return { @@ -291,7 +350,7 @@ angular.module('ui.bootstrap.modal', []) body.append(backdropDomEl); } - var angularDomEl = angular.element(''); + var angularDomEl = angular.element(''); angularDomEl.attr({ 'template-url': modal.windowTemplateUrl, 'window-class': modal.windowClass, diff --git a/src/modal/test/modal.spec.js b/src/modal/test/modal.spec.js index c1d5fe4c29..dcc1c6e01e 100644 --- a/src/modal/test/modal.spec.js +++ b/src/modal/test/modal.spec.js @@ -272,6 +272,29 @@ describe('$modal', function () { element.remove(); }); + it('should support Tab and return focus to the dialog', function () { + var link = 'Link'; + var aElement = angular.element(link); + angular.element(document.body).append(aElement); + aElement.focus(); + expect(document.activeElement.tagName).toBe('A'); + + var modal = open({template: '