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',
);