}
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.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.
if (focusSelector !== null) {
focusNode = this.get('boundingBox').one(focusSelector);
}
}
+ if (this.get('modal')) {
+ // Hide this dialogue from screen readers.
+ this.setAccessibilityHidden();
+ }
+
// Unlock scroll if the plugin is present.
if (this.lockScroll) {
this.lockScroll.disableScrollLock();
} else if (target === firstitem && direction === 'backward') { // Tab+shift key.
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,