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