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