MDL-42796 ActionMenu: Add additional keyboard event handling
Add additional handling for up/down/escape key presses and consolidate them with the existing tab/shift+tab handling.
This commit is contained in:
@@ -1099,24 +1099,20 @@ class core_renderer extends renderer_base {
|
||||
foreach ($menu->get_primary_actions($this) as $action) {
|
||||
if ($action instanceof renderable) {
|
||||
$content = $this->render($action);
|
||||
$role = 'presentation';
|
||||
} else {
|
||||
$content = $action;
|
||||
$role = 'menuitem';
|
||||
}
|
||||
$output .= html_writer::tag('li', $content, array('role' => $role));
|
||||
$output .= html_writer::tag('li', $content, array('role' => 'presentation'));
|
||||
}
|
||||
$output .= html_writer::end_tag('ul');
|
||||
$output .= html_writer::start_tag('ul', $menu->attributessecondary);
|
||||
foreach ($menu->get_secondary_actions() as $action) {
|
||||
if ($action instanceof renderable) {
|
||||
$content = $this->render($action);
|
||||
$role = 'presentation';
|
||||
} else {
|
||||
$content = $action;
|
||||
$role = 'menuitem';
|
||||
}
|
||||
$output .= html_writer::tag('li', $content, array('role' => $role));
|
||||
$output .= html_writer::tag('li', $content, array('role' => 'presentation'));
|
||||
}
|
||||
$output .= html_writer::end_tag('ul');
|
||||
$output .= html_writer::end_tag('div');
|
||||
|
||||
@@ -13,11 +13,21 @@ var BODY = Y.one(Y.config.doc.body),
|
||||
SELECTOR = {
|
||||
CAN_RECEIVE_FOCUS_SELECTOR: 'input:not([type="hidden"]), a[href], button, textarea, select, [tabindex]',
|
||||
MENU : '.moodle-actionmenu[data-enhance=moodle-core-actionmenu]',
|
||||
MENUBAR: '[role="menubar"]',
|
||||
MENUITEM: '[role="menuitem"]',
|
||||
MENUCONTENT : '.menu[data-rel=menu-content]',
|
||||
MENUCONTENTCHILD: 'li a',
|
||||
MENUCHILD: '.menu li a',
|
||||
TOGGLE : '.toggle-display',
|
||||
KEEPOPEN: '[data-keepopen="1"]'
|
||||
KEEPOPEN: '[data-keepopen="1"]',
|
||||
MENUBARITEMS: [
|
||||
'[role="menubar"] > [role="menuitem"]',
|
||||
'[role="menubar"] > [role="presentation"] > [role="menuitem"]'
|
||||
],
|
||||
MENUITEMS: [
|
||||
'> [role="menuitem"]',
|
||||
'> [role="presentation"] > [role="menuitem"]'
|
||||
]
|
||||
},
|
||||
ACTIONMENU,
|
||||
ALIGN = {
|
||||
@@ -72,6 +82,33 @@ ACTIONMENU.prototype = {
|
||||
*/
|
||||
menulink: null,
|
||||
|
||||
/**
|
||||
* The set of menu nodes.
|
||||
*
|
||||
* @property menuChildren
|
||||
* @type NodeList
|
||||
* @protected
|
||||
*/
|
||||
menuChildren: null,
|
||||
|
||||
/**
|
||||
* The first menu item.
|
||||
*
|
||||
* @property firstMenuChild
|
||||
* @type Node
|
||||
* @protected
|
||||
*/
|
||||
firstMenuChild: null,
|
||||
|
||||
/**
|
||||
* The last menu item.
|
||||
*
|
||||
* @property lastMenuChild
|
||||
* @type Node
|
||||
* @protected
|
||||
*/
|
||||
lastMenuChild: null,
|
||||
|
||||
/**
|
||||
* Called during the initialisation process of the object.
|
||||
* @method initializer
|
||||
@@ -79,8 +116,16 @@ ACTIONMENU.prototype = {
|
||||
initializer : function() {
|
||||
Y.log('Initialising the action menu manager', 'debug', ACTIONMENU.NAME);
|
||||
Y.all(SELECTOR.MENU).each(this.enhance, this);
|
||||
BODY.delegate('key', this.moveMenuItem, 'down:37,39', SELECTOR.MENUBARITEMS.join(','), this);
|
||||
|
||||
BODY.delegate('click', this.toggleMenu, SELECTOR.MENU + ' ' + SELECTOR.TOGGLE, this);
|
||||
BODY.delegate('key', this.toggleMenu, 'enter,space', SELECTOR.MENU + ' ' + SELECTOR.TOGGLE, this);
|
||||
BODY.delegate('key', this.showIfHidden, 'down:enter,38,40', SELECTOR.MENU + ' ' + SELECTOR.TOGGLE, this);
|
||||
|
||||
// Ensure that we toggle on menuitems when the spacebar is pressed.
|
||||
BODY.delegate('key', function(e) {
|
||||
e.currentTarget.simulate('click');
|
||||
e.preventDefault();
|
||||
}, 'down:32', SELECTOR.MENUBARITEMS.join(','));
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -106,6 +151,93 @@ ACTIONMENU.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle movement between menu items in a menubar.
|
||||
*
|
||||
* @method moveMenuItem
|
||||
* @param {EventFacade} e The event generating the move request
|
||||
* @chainable
|
||||
*/
|
||||
moveMenuItem: function(e) {
|
||||
var nextFocus,
|
||||
menuitem = e.target.ancestor(SELECTOR.MENUITEM, true);
|
||||
|
||||
if (e.keyCode === 37) {
|
||||
nextFocus = this.getMenuItem(menuitem, true);
|
||||
} else if (e.keyCode === 39) {
|
||||
nextFocus = this.getMenuItem(menuitem);
|
||||
}
|
||||
|
||||
if (nextFocus) {
|
||||
nextFocus.focus();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the next menuitem in a menubar.
|
||||
*
|
||||
* @method getMenuItem
|
||||
* @param {Node} currentItem The currently focused item in the menubar
|
||||
* @param {Boolean} [previous=false] Move backwards in the menubar instead of forwards
|
||||
* @return {Node|null} The next item, or null if none was found
|
||||
*/
|
||||
getMenuItem: function(currentItem, previous) {
|
||||
var menubar = currentItem.ancestor(SELECTOR.MENUBAR),
|
||||
menuitems;
|
||||
|
||||
if (!menubar) {
|
||||
return null;
|
||||
}
|
||||
|
||||
menuitems = menubar.all(SELECTOR.MENUITEMS.join(','));
|
||||
|
||||
if (!menuitems) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var childCount = menuitems.size();
|
||||
|
||||
if (childCount === 1) {
|
||||
// Only one item, exit now because we should already be on it.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Determine the next child.
|
||||
var index = 0,
|
||||
direction = 1,
|
||||
checkCount = 0;
|
||||
|
||||
// Work out the index of the currently selected item.
|
||||
for (index = 0; index < childCount; index++) {
|
||||
if (menuitems.item(index) === currentItem) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the menu item was found - otherwise return null.
|
||||
if (menuitems.item(index) !== currentItem) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Reverse the direction if we want the previous item.
|
||||
if (previous) {
|
||||
direction = -1;
|
||||
}
|
||||
|
||||
do {
|
||||
// Update the index in the direction of travel.
|
||||
index += direction;
|
||||
|
||||
next = menuitems.item(index);
|
||||
|
||||
// Check that we don't loop multiple times.
|
||||
checkCount++;
|
||||
} while (next && next.hasAttribute('hidden'));
|
||||
|
||||
return next;
|
||||
},
|
||||
|
||||
/**
|
||||
* Hides the menu if it is visible.
|
||||
* @method hideMenu
|
||||
@@ -134,6 +266,17 @@ ACTIONMENU.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
showIfHidden: function(e) {
|
||||
var menu = e.target.ancestor(SELECTOR.MENU),
|
||||
menuvisible = (menu.hasClass('show'));
|
||||
|
||||
if (!menuvisible) {
|
||||
e.preventDefault();
|
||||
this.showMenu(e, menu);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles the display of the menu.
|
||||
* @method toggleMenu
|
||||
@@ -142,9 +285,8 @@ ACTIONMENU.prototype = {
|
||||
toggleMenu : function(e) {
|
||||
var menu = e.target.ancestor(SELECTOR.MENU),
|
||||
menuvisible = (menu.hasClass('show'));
|
||||
// Stop all immediate propogation of the events we attach later will be
|
||||
// triggered and the dialogue immediately hidden again.
|
||||
e.halt(true);
|
||||
|
||||
e.preventDefault();
|
||||
this.hideMenu();
|
||||
if (menuvisible) {
|
||||
// The menu was visible and the user has clicked to toggle it again.
|
||||
@@ -154,35 +296,103 @@ ACTIONMENU.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Check current focus when moving around with the tab key.
|
||||
* This will ensure that when the etreme menu items are reached, the
|
||||
* menu is closed and the next DOM element is focused.
|
||||
* Handle keyboard events when the menu is open. We respond to:
|
||||
* * escape (exit)
|
||||
* * tab (move to next menu item)
|
||||
* * up/down (move to previous/next menu item)
|
||||
*
|
||||
* @method checkFocus
|
||||
* @method handleKeyboardEvent
|
||||
* @param {EventFacade} e The key event
|
||||
*/
|
||||
checkFocus: function(e) {
|
||||
var nodelist = this.dialogue.all(SELECTOR.MENUCHILD),
|
||||
firstNode,
|
||||
lastNode;
|
||||
handleKeyboardEvent: function(e) {
|
||||
var next;
|
||||
|
||||
if (nodelist) {
|
||||
firstNode = nodelist.item(0);
|
||||
lastNode = nodelist.pop();
|
||||
// Handle when the menu is still selected.
|
||||
if (e.currentTarget.ancestor(SELECTOR.TOGGLE, true)) {
|
||||
if ((e.keyCode === 40 || (e.keyCode === 9 && !e.shiftKey)) && this.firstMenuChild) {
|
||||
this.firstMenuChild.focus();
|
||||
e.preventDefault();
|
||||
} else if (e.keyCode === 38 && this.lastMenuChild) {
|
||||
this.lastMenuChild.focus();
|
||||
e.preventDefault();
|
||||
} else if (e.keyCode === 9 && e.shiftKey) {
|
||||
this.hideMenu();
|
||||
e.preventDefault();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
var menulink = this.menulink;
|
||||
if (e.target === firstNode && e.shiftKey) {
|
||||
if (e.keyCode === 27) {
|
||||
// The escape key was pressed so close the menu.
|
||||
this.hideMenu();
|
||||
e.preventDefault();
|
||||
} else if (e.target === lastNode && !e.shiftKey) {
|
||||
var next;
|
||||
if (this.hideMenu()) {
|
||||
next = menulink.next(SELECTOR.CAN_RECEIVE_FOCUS_SELECTOR);
|
||||
if (next) {
|
||||
next.focus();
|
||||
|
||||
} else if (e.keyCode === 32) {
|
||||
// The space bar was pressed. Trigger a click.
|
||||
e.preventDefault();
|
||||
e.currentTarget.simulate('click');
|
||||
} else if (e.keyCode === 9) {
|
||||
// The tab key was pressed. Tab moves forwards, Shift + Tab moves backwards through the menu options.
|
||||
// We only override the Shift + Tab on the first option, and Tab on the last option to change where the focus is moved to.
|
||||
if (e.target === this.firstMenuChild && e.shiftKey) {
|
||||
this.hideMenu();
|
||||
e.preventDefault();
|
||||
} else if (e.target === this.lastMenuChild && !e.shiftKey) {
|
||||
if (this.hideMenu()) {
|
||||
// Determine the next selector and focus on it.
|
||||
next = this.menulink.next(SELECTOR.CAN_RECEIVE_FOCUS_SELECTOR);
|
||||
if (next) {
|
||||
next.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if (e.keyCode === 38 || e.keyCode === 40) {
|
||||
// The up (38) or down (40) key was pushed.
|
||||
// On cursor moves we loops through the menu rather than exiting it as in the tab behaviour.
|
||||
var found = false,
|
||||
index = 0,
|
||||
direction = 1,
|
||||
checkCount = 0;
|
||||
|
||||
// Determine which menu item is currently selected.
|
||||
while (!found && index < this.menuChildren.size()) {
|
||||
if (this.menuChildren.item(index) === e.currentTarget) {
|
||||
found = true;
|
||||
} else {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
Y.log("Unable to find this menu item in the list of menu children", 'debug', 'moodle-core-actionmenu');
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.keyCode === 38) {
|
||||
// Moving up so reverse the direction.
|
||||
direction = -1;
|
||||
}
|
||||
|
||||
// Try to find the next
|
||||
do {
|
||||
index += direction;
|
||||
if (index < 0) {
|
||||
index = this.menuChildren.size() - 1;
|
||||
} else if (index >= this.menuChildren.size()) {
|
||||
// Handle wrapping.
|
||||
index = 0;
|
||||
}
|
||||
next = this.menuChildren.item(index);
|
||||
|
||||
// Add a counter to ensure we don't get stuck in a loop if there's only one visible menu item.
|
||||
checkCount++;
|
||||
} while (checkCount < this.menuChildren.size() && next !== e.currentTarget && next.hasClass('hidden'));
|
||||
|
||||
if (next) {
|
||||
next.focus();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -208,33 +418,37 @@ ACTIONMENU.prototype = {
|
||||
showMenu : function(e, menu) {
|
||||
Y.log('Displaying an action menu', 'debug', ACTIONMENU.NAME);
|
||||
var ownerselector = menu.getData('owner'),
|
||||
menucontent = menu.one(SELECTOR.MENUCONTENT),
|
||||
menuchild;
|
||||
menucontent = menu.one(SELECTOR.MENUCONTENT);
|
||||
this.owner = (ownerselector) ? menu.ancestor(ownerselector) : null;
|
||||
this.dialogue = menu;
|
||||
menu.addClass('show');
|
||||
if (this.owner) {
|
||||
this.owner.addClass(CSS.MENUSHOWN);
|
||||
this.menulink = this.owner.one(SELECTOR.TOGGLE);
|
||||
} else {
|
||||
this.menulink = e.target.ancestor(SELECTOR.TOGGLE, true);
|
||||
}
|
||||
this.constrain(menucontent.set('aria-hidden', false));
|
||||
|
||||
if (e.type && e.type === 'key') {
|
||||
menuchild = menucontent.one(SELECTOR.MENUCONTENTCHILD);
|
||||
if (menuchild) {
|
||||
menuchild.focus();
|
||||
}
|
||||
this.menuChildren = this.dialogue.all(SELECTOR.MENUCHILD);
|
||||
if (this.menuChildren) {
|
||||
this.firstMenuChild = this.menuChildren.item(0);
|
||||
this.lastMenuChild = this.menuChildren.item(this.menuChildren.size() - 1);
|
||||
|
||||
this.firstMenuChild.focus();
|
||||
}
|
||||
|
||||
// Close the menu if the user presses escape.
|
||||
this.events.push(BODY.on('key', this.hideMenu, 'esc', this));
|
||||
|
||||
// Close the menu if the user clicks outside the menu.
|
||||
this.events.push(BODY.on('click', this.hideIfOutside, this));
|
||||
|
||||
// Close the menu if the user focuses outside the menu.
|
||||
this.events.push(BODY.delegate('focus', this.hideIfOutside, '*', this));
|
||||
|
||||
// Check tabbing.
|
||||
this.events.push(menu.delegate('key', this.checkFocus, 'down:9', SELECTOR.MENUCHILD, this));
|
||||
// Check keyboard changes.
|
||||
this.events.push(menu.delegate('key', this.handleKeyboardEvent, 'down:9, 27, 38, 40, 32', SELECTOR.MENUCHILD + ', ' + SELECTOR.TOGGLE, this));
|
||||
|
||||
// Close the menu after a button was pushed.
|
||||
this.events.push(menu.delegate('click', function(e) {
|
||||
@@ -396,4 +610,4 @@ M.core.actionmenu.newDOMNode = function(node) {
|
||||
};
|
||||
|
||||
|
||||
}, '@VERSION@', {"requires": ["base", "event"]});
|
||||
}, '@VERSION@', {"requires": ["base", "event", "node-event-simulate"]});
|
||||
|
||||
File diff suppressed because one or more lines are too long
+247
-34
@@ -13,11 +13,21 @@ var BODY = Y.one(Y.config.doc.body),
|
||||
SELECTOR = {
|
||||
CAN_RECEIVE_FOCUS_SELECTOR: 'input:not([type="hidden"]), a[href], button, textarea, select, [tabindex]',
|
||||
MENU : '.moodle-actionmenu[data-enhance=moodle-core-actionmenu]',
|
||||
MENUBAR: '[role="menubar"]',
|
||||
MENUITEM: '[role="menuitem"]',
|
||||
MENUCONTENT : '.menu[data-rel=menu-content]',
|
||||
MENUCONTENTCHILD: 'li a',
|
||||
MENUCHILD: '.menu li a',
|
||||
TOGGLE : '.toggle-display',
|
||||
KEEPOPEN: '[data-keepopen="1"]'
|
||||
KEEPOPEN: '[data-keepopen="1"]',
|
||||
MENUBARITEMS: [
|
||||
'[role="menubar"] > [role="menuitem"]',
|
||||
'[role="menubar"] > [role="presentation"] > [role="menuitem"]'
|
||||
],
|
||||
MENUITEMS: [
|
||||
'> [role="menuitem"]',
|
||||
'> [role="presentation"] > [role="menuitem"]'
|
||||
]
|
||||
},
|
||||
ACTIONMENU,
|
||||
ALIGN = {
|
||||
@@ -72,14 +82,49 @@ ACTIONMENU.prototype = {
|
||||
*/
|
||||
menulink: null,
|
||||
|
||||
/**
|
||||
* The set of menu nodes.
|
||||
*
|
||||
* @property menuChildren
|
||||
* @type NodeList
|
||||
* @protected
|
||||
*/
|
||||
menuChildren: null,
|
||||
|
||||
/**
|
||||
* The first menu item.
|
||||
*
|
||||
* @property firstMenuChild
|
||||
* @type Node
|
||||
* @protected
|
||||
*/
|
||||
firstMenuChild: null,
|
||||
|
||||
/**
|
||||
* The last menu item.
|
||||
*
|
||||
* @property lastMenuChild
|
||||
* @type Node
|
||||
* @protected
|
||||
*/
|
||||
lastMenuChild: null,
|
||||
|
||||
/**
|
||||
* Called during the initialisation process of the object.
|
||||
* @method initializer
|
||||
*/
|
||||
initializer : function() {
|
||||
Y.all(SELECTOR.MENU).each(this.enhance, this);
|
||||
BODY.delegate('key', this.moveMenuItem, 'down:37,39', SELECTOR.MENUBARITEMS.join(','), this);
|
||||
|
||||
BODY.delegate('click', this.toggleMenu, SELECTOR.MENU + ' ' + SELECTOR.TOGGLE, this);
|
||||
BODY.delegate('key', this.toggleMenu, 'enter,space', SELECTOR.MENU + ' ' + SELECTOR.TOGGLE, this);
|
||||
BODY.delegate('key', this.showIfHidden, 'down:enter,38,40', SELECTOR.MENU + ' ' + SELECTOR.TOGGLE, this);
|
||||
|
||||
// Ensure that we toggle on menuitems when the spacebar is pressed.
|
||||
BODY.delegate('key', function(e) {
|
||||
e.currentTarget.simulate('click');
|
||||
e.preventDefault();
|
||||
}, 'down:32', SELECTOR.MENUBARITEMS.join(','));
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -105,6 +150,93 @@ ACTIONMENU.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle movement between menu items in a menubar.
|
||||
*
|
||||
* @method moveMenuItem
|
||||
* @param {EventFacade} e The event generating the move request
|
||||
* @chainable
|
||||
*/
|
||||
moveMenuItem: function(e) {
|
||||
var nextFocus,
|
||||
menuitem = e.target.ancestor(SELECTOR.MENUITEM, true);
|
||||
|
||||
if (e.keyCode === 37) {
|
||||
nextFocus = this.getMenuItem(menuitem, true);
|
||||
} else if (e.keyCode === 39) {
|
||||
nextFocus = this.getMenuItem(menuitem);
|
||||
}
|
||||
|
||||
if (nextFocus) {
|
||||
nextFocus.focus();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the next menuitem in a menubar.
|
||||
*
|
||||
* @method getMenuItem
|
||||
* @param {Node} currentItem The currently focused item in the menubar
|
||||
* @param {Boolean} [previous=false] Move backwards in the menubar instead of forwards
|
||||
* @return {Node|null} The next item, or null if none was found
|
||||
*/
|
||||
getMenuItem: function(currentItem, previous) {
|
||||
var menubar = currentItem.ancestor(SELECTOR.MENUBAR),
|
||||
menuitems;
|
||||
|
||||
if (!menubar) {
|
||||
return null;
|
||||
}
|
||||
|
||||
menuitems = menubar.all(SELECTOR.MENUITEMS.join(','));
|
||||
|
||||
if (!menuitems) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var childCount = menuitems.size();
|
||||
|
||||
if (childCount === 1) {
|
||||
// Only one item, exit now because we should already be on it.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Determine the next child.
|
||||
var index = 0,
|
||||
direction = 1,
|
||||
checkCount = 0;
|
||||
|
||||
// Work out the index of the currently selected item.
|
||||
for (index = 0; index < childCount; index++) {
|
||||
if (menuitems.item(index) === currentItem) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the menu item was found - otherwise return null.
|
||||
if (menuitems.item(index) !== currentItem) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Reverse the direction if we want the previous item.
|
||||
if (previous) {
|
||||
direction = -1;
|
||||
}
|
||||
|
||||
do {
|
||||
// Update the index in the direction of travel.
|
||||
index += direction;
|
||||
|
||||
next = menuitems.item(index);
|
||||
|
||||
// Check that we don't loop multiple times.
|
||||
checkCount++;
|
||||
} while (next && next.hasAttribute('hidden'));
|
||||
|
||||
return next;
|
||||
},
|
||||
|
||||
/**
|
||||
* Hides the menu if it is visible.
|
||||
* @method hideMenu
|
||||
@@ -132,6 +264,17 @@ ACTIONMENU.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
showIfHidden: function(e) {
|
||||
var menu = e.target.ancestor(SELECTOR.MENU),
|
||||
menuvisible = (menu.hasClass('show'));
|
||||
|
||||
if (!menuvisible) {
|
||||
e.preventDefault();
|
||||
this.showMenu(e, menu);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles the display of the menu.
|
||||
* @method toggleMenu
|
||||
@@ -140,9 +283,8 @@ ACTIONMENU.prototype = {
|
||||
toggleMenu : function(e) {
|
||||
var menu = e.target.ancestor(SELECTOR.MENU),
|
||||
menuvisible = (menu.hasClass('show'));
|
||||
// Stop all immediate propogation of the events we attach later will be
|
||||
// triggered and the dialogue immediately hidden again.
|
||||
e.halt(true);
|
||||
|
||||
e.preventDefault();
|
||||
this.hideMenu();
|
||||
if (menuvisible) {
|
||||
// The menu was visible and the user has clicked to toggle it again.
|
||||
@@ -152,35 +294,102 @@ ACTIONMENU.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Check current focus when moving around with the tab key.
|
||||
* This will ensure that when the etreme menu items are reached, the
|
||||
* menu is closed and the next DOM element is focused.
|
||||
* Handle keyboard events when the menu is open. We respond to:
|
||||
* * escape (exit)
|
||||
* * tab (move to next menu item)
|
||||
* * up/down (move to previous/next menu item)
|
||||
*
|
||||
* @method checkFocus
|
||||
* @method handleKeyboardEvent
|
||||
* @param {EventFacade} e The key event
|
||||
*/
|
||||
checkFocus: function(e) {
|
||||
var nodelist = this.dialogue.all(SELECTOR.MENUCHILD),
|
||||
firstNode,
|
||||
lastNode;
|
||||
handleKeyboardEvent: function(e) {
|
||||
var next;
|
||||
|
||||
if (nodelist) {
|
||||
firstNode = nodelist.item(0);
|
||||
lastNode = nodelist.pop();
|
||||
// Handle when the menu is still selected.
|
||||
if (e.currentTarget.ancestor(SELECTOR.TOGGLE, true)) {
|
||||
if ((e.keyCode === 40 || (e.keyCode === 9 && !e.shiftKey)) && this.firstMenuChild) {
|
||||
this.firstMenuChild.focus();
|
||||
e.preventDefault();
|
||||
} else if (e.keyCode === 38 && this.lastMenuChild) {
|
||||
this.lastMenuChild.focus();
|
||||
e.preventDefault();
|
||||
} else if (e.keyCode === 9 && e.shiftKey) {
|
||||
this.hideMenu();
|
||||
e.preventDefault();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
var menulink = this.menulink;
|
||||
if (e.target === firstNode && e.shiftKey) {
|
||||
if (e.keyCode === 27) {
|
||||
// The escape key was pressed so close the menu.
|
||||
this.hideMenu();
|
||||
e.preventDefault();
|
||||
} else if (e.target === lastNode && !e.shiftKey) {
|
||||
var next;
|
||||
if (this.hideMenu()) {
|
||||
next = menulink.next(SELECTOR.CAN_RECEIVE_FOCUS_SELECTOR);
|
||||
if (next) {
|
||||
next.focus();
|
||||
|
||||
} else if (e.keyCode === 32) {
|
||||
// The space bar was pressed. Trigger a click.
|
||||
e.preventDefault();
|
||||
e.currentTarget.simulate('click');
|
||||
} else if (e.keyCode === 9) {
|
||||
// The tab key was pressed. Tab moves forwards, Shift + Tab moves backwards through the menu options.
|
||||
// We only override the Shift + Tab on the first option, and Tab on the last option to change where the focus is moved to.
|
||||
if (e.target === this.firstMenuChild && e.shiftKey) {
|
||||
this.hideMenu();
|
||||
e.preventDefault();
|
||||
} else if (e.target === this.lastMenuChild && !e.shiftKey) {
|
||||
if (this.hideMenu()) {
|
||||
// Determine the next selector and focus on it.
|
||||
next = this.menulink.next(SELECTOR.CAN_RECEIVE_FOCUS_SELECTOR);
|
||||
if (next) {
|
||||
next.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if (e.keyCode === 38 || e.keyCode === 40) {
|
||||
// The up (38) or down (40) key was pushed.
|
||||
// On cursor moves we loops through the menu rather than exiting it as in the tab behaviour.
|
||||
var found = false,
|
||||
index = 0,
|
||||
direction = 1,
|
||||
checkCount = 0;
|
||||
|
||||
// Determine which menu item is currently selected.
|
||||
while (!found && index < this.menuChildren.size()) {
|
||||
if (this.menuChildren.item(index) === e.currentTarget) {
|
||||
found = true;
|
||||
} else {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.keyCode === 38) {
|
||||
// Moving up so reverse the direction.
|
||||
direction = -1;
|
||||
}
|
||||
|
||||
// Try to find the next
|
||||
do {
|
||||
index += direction;
|
||||
if (index < 0) {
|
||||
index = this.menuChildren.size() - 1;
|
||||
} else if (index >= this.menuChildren.size()) {
|
||||
// Handle wrapping.
|
||||
index = 0;
|
||||
}
|
||||
next = this.menuChildren.item(index);
|
||||
|
||||
// Add a counter to ensure we don't get stuck in a loop if there's only one visible menu item.
|
||||
checkCount++;
|
||||
} while (checkCount < this.menuChildren.size() && next !== e.currentTarget && next.hasClass('hidden'));
|
||||
|
||||
if (next) {
|
||||
next.focus();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -205,33 +414,37 @@ ACTIONMENU.prototype = {
|
||||
*/
|
||||
showMenu : function(e, menu) {
|
||||
var ownerselector = menu.getData('owner'),
|
||||
menucontent = menu.one(SELECTOR.MENUCONTENT),
|
||||
menuchild;
|
||||
menucontent = menu.one(SELECTOR.MENUCONTENT);
|
||||
this.owner = (ownerselector) ? menu.ancestor(ownerselector) : null;
|
||||
this.dialogue = menu;
|
||||
menu.addClass('show');
|
||||
if (this.owner) {
|
||||
this.owner.addClass(CSS.MENUSHOWN);
|
||||
this.menulink = this.owner.one(SELECTOR.TOGGLE);
|
||||
} else {
|
||||
this.menulink = e.target.ancestor(SELECTOR.TOGGLE, true);
|
||||
}
|
||||
this.constrain(menucontent.set('aria-hidden', false));
|
||||
|
||||
if (e.type && e.type === 'key') {
|
||||
menuchild = menucontent.one(SELECTOR.MENUCONTENTCHILD);
|
||||
if (menuchild) {
|
||||
menuchild.focus();
|
||||
}
|
||||
this.menuChildren = this.dialogue.all(SELECTOR.MENUCHILD);
|
||||
if (this.menuChildren) {
|
||||
this.firstMenuChild = this.menuChildren.item(0);
|
||||
this.lastMenuChild = this.menuChildren.item(this.menuChildren.size() - 1);
|
||||
|
||||
this.firstMenuChild.focus();
|
||||
}
|
||||
|
||||
// Close the menu if the user presses escape.
|
||||
this.events.push(BODY.on('key', this.hideMenu, 'esc', this));
|
||||
|
||||
// Close the menu if the user clicks outside the menu.
|
||||
this.events.push(BODY.on('click', this.hideIfOutside, this));
|
||||
|
||||
// Close the menu if the user focuses outside the menu.
|
||||
this.events.push(BODY.delegate('focus', this.hideIfOutside, '*', this));
|
||||
|
||||
// Check tabbing.
|
||||
this.events.push(menu.delegate('key', this.checkFocus, 'down:9', SELECTOR.MENUCHILD, this));
|
||||
// Check keyboard changes.
|
||||
this.events.push(menu.delegate('key', this.handleKeyboardEvent, 'down:9, 27, 38, 40, 32', SELECTOR.MENUCHILD + ', ' + SELECTOR.TOGGLE, this));
|
||||
|
||||
// Close the menu after a button was pushed.
|
||||
this.events.push(menu.delegate('click', function(e) {
|
||||
@@ -392,4 +605,4 @@ M.core.actionmenu.newDOMNode = function(node) {
|
||||
};
|
||||
|
||||
|
||||
}, '@VERSION@', {"requires": ["base", "event"]});
|
||||
}, '@VERSION@', {"requires": ["base", "event", "node-event-simulate"]});
|
||||
|
||||
+247
-33
@@ -11,11 +11,21 @@ var BODY = Y.one(Y.config.doc.body),
|
||||
SELECTOR = {
|
||||
CAN_RECEIVE_FOCUS_SELECTOR: 'input:not([type="hidden"]), a[href], button, textarea, select, [tabindex]',
|
||||
MENU : '.moodle-actionmenu[data-enhance=moodle-core-actionmenu]',
|
||||
MENUBAR: '[role="menubar"]',
|
||||
MENUITEM: '[role="menuitem"]',
|
||||
MENUCONTENT : '.menu[data-rel=menu-content]',
|
||||
MENUCONTENTCHILD: 'li a',
|
||||
MENUCHILD: '.menu li a',
|
||||
TOGGLE : '.toggle-display',
|
||||
KEEPOPEN: '[data-keepopen="1"]'
|
||||
KEEPOPEN: '[data-keepopen="1"]',
|
||||
MENUBARITEMS: [
|
||||
'[role="menubar"] > [role="menuitem"]',
|
||||
'[role="menubar"] > [role="presentation"] > [role="menuitem"]'
|
||||
],
|
||||
MENUITEMS: [
|
||||
'> [role="menuitem"]',
|
||||
'> [role="presentation"] > [role="menuitem"]'
|
||||
]
|
||||
},
|
||||
ACTIONMENU,
|
||||
ALIGN = {
|
||||
@@ -70,6 +80,33 @@ ACTIONMENU.prototype = {
|
||||
*/
|
||||
menulink: null,
|
||||
|
||||
/**
|
||||
* The set of menu nodes.
|
||||
*
|
||||
* @property menuChildren
|
||||
* @type NodeList
|
||||
* @protected
|
||||
*/
|
||||
menuChildren: null,
|
||||
|
||||
/**
|
||||
* The first menu item.
|
||||
*
|
||||
* @property firstMenuChild
|
||||
* @type Node
|
||||
* @protected
|
||||
*/
|
||||
firstMenuChild: null,
|
||||
|
||||
/**
|
||||
* The last menu item.
|
||||
*
|
||||
* @property lastMenuChild
|
||||
* @type Node
|
||||
* @protected
|
||||
*/
|
||||
lastMenuChild: null,
|
||||
|
||||
/**
|
||||
* Called during the initialisation process of the object.
|
||||
* @method initializer
|
||||
@@ -77,8 +114,16 @@ ACTIONMENU.prototype = {
|
||||
initializer : function() {
|
||||
Y.log('Initialising the action menu manager', 'debug', ACTIONMENU.NAME);
|
||||
Y.all(SELECTOR.MENU).each(this.enhance, this);
|
||||
BODY.delegate('key', this.moveMenuItem, 'down:37,39', SELECTOR.MENUBARITEMS.join(','), this);
|
||||
|
||||
BODY.delegate('click', this.toggleMenu, SELECTOR.MENU + ' ' + SELECTOR.TOGGLE, this);
|
||||
BODY.delegate('key', this.toggleMenu, 'enter,space', SELECTOR.MENU + ' ' + SELECTOR.TOGGLE, this);
|
||||
BODY.delegate('key', this.showIfHidden, 'down:enter,38,40', SELECTOR.MENU + ' ' + SELECTOR.TOGGLE, this);
|
||||
|
||||
// Ensure that we toggle on menuitems when the spacebar is pressed.
|
||||
BODY.delegate('key', function(e) {
|
||||
e.currentTarget.simulate('click');
|
||||
e.preventDefault();
|
||||
}, 'down:32', SELECTOR.MENUBARITEMS.join(','));
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -104,6 +149,93 @@ ACTIONMENU.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle movement between menu items in a menubar.
|
||||
*
|
||||
* @method moveMenuItem
|
||||
* @param {EventFacade} e The event generating the move request
|
||||
* @chainable
|
||||
*/
|
||||
moveMenuItem: function(e) {
|
||||
var nextFocus,
|
||||
menuitem = e.target.ancestor(SELECTOR.MENUITEM, true);
|
||||
|
||||
if (e.keyCode === 37) {
|
||||
nextFocus = this.getMenuItem(menuitem, true);
|
||||
} else if (e.keyCode === 39) {
|
||||
nextFocus = this.getMenuItem(menuitem);
|
||||
}
|
||||
|
||||
if (nextFocus) {
|
||||
nextFocus.focus();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the next menuitem in a menubar.
|
||||
*
|
||||
* @method getMenuItem
|
||||
* @param {Node} currentItem The currently focused item in the menubar
|
||||
* @param {Boolean} [previous=false] Move backwards in the menubar instead of forwards
|
||||
* @return {Node|null} The next item, or null if none was found
|
||||
*/
|
||||
getMenuItem: function(currentItem, previous) {
|
||||
var menubar = currentItem.ancestor(SELECTOR.MENUBAR),
|
||||
menuitems;
|
||||
|
||||
if (!menubar) {
|
||||
return null;
|
||||
}
|
||||
|
||||
menuitems = menubar.all(SELECTOR.MENUITEMS.join(','));
|
||||
|
||||
if (!menuitems) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var childCount = menuitems.size();
|
||||
|
||||
if (childCount === 1) {
|
||||
// Only one item, exit now because we should already be on it.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Determine the next child.
|
||||
var index = 0,
|
||||
direction = 1,
|
||||
checkCount = 0;
|
||||
|
||||
// Work out the index of the currently selected item.
|
||||
for (index = 0; index < childCount; index++) {
|
||||
if (menuitems.item(index) === currentItem) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the menu item was found - otherwise return null.
|
||||
if (menuitems.item(index) !== currentItem) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Reverse the direction if we want the previous item.
|
||||
if (previous) {
|
||||
direction = -1;
|
||||
}
|
||||
|
||||
do {
|
||||
// Update the index in the direction of travel.
|
||||
index += direction;
|
||||
|
||||
next = menuitems.item(index);
|
||||
|
||||
// Check that we don't loop multiple times.
|
||||
checkCount++;
|
||||
} while (next && next.hasAttribute('hidden'));
|
||||
|
||||
return next;
|
||||
},
|
||||
|
||||
/**
|
||||
* Hides the menu if it is visible.
|
||||
* @method hideMenu
|
||||
@@ -132,6 +264,17 @@ ACTIONMENU.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
showIfHidden: function(e) {
|
||||
var menu = e.target.ancestor(SELECTOR.MENU),
|
||||
menuvisible = (menu.hasClass('show'));
|
||||
|
||||
if (!menuvisible) {
|
||||
e.preventDefault();
|
||||
this.showMenu(e, menu);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles the display of the menu.
|
||||
* @method toggleMenu
|
||||
@@ -140,9 +283,8 @@ ACTIONMENU.prototype = {
|
||||
toggleMenu : function(e) {
|
||||
var menu = e.target.ancestor(SELECTOR.MENU),
|
||||
menuvisible = (menu.hasClass('show'));
|
||||
// Stop all immediate propogation of the events we attach later will be
|
||||
// triggered and the dialogue immediately hidden again.
|
||||
e.halt(true);
|
||||
|
||||
e.preventDefault();
|
||||
this.hideMenu();
|
||||
if (menuvisible) {
|
||||
// The menu was visible and the user has clicked to toggle it again.
|
||||
@@ -152,35 +294,103 @@ ACTIONMENU.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Check current focus when moving around with the tab key.
|
||||
* This will ensure that when the etreme menu items are reached, the
|
||||
* menu is closed and the next DOM element is focused.
|
||||
* Handle keyboard events when the menu is open. We respond to:
|
||||
* * escape (exit)
|
||||
* * tab (move to next menu item)
|
||||
* * up/down (move to previous/next menu item)
|
||||
*
|
||||
* @method checkFocus
|
||||
* @method handleKeyboardEvent
|
||||
* @param {EventFacade} e The key event
|
||||
*/
|
||||
checkFocus: function(e) {
|
||||
var nodelist = this.dialogue.all(SELECTOR.MENUCHILD),
|
||||
firstNode,
|
||||
lastNode;
|
||||
handleKeyboardEvent: function(e) {
|
||||
var next;
|
||||
|
||||
if (nodelist) {
|
||||
firstNode = nodelist.item(0);
|
||||
lastNode = nodelist.pop();
|
||||
// Handle when the menu is still selected.
|
||||
if (e.currentTarget.ancestor(SELECTOR.TOGGLE, true)) {
|
||||
if ((e.keyCode === 40 || (e.keyCode === 9 && !e.shiftKey)) && this.firstMenuChild) {
|
||||
this.firstMenuChild.focus();
|
||||
e.preventDefault();
|
||||
} else if (e.keyCode === 38 && this.lastMenuChild) {
|
||||
this.lastMenuChild.focus();
|
||||
e.preventDefault();
|
||||
} else if (e.keyCode === 9 && e.shiftKey) {
|
||||
this.hideMenu();
|
||||
e.preventDefault();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
var menulink = this.menulink;
|
||||
if (e.target === firstNode && e.shiftKey) {
|
||||
if (e.keyCode === 27) {
|
||||
// The escape key was pressed so close the menu.
|
||||
this.hideMenu();
|
||||
e.preventDefault();
|
||||
} else if (e.target === lastNode && !e.shiftKey) {
|
||||
var next;
|
||||
if (this.hideMenu()) {
|
||||
next = menulink.next(SELECTOR.CAN_RECEIVE_FOCUS_SELECTOR);
|
||||
if (next) {
|
||||
next.focus();
|
||||
|
||||
} else if (e.keyCode === 32) {
|
||||
// The space bar was pressed. Trigger a click.
|
||||
e.preventDefault();
|
||||
e.currentTarget.simulate('click');
|
||||
} else if (e.keyCode === 9) {
|
||||
// The tab key was pressed. Tab moves forwards, Shift + Tab moves backwards through the menu options.
|
||||
// We only override the Shift + Tab on the first option, and Tab on the last option to change where the focus is moved to.
|
||||
if (e.target === this.firstMenuChild && e.shiftKey) {
|
||||
this.hideMenu();
|
||||
e.preventDefault();
|
||||
} else if (e.target === this.lastMenuChild && !e.shiftKey) {
|
||||
if (this.hideMenu()) {
|
||||
// Determine the next selector and focus on it.
|
||||
next = this.menulink.next(SELECTOR.CAN_RECEIVE_FOCUS_SELECTOR);
|
||||
if (next) {
|
||||
next.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if (e.keyCode === 38 || e.keyCode === 40) {
|
||||
// The up (38) or down (40) key was pushed.
|
||||
// On cursor moves we loops through the menu rather than exiting it as in the tab behaviour.
|
||||
var found = false,
|
||||
index = 0,
|
||||
direction = 1,
|
||||
checkCount = 0;
|
||||
|
||||
// Determine which menu item is currently selected.
|
||||
while (!found && index < this.menuChildren.size()) {
|
||||
if (this.menuChildren.item(index) === e.currentTarget) {
|
||||
found = true;
|
||||
} else {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
Y.log("Unable to find this menu item in the list of menu children", 'debug', 'moodle-core-actionmenu');
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.keyCode === 38) {
|
||||
// Moving up so reverse the direction.
|
||||
direction = -1;
|
||||
}
|
||||
|
||||
// Try to find the next
|
||||
do {
|
||||
index += direction;
|
||||
if (index < 0) {
|
||||
index = this.menuChildren.size() - 1;
|
||||
} else if (index >= this.menuChildren.size()) {
|
||||
// Handle wrapping.
|
||||
index = 0;
|
||||
}
|
||||
next = this.menuChildren.item(index);
|
||||
|
||||
// Add a counter to ensure we don't get stuck in a loop if there's only one visible menu item.
|
||||
checkCount++;
|
||||
} while (checkCount < this.menuChildren.size() && next !== e.currentTarget && next.hasClass('hidden'));
|
||||
|
||||
if (next) {
|
||||
next.focus();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -206,33 +416,37 @@ ACTIONMENU.prototype = {
|
||||
showMenu : function(e, menu) {
|
||||
Y.log('Displaying an action menu', 'debug', ACTIONMENU.NAME);
|
||||
var ownerselector = menu.getData('owner'),
|
||||
menucontent = menu.one(SELECTOR.MENUCONTENT),
|
||||
menuchild;
|
||||
menucontent = menu.one(SELECTOR.MENUCONTENT);
|
||||
this.owner = (ownerselector) ? menu.ancestor(ownerselector) : null;
|
||||
this.dialogue = menu;
|
||||
menu.addClass('show');
|
||||
if (this.owner) {
|
||||
this.owner.addClass(CSS.MENUSHOWN);
|
||||
this.menulink = this.owner.one(SELECTOR.TOGGLE);
|
||||
} else {
|
||||
this.menulink = e.target.ancestor(SELECTOR.TOGGLE, true);
|
||||
}
|
||||
this.constrain(menucontent.set('aria-hidden', false));
|
||||
|
||||
if (e.type && e.type === 'key') {
|
||||
menuchild = menucontent.one(SELECTOR.MENUCONTENTCHILD);
|
||||
if (menuchild) {
|
||||
menuchild.focus();
|
||||
}
|
||||
this.menuChildren = this.dialogue.all(SELECTOR.MENUCHILD);
|
||||
if (this.menuChildren) {
|
||||
this.firstMenuChild = this.menuChildren.item(0);
|
||||
this.lastMenuChild = this.menuChildren.item(this.menuChildren.size() - 1);
|
||||
|
||||
this.firstMenuChild.focus();
|
||||
}
|
||||
|
||||
// Close the menu if the user presses escape.
|
||||
this.events.push(BODY.on('key', this.hideMenu, 'esc', this));
|
||||
|
||||
// Close the menu if the user clicks outside the menu.
|
||||
this.events.push(BODY.on('click', this.hideIfOutside, this));
|
||||
|
||||
// Close the menu if the user focuses outside the menu.
|
||||
this.events.push(BODY.delegate('focus', this.hideIfOutside, '*', this));
|
||||
|
||||
// Check tabbing.
|
||||
this.events.push(menu.delegate('key', this.checkFocus, 'down:9', SELECTOR.MENUCHILD, this));
|
||||
// Check keyboard changes.
|
||||
this.events.push(menu.delegate('key', this.handleKeyboardEvent, 'down:9, 27, 38, 40, 32', SELECTOR.MENUCHILD + ', ' + SELECTOR.TOGGLE, this));
|
||||
|
||||
// Close the menu after a button was pushed.
|
||||
this.events.push(menu.delegate('click', function(e) {
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
"moodle-core-actionmenu": {
|
||||
"requires": [
|
||||
"base",
|
||||
"event"
|
||||
"event",
|
||||
"node-event-simulate"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1575,8 +1575,7 @@ img#persona_signin { cursor: pointer; }
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
.moodle-actionmenu[data-enhanced].show .menu a:hover,
|
||||
.moodle-actionmenu[data-enhanced].show .menu a:focus {color: #ffffff;background-color: #0088cc;}
|
||||
.moodle-actionmenu[data-enhanced].show .menu a:hover {color: #ffffff;background-color: #0088cc;}
|
||||
.moodle-actionmenu[data-enhanced].show .menu a:first-child {-webkit-border-top-right-radius: 4px;border-top-right-radius: 4px;-webkit-border-top-left-radius: 4px;border-top-left-radius: 4px;-moz-border-radius-topright: 4px;-moz-border-radius-topleft: 4px;}
|
||||
.moodle-actionmenu[data-enhanced].show .menu a:last-child {-webkit-border-bottom-right-radius: 4px;border-bottom-right-radius: 4px;-webkit-border-bottom-left-radius: 4px;border-bottom-left-radius: 4px;-moz-border-radius-bottomright: 4px;-moz-border-radius-bottomleft: 4px;}
|
||||
.moodle-actionmenu[data-enhanced].show .menu a.hidden {display: none;}
|
||||
|
||||
@@ -2060,8 +2060,7 @@ img#persona_signin {
|
||||
display: block;
|
||||
color: @dropdownLinkColor;
|
||||
padding:2px 1em 2px 28px;
|
||||
&:hover,
|
||||
&:focus {
|
||||
&:hover {
|
||||
color: @dropdownLinkColorHover;
|
||||
background-color: @dropdownLinkBackgroundHover;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user