wp/wp-includes/js/jquery/ui/menu.js
changeset 18 be944660c56a
child 19 3d72ae0968f4
equal deleted inserted replaced
17:34716fd837a4 18:be944660c56a
       
     1 /*!
       
     2  * jQuery UI Menu 1.12.1
       
     3  * http://jqueryui.com
       
     4  *
       
     5  * Copyright jQuery Foundation and other contributors
       
     6  * Released under the MIT license.
       
     7  * http://jquery.org/license
       
     8  */
       
     9 
       
    10 //>>label: Menu
       
    11 //>>group: Widgets
       
    12 //>>description: Creates nestable menus.
       
    13 //>>docs: http://api.jqueryui.com/menu/
       
    14 //>>demos: http://jqueryui.com/menu/
       
    15 //>>css.structure: ../../themes/base/core.css
       
    16 //>>css.structure: ../../themes/base/menu.css
       
    17 //>>css.theme: ../../themes/base/theme.css
       
    18 
       
    19 ( function( factory ) {
       
    20 	if ( typeof define === "function" && define.amd ) {
       
    21 
       
    22 		// AMD. Register as an anonymous module.
       
    23 		define( [
       
    24 			"jquery",
       
    25 			"./core"
       
    26 		], factory );
       
    27 	} else {
       
    28 
       
    29 		// Browser globals
       
    30 		factory( jQuery );
       
    31 	}
       
    32 }( function( $ ) {
       
    33 
       
    34 return $.widget( "ui.menu", {
       
    35 	version: "1.12.1",
       
    36 	defaultElement: "<ul>",
       
    37 	delay: 300,
       
    38 	options: {
       
    39 		icons: {
       
    40 			submenu: "ui-icon-caret-1-e"
       
    41 		},
       
    42 		items: "> *",
       
    43 		menus: "ul",
       
    44 		position: {
       
    45 			my: "left top",
       
    46 			at: "right top"
       
    47 		},
       
    48 		role: "menu",
       
    49 
       
    50 		// Callbacks
       
    51 		blur: null,
       
    52 		focus: null,
       
    53 		select: null
       
    54 	},
       
    55 
       
    56 	_create: function() {
       
    57 		this.activeMenu = this.element;
       
    58 
       
    59 		// Flag used to prevent firing of the click handler
       
    60 		// as the event bubbles up through nested menus
       
    61 		this.mouseHandled = false;
       
    62 		this.element
       
    63 			.uniqueId()
       
    64 			.attr( {
       
    65 				role: this.options.role,
       
    66 				tabIndex: 0
       
    67 			} );
       
    68 
       
    69 		this._addClass( "ui-menu", "ui-widget ui-widget-content" );
       
    70 		this._on( {
       
    71 
       
    72 			// Prevent focus from sticking to links inside menu after clicking
       
    73 			// them (focus should always stay on UL during navigation).
       
    74 			"mousedown .ui-menu-item": function( event ) {
       
    75 				event.preventDefault();
       
    76 			},
       
    77 			"click .ui-menu-item": function( event ) {
       
    78 				var target = $( event.target );
       
    79 				var active = $( $.ui.safeActiveElement( this.document[ 0 ] ) );
       
    80 				if ( !this.mouseHandled && target.not( ".ui-state-disabled" ).length ) {
       
    81 					this.select( event );
       
    82 
       
    83 					// Only set the mouseHandled flag if the event will bubble, see #9469.
       
    84 					if ( !event.isPropagationStopped() ) {
       
    85 						this.mouseHandled = true;
       
    86 					}
       
    87 
       
    88 					// Open submenu on click
       
    89 					if ( target.has( ".ui-menu" ).length ) {
       
    90 						this.expand( event );
       
    91 					} else if ( !this.element.is( ":focus" ) &&
       
    92 							active.closest( ".ui-menu" ).length ) {
       
    93 
       
    94 						// Redirect focus to the menu
       
    95 						this.element.trigger( "focus", [ true ] );
       
    96 
       
    97 						// If the active item is on the top level, let it stay active.
       
    98 						// Otherwise, blur the active item since it is no longer visible.
       
    99 						if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) {
       
   100 							clearTimeout( this.timer );
       
   101 						}
       
   102 					}
       
   103 				}
       
   104 			},
       
   105 			"mouseenter .ui-menu-item": function( event ) {
       
   106 
       
   107 				// Ignore mouse events while typeahead is active, see #10458.
       
   108 				// Prevents focusing the wrong item when typeahead causes a scroll while the mouse
       
   109 				// is over an item in the menu
       
   110 				if ( this.previousFilter ) {
       
   111 					return;
       
   112 				}
       
   113 
       
   114 				var actualTarget = $( event.target ).closest( ".ui-menu-item" ),
       
   115 					target = $( event.currentTarget );
       
   116 
       
   117 				// Ignore bubbled events on parent items, see #11641
       
   118 				if ( actualTarget[ 0 ] !== target[ 0 ] ) {
       
   119 					return;
       
   120 				}
       
   121 
       
   122 				// Remove ui-state-active class from siblings of the newly focused menu item
       
   123 				// to avoid a jump caused by adjacent elements both having a class with a border
       
   124 				this._removeClass( target.siblings().children( ".ui-state-active" ),
       
   125 					null, "ui-state-active" );
       
   126 				this.focus( event, target );
       
   127 			},
       
   128 			mouseleave: "collapseAll",
       
   129 			"mouseleave .ui-menu": "collapseAll",
       
   130 			focus: function( event, keepActiveItem ) {
       
   131 
       
   132 				// If there's already an active item, keep it active
       
   133 				// If not, activate the first item
       
   134 				var item = this.active || this.element.find( this.options.items ).eq( 0 );
       
   135 
       
   136 				if ( !keepActiveItem ) {
       
   137 					this.focus( event, item );
       
   138 				}
       
   139 			},
       
   140 			blur: function( event ) {
       
   141 				this._delay( function() {
       
   142 					var notContained = !$.contains(
       
   143 						this.element[ 0 ],
       
   144 						$.ui.safeActiveElement( this.document[ 0 ] )
       
   145 					);
       
   146 					if ( notContained ) {
       
   147 						this.collapseAll( event );
       
   148 					}
       
   149 				} );
       
   150 			},
       
   151 			keydown: "_keydown"
       
   152 		} );
       
   153 
       
   154 		this.refresh();
       
   155 
       
   156 		// Clicks outside of a menu collapse any open menus
       
   157 		this._on( this.document, {
       
   158 			click: function( event ) {
       
   159 				if ( this._closeOnDocumentClick( event ) ) {
       
   160 					this.collapseAll( event );
       
   161 				}
       
   162 
       
   163 				// Reset the mouseHandled flag
       
   164 				this.mouseHandled = false;
       
   165 			}
       
   166 		} );
       
   167 	},
       
   168 
       
   169 	_destroy: function() {
       
   170 		var items = this.element.find( ".ui-menu-item" )
       
   171 				.removeAttr( "role aria-disabled" ),
       
   172 			submenus = items.children( ".ui-menu-item-wrapper" )
       
   173 				.removeUniqueId()
       
   174 				.removeAttr( "tabIndex role aria-haspopup" );
       
   175 
       
   176 		// Destroy (sub)menus
       
   177 		this.element
       
   178 			.removeAttr( "aria-activedescendant" )
       
   179 			.find( ".ui-menu" ).addBack()
       
   180 				.removeAttr( "role aria-labelledby aria-expanded aria-hidden aria-disabled " +
       
   181 					"tabIndex" )
       
   182 				.removeUniqueId()
       
   183 				.show();
       
   184 
       
   185 		submenus.children().each( function() {
       
   186 			var elem = $( this );
       
   187 			if ( elem.data( "ui-menu-submenu-caret" ) ) {
       
   188 				elem.remove();
       
   189 			}
       
   190 		} );
       
   191 	},
       
   192 
       
   193 	_keydown: function( event ) {
       
   194 		var match, prev, character, skip,
       
   195 			preventDefault = true;
       
   196 
       
   197 		switch ( event.keyCode ) {
       
   198 		case $.ui.keyCode.PAGE_UP:
       
   199 			this.previousPage( event );
       
   200 			break;
       
   201 		case $.ui.keyCode.PAGE_DOWN:
       
   202 			this.nextPage( event );
       
   203 			break;
       
   204 		case $.ui.keyCode.HOME:
       
   205 			this._move( "first", "first", event );
       
   206 			break;
       
   207 		case $.ui.keyCode.END:
       
   208 			this._move( "last", "last", event );
       
   209 			break;
       
   210 		case $.ui.keyCode.UP:
       
   211 			this.previous( event );
       
   212 			break;
       
   213 		case $.ui.keyCode.DOWN:
       
   214 			this.next( event );
       
   215 			break;
       
   216 		case $.ui.keyCode.LEFT:
       
   217 			this.collapse( event );
       
   218 			break;
       
   219 		case $.ui.keyCode.RIGHT:
       
   220 			if ( this.active && !this.active.is( ".ui-state-disabled" ) ) {
       
   221 				this.expand( event );
       
   222 			}
       
   223 			break;
       
   224 		case $.ui.keyCode.ENTER:
       
   225 		case $.ui.keyCode.SPACE:
       
   226 			this._activate( event );
       
   227 			break;
       
   228 		case $.ui.keyCode.ESCAPE:
       
   229 			this.collapse( event );
       
   230 			break;
       
   231 		default:
       
   232 			preventDefault = false;
       
   233 			prev = this.previousFilter || "";
       
   234 			skip = false;
       
   235 
       
   236 			// Support number pad values
       
   237 			character = event.keyCode >= 96 && event.keyCode <= 105 ?
       
   238 				( event.keyCode - 96 ).toString() : String.fromCharCode( event.keyCode );
       
   239 
       
   240 			clearTimeout( this.filterTimer );
       
   241 
       
   242 			if ( character === prev ) {
       
   243 				skip = true;
       
   244 			} else {
       
   245 				character = prev + character;
       
   246 			}
       
   247 
       
   248 			match = this._filterMenuItems( character );
       
   249 			match = skip && match.index( this.active.next() ) !== -1 ?
       
   250 				this.active.nextAll( ".ui-menu-item" ) :
       
   251 				match;
       
   252 
       
   253 			// If no matches on the current filter, reset to the last character pressed
       
   254 			// to move down the menu to the first item that starts with that character
       
   255 			if ( !match.length ) {
       
   256 				character = String.fromCharCode( event.keyCode );
       
   257 				match = this._filterMenuItems( character );
       
   258 			}
       
   259 
       
   260 			if ( match.length ) {
       
   261 				this.focus( event, match );
       
   262 				this.previousFilter = character;
       
   263 				this.filterTimer = this._delay( function() {
       
   264 					delete this.previousFilter;
       
   265 				}, 1000 );
       
   266 			} else {
       
   267 				delete this.previousFilter;
       
   268 			}
       
   269 		}
       
   270 
       
   271 		if ( preventDefault ) {
       
   272 			event.preventDefault();
       
   273 		}
       
   274 	},
       
   275 
       
   276 	_activate: function( event ) {
       
   277 		if ( this.active && !this.active.is( ".ui-state-disabled" ) ) {
       
   278 			if ( this.active.children( "[aria-haspopup='true']" ).length ) {
       
   279 				this.expand( event );
       
   280 			} else {
       
   281 				this.select( event );
       
   282 			}
       
   283 		}
       
   284 	},
       
   285 
       
   286 	refresh: function() {
       
   287 		var menus, items, newSubmenus, newItems, newWrappers,
       
   288 			that = this,
       
   289 			icon = this.options.icons.submenu,
       
   290 			submenus = this.element.find( this.options.menus );
       
   291 
       
   292 		this._toggleClass( "ui-menu-icons", null, !!this.element.find( ".ui-icon" ).length );
       
   293 
       
   294 		// Initialize nested menus
       
   295 		newSubmenus = submenus.filter( ":not(.ui-menu)" )
       
   296 			.hide()
       
   297 			.attr( {
       
   298 				role: this.options.role,
       
   299 				"aria-hidden": "true",
       
   300 				"aria-expanded": "false"
       
   301 			} )
       
   302 			.each( function() {
       
   303 				var menu = $( this ),
       
   304 					item = menu.prev(),
       
   305 					submenuCaret = $( "<span>" ).data( "ui-menu-submenu-caret", true );
       
   306 
       
   307 				that._addClass( submenuCaret, "ui-menu-icon", "ui-icon " + icon );
       
   308 				item
       
   309 					.attr( "aria-haspopup", "true" )
       
   310 					.prepend( submenuCaret );
       
   311 				menu.attr( "aria-labelledby", item.attr( "id" ) );
       
   312 			} );
       
   313 
       
   314 		this._addClass( newSubmenus, "ui-menu", "ui-widget ui-widget-content ui-front" );
       
   315 
       
   316 		menus = submenus.add( this.element );
       
   317 		items = menus.find( this.options.items );
       
   318 
       
   319 		// Initialize menu-items containing spaces and/or dashes only as dividers
       
   320 		items.not( ".ui-menu-item" ).each( function() {
       
   321 			var item = $( this );
       
   322 			if ( that._isDivider( item ) ) {
       
   323 				that._addClass( item, "ui-menu-divider", "ui-widget-content" );
       
   324 			}
       
   325 		} );
       
   326 
       
   327 		// Don't refresh list items that are already adapted
       
   328 		newItems = items.not( ".ui-menu-item, .ui-menu-divider" );
       
   329 		newWrappers = newItems.children()
       
   330 			.not( ".ui-menu" )
       
   331 				.uniqueId()
       
   332 				.attr( {
       
   333 					tabIndex: -1,
       
   334 					role: this._itemRole()
       
   335 				} );
       
   336 		this._addClass( newItems, "ui-menu-item" )
       
   337 			._addClass( newWrappers, "ui-menu-item-wrapper" );
       
   338 
       
   339 		// Add aria-disabled attribute to any disabled menu item
       
   340 		items.filter( ".ui-state-disabled" ).attr( "aria-disabled", "true" );
       
   341 
       
   342 		// If the active item has been removed, blur the menu
       
   343 		if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
       
   344 			this.blur();
       
   345 		}
       
   346 	},
       
   347 
       
   348 	_itemRole: function() {
       
   349 		return {
       
   350 			menu: "menuitem",
       
   351 			listbox: "option"
       
   352 		}[ this.options.role ];
       
   353 	},
       
   354 
       
   355 	_setOption: function( key, value ) {
       
   356 		if ( key === "icons" ) {
       
   357 			var icons = this.element.find( ".ui-menu-icon" );
       
   358 			this._removeClass( icons, null, this.options.icons.submenu )
       
   359 				._addClass( icons, null, value.submenu );
       
   360 		}
       
   361 		this._super( key, value );
       
   362 	},
       
   363 
       
   364 	_setOptionDisabled: function( value ) {
       
   365 		this._super( value );
       
   366 
       
   367 		this.element.attr( "aria-disabled", String( value ) );
       
   368 		this._toggleClass( null, "ui-state-disabled", !!value );
       
   369 	},
       
   370 
       
   371 	focus: function( event, item ) {
       
   372 		var nested, focused, activeParent;
       
   373 		this.blur( event, event && event.type === "focus" );
       
   374 
       
   375 		this._scrollIntoView( item );
       
   376 
       
   377 		this.active = item.first();
       
   378 
       
   379 		focused = this.active.children( ".ui-menu-item-wrapper" );
       
   380 		this._addClass( focused, null, "ui-state-active" );
       
   381 
       
   382 		// Only update aria-activedescendant if there's a role
       
   383 		// otherwise we assume focus is managed elsewhere
       
   384 		if ( this.options.role ) {
       
   385 			this.element.attr( "aria-activedescendant", focused.attr( "id" ) );
       
   386 		}
       
   387 
       
   388 		// Highlight active parent menu item, if any
       
   389 		activeParent = this.active
       
   390 			.parent()
       
   391 				.closest( ".ui-menu-item" )
       
   392 					.children( ".ui-menu-item-wrapper" );
       
   393 		this._addClass( activeParent, null, "ui-state-active" );
       
   394 
       
   395 		if ( event && event.type === "keydown" ) {
       
   396 			this._close();
       
   397 		} else {
       
   398 			this.timer = this._delay( function() {
       
   399 				this._close();
       
   400 			}, this.delay );
       
   401 		}
       
   402 
       
   403 		nested = item.children( ".ui-menu" );
       
   404 		if ( nested.length && event && ( /^mouse/.test( event.type ) ) ) {
       
   405 			this._startOpening( nested );
       
   406 		}
       
   407 		this.activeMenu = item.parent();
       
   408 
       
   409 		this._trigger( "focus", event, { item: item } );
       
   410 	},
       
   411 
       
   412 	_scrollIntoView: function( item ) {
       
   413 		var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;
       
   414 		if ( this._hasScroll() ) {
       
   415 			borderTop = parseFloat( $.css( this.activeMenu[ 0 ], "borderTopWidth" ) ) || 0;
       
   416 			paddingTop = parseFloat( $.css( this.activeMenu[ 0 ], "paddingTop" ) ) || 0;
       
   417 			offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop;
       
   418 			scroll = this.activeMenu.scrollTop();
       
   419 			elementHeight = this.activeMenu.height();
       
   420 			itemHeight = item.outerHeight();
       
   421 
       
   422 			if ( offset < 0 ) {
       
   423 				this.activeMenu.scrollTop( scroll + offset );
       
   424 			} else if ( offset + itemHeight > elementHeight ) {
       
   425 				this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight );
       
   426 			}
       
   427 		}
       
   428 	},
       
   429 
       
   430 	blur: function( event, fromFocus ) {
       
   431 		if ( !fromFocus ) {
       
   432 			clearTimeout( this.timer );
       
   433 		}
       
   434 
       
   435 		if ( !this.active ) {
       
   436 			return;
       
   437 		}
       
   438 
       
   439 		this._removeClass( this.active.children( ".ui-menu-item-wrapper" ),
       
   440 			null, "ui-state-active" );
       
   441 
       
   442 		this._trigger( "blur", event, { item: this.active } );
       
   443 		this.active = null;
       
   444 	},
       
   445 
       
   446 	_startOpening: function( submenu ) {
       
   447 		clearTimeout( this.timer );
       
   448 
       
   449 		// Don't open if already open fixes a Firefox bug that caused a .5 pixel
       
   450 		// shift in the submenu position when mousing over the caret icon
       
   451 		if ( submenu.attr( "aria-hidden" ) !== "true" ) {
       
   452 			return;
       
   453 		}
       
   454 
       
   455 		this.timer = this._delay( function() {
       
   456 			this._close();
       
   457 			this._open( submenu );
       
   458 		}, this.delay );
       
   459 	},
       
   460 
       
   461 	_open: function( submenu ) {
       
   462 		var position = $.extend( {
       
   463 			of: this.active
       
   464 		}, this.options.position );
       
   465 
       
   466 		clearTimeout( this.timer );
       
   467 		this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) )
       
   468 			.hide()
       
   469 			.attr( "aria-hidden", "true" );
       
   470 
       
   471 		submenu
       
   472 			.show()
       
   473 			.removeAttr( "aria-hidden" )
       
   474 			.attr( "aria-expanded", "true" )
       
   475 			.position( position );
       
   476 	},
       
   477 
       
   478 	collapseAll: function( event, all ) {
       
   479 		clearTimeout( this.timer );
       
   480 		this.timer = this._delay( function() {
       
   481 
       
   482 			// If we were passed an event, look for the submenu that contains the event
       
   483 			var currentMenu = all ? this.element :
       
   484 				$( event && event.target ).closest( this.element.find( ".ui-menu" ) );
       
   485 
       
   486 			// If we found no valid submenu ancestor, use the main menu to close all
       
   487 			// sub menus anyway
       
   488 			if ( !currentMenu.length ) {
       
   489 				currentMenu = this.element;
       
   490 			}
       
   491 
       
   492 			this._close( currentMenu );
       
   493 
       
   494 			this.blur( event );
       
   495 
       
   496 			// Work around active item staying active after menu is blurred
       
   497 			this._removeClass( currentMenu.find( ".ui-state-active" ), null, "ui-state-active" );
       
   498 
       
   499 			this.activeMenu = currentMenu;
       
   500 		}, this.delay );
       
   501 	},
       
   502 
       
   503 	// With no arguments, closes the currently active menu - if nothing is active
       
   504 	// it closes all menus.  If passed an argument, it will search for menus BELOW
       
   505 	_close: function( startMenu ) {
       
   506 		if ( !startMenu ) {
       
   507 			startMenu = this.active ? this.active.parent() : this.element;
       
   508 		}
       
   509 
       
   510 		startMenu.find( ".ui-menu" )
       
   511 			.hide()
       
   512 			.attr( "aria-hidden", "true" )
       
   513 			.attr( "aria-expanded", "false" );
       
   514 	},
       
   515 
       
   516 	_closeOnDocumentClick: function( event ) {
       
   517 		return !$( event.target ).closest( ".ui-menu" ).length;
       
   518 	},
       
   519 
       
   520 	_isDivider: function( item ) {
       
   521 
       
   522 		// Match hyphen, em dash, en dash
       
   523 		return !/[^\-\u2014\u2013\s]/.test( item.text() );
       
   524 	},
       
   525 
       
   526 	collapse: function( event ) {
       
   527 		var newItem = this.active &&
       
   528 			this.active.parent().closest( ".ui-menu-item", this.element );
       
   529 		if ( newItem && newItem.length ) {
       
   530 			this._close();
       
   531 			this.focus( event, newItem );
       
   532 		}
       
   533 	},
       
   534 
       
   535 	expand: function( event ) {
       
   536 		var newItem = this.active &&
       
   537 			this.active
       
   538 				.children( ".ui-menu " )
       
   539 					.find( this.options.items )
       
   540 						.first();
       
   541 
       
   542 		if ( newItem && newItem.length ) {
       
   543 			this._open( newItem.parent() );
       
   544 
       
   545 			// Delay so Firefox will not hide activedescendant change in expanding submenu from AT
       
   546 			this._delay( function() {
       
   547 				this.focus( event, newItem );
       
   548 			} );
       
   549 		}
       
   550 	},
       
   551 
       
   552 	next: function( event ) {
       
   553 		this._move( "next", "first", event );
       
   554 	},
       
   555 
       
   556 	previous: function( event ) {
       
   557 		this._move( "prev", "last", event );
       
   558 	},
       
   559 
       
   560 	isFirstItem: function() {
       
   561 		return this.active && !this.active.prevAll( ".ui-menu-item" ).length;
       
   562 	},
       
   563 
       
   564 	isLastItem: function() {
       
   565 		return this.active && !this.active.nextAll( ".ui-menu-item" ).length;
       
   566 	},
       
   567 
       
   568 	_move: function( direction, filter, event ) {
       
   569 		var next;
       
   570 		if ( this.active ) {
       
   571 			if ( direction === "first" || direction === "last" ) {
       
   572 				next = this.active
       
   573 					[ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" )
       
   574 					.eq( -1 );
       
   575 			} else {
       
   576 				next = this.active
       
   577 					[ direction + "All" ]( ".ui-menu-item" )
       
   578 					.eq( 0 );
       
   579 			}
       
   580 		}
       
   581 		if ( !next || !next.length || !this.active ) {
       
   582 			next = this.activeMenu.find( this.options.items )[ filter ]();
       
   583 		}
       
   584 
       
   585 		this.focus( event, next );
       
   586 	},
       
   587 
       
   588 	nextPage: function( event ) {
       
   589 		var item, base, height;
       
   590 
       
   591 		if ( !this.active ) {
       
   592 			this.next( event );
       
   593 			return;
       
   594 		}
       
   595 		if ( this.isLastItem() ) {
       
   596 			return;
       
   597 		}
       
   598 		if ( this._hasScroll() ) {
       
   599 			base = this.active.offset().top;
       
   600 			height = this.element.height();
       
   601 			this.active.nextAll( ".ui-menu-item" ).each( function() {
       
   602 				item = $( this );
       
   603 				return item.offset().top - base - height < 0;
       
   604 			} );
       
   605 
       
   606 			this.focus( event, item );
       
   607 		} else {
       
   608 			this.focus( event, this.activeMenu.find( this.options.items )
       
   609 				[ !this.active ? "first" : "last" ]() );
       
   610 		}
       
   611 	},
       
   612 
       
   613 	previousPage: function( event ) {
       
   614 		var item, base, height;
       
   615 		if ( !this.active ) {
       
   616 			this.next( event );
       
   617 			return;
       
   618 		}
       
   619 		if ( this.isFirstItem() ) {
       
   620 			return;
       
   621 		}
       
   622 		if ( this._hasScroll() ) {
       
   623 			base = this.active.offset().top;
       
   624 			height = this.element.height();
       
   625 			this.active.prevAll( ".ui-menu-item" ).each( function() {
       
   626 				item = $( this );
       
   627 				return item.offset().top - base + height > 0;
       
   628 			} );
       
   629 
       
   630 			this.focus( event, item );
       
   631 		} else {
       
   632 			this.focus( event, this.activeMenu.find( this.options.items ).first() );
       
   633 		}
       
   634 	},
       
   635 
       
   636 	_hasScroll: function() {
       
   637 		return this.element.outerHeight() < this.element.prop( "scrollHeight" );
       
   638 	},
       
   639 
       
   640 	select: function( event ) {
       
   641 
       
   642 		// TODO: It should never be possible to not have an active item at this
       
   643 		// point, but the tests don't trigger mouseenter before click.
       
   644 		this.active = this.active || $( event.target ).closest( ".ui-menu-item" );
       
   645 		var ui = { item: this.active };
       
   646 		if ( !this.active.has( ".ui-menu" ).length ) {
       
   647 			this.collapseAll( event, true );
       
   648 		}
       
   649 		this._trigger( "select", event, ui );
       
   650 	},
       
   651 
       
   652 	_filterMenuItems: function( character ) {
       
   653 		var escapedCharacter = character.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ),
       
   654 			regex = new RegExp( "^" + escapedCharacter, "i" );
       
   655 
       
   656 		return this.activeMenu
       
   657 			.find( this.options.items )
       
   658 
       
   659 				// Only match on items, not dividers or other content (#10571)
       
   660 				.filter( ".ui-menu-item" )
       
   661 					.filter( function() {
       
   662 						return regex.test(
       
   663 							$.trim( $( this ).children( ".ui-menu-item-wrapper" ).text() ) );
       
   664 					} );
       
   665 	}
       
   666 } );
       
   667 
       
   668 } ) );