diff --git a/ui/jquery.ui.menu.js b/ui/jquery.ui.menu.js index 34a339157..6982da3c3 100644 --- a/ui/jquery.ui.menu.js +++ b/ui/jquery.ui.menu.js @@ -41,18 +41,21 @@ $.widget( "ui.menu", { .addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" ) .attr({ id: this.menuId, - role: "menu" + role: "menu", + tabIndex: 0 }) // need to catch all clicks on disabled menu // not possible through _bind - .bind( "click.menu", $.proxy( function( event ) { + .bind( "click.menu", $.proxy(function( event ) { if ( this.options.disabled ) { event.preventDefault(); } - }, this)); + }, this )); + if ( this.options.disabled ) { this.element.addClass( "ui-state-disabled" ); } + this._bind({ // Prevent focus from sticking to links inside menu after clicking // them (focus should always stay on UL during navigation). @@ -64,21 +67,22 @@ $.widget( "ui.menu", { }, "click .ui-menu-item:has(a)": function( event ) { event.stopImmediatePropagation(); - //Don't select disabled menu items + // Don't select disabled menu items if ( !$( event.target ).closest( ".ui-menu-item" ).is( ".ui-state-disabled" ) ) { this.select( event ); // Redirect focus to the menu with a delay for firefox - this._delay( function() { + this._delay(function() { if ( !this.element.is(":focus") ) { this.element.focus(); } - }, 20); + }, 20 ); } }, "mouseover .ui-menu-item": function( event ) { event.stopImmediatePropagation(); var target = $( event.currentTarget ); - // Remove ui-state-active class from siblings of the newly focused menu item to avoid a jump caused by adjacent elements both having a class with a border + // Remove ui-state-active class from siblings of the newly focused menu item + // to avoid a jump caused by adjacent elements both having a class with a border target.siblings().children( ".ui-state-active" ).removeClass( "ui-state-active" ); this.focus( event, target ); }, @@ -88,7 +92,7 @@ $.widget( "ui.menu", { var menu = this.element, firstItem = menu.children( ".ui-menu-item" ).not( ".ui-state-disabled" ).eq( 0 ); if ( this._hasScroll() && !this.active ) { - menu.children().each( function() { + menu.children().each(function() { var currentItem = $( this ); if ( currentItem.offset().top - menu.offset().top >= 0 ) { firstItem = currentItem; @@ -101,125 +105,17 @@ $.widget( "ui.menu", { this.focus( event, firstItem ); }, blur: function( event ) { - this._delay( function() { - if ( ! $.contains( this.element[0], this.document[0].activeElement ) ) { + this._delay(function() { + if ( !$.contains( this.element[0], this.document[0].activeElement ) ) { this.collapseAll( event ); } - }, 0); - } + }); + }, + "keydown": "_keydown" }); this.refresh(); - this.element.attr( "tabIndex", 0 ); - this._bind({ - "keydown": function( event ) { - switch ( event.keyCode ) { - case $.ui.keyCode.PAGE_UP: - this.previousPage( event ); - event.preventDefault(); - event.stopImmediatePropagation(); - break; - case $.ui.keyCode.PAGE_DOWN: - this.nextPage( event ); - event.preventDefault(); - event.stopImmediatePropagation(); - break; - case $.ui.keyCode.HOME: - this._move( "first", "first", event ); - event.preventDefault(); - event.stopImmediatePropagation(); - break; - case $.ui.keyCode.END: - this._move( "last", "last", event ); - event.preventDefault(); - event.stopImmediatePropagation(); - break; - case $.ui.keyCode.UP: - this.previous( event ); - event.preventDefault(); - event.stopImmediatePropagation(); - break; - case $.ui.keyCode.DOWN: - this.next( event ); - event.preventDefault(); - event.stopImmediatePropagation(); - break; - case $.ui.keyCode.LEFT: - if (this.collapse( event )) { - event.stopImmediatePropagation(); - } - event.preventDefault(); - break; - case $.ui.keyCode.RIGHT: - if (this.expand( event )) { - event.stopImmediatePropagation(); - } - event.preventDefault(); - break; - case $.ui.keyCode.ENTER: - if ( this.active.children( "a[aria-haspopup='true']" ).length ) { - if ( this.expand( event ) ) { - event.stopImmediatePropagation(); - } - } - else { - this.select( event ); - event.stopImmediatePropagation(); - } - event.preventDefault(); - break; - case $.ui.keyCode.ESCAPE: - if ( this.collapse( event ) ) { - event.stopImmediatePropagation(); - } - event.preventDefault(); - break; - default: - event.stopPropagation(); - clearTimeout( this.filterTimer ); - var match, - prev = this.previousFilter || "", - character = String.fromCharCode( event.keyCode ), - skip = false; - - if (character === prev) { - skip = true; - } else { - character = prev + character; - } - function escape( value ) { - return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&"); - } - match = this.activeMenu.children( ".ui-menu-item" ).filter( function() { - return new RegExp("^" + escape(character), "i") - .test( $( this ).children( "a" ).text() ); - }); - match = skip && match.index(this.active.next()) !== -1 ? this.active.nextAll(".ui-menu-item") : match; - if ( !match.length ) { - character = String.fromCharCode(event.keyCode); - match = this.activeMenu.children(".ui-menu-item").filter( function() { - return new RegExp("^" + escape(character), "i") - .test( $( this ).children( "a" ).text() ); - }); - } - if ( match.length ) { - this.focus( event, match ); - if (match.length > 1) { - this.previousFilter = character; - this.filterTimer = this._delay( function() { - delete this.previousFilter; - }, 1000 ); - } else { - delete this.previousFilter; - } - } else { - delete this.previousFilter; - } - } - } - }); - this._bind( this.document, { click: function( event ) { if ( !$( event.target ).closest( ".ui-menu" ).length ) { @@ -230,39 +126,146 @@ $.widget( "ui.menu", { }, _destroy: function() { - //destroy (sub)menus + // destroy (sub)menus this.element .removeAttr( "aria-activedescendant" ) - .find( ".ui-menu" ) - .andSelf() - .removeClass( "ui-menu ui-widget ui-widget-content ui-corner-all" ) - .removeAttr( "role" ) - .removeAttr( "tabIndex" ) - .removeAttr( "aria-labelledby" ) - .removeAttr( "aria-expanded" ) - .removeAttr( "aria-hidden" ) - .show(); + .find( ".ui-menu" ).andSelf() + .removeClass( "ui-menu ui-widget ui-widget-content ui-corner-all" ) + .removeAttr( "role" ) + .removeAttr( "tabIndex" ) + .removeAttr( "aria-labelledby" ) + .removeAttr( "aria-expanded" ) + .removeAttr( "aria-hidden" ) + .show(); - //destroy menu items + // 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" ) - .removeAttr( "role" ) - .removeAttr( "aria-haspopup" ) - .removeAttr( "id" ) - .children( ".ui-icon" ) - .remove(); + .removeClass( "ui-corner-all ui-state-hover" ) + .removeAttr( "tabIndex" ) + .removeAttr( "role" ) + .removeAttr( "aria-haspopup" ) + .removeAttr( "id" ) + .children( ".ui-icon" ) + .remove(); + }, + + _keydown: function( event ) { + switch ( event.keyCode ) { + case $.ui.keyCode.PAGE_UP: + this.previousPage( event ); + event.preventDefault(); + event.stopImmediatePropagation(); + break; + case $.ui.keyCode.PAGE_DOWN: + this.nextPage( event ); + event.preventDefault(); + event.stopImmediatePropagation(); + break; + case $.ui.keyCode.HOME: + this._move( "first", "first", event ); + event.preventDefault(); + event.stopImmediatePropagation(); + break; + case $.ui.keyCode.END: + this._move( "last", "last", event ); + event.preventDefault(); + event.stopImmediatePropagation(); + break; + case $.ui.keyCode.UP: + this.previous( event ); + event.preventDefault(); + event.stopImmediatePropagation(); + break; + case $.ui.keyCode.DOWN: + this.next( event ); + event.preventDefault(); + event.stopImmediatePropagation(); + break; + case $.ui.keyCode.LEFT: + if (this.collapse( event )) { + event.stopImmediatePropagation(); + } + event.preventDefault(); + break; + case $.ui.keyCode.RIGHT: + if (this.expand( event )) { + event.stopImmediatePropagation(); + } + event.preventDefault(); + break; + case $.ui.keyCode.ENTER: + if ( this.active.children( "a[aria-haspopup='true']" ).length ) { + if ( this.expand( event ) ) { + event.stopImmediatePropagation(); + } + } + else { + this.select( event ); + event.stopImmediatePropagation(); + } + event.preventDefault(); + break; + case $.ui.keyCode.ESCAPE: + if ( this.collapse( event ) ) { + event.stopImmediatePropagation(); + } + event.preventDefault(); + break; + default: + event.stopPropagation(); + clearTimeout( this.filterTimer ); + var match, + prev = this.previousFilter || "", + character = String.fromCharCode( event.keyCode ), + skip = false; + + if ( character === prev ) { + skip = true; + } else { + character = prev + character; + } + function escape( value ) { + return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&"); + } + match = this.activeMenu.children( ".ui-menu-item" ).filter(function() { + return new RegExp( "^" + escape( character ), "i" ) + .test( $( this ).children( "a" ).text() ); + }); + match = skip && match.index(this.active.next()) !== -1 ? + this.active.nextAll(".ui-menu-item") : + match; + if ( !match.length ) { + character = String.fromCharCode(event.keyCode); + match = this.activeMenu.children(".ui-menu-item").filter(function() { + return new RegExp( "^" + escape(character), "i" ) + .test( $( this ).children( "a" ).text() ); + }); + } + if ( match.length ) { + this.focus( event, match ); + if ( match.length > 1 ) { + this.previousFilter = character; + this.filterTimer = this._delay(function() { + delete this.previousFilter; + }, 1000 ); + } else { + delete this.previousFilter; + } + } else { + delete this.previousFilter; + } + } }, refresh: function() { // initialize nested menus var menus, menuId = this.menuId, - submenus = this.element.find( this.options.menus + ":not( .ui-menu )" ) + submenus = this.element.find( this.options.menus + ":not(.ui-menu)" ) .addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" ) .hide() .attr({ @@ -286,9 +289,9 @@ $.widget( "ui.menu", { }); // initialize unlinked menu-items as dividers - menus.children( ":not( .ui-menu-item )" ).addClass( "ui-widget-content ui-menu-divider" ); + menus.children( ":not(.ui-menu-item)" ).addClass( "ui-widget-content ui-menu-divider" ); - submenus.each( function() { + submenus.each(function() { var menu = $( this ), item = menu.prev( "a" ); @@ -317,16 +320,16 @@ $.widget( "ui.menu", { } } - this.active = item.first() - .children( "a" ) + this.active = item.first(); + this.element.attr( "aria-activedescendant", + this.active.children( "a" ) .addClass( "ui-state-focus" ) - .end(); - this.element.attr( "aria-activedescendant", this.active.children( "a" ).attr( "id" ) ); + .attr( "id" ) ); // highlight active parent menu item, if any this.active.parent().closest( ".ui-menu-item" ).children( "a:first" ).addClass( "ui-state-active" ); - this.timer = this._delay( function() { + this.timer = this._delay(function() { this._close(); }, this.delay ); @@ -361,7 +364,7 @@ $.widget( "ui.menu", { return; } - this.timer = this._delay( function() { + this.timer = this._delay(function() { this._close(); this._open( submenu ); }, this.delay ); @@ -369,20 +372,19 @@ $.widget( "ui.menu", { _open: function( submenu ) { clearTimeout( this.timer ); - this.element - .find( ".ui-menu" ) - .not( submenu.parents() ) + this.element.find( ".ui-menu" ).not( submenu.parents() ) .hide() .attr( "aria-hidden", "true" ); - var position = $.extend({}, { + var position = $.extend( {}, { of: this.active }, $.type(this.options.position) === "function" ? this.options.position(this.active) : this.options.position ); - submenu.show() + submenu + .show() .removeAttr( "aria-hidden" ) .attr( "aria-expanded", "true" ) .position( position ); @@ -390,7 +392,7 @@ $.widget( "ui.menu", { collapseAll: function( event, all ) { clearTimeout( this.timer ); - this.timer = this._delay( function() { + this.timer = this._delay(function() { // if we were passed an event, look for the submenu that contains the event var currentMenu = all ? this.element : $( event && event.target ).closest( this.element.find( ".ui-menu" ) ); @@ -404,7 +406,7 @@ $.widget( "ui.menu", { this.blur( event ); this.activeMenu = currentMenu; - }, this.delay); + }, this.delay ); }, // With no arguments, closes the currently active menu - if nothing is active @@ -421,11 +423,12 @@ $.widget( "ui.menu", { .attr( "aria-expanded", "false" ) .end() .find( "a.ui-state-active" ) - .removeClass( "ui-state-active" ); + .removeClass( "ui-state-active" ); }, collapse: function( event ) { - var newItem = this.active && this.active.parent().closest( ".ui-menu-item", this.element ); + var newItem = this.active && + this.active.parent().closest( ".ui-menu-item", this.element ); if ( newItem && newItem.length ) { this._close(); this.focus( event, newItem ); @@ -434,13 +437,18 @@ $.widget( "ui.menu", { }, expand: function( event ) { - var newItem = this.active && this.active.children( ".ui-menu " ).children( ".ui-menu-item" ).not( ".ui-state-disabled" ).first(); + var newItem = this.active && + this.active + .children( ".ui-menu " ) + .children( ".ui-menu-item" ) + .not( ".ui-state-disabled" ) + .first(); if ( newItem && newItem.length ) { this._open( newItem.parent() ); //timeout so Firefox will not hide activedescendant change in expanding submenu from AT - this._delay( function() { + this._delay(function() { this.focus( event, newItem ); }, 20 ); return true; @@ -467,9 +475,15 @@ $.widget( "ui.menu", { var next; if ( this.active ) { if ( direction === "first" || direction === "last" ) { - next = this.active[ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" ).not( ".ui-state-disabled" ).eq( -1 ); + next = this.active + [ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" ) + .not( ".ui-state-disabled" ) + .eq( -1 ); } else { - next = this.active[ direction + "All" ]( ".ui-menu-item" ).not( ".ui-state-disabled" ).eq( 0 ); + next = this.active + [ direction + "All" ]( ".ui-menu-item" ) + .not( ".ui-state-disabled" ) + .eq( 0 ); } } if ( !next || !next.length || !this.active ) { @@ -494,7 +508,7 @@ $.widget( "ui.menu", { var base = this.active.offset().top, height = this.element.height(), result; - this.active.nextAll( ".ui-menu-item" ).not( ".ui-state-disabled" ).each( function() { + this.active.nextAll( ".ui-menu-item" ).not( ".ui-state-disabled" ).each(function() { result = $( this ); return $( this ).offset().top - base - height < 0; }); @@ -518,7 +532,7 @@ $.widget( "ui.menu", { var base = this.active.offset().top, height = this.element.height(), result; - this.active.prevAll( ".ui-menu-item" ).not( ".ui-state-disabled" ).each( function() { + this.active.prevAll( ".ui-menu-item" ).not( ".ui-state-disabled" ).each(function() { result = $( this ); return $(this).offset().top - base + height > 0; }); @@ -534,7 +548,6 @@ $.widget( "ui.menu", { }, select: function( event ) { - // save active reference before collapseAll triggers blur var ui = { item: this.active