Commit 08db4b58 authored by Ryan Wyllie's avatar Ryan Wyllie
Browse files

MDL-35918 javascript: dialogues screen reader support

The YUI notification dialogues will now set the appropriate
aria attributes when they are shown/hidden to allow screen
readers to navigate them correctly.

When the dialogue is shown all background elements will be marked
as hidden while the dialogue is marked as visible to a screen reader.

When the dialogue is closed the modified elements will be reverted
to their previous state and the dialogue will be marked as hidden to
a screen reader.
parent e494078a
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
...@@ -96,6 +96,9 @@ Y.extend(DIALOGUE, Y.Panel, { ...@@ -96,6 +96,9 @@ Y.extend(DIALOGUE, Y.Panel, {
} }
if (this.get('modal')) { if (this.get('modal')) {
// If we're a modal then make sure our container is ARIA
// hidden by default. ARIA visibility is managed for modal dialogues.
this.get(BASE).set('aria-hidden', 'true');
this.plug(Y.M.core.LockScroll); this.plug(Y.M.core.LockScroll);
} }
...@@ -334,6 +337,13 @@ Y.extend(DIALOGUE, Y.Panel, { ...@@ -334,6 +337,13 @@ Y.extend(DIALOGUE, Y.Panel, {
this.lockScroll.enableScrollLock(this.shouldResizeFullscreen()); this.lockScroll.enableScrollLock(this.shouldResizeFullscreen());
} }
// Only do accessibility hiding for modals because the ARIA spec
// says that all ARIA dialogues should be modal.
if (this.get('modal')) {
// Make this dialogue visible to screen readers.
this.setAccessibilityVisible();
}
// Try and find a node to focus on using the focusOnShowSelector attribute. // Try and find a node to focus on using the focusOnShowSelector attribute.
if (focusSelector !== null) { if (focusSelector !== null) {
focusNode = this.get('boundingBox').one(focusSelector); focusNode = this.get('boundingBox').one(focusSelector);
...@@ -361,6 +371,11 @@ Y.extend(DIALOGUE, Y.Panel, { ...@@ -361,6 +371,11 @@ Y.extend(DIALOGUE, Y.Panel, {
} }
} }
if (this.get('modal')) {
// Hide this dialogue from screen readers.
this.setAccessibilityHidden();
}
// Unlock scroll if the plugin is present. // Unlock scroll if the plugin is present.
if (this.lockScroll) { if (this.lockScroll) {
this.lockScroll.disableScrollLock(); this.lockScroll.disableScrollLock();
...@@ -405,6 +420,73 @@ Y.extend(DIALOGUE, Y.Panel, { ...@@ -405,6 +420,73 @@ Y.extend(DIALOGUE, Y.Panel, {
} else if (target === firstitem && direction === 'backward') { // Tab+shift key. } else if (target === firstitem && direction === 'backward') { // Tab+shift key.
return lastitem.focus(); return lastitem.focus();
} }
},
/**
* Sets the appropriate aria attributes on this dialogue and the other
* elements in the DOM to ensure that screen readers are able to navigate
* the dialogue popup correctly.
*
* @method setAccessibilityVisible
*/
setAccessibilityVisible: function() {
// Get the element that contains this dialogue because we need it
// to filter out from the document.body child elements.
var container = this.get(BASE);
// Keep a record of any elements we change so that they can be reverted later.
this.hiddenSiblings = [];
// We need to get a list containing each sibling element and the shallowest
// non-ancestral nodes in the DOM. We can shortcut this a little by leveraging
// the fact that this dialogue is always appended to the document body therefore
// it's siblings are the shallowest non-ancestral nodes. If that changes then
// this code should also be updated.
Y.one(document.body).get('children').each(function(node) {
// Skip the element that contains us.
if (node !== container) {
var hidden = node.get('aria-hidden');
// If they are already hidden we can ignore them.
if (hidden !== 'true') {
// Save their current state.
node.setData('previous-aria-hidden', hidden);
this.hiddenSiblings.push(node);
// Hide this node from screen readers.
node.set('aria-hidden', 'true');
}
}
}, this);
// Make us visible to screen readers.
container.set('aria-hidden', 'false');
},
/**
* Restores the aria visibility on the DOM elements changed when displaying
* the dialogue popup and makes the dialogue aria hidden to allow screen
* readers to navigate the main page correctly when the dialogue is closed.
*
* @method setAccessibilityHidden
*/
setAccessibilityHidden: function() {
var container = this.get(BASE);
container.set('aria-hidden', 'true');
// Restore the sibling nodes back to their original values.
Y.Array.each(this.hiddenSiblings, function(node) {
var previousValue = node.getData('previous-aria-hidden');
// If the element didn't previously have an aria-hidden attribute
// then we can just remove the one we set.
if (previousValue === null) {
node.removeAttribute('aria-hidden');
} else {
// Otherwise set it back to the old value (which will be false).
node.set('aria-hidden', previousValue);
}
});
// Clear the cache. No longer need to store these.
this.hiddenSiblings = [];
} }
}, { }, {
NAME : DIALOGUE_NAME, NAME : DIALOGUE_NAME,
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment