Index: admin_menu.css =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/admin_menu/admin_menu.css,v retrieving revision 1.25.2.4 diff -u -p -r1.25.2.4 admin_menu.css --- admin_menu.css 2 Nov 2008 21:36:22 -0000 1.25.2.4 +++ admin_menu.css 9 Nov 2008 04:38:32 -0000 @@ -97,6 +97,9 @@ body.admin-menu { margin-top: 20px !impo /* #210615: Mozilla on Mac fix */ html.js fieldset.collapsible div.fieldset-wrapper { overflow: visible; } +/* Keyboard navigation */ +div#admin-menu li.focused { background-color: #75757a; } + @media print { #admin-menu { display: none; } body.admin-menu { margin-top: 0 !important; } Index: admin_menu.js =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/admin_menu/admin_menu.js,v retrieving revision 1.7.2.6 diff -u -p -r1.7.2.6 admin_menu.js --- admin_menu.js 2 Nov 2008 21:36:22 -0000 1.7.2.6 +++ admin_menu.js 9 Nov 2008 04:48:27 -0000 @@ -47,4 +47,143 @@ $(document).ready(function() { uls.css({left: '-999em', display: 'none'}); }, 400); }); + + // Keyboard navigation. + (function() { + var tree = $.map($("#admin-menu").find("> ul > li.expandable"), function (element, index) { + var _this = $(element); + var tree = { li: _this, a: _this.find("> a") }; + var children; + if ((children = _this.find("> ul > li")).length) { + tree.children = $.map(children, arguments.callee); + } + return tree; + }); + + // Create object parents + (function (array, parent, parentIndex, level) { + var level = level || 1; + for (var i = 0; i < array.length; i++) { + var object = array[i]; + object.level = level; + + if (object.children) { + arguments.callee(object.children, object, i, level + 1); + } + if (parent) { + object.parent = parent; + } + object.parentIndex = parentIndex || i; + object.index = i; + }; + })(tree); + + var active = false; + var current; + + $(document).bind('keydown', function (event) { + var key = event.keyCode; + // Left, top, right, down or escape pressed. + if (!(key == 37 || key == 38 || key == 39 || key == 40 || key == 27)) { + return true; + } + + // Un-hover the menu if escape is pressed. + if (key == 27) { + active = !active; + if (!active) { + $("#admin-menu").find('li.expandable').trigger('mouseleave'); + current.li.removeClass('focused'); + current.a.blur(); + current = undefined; + return false; + } + } + // If the menu is not active, break here. + if (!active) { + return false; + } + + // Reset first element on initialization. + if (!current) { + // @todo Find 'Content Management', since modules can alter the menu via + // hook_admin_menu(). + current = tree[2]; + } + + var next; + switch (key) { + case 37: // Left + // Handle first level. + if (current.level == 1 && tree[current.parentIndex - 1]) { + next = tree[current.parentIndex - 1]; + } + // Handle third and deeper levels. + else if (current.level > 2) { + next = current.parent; + } + // Handle second level. + else { + next = tree[current.parent.parentIndex - 1]; + } + break; + + case 38: // Up + if (current.level == 1) { + next = current.children[current.children.length - 1]; + } + // Handle first level. + else if (!(current.level == 2 && current.index == 0)) { + next = current.parent.children[current.index - 1]; + } + else if (current.level == 2 && current.index == 0) { + next = current.parent; + } + break; + + case 39: // Right + // Handle first level. + if (current.level == 1 && tree[current.parentIndex + 1]) { + next = tree[current.parentIndex + 1]; + } + else if (current.children) { + next = current.children[0]; + } + else if (current.level == 2) { + next = tree[current.parent.parentIndex + 1]; + } + break; + + case 40: // Down + // Handle first level. + if (current.level == 1 && tree[current.parentIndex].children) { + next = tree[current.parentIndex].children[0]; + } + else { + next = current.parent.children[current.index + 1]; + } + break; + } + // Fallback to current if next is not set. + if (!next) { + next = current; + } + current.li.removeClass('focused'); + if ((next.level < current.level) || (!next.children && next.level == current.level)) { + current.li.trigger('mouseleave'); + } + next.li.addClass('focused'); + next.a.focus(); + next.li.trigger('mouseenter'); + + current = next; + + event.preventDefault(); + event.cancelBubble = true; + if (event.stopPropagation) { + event.stopPropagation(); + } + return false; + }); + })(); });