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