Skip to content

Commit

Permalink
Disable keyboard accessibility on confirmation modal background eleme…
Browse files Browse the repository at this point in the history
…nts (SAP#2672)
  • Loading branch information
ndricimrr authored Apr 28, 2022
1 parent cf26d81 commit 46a1532
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 2 deletions.
10 changes: 8 additions & 2 deletions core/src/ConfirmationModal.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<script>
import { createEventDispatcher, onMount } from 'svelte';
import { createEventDispatcher, onMount, onDestroy } from 'svelte';
import { LuigiI18N } from './core-api';
import { KEYCODE_ESC } from './utilities/keycode.js';
import { EscapingHelpers, NavigationHelpers } from './utilities/helpers';
import { EscapingHelpers, NavigationHelpers, IframeHelpers } from './utilities/helpers';
const dispatch = createEventDispatcher();
Expand All @@ -15,6 +15,10 @@
success: 'message-success',
};
onDestroy(()=> {
IframeHelpers.enableA11YKeyboardBackdropExceptClassName('.fd-message-box-docs-static');
})
onMount(() => {
const defaultSettings = {
icon: iconMapping[settings.type],
Expand All @@ -29,6 +33,8 @@
};
settings = Object.assign(defaultSettings, settings);
IframeHelpers.disableA11YKeyboardExceptClassName('.fd-message-box-docs-static');
const focusedButton = settings.buttonConfirm
? 'confirm-button'
: 'dismiss-button';
Expand Down
37 changes: 37 additions & 0 deletions core/src/utilities/helpers/iframe-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,43 @@ class IframeHelpersClass {
});
}

/**
* Sets tabindex for all elements to -1, except for one element and all its children which needs the focus.
* Setting tabindex to a negative value removes keyboard acessibility from the specified elements.
* @param {string} targetElementClassName the class name/s of the element to be excluded
*/
disableA11YKeyboardExceptClassName(targetElementClassName) {
const nodeList = document.querySelectorAll('*');
[...nodeList].forEach(element => {
const isNotAChildOfTargetElement = !element.closest(targetElementClassName);
const prevTabIndex = element.getAttribute('tabindex');
// save tabIndex in case one already exists
if ((prevTabIndex || prevTabIndex === 0) && isNotAChildOfTargetElement) {
element.setAttribute('oldtab', prevTabIndex);
}
// set tabindex to negative only if the current element is not a descendant of element with class 'targetElementClassName'
isNotAChildOfTargetElement ? element.setAttribute('tabindex', '-1') : '';
});
}

/**
* Resets tabindex value to previous value if it exists, or remove altogether if not.
* Applies to all elements except for the target element which we do not touch
*/
enableA11YKeyboardBackdropExceptClassName(targetElementClassName) {
const nodeList = document.querySelectorAll('*');
[...nodeList].forEach(element => {
const restoreVal = element.getAttribute('oldtab');
const isNotAChildOfTargetElement = !element.closest(targetElementClassName);
if (restoreVal) {
element.setAttribute('tabindex', restoreVal);
element.removeAttribute('oldtab');
} else {
isNotAChildOfTargetElement ? element.removeAttribute('tabindex') : '';
}
});
}

applyCoreStateData(data) {
return {
...data,
Expand Down
109 changes: 109 additions & 0 deletions core/test/utilities/helpers/iframe-helpers.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,4 +344,113 @@ describe('Iframe-helpers', () => {
assert.deepEqual(IframeHelpers.applyCoreStateData(undefined), expected);
});
});

describe('disable/enable keyboard accessibility on background elements', () => {
/**
* Ths function produces this html containing 6 DOM elements inside a body tag
* <html>
* <head>
* <title>Mocked DOM</title>
* </head>
* <body>
* <div>
* <span class="outsideModal">I am some text</span>
* <span tabindex="0" class="oldTabIndexOutsideModal">I am a text span with existing tabindex value</span>
* <div class="modalElement">
* <button>Click me</button>
* <button class="oldTabIndexInModal" tabindex="1">Click that</button>
* </div>
* </div>
* </body>
* </html>
*
* @returns mocked data
*/
const getMockedDocument = () => {
let doc = document.implementation.createHTMLDocument('Mocked DOM');

const divParent = doc.createElement('div');
divParent.className = 'divParent';
doc.body.appendChild(divParent);

const spanChild = doc.createElement('span');
spanChild.textContent = 'I am some text';
spanChild.className = 'spanChild';
divParent.appendChild(spanChild);

const spanChild2 = doc.createElement('span');
spanChild2.textContent = 'I am a text span with existing tabindex value';
spanChild2.setAttribute('tabindex', '0');
spanChild2.className = 'oldTabIndexOutsideModal';

divParent.appendChild(spanChild2);
divParent.appendChild(spanChild2);

const divChild = doc.createElement('div');
divChild.className = 'modalElement';

const childButton1 = doc.createElement('button');
childButton1.textContent = 'Click me';
childButton1.className = 'childButton1';

const childButton2 = doc.createElement('button');
childButton2.textContent = 'Click that';
childButton2.className = 'oldTabIndexInModal';
childButton2.setAttribute('tabindex', '1');

divChild.appendChild(childButton1);
divChild.appendChild(childButton2);
divParent.appendChild(divChild);
return doc;
};

describe('disableA11YKeyboardExceptClassName', () => {
beforeEach(() => {
global.document = getMockedDocument();
});

it('saves old tabindex value properly', () => {
IframeHelpers.disableA11YKeyboardExceptClassName('.modalElement');
assert.equal(global.document.getElementsByClassName('oldTabIndexOutsideModal')[0].getAttribute('oldtab'), 0);
assert.isNull(global.document.getElementsByClassName('oldTabIndexInModal')[0].getAttribute('oldtab'));
});

it('set tabindex properly on all but specified classname element', () => {
IframeHelpers.disableA11YKeyboardExceptClassName('.modalElement');
assert.equal(global.document.getElementsByClassName('divParent')[0].getAttribute('tabindex'), -1);
assert.equal(global.document.getElementsByClassName('spanChild')[0].getAttribute('tabindex'), -1);
assert.equal(global.document.getElementsByClassName('oldTabIndexOutsideModal')[0].getAttribute('tabindex'), -1);
assert.isNull(global.document.getElementsByClassName('modalElement')[0].getAttribute('tabindex'));
assert.isNull(global.document.getElementsByClassName('childButton1')[0].getAttribute('tabindex'));
assert.equal(global.document.getElementsByClassName('oldTabIndexInModal')[0].getAttribute('tabindex'), 1);
});
});

describe('enableA11YKeyboardBackdrop', () => {
beforeEach(() => {
global.document = getMockedDocument();
IframeHelpers.disableA11YKeyboardExceptClassName('.modalElement');
});

it('check oldtab property properly removed', () => {
IframeHelpers.enableA11YKeyboardBackdropExceptClassName('.modalElement');
assert.isNull(global.document.getElementsByClassName('divParent')[0].getAttribute('oldtab'));
assert.isNull(global.document.getElementsByClassName('spanChild')[0].getAttribute('oldtab'));
assert.isNull(global.document.getElementsByClassName('oldTabIndexOutsideModal')[0].getAttribute('oldtab'));
assert.isNull(global.document.getElementsByClassName('modalElement')[0].getAttribute('oldtab'));
assert.isNull(global.document.getElementsByClassName('childButton1')[0].getAttribute('oldtab'));
assert.isNull(global.document.getElementsByClassName('oldTabIndexInModal')[0].getAttribute('oldtab'));
});

it('check oldtabindex value properly restored', () => {
IframeHelpers.enableA11YKeyboardBackdropExceptClassName('.modalElement');
assert.isNull(global.document.getElementsByClassName('divParent')[0].getAttribute('tabindex'));
assert.isNull(global.document.getElementsByClassName('spanChild')[0].getAttribute('tabindex'));
assert.equal(global.document.getElementsByClassName('oldTabIndexOutsideModal')[0].getAttribute('tabindex'), 0);
assert.isNull(global.document.getElementsByClassName('modalElement')[0].getAttribute('tabindex'));
assert.isNull(global.document.getElementsByClassName('childButton1')[0].getAttribute('tabindex'));
assert.equal(global.document.getElementsByClassName('oldTabIndexInModal')[0].getAttribute('tabindex'), 1);
});
});
});
});

0 comments on commit 46a1532

Please sign in to comment.