diff --git a/lib/yui/build/moodle-core-widget-focusafterclose/moodle-core-widget-focusafterclose-debug.js b/lib/yui/build/moodle-core-widget-focusafterclose/moodle-core-widget-focusafterclose-debug.js new file mode 100644 index 00000000000..39e057a2b3b --- /dev/null +++ b/lib/yui/build/moodle-core-widget-focusafterclose/moodle-core-widget-focusafterclose-debug.js @@ -0,0 +1,176 @@ +YUI.add('moodle-core-widget-focusafterclose', function (Y, NAME) { + +/** + * Provides support for focusing on different nodes after the Widget is + * hidden. + * + * If the focusOnPreviousTargetAfterHide attribute is true, then the module hooks + * into the show function for that Widget to try and determine which Node + * caused the Widget to be shown. + * + * Alternatively, the focusAfterHide attribute can be passed a Node. + * + * @module moodle-core-widget-focusafterhide + */ + +var CAN_RECEIVE_FOCUS_SELECTOR = 'input:not([type="hidden"]), a[href], button, textarea, select, [tabindex], [contenteditable="true"]'; + +/** + * Provides support for focusing on different nodes after the Widget is + * hidden. + * + * @class M.core.WidgetFocusAfterHide + */ +function WidgetFocusAfterHide() { + Y.after(this._bindUIFocusAfterHide, this, 'bindUI'); + if (this.get('rendered')) { + this._bindUIFocusAfterHide(); + } +} + +WidgetFocusAfterHide.ATTRS = { + /** + * Whether to focus on the target that caused the Widget to be shown. + * + * If this is true, and a valid Node is found, any Node specified to focusAfterHide + * will be ignored. + * + * @attribute focusOnPreviousTargetAfterHide + * @default false + * @type boolean + */ + focusOnPreviousTargetAfterHide: { + value: false + }, + + /** + * The Node to focus on after hiding the Widget. + * + * Note: If focusOnPreviousTargetAfterHide is true, and a valid Node is found, then this + * value will be ignored. If it is true and not found, then this value will be used as + * a fallback. + * + * @attribute focusAfterHide + * @default null + * @type Node + */ + focusAfterHide: { + value: null, + type: Y.Node + } +}; + +WidgetFocusAfterHide.prototype = { + /** + * The list of Event Handles which we should cancel when the dialogue is destroyed. + * + * @property uiHandleFocusAfterHide + * @type array + * @protected + */ + _uiHandlesFocusAfterHide: [], + + /** + * A reference to the real show method which is being overwritten. + * + * @property _showFocusAfterHide + * @type function + * @default null + * @protected + */ + _showFocusAfterHide: null, + + /** + * A reference to the detected previous target. + * + * @property _previousTargetFocusAfterHide + * @type function + * @default null + * @protected + */ + _previousTargetFocusAfterHide: null, + + initializer: function() { + + if (this.get('focusOnPreviousTargetAfterHide') && this.show) { + // Overwrite the parent method so that we can get the focused + // target. + this._showFocusAfterHide = this.show; + this.show = function(e) { + this._showFocusAfterHide.apply(this, arguments); + + // We use a property rather than overriding the focusAfterHide parameter in + // case the target cannot be found at hide time. + this._previousTargetFocusAfterHide = null; + if (e && e.currentTarget) { + Y.log("Determined a Node which caused the Widget to be shown", + 'debug', 'moodle-core-widget-focusafterhide'); + this._previousTargetFocusAfterHide = e.currentTarget; + } + }; + } + }, + + destructor: function() { + new Y.EventHandle(this.uiHandleFocusAfterHide).detach(); + }, + + /** + * Set up the event handling required for this module to work. + * + * @method _bindUIFocusAfterHide + * @private + */ + _bindUIFocusAfterHide: function() { + // Detach the old handles first. + new Y.EventHandle(this.uiHandleFocusAfterHide).detach(); + this.uiHandleFocusAfterHide = [ + this.after('visibleChange', this._afterHostVisibleChangeFocusAfterHide) + ]; + }, + + /** + * Handle the change in UI visibility. + * + * This method changes the focus after the hide has taken place. + * + * @method _afterHostVisibleChangeFocusAfterHide + * @private + */ + _afterHostVisibleChangeFocusAfterHide: function() { + if (!this.get('visible')) { + if (this._attemptFocus(this._previousTargetFocusAfterHide)) { + Y.log("Focusing on the target automatically determined when the Widget was opened", + 'debug', 'moodle-core-widget-focusafterhide'); + + } else if (this._attemptFocus(this.get('focusAfterHide'))) { + // Fall back to the focusAfterHide value if one was specified. + Y.log("Focusing on the target provided to focusAfterHide", + 'debug', 'moodle-core-widget-focusafterhide'); + + } else { + Y.log("No valid focus target found - not returning focus.", + 'debug', 'moodle-core-widget-focusafterhide'); + + } + } + }, + + _attemptFocus: function(node) { + var focusTarget = Y.one(node); + if (focusTarget) { + focusTarget = focusTarget.ancestor(CAN_RECEIVE_FOCUS_SELECTOR, true); + if (focusTarget) { + focusTarget.focus(); + return true; + } + } + return false; + } +}; + +var NS = Y.namespace('M.core'); +NS.WidgetFocusAfterHide = WidgetFocusAfterHide; + + +}, '@VERSION@', {"requires": ["base-build", "widget"]}); diff --git a/lib/yui/build/moodle-core-widget-focusafterclose/moodle-core-widget-focusafterclose-min.js b/lib/yui/build/moodle-core-widget-focusafterclose/moodle-core-widget-focusafterclose-min.js new file mode 100644 index 00000000000..ee027b030c7 --- /dev/null +++ b/lib/yui/build/moodle-core-widget-focusafterclose/moodle-core-widget-focusafterclose-min.js @@ -0,0 +1 @@ +YUI.add("moodle-core-widget-focusafterclose",function(e,t){function r(){e.after(this._bindUIFocusAfterHide,this,"bindUI"),this.get("rendered")&&this._bindUIFocusAfterHide()}var n='input:not([type="hidden"]), a[href], button, textarea, select, [tabindex], [contenteditable="true"]';r.ATTRS={focusOnPreviousTargetAfterHide:{value:!1},focusAfterHide:{value:null,type:e.Node}},r.prototype={_uiHandlesFocusAfterHide:[],_showFocusAfterHide:null,_previousTargetFocusAfterHide:null,initializer:function(){this.get("focusOnPreviousTargetAfterHide")&&this.show&&(this._showFocusAfterHide=this.show,this.show=function(e){this._showFocusAfterHide.apply(this,arguments),this._previousTargetFocusAfterHide=null,e&&e.currentTarget&&(this._previousTargetFocusAfterHide=e.currentTarget)})},destructor:function(){(new e.EventHandle(this.uiHandleFocusAfterHide)).detach()},_bindUIFocusAfterHide:function(){(new e.EventHandle(this.uiHandleFocusAfterHide)).detach(),this.uiHandleFocusAfterHide=[this.after("visibleChange",this._afterHostVisibleChangeFocusAfterHide)]},_afterHostVisibleChangeFocusAfterHide:function(){this.get("visible")||this._attemptFocus(this._previousTargetFocusAfterHide)||this._attemptFocus(this.get("focusAfterHide"))},_attemptFocus:function(t){var r=e.one(t);if(r){r=r.ancestor(n,!0);if(r)return r.focus(),!0}return!1}};var i=e.namespace("M.core");i.WidgetFocusAfterHide=r},"@VERSION@",{requires:["base-build","widget"]}); diff --git a/lib/yui/build/moodle-core-widget-focusafterclose/moodle-core-widget-focusafterclose.js b/lib/yui/build/moodle-core-widget-focusafterclose/moodle-core-widget-focusafterclose.js new file mode 100644 index 00000000000..b4fd9b5eda6 --- /dev/null +++ b/lib/yui/build/moodle-core-widget-focusafterclose/moodle-core-widget-focusafterclose.js @@ -0,0 +1,168 @@ +YUI.add('moodle-core-widget-focusafterclose', function (Y, NAME) { + +/** + * Provides support for focusing on different nodes after the Widget is + * hidden. + * + * If the focusOnPreviousTargetAfterHide attribute is true, then the module hooks + * into the show function for that Widget to try and determine which Node + * caused the Widget to be shown. + * + * Alternatively, the focusAfterHide attribute can be passed a Node. + * + * @module moodle-core-widget-focusafterhide + */ + +var CAN_RECEIVE_FOCUS_SELECTOR = 'input:not([type="hidden"]), a[href], button, textarea, select, [tabindex], [contenteditable="true"]'; + +/** + * Provides support for focusing on different nodes after the Widget is + * hidden. + * + * @class M.core.WidgetFocusAfterHide + */ +function WidgetFocusAfterHide() { + Y.after(this._bindUIFocusAfterHide, this, 'bindUI'); + if (this.get('rendered')) { + this._bindUIFocusAfterHide(); + } +} + +WidgetFocusAfterHide.ATTRS = { + /** + * Whether to focus on the target that caused the Widget to be shown. + * + * If this is true, and a valid Node is found, any Node specified to focusAfterHide + * will be ignored. + * + * @attribute focusOnPreviousTargetAfterHide + * @default false + * @type boolean + */ + focusOnPreviousTargetAfterHide: { + value: false + }, + + /** + * The Node to focus on after hiding the Widget. + * + * Note: If focusOnPreviousTargetAfterHide is true, and a valid Node is found, then this + * value will be ignored. If it is true and not found, then this value will be used as + * a fallback. + * + * @attribute focusAfterHide + * @default null + * @type Node + */ + focusAfterHide: { + value: null, + type: Y.Node + } +}; + +WidgetFocusAfterHide.prototype = { + /** + * The list of Event Handles which we should cancel when the dialogue is destroyed. + * + * @property uiHandleFocusAfterHide + * @type array + * @protected + */ + _uiHandlesFocusAfterHide: [], + + /** + * A reference to the real show method which is being overwritten. + * + * @property _showFocusAfterHide + * @type function + * @default null + * @protected + */ + _showFocusAfterHide: null, + + /** + * A reference to the detected previous target. + * + * @property _previousTargetFocusAfterHide + * @type function + * @default null + * @protected + */ + _previousTargetFocusAfterHide: null, + + initializer: function() { + + if (this.get('focusOnPreviousTargetAfterHide') && this.show) { + // Overwrite the parent method so that we can get the focused + // target. + this._showFocusAfterHide = this.show; + this.show = function(e) { + this._showFocusAfterHide.apply(this, arguments); + + // We use a property rather than overriding the focusAfterHide parameter in + // case the target cannot be found at hide time. + this._previousTargetFocusAfterHide = null; + if (e && e.currentTarget) { + this._previousTargetFocusAfterHide = e.currentTarget; + } + }; + } + }, + + destructor: function() { + new Y.EventHandle(this.uiHandleFocusAfterHide).detach(); + }, + + /** + * Set up the event handling required for this module to work. + * + * @method _bindUIFocusAfterHide + * @private + */ + _bindUIFocusAfterHide: function() { + // Detach the old handles first. + new Y.EventHandle(this.uiHandleFocusAfterHide).detach(); + this.uiHandleFocusAfterHide = [ + this.after('visibleChange', this._afterHostVisibleChangeFocusAfterHide) + ]; + }, + + /** + * Handle the change in UI visibility. + * + * This method changes the focus after the hide has taken place. + * + * @method _afterHostVisibleChangeFocusAfterHide + * @private + */ + _afterHostVisibleChangeFocusAfterHide: function() { + if (!this.get('visible')) { + if (this._attemptFocus(this._previousTargetFocusAfterHide)) { + + } else if (this._attemptFocus(this.get('focusAfterHide'))) { + // Fall back to the focusAfterHide value if one was specified. + + } else { + + } + } + }, + + _attemptFocus: function(node) { + var focusTarget = Y.one(node); + if (focusTarget) { + focusTarget = focusTarget.ancestor(CAN_RECEIVE_FOCUS_SELECTOR, true); + if (focusTarget) { + focusTarget.focus(); + return true; + } + } + return false; + } +}; + +var NS = Y.namespace('M.core'); +NS.WidgetFocusAfterHide = WidgetFocusAfterHide; + + +}, '@VERSION@', {"requires": ["base-build", "widget"]}); diff --git a/lib/yui/src/widget-focusafterclose/build.json b/lib/yui/src/widget-focusafterclose/build.json new file mode 100644 index 00000000000..05b26aea422 --- /dev/null +++ b/lib/yui/src/widget-focusafterclose/build.json @@ -0,0 +1,10 @@ +{ + "name": "moodle-core-widget-focusafterclose", + "builds": { + "moodle-core-widget-focusafterclose": { + "jsfiles": [ + "focusafter.js" + ] + } + } +} diff --git a/lib/yui/src/widget-focusafterclose/js/focusafter.js b/lib/yui/src/widget-focusafterclose/js/focusafter.js new file mode 100644 index 00000000000..112503ef07f --- /dev/null +++ b/lib/yui/src/widget-focusafterclose/js/focusafter.js @@ -0,0 +1,171 @@ +/** + * Provides support for focusing on different nodes after the Widget is + * hidden. + * + * If the focusOnPreviousTargetAfterHide attribute is true, then the module hooks + * into the show function for that Widget to try and determine which Node + * caused the Widget to be shown. + * + * Alternatively, the focusAfterHide attribute can be passed a Node. + * + * @module moodle-core-widget-focusafterhide + */ + +var CAN_RECEIVE_FOCUS_SELECTOR = 'input:not([type="hidden"]), a[href], button, textarea, select, [tabindex], [contenteditable="true"]'; + +/** + * Provides support for focusing on different nodes after the Widget is + * hidden. + * + * @class M.core.WidgetFocusAfterHide + */ +function WidgetFocusAfterHide() { + Y.after(this._bindUIFocusAfterHide, this, 'bindUI'); + if (this.get('rendered')) { + this._bindUIFocusAfterHide(); + } +} + +WidgetFocusAfterHide.ATTRS = { + /** + * Whether to focus on the target that caused the Widget to be shown. + * + * If this is true, and a valid Node is found, any Node specified to focusAfterHide + * will be ignored. + * + * @attribute focusOnPreviousTargetAfterHide + * @default false + * @type boolean + */ + focusOnPreviousTargetAfterHide: { + value: false + }, + + /** + * The Node to focus on after hiding the Widget. + * + * Note: If focusOnPreviousTargetAfterHide is true, and a valid Node is found, then this + * value will be ignored. If it is true and not found, then this value will be used as + * a fallback. + * + * @attribute focusAfterHide + * @default null + * @type Node + */ + focusAfterHide: { + value: null, + type: Y.Node + } +}; + +WidgetFocusAfterHide.prototype = { + /** + * The list of Event Handles which we should cancel when the dialogue is destroyed. + * + * @property uiHandleFocusAfterHide + * @type array + * @protected + */ + _uiHandlesFocusAfterHide: [], + + /** + * A reference to the real show method which is being overwritten. + * + * @property _showFocusAfterHide + * @type function + * @default null + * @protected + */ + _showFocusAfterHide: null, + + /** + * A reference to the detected previous target. + * + * @property _previousTargetFocusAfterHide + * @type function + * @default null + * @protected + */ + _previousTargetFocusAfterHide: null, + + initializer: function() { + + if (this.get('focusOnPreviousTargetAfterHide') && this.show) { + // Overwrite the parent method so that we can get the focused + // target. + this._showFocusAfterHide = this.show; + this.show = function(e) { + this._showFocusAfterHide.apply(this, arguments); + + // We use a property rather than overriding the focusAfterHide parameter in + // case the target cannot be found at hide time. + this._previousTargetFocusAfterHide = null; + if (e && e.currentTarget) { + Y.log("Determined a Node which caused the Widget to be shown", + 'debug', 'moodle-core-widget-focusafterhide'); + this._previousTargetFocusAfterHide = e.currentTarget; + } + }; + } + }, + + destructor: function() { + new Y.EventHandle(this.uiHandleFocusAfterHide).detach(); + }, + + /** + * Set up the event handling required for this module to work. + * + * @method _bindUIFocusAfterHide + * @private + */ + _bindUIFocusAfterHide: function() { + // Detach the old handles first. + new Y.EventHandle(this.uiHandleFocusAfterHide).detach(); + this.uiHandleFocusAfterHide = [ + this.after('visibleChange', this._afterHostVisibleChangeFocusAfterHide) + ]; + }, + + /** + * Handle the change in UI visibility. + * + * This method changes the focus after the hide has taken place. + * + * @method _afterHostVisibleChangeFocusAfterHide + * @private + */ + _afterHostVisibleChangeFocusAfterHide: function() { + if (!this.get('visible')) { + if (this._attemptFocus(this._previousTargetFocusAfterHide)) { + Y.log("Focusing on the target automatically determined when the Widget was opened", + 'debug', 'moodle-core-widget-focusafterhide'); + + } else if (this._attemptFocus(this.get('focusAfterHide'))) { + // Fall back to the focusAfterHide value if one was specified. + Y.log("Focusing on the target provided to focusAfterHide", + 'debug', 'moodle-core-widget-focusafterhide'); + + } else { + Y.log("No valid focus target found - not returning focus.", + 'debug', 'moodle-core-widget-focusafterhide'); + + } + } + }, + + _attemptFocus: function(node) { + var focusTarget = Y.one(node); + if (focusTarget) { + focusTarget = focusTarget.ancestor(CAN_RECEIVE_FOCUS_SELECTOR, true); + if (focusTarget) { + focusTarget.focus(); + return true; + } + } + return false; + } +}; + +var NS = Y.namespace('M.core'); +NS.WidgetFocusAfterHide = WidgetFocusAfterHide; diff --git a/lib/yui/src/widget-focusafterclose/meta/notification.json b/lib/yui/src/widget-focusafterclose/meta/notification.json new file mode 100644 index 00000000000..0a836b22db0 --- /dev/null +++ b/lib/yui/src/widget-focusafterclose/meta/notification.json @@ -0,0 +1,8 @@ +{ + "moodle-core-widget-focusafterclose": { + "requires": [ + "base-build", + "widget" + ] + } +} diff --git a/theme/yui_combo.php b/theme/yui_combo.php index 77f7c7ed199..ab1eeaba323 100644 --- a/theme/yui_combo.php +++ b/theme/yui_combo.php @@ -223,6 +223,7 @@ while (count($parts)) { $yuimodules = array( 'core/tooltip/tooltip', 'core/popuphelp/popuphelp', + 'core/widget-focusafterclose/widget-focusafterclose', 'core/dock/dock-loader', 'core/notification/notification-dialogue', );