From c95a7f2ef468f5f338edb0ee0d5c2df3d5718fc0 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard <1932158+BatJan@users.noreply.github.com> Date: Thu, 23 Jul 2020 17:50:50 +0200 Subject: [PATCH] Add focus-lock directive (#8141) --- .../gulp/tasks/dependencies.js | 8 ++ src/Umbraco.Web.UI.Client/package.json | 3 +- .../forms/umbfocuslock.directive.js | 82 +++++++++++++++++++ .../src/common/services/focuslock.service.js | 26 ++++++ .../src/common/services/overlay.service.js | 4 +- .../itempicker/itempicker.html | 2 +- .../overlays/itempicker/itempicker.html | 3 +- .../src/views/common/overlays/user/user.html | 2 +- .../components/overlays/umb-overlay.html | 9 +- .../components/users/change-password.html | 2 +- src/Umbraco.Web/JavaScript/JsInitialize.js | 1 + 11 files changed, 134 insertions(+), 8 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js create mode 100644 src/Umbraco.Web.UI.Client/src/common/services/focuslock.service.js diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js index 77a64371f909..b85f7c8fa3bf 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js @@ -241,6 +241,14 @@ function dependencies() { "name": "underscore", "src": ["node_modules/underscore/underscore-min.js"], "base": "./node_modules/underscore" + }, + { + "name": "wicg-inert", + "src": [ + "./node_modules/wicg-inert/dist/inert.min.js", + "./node_modules/wicg-inert/dist/inert.min.js.map" + ], + "base": "./node_modules/wicg-inert" } ]; diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 459f3325e94c..720f4b828544 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -44,7 +44,8 @@ "spectrum-colorpicker": "1.8.0", "tinymce": "4.9.10", "typeahead.js": "0.11.1", - "underscore": "1.9.1" + "underscore": "1.9.1", + "wicg-inert": "^3.0.2" }, "devDependencies": { "@babel/core": "7.6.4", diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js new file mode 100644 index 000000000000..569f49b88a48 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js @@ -0,0 +1,82 @@ +(function() { + 'use strict'; + + function FocusLock($timeout) { + + function getAutoFocusElement (elements) { + var elmentWithAutoFocus = null; + + elements.forEach((element) => { + if(element.getAttribute('umb-auto-focus') === 'true') { + elmentWithAutoFocus = element; + } + }); + + return elmentWithAutoFocus; + } + + function link(scope, element) { + + function onInit() { + // List of elements that can be focusable within the focus lock + var focusableElementsSelector = 'a[href]:not([disabled]), button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled])'; + var bodyElement = document.querySelector('body'); + + $timeout(function() { + var target = element[0]; + + var focusableElements = target.querySelectorAll(focusableElementsSelector); + var defaultFocusedElement = getAutoFocusElement(focusableElements); + var firstFocusableElement = focusableElements[0]; + var lastFocusableElement = focusableElements[focusableElements.length -1]; + + // We need to add the tabbing-active class in order to highlight the focused button since the default style is + // outline: none; set in the stylesheet specifically + bodyElement.classList.add('tabbing-active'); + + // If there is no default focused element put focus on the first focusable element in the nodelist + if(defaultFocusedElement === null ){ + firstFocusableElement.focus(); + } + + target.addEventListener('keydown', function(event){ + var isTabPressed = (event.key === 'Tab' || event.keyCode === 9); + + if (!isTabPressed){ + return; + } + + // If shift + tab key + if(event.shiftKey){ + // Set focus on the last focusable element if shift+tab are pressed meaning we go backwards + if(document.activeElement === firstFocusableElement){ + lastFocusableElement.focus(); + event.preventDefault(); + } + } + // Else only the tab key is pressed + else{ + // Using only the tab key we set focus on the first focusable element mening we go forward + if (document.activeElement === lastFocusableElement) { + firstFocusableElement.focus(); + event.preventDefault(); + } + } + }); + }, 250); + } + + onInit(); + } + + var directive = { + restrict: 'A', + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbFocusLock', FocusLock); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/focuslock.service.js b/src/Umbraco.Web.UI.Client/src/common/services/focuslock.service.js new file mode 100644 index 000000000000..a3dd91194e6e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/focuslock.service.js @@ -0,0 +1,26 @@ +(function () { + "use strict"; + + function focusLockService() { + var elementToInert = document.querySelector('#mainwrapper'); + + function addInertAttribute() { + elementToInert.setAttribute('inert', true); + } + + function removeInertAttribute() { + elementToInert.removeAttribute('inert'); + } + + var service = { + addInertAttribute: addInertAttribute, + removeInertAttribute: removeInertAttribute + } + + return service; + + } + + angular.module("umbraco.services").factory("focusLockService", focusLockService); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js index 5e11d44bda8f..13cdd8bdea45 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js @@ -8,7 +8,7 @@ (function () { "use strict"; - function overlayService(eventsService, backdropService) { + function overlayService(eventsService, backdropService, focusLockService) { var currentOverlay = null; @@ -43,12 +43,14 @@ } overlay.show = true; + focusLockService.addInertAttribute(); backdropService.open(backdropOptions); currentOverlay = overlay; eventsService.emit("appState.overlay", overlay); } function close() { + focusLockService.removeInertAttribute(); backdropService.close(); currentOverlay = null; eventsService.emit("appState.overlay", null); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/itempicker/itempicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/itempicker/itempicker.html index 18d0c40b6abf..5b121c172ff1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/itempicker/itempicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/itempicker/itempicker.html @@ -13,7 +13,7 @@ -