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