diff options
Diffstat (limited to 'ui/jquery.ui.menu.js')
-rw-r--r-- | ui/jquery.ui.menu.js | 100 |
1 files changed, 62 insertions, 38 deletions
diff --git a/ui/jquery.ui.menu.js b/ui/jquery.ui.menu.js index af1101f73..0f585d976 100644 --- a/ui/jquery.ui.menu.js +++ b/ui/jquery.ui.menu.js @@ -28,11 +28,14 @@ $.widget("ui.menu", { var self = this; this.activeMenu = this.element; this.menuId = this.element.attr( "id" ) || "ui-menu-" + idIncrement++; + if (this.element.find(".ui-icon").length) { + this.element.addClass("ui-menu-icons"); + } this.element .addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" ) .attr({ id: this.menuId, - role: "listbox" + role: "menu" }) .bind( "click.menu", function( event ) { var item = $( event.target ).closest( ".ui-menu-item:has(a)" ); @@ -108,9 +111,16 @@ $.widget("ui.menu", { event.preventDefault(); break; case $.ui.keyCode.ENTER: - self.select( event ); + if (self.active.children("a[aria-haspopup='true']").length) { + if (self.right( event )) { + event.stopImmediatePropagation(); + } + } + else { + self.select( event ); + event.stopImmediatePropagation(); + } event.preventDefault(); - event.stopImmediatePropagation(); break; case $.ui.keyCode.ESCAPE: if ( self.left( event ) ) { @@ -132,13 +142,13 @@ $.widget("ui.menu", { function escape(value) { return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); } - var match = self.widget().children(".ui-menu-item").filter(function() { + var match = self.activeMenu.children(".ui-menu-item").filter(function() { return new RegExp("^" + escape(character), "i").test($(this).children("a").text()); }); var match = skip && match.index(self.active.next()) != -1 ? self.active.nextAll(".ui-menu-item") : match; if (!match.length) { character = String.fromCharCode(event.keyCode); - match = self.widget().children(".ui-menu-item").filter(function() { + match = self.activeMenu.children(".ui-menu-item").filter(function() { return new RegExp("^" + escape(character), "i").test($(this).children("a").text()); }); } @@ -160,41 +170,62 @@ $.widget("ui.menu", { }, _destroy: function() { + //destroy (sub)menus this.element + .removeAttr( "aria-activedescendant" ) + .find("ul") + .andSelf() .removeClass( "ui-menu ui-widget ui-widget-content ui-corner-all" ) - .removeAttr( "tabIndex" ) .removeAttr( "role" ) - .removeAttr( "aria-activedescendant" ); + .removeAttr("id") + .removeAttr( "aria-labelledby" ) + .removeAttr( "aria-expanded" ) + .removeAttr( "aria-hidden" ) + .show(); - this.element.children( ".ui-menu-item" ) + //destroy menu items + this.element.find( ".ui-menu-item" ) + .unbind( ".menu" ) .removeClass( "ui-menu-item" ) .removeAttr( "role" ) .children( "a" ) .removeClass( "ui-corner-all ui-state-hover" ) .removeAttr( "tabIndex" ) - .unbind( ".menu" ); + .removeAttr( "role" ) + .removeAttr( "aria-haspopup" ) + .removeAttr( "id" ) + .children(".ui-icon").remove(); }, refresh: function() { + var self = this; // initialize nested menus - // TODO add role=listbox to these, too? or just the top level menu? var submenus = this.element.find("ul:not(.ui-menu)") .addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" ) + .attr("role", "menu") .hide() - - submenus - .prev("a") - .prepend('<span class="ui-icon ui-icon-carat-1-e"></span>'); - + .attr("aria-hidden", "true") + .attr("aria-expanded", "false") + ; // don't refresh list items that are already adapted var items = submenus.add(this.element).children( "li:not(.ui-menu-item):has(a)" ) .addClass( "ui-menu-item" ) - .attr( "role", "menuitem" ); + .attr( "role", "presentation" ); items.children( "a" ) .addClass( "ui-corner-all" ) - .attr( "tabIndex", -1 ); + .attr( "tabIndex", -1 ) + .attr( "role", "menuitem" ) + .attr("id", function(i) {return self.element.attr("id") + "-" + i}); + + submenus.each(function() { + var menu = $(this); + var item = menu.prev("a") + item.attr("aria-haspopup", "true") + .prepend('<span class="ui-menu-icon ui-icon ui-icon-carat-1-e"></span>'); + menu.attr("aria-labelledby", item.attr("id")); + }); }, focus: function( event, item ) { @@ -219,13 +250,11 @@ $.widget("ui.menu", { this.active = item.first() .children( "a" ) .addClass( "ui-state-focus" ) - .attr( "id", function(index, id) { - return (self.itemId = id || self.menuId + "-activedescendant"); - }) .end(); - // need to remove the attribute before adding it for the screenreader to pick up the change - // see http://groups.google.com/group/jquery-a11y/msg/929e0c1e8c5efc8f - this.element.removeAttr("aria-activedescendant").attr("aria-activedescendant", self.itemId) + self.element.attr("aria-activedescendant", self.active.children("a").attr("id")) + + // highlight active parent menu item, if any + this.active.parent().closest(".ui-menu-item").children("a:first").addClass("ui-state-active"); self.timer = setTimeout(function() { self._close(); @@ -247,10 +276,6 @@ $.widget("ui.menu", { clearTimeout(this.timer); this.active.children( "a" ).removeClass( "ui-state-focus" ); - // remove only generated id - $( "#" + this.menuId + "-activedescendant" ).removeAttr( "id" ); - this.element.removeAttr( "aria-activedescenant" ); - this._trigger( "blur", event ); this.active = null; }, @@ -264,23 +289,20 @@ $.widget("ui.menu", { }, _open: function(submenu) { - this.element.find(".ui-menu").not(submenu.parents()).hide(); - + clearTimeout(this.timer); + this.element.find(".ui-menu").not(submenu.parents()).hide().attr("aria-hidden", "true"); var position = $.extend({}, { of: this.active }, $.type(this.options.position) == "function" ? this.options.position(this.active) : this.options.position ); - - submenu.show().position(position); - - this.active.find(">a:first").addClass("ui-state-active"); + submenu.show().removeAttr("aria-hidden").attr("aria-expanded", "true").position(position); }, closeAll: function() { this.element - .find("ul").hide().end() + .find("ul").hide().attr("aria-hidden", "true").attr("aria-expanded", "false").end() .find("a.ui-state-active").removeClass("ui-state-active"); this.blur(); this.activeMenu = this.element; @@ -288,25 +310,27 @@ $.widget("ui.menu", { _close: function() { this.active.parent() - .find("ul").hide().end() + .find("ul").hide().attr("aria-hidden", "true").attr("aria-expanded", "false").end() .find("a.ui-state-active").removeClass("ui-state-active"); }, left: function(event) { - var newItem = this.active && this.active.parents("li").first(); + var newItem = this.active && this.active.parents("li:not(.ui-menubar-item)").first(); if (newItem && newItem.length) { - this.active.parent().hide(); + this.active.parent().attr("aria-hidden", "true").attr("aria-expanded", "false").hide(); this.focus(event, newItem); return true; } }, right: function(event) { + var self= this; var newItem = this.active && this.active.children("ul").children("li").first(); if (newItem && newItem.length) { this._open(newItem.parent()); var current = this.active; - this.focus(event, newItem); + //timeout so Firefox will not hide activedescendant change in expanding submenu from AT + setTimeout(function(){self.focus(event, newItem)}, 20); return true; } }, |