From 8615deb23b8021cba4c29a55387ede2fc79f26e5 Mon Sep 17 00:00:00 2001 From: Thomas Marstrander Date: Wed, 8 Jun 2016 11:06:03 +0200 Subject: [PATCH] Hide elements that confirmation dialog covers from assistive technologies. HFJ-1995 --- js/h5p-confirmation-dialog.js | 71 ++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/js/h5p-confirmation-dialog.js b/js/h5p-confirmation-dialog.js index 3c16214..ab551dd 100644 --- a/js/h5p-confirmation-dialog.js +++ b/js/h5p-confirmation-dialog.js @@ -161,6 +161,13 @@ H5P.ConfirmationDialog = (function (EventDispatcher) { // Focus capturing var focusPredator; + // Maintains hidden state of elements + var wrapperSiblingsHidden = []; + var popupSiblingsHidden = []; + + // Element with focus before dialog + var previouslyFocused; + /** * Set parent of confirmation dialog * @param {HTMLElement} wrapper @@ -182,6 +189,44 @@ H5P.ConfirmationDialog = (function (EventDispatcher) { } }; + /** + * Hide siblings of element from assistive technology + * + * @param {HTMLElement} element + * @returns {Array} The previous hidden state of all siblings + */ + var hideSiblings = function (element) { + var hiddenSiblings = []; + var siblings = element.parentNode.children; + var i; + for (i = 0; i < siblings.length; i += 1) { + // Preserve hidden state + hiddenSiblings[i] = siblings[i].getAttribute('aria-hidden') ? + true : false; + + if (siblings[i] !== element) { + siblings[i].setAttribute('aria-hidden', true); + } + } + return hiddenSiblings; + }; + + /** + * Restores assistive technology state of element's siblings + * + * @param {HTMLElement} element + * @param {Array} hiddenSiblings Hidden state of all siblings + */ + var restoreSiblings = function (element, hiddenSiblings) { + var siblings = element.parentNode.children; + var i; + for (i = 0; i < siblings.length; i += 1) { + if (siblings[i] !== element && !hiddenSiblings[i]) { + siblings[i].removeAttribute('aria-hidden'); + } + } + }; + /** * Start capturing focus of parent and send it to dialog */ @@ -194,9 +239,26 @@ H5P.ConfirmationDialog = (function (EventDispatcher) { * Clean up event listener for capturing focus */ var stopCapturingFocus = function () { + focusPredator.removeAttribute('aria-hidden'); focusPredator.removeEventListener('focus', captureFocus, true); }; + /** + * Hide siblings in underlay from assistive technologies + */ + var disableUnderlay = function () { + wrapperSiblingsHidden = hideSiblings(wrapperElement); + popupSiblingsHidden = hideSiblings(popupBackground); + }; + + /** + * Restore state of underlay for assistive technologies + */ + var restoreUnderlay = function () { + restoreSiblings(wrapperElement, wrapperSiblingsHidden); + restoreSiblings(popupBackground, popupSiblingsHidden); + }; + /** * Fit popup to container. Makes sure it doesn't overflow. * @params {number} [offsetTop] Offset of popup @@ -227,8 +289,11 @@ H5P.ConfirmationDialog = (function (EventDispatcher) { * @returns {H5P.ConfirmationDialog} */ this.show = function (offsetTop) { + // Capture focused item + previouslyFocused = document.activeElement; wrapperElement.appendChild(popupBackground); startCapturingFocus(); + disableUnderlay(); popupBackground.classList.remove('hidden'); fitToContainer(offsetTop); setTimeout(function () { @@ -260,9 +325,13 @@ H5P.ConfirmationDialog = (function (EventDispatcher) { this.hide = function () { popupBackground.classList.add('hiding'); popup.classList.add('hidden'); + + // Restore focus + stopCapturingFocus(); + previouslyFocused.focus(); + restoreUnderlay(); setTimeout(function () { popupBackground.classList.add('hidden'); - stopCapturingFocus(); wrapperElement.removeChild(popupBackground); }, 100);