')
.append(Y.Node.create('
')
.append(Y.Node.create(''))
@@ -364,6 +364,9 @@ Y.extend(DIALOGUE, Y.Panel, {
this.lockScroll.enableScrollLock(this.shouldResizeFullscreen());
}
+ // 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);
@@ -391,6 +394,9 @@ Y.extend(DIALOGUE, Y.Panel, {
}
}
+ // Hide this dialogue from screen readers.
+ this.setAccessibilityHidden();
+
// Unlock scroll if the plugin is present.
if (this.lockScroll) {
this.lockScroll.disableScrollLock();
@@ -435,6 +441,75 @@ Y.extend(DIALOGUE, Y.Panel, {
} 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() {
+ var thisDialogue = this;
+ // Get the element that contains this dialogue because we need it
+ // to filter out from the document.body child elements.
+ var container = thisDialogue.get(BASE);
+ // Keep a record of any elements we change so that they can be reverted later.
+ thisDialogue.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.
+ thisDialogue.hiddenSiblings.push({
+ oldValue: hidden,
+ node: node
+ });
+
+ // Hide this node from screen readers.
+ node.set('aria-hidden', 'true');
+ }
+ }
+ });
+
+ // 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(sibling) {
+ // If the element didn't previously have an aria-hidden attribute
+ // then we can just remove the one we set.
+ if (sibling.oldValue === null) {
+ sibling.node.removeAttribute('aria-hidden');
+ } else {
+ // Otherwise set it back to the old value (which will be false).
+ sibling.node.set('aria-hidden', sibling.oldValue);
+ }
+ });
+
+ // Clear the cache. No longer need to store these.
+ this.hiddenSiblings = [];
}
}, {
NAME : DIALOGUE_NAME,
diff --git a/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-min.js b/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-min.js
index 32433e4479b..08f2f65f942 100644
--- a/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-min.js
+++ b/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-min.js
@@ -1,2 +1,2 @@
-YUI.add("moodle-core-notification-dialogue",function(e,t){var n,r,i,s,o,u,a;n="moodle-dialogue",r="notificationBase",i="yesLabel",s="noLabel",o="title",u="question",a={BASE:"moodle-dialogue-base",WRAP:"moodle-dialogue-wrap",HEADER:"moodle-dialogue-hd",BODY:"moodle-dialogue-bd",CONTENT:"moodle-dialogue-content",FOOTER:"moodle-dialogue-ft",HIDDEN:"hidden",LIGHTBOX:"moodle-dialogue-lightbox"},M.core=M.core||{};var f="Moodle dialogue",l,c=n+"-fullscreen",h=n+"-hidden",p=" [role=dialog]",d="[role=menubar]",v=".",m="moodle-has-zindex",g='input:not([type="hidden"]), a[href], button, textarea, select, [tabindex]';l=function(t){var n=e.clone(t);n.COUNT=e.stamp(this);var r="moodle-dialogue-"+n.COUNT;n.notificationBase=e.Node.create('
').append(e.Node.create('
').append(e.Node.create('')).append(e.Node.create('
')).append(e.Node.create(''))),e.one(document.body).append(n.notificationBase),n.additionalBaseClass&&n.notificationBase.addClass(n.additionalBaseClass),n.srcNode="#"+r,n.closeButton===!1?n.buttons=null:n.buttons=[{section:e.WidgetStdMod.HEADER,classNames:"closebutton",action:function(){this.hide()}}],l.superclass.constructor.apply(this,[n]),n.closeButton!==!1&&this.get("buttons").header[0].setAttribute("title",this.get("closeButtonTitle"))},e.extend(l,e.Panel,{_resizeevent:null,_orientationevent:null,_calculatedzindex:!1,_originalPosition:null,initializer:function(){var t;this.get("render")&&this.render(),this.after("visibleChange",this.visibilityChanged,this),this.get("center")&&this.centerDialogue(),this.get("modal")&&this.plug(e.M.core.LockScroll),t=this.get("boundingBox"),t.addClass(m),e.Array.each(this.get("extraClasses"),t.addClass,t),this.get("visible")&&this.applyZIndex(),this.on("maskShow",this.applyZIndex),this.on("maskShow",function(){var t=e.one(e.config.win),n=this.get("boundingBox");this.get("center")||(this._originalPosition=n.getXY()),n.getStyle("position")!=="fixed"&&n.setStyles({top:t.get("scrollTop"),left:t.get("scrollLeft")})},this),this.after("destroyedChange",function(){this.get(r).remove(!0)},this)},applyZIndex:function(){var t=1,n=1,r=this.get("boundingBox"),i=this.get("maskNode"),s=this.get("zIndex");s!==0&&!this._calculatedzindex?r.setStyle("zIndex",s):(e.all(p+", "+d+", "+v+m).each(function(e){var n=this.findZIndex(e);n>t&&(t=n)},this),n=(t+1).toString(),r.setStyle("zIndex",n),this.set("zIndex",n),this.get("modal")&&(i.setStyle("zIndex",n),e.UA.ie&&e.UA.compareVersions(e.UA.ie,9)<0&&setTimeout(function(){i.setStyle("position","static"),setTimeout(function(){i.setStyle("position","fixed")},0)},0)),this._calculatedzindex=!0)},findZIndex:function(e){var t=e.getStyle("zIndex")||e.ancestor().getStyle("zIndex");return t?parseInt(t,10):0},visibilityChanged:function(t){var n,r;t.attrName==="visible"&&(this.get("maskNode").addClass(a.LIGHTBOX),t.prevVal&&!t.newVal&&(r=this.get("boundingBox"),this._resizeevent&&(this._resizeevent.detach(),this._resizeevent=null),this._orientationevent&&(this._orientationevent.detach(),this._orientationevent=null),r.detach("key",this.keyDelegation)),!t.prevVal&&t.newVal&&(this.applyZIndex(),this.makeResponsive(),this.shouldResizeFullscreen()||this.get("draggable")&&(n="#"+this.get("id")+" ."+a.HEADER,this.plug(e.Plugin.Drag,{handles:[n]}),e.one(n).setStyle("cursor","move")),this.keyDelegation()),this.get("center")&&!t.prevVal&&t.newVal&&this.centerDialogue())},makeResponsive:function(){var e=this.get("boundingBox");this.shouldResizeFullscreen()?(e.addClass(c),e.setStyles({left:null,top:null,width:null,height:null,right:null,bottom:null})):this.get("responsive")&&e.removeClass(c).setStyles({width:this.get("width"),height:this.get("height")})},centerDialogue:function(){var t=this.get("boundingBox"),n=t.hasClass(h),r,i;if(this.shouldResizeFullscreen())return;n&&t.setStyle("top","-1000px").removeClass(h),r=Math.max(Math.round((t.get("winWidth")-t.get("offsetWidth"))/2),15),i=Math.max(Math.round((t.get("winHeight")-t.get("offsetHeight"))/2),15)+e.one(window).get("scrollTop"),t.setStyles({left:r,top:i}),n&&t.addClass(h),this.makeResponsive()},shouldResizeFullscreen:function(){return window===window.parent&&this.get("responsive")&&Math.floor(e.one(document.body).get("winWidth"))
').append(e.Node.create('').append(e.Node.create('')).append(e.Node.create('')).append(e.Node.create(''))),e.one(document.body).append(n.notificationBase),n.additionalBaseClass&&n.notificationBase.addClass(n.additionalBaseClass),n.srcNode="#"+r,n.closeButton===!1?n.buttons=null:n.buttons=[{section:e.WidgetStdMod.HEADER,classNames:"closebutton",action:function(){this.hide()}}],l.superclass.constructor.apply(this,[n]),n.closeButton!==!1&&this.get("buttons").header[0].setAttribute("title",this.get("closeButtonTitle"))},e.extend(l,e.Panel,{_resizeevent:null,_orientationevent:null,_calculatedzindex:!1,_originalPosition:null,initializer:function(){var t;this.get("render")&&this.render(),this.after("visibleChange",this.visibilityChanged,this),this.get("center")&&this.centerDialogue(),this.get("modal")&&this.plug(e.M.core.LockScroll),t=this.get("boundingBox"),t.addClass(m),e.Array.each(this.get("extraClasses"),t.addClass,t),this.get("visible")&&this.applyZIndex(),this.on("maskShow",this.applyZIndex),this.on("maskShow",function(){var t=e.one(e.config.win),n=this.get("boundingBox");this.get("center")||(this._originalPosition=n.getXY()),n.getStyle("position")!=="fixed"&&n.setStyles({top:t.get("scrollTop"),left:t.get("scrollLeft")})},this),this.after("destroyedChange",function(){this.get(r).remove(!0)},this)},applyZIndex:function(){var t=1,n=1,r=this.get("boundingBox"),i=this.get("maskNode"),s=this.get("zIndex");s!==0&&!this._calculatedzindex?r.setStyle("zIndex",s):(e.all(p+", "+d+", "+v+m).each(function(e){var n=this.findZIndex(e);n>t&&(t=n)},this),n=(t+1).toString(),r.setStyle("zIndex",n),this.set("zIndex",n),this.get("modal")&&(i.setStyle("zIndex",n),e.UA.ie&&e.UA.compareVersions(e.UA.ie,9)<0&&setTimeout(function(){i.setStyle("position","static"),setTimeout(function(){i.setStyle("position","fixed")},0)},0)),this._calculatedzindex=!0)},findZIndex:function(e){var t=e.getStyle("zIndex")||e.ancestor().getStyle("zIndex");return t?parseInt(t,10):0},visibilityChanged:function(t){var n,r;t.attrName==="visible"&&(this.get("maskNode").addClass(a.LIGHTBOX),t.prevVal&&!t.newVal&&(r=this.get("boundingBox"),this._resizeevent&&(this._resizeevent.detach(),this._resizeevent=null),this._orientationevent&&(this._orientationevent.detach(),this._orientationevent=null),r.detach("key",this.keyDelegation)),!t.prevVal&&t.newVal&&(this.applyZIndex(),this.makeResponsive(),this.shouldResizeFullscreen()||this.get("draggable")&&(n="#"+this.get("id")+" ."+a.HEADER,this.plug(e.Plugin.Drag,{handles:[n]}),e.one(n).setStyle("cursor","move")),this.keyDelegation()),this.get("center")&&!t.prevVal&&t.newVal&&this.centerDialogue())},makeResponsive:function(){var e=this.get("boundingBox");this.shouldResizeFullscreen()?(e.addClass(c),e.setStyles({left:null,top:null,width:null,height:null,right:null,bottom:null})):this.get("responsive")&&e.removeClass(c).setStyles({width:this.get("width"),height:this.get("height")})},centerDialogue:function(){var t=this.get("boundingBox"),n=t.hasClass(h),r,i;if(this.shouldResizeFullscreen())return;n&&t.setStyle("top","-1000px").removeClass(h),r=Math.max(Math.round((t.get("winWidth")-t.get("offsetWidth"))/2),15),i=Math.max(Math.round((t.get("winHeight")-t.get("offsetHeight"))/2),15)+e.one(window).get("scrollTop"),t.setStyles({left:r,top:i}),n&&t.addClass(h),this.makeResponsive()},shouldResizeFullscreen:function(){return window===window.parent&&this.get("responsive")&&Math.floor(e.one(document.body).get("winWidth"))')
+ Y.Node.create('')
.append(Y.Node.create('
')
.append(Y.Node.create(''))
@@ -364,6 +364,9 @@ Y.extend(DIALOGUE, Y.Panel, {
this.lockScroll.enableScrollLock(this.shouldResizeFullscreen());
}
+ // 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);
@@ -391,6 +394,9 @@ Y.extend(DIALOGUE, Y.Panel, {
}
}
+ // Hide this dialogue from screen readers.
+ this.setAccessibilityHidden();
+
// Unlock scroll if the plugin is present.
if (this.lockScroll) {
this.lockScroll.disableScrollLock();
@@ -435,6 +441,75 @@ Y.extend(DIALOGUE, Y.Panel, {
} 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() {
+ var thisDialogue = this;
+ // Get the element that contains this dialogue because we need it
+ // to filter out from the document.body child elements.
+ var container = thisDialogue.get(BASE);
+ // Keep a record of any elements we change so that they can be reverted later.
+ thisDialogue.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.
+ thisDialogue.hiddenSiblings.push({
+ oldValue: hidden,
+ node: node
+ });
+
+ // Hide this node from screen readers.
+ node.set('aria-hidden', 'true');
+ }
+ }
+ });
+
+ // 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(sibling) {
+ // If the element didn't previously have an aria-hidden attribute
+ // then we can just remove the one we set.
+ if (sibling.oldValue === null) {
+ sibling.node.removeAttribute('aria-hidden');
+ } else {
+ // Otherwise set it back to the old value (which will be false).
+ sibling.node.set('aria-hidden', sibling.oldValue);
+ }
+ });
+
+ // Clear the cache. No longer need to store these.
+ this.hiddenSiblings = [];
}
}, {
NAME : DIALOGUE_NAME,
diff --git a/lib/yui/src/notification/js/dialogue.js b/lib/yui/src/notification/js/dialogue.js
index 0ff4f8bb545..908457d14d6 100644
--- a/lib/yui/src/notification/js/dialogue.js
+++ b/lib/yui/src/notification/js/dialogue.js
@@ -28,7 +28,7 @@ DIALOGUE = function(c) {
config.COUNT = Y.stamp(this);
var id = 'moodle-dialogue-' + config.COUNT;
config.notificationBase =
- Y.Node.create('
')
+ Y.Node.create('
')
.append(Y.Node.create('
')
.append(Y.Node.create(''))
@@ -335,6 +335,9 @@ Y.extend(DIALOGUE, Y.Panel, {
this.lockScroll.enableScrollLock(this.shouldResizeFullscreen());
}
+ // 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);
@@ -362,6 +365,9 @@ Y.extend(DIALOGUE, Y.Panel, {
}
}
+ // Hide this dialogue from screen readers.
+ this.setAccessibilityHidden();
+
// Unlock scroll if the plugin is present.
if (this.lockScroll) {
this.lockScroll.disableScrollLock();
@@ -406,6 +412,75 @@ Y.extend(DIALOGUE, Y.Panel, {
} 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() {
+ var thisDialogue = this;
+ // Get the element that contains this dialogue because we need it
+ // to filter out from the document.body child elements.
+ var container = thisDialogue.get(BASE);
+ // Keep a record of any elements we change so that they can be reverted later.
+ thisDialogue.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.
+ thisDialogue.hiddenSiblings.push({
+ oldValue: hidden,
+ node: node
+ });
+
+ // Hide this node from screen readers.
+ node.set('aria-hidden', 'true');
+ }
+ }
+ });
+
+ // 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(sibling) {
+ // If the element didn't previously have an aria-hidden attribute
+ // then we can just remove the one we set.
+ if (sibling.oldValue === null) {
+ sibling.node.removeAttribute('aria-hidden');
+ } else {
+ // Otherwise set it back to the old value (which will be false).
+ sibling.node.set('aria-hidden', sibling.oldValue);
+ }
+ });
+
+ // Clear the cache. No longer need to store these.
+ this.hiddenSiblings = [];
}
}, {
NAME : DIALOGUE_NAME,