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