[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/js/jquery/ui/ -> selectmenu.js (source)

   1  /*!
   2   * jQuery UI Selectmenu 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: Selectmenu
  11  //>>group: Widgets
  12  /* eslint-disable max-len */
  13  //>>description: Duplicates and extends the functionality of a native HTML select element, allowing it to be customizable in behavior and appearance far beyond the limitations of a native select.
  14  /* eslint-enable max-len */
  15  //>>docs: https://api.jqueryui.com/selectmenu/
  16  //>>demos: https://jqueryui.com/selectmenu/
  17  //>>css.structure: ../../themes/base/core.css
  18  //>>css.structure: ../../themes/base/selectmenu.css, ../../themes/base/button.css
  19  //>>css.theme: ../../themes/base/theme.css
  20  
  21  ( function( factory ) {
  22      "use strict";
  23  
  24      if ( typeof define === "function" && define.amd ) {
  25  
  26          // AMD. Register as an anonymous module.
  27          define( [
  28              "jquery",
  29              "./menu",
  30              "../form-reset-mixin",
  31              "../keycode",
  32              "../labels",
  33              "../position",
  34              "../unique-id",
  35              "../version",
  36              "../widget"
  37          ], factory );
  38      } else {
  39  
  40          // Browser globals
  41          factory( jQuery );
  42      }
  43  } )( function( $ ) {
  44  "use strict";
  45  
  46  return $.widget( "ui.selectmenu", [ $.ui.formResetMixin, {
  47      version: "1.13.3",
  48      defaultElement: "<select>",
  49      options: {
  50          appendTo: null,
  51          classes: {
  52              "ui-selectmenu-button-open": "ui-corner-top",
  53              "ui-selectmenu-button-closed": "ui-corner-all"
  54          },
  55          disabled: null,
  56          icons: {
  57              button: "ui-icon-triangle-1-s"
  58          },
  59          position: {
  60              my: "left top",
  61              at: "left bottom",
  62              collision: "none"
  63          },
  64          width: false,
  65  
  66          // Callbacks
  67          change: null,
  68          close: null,
  69          focus: null,
  70          open: null,
  71          select: null
  72      },
  73  
  74      _create: function() {
  75          var selectmenuId = this.element.uniqueId().attr( "id" );
  76          this.ids = {
  77              element: selectmenuId,
  78              button: selectmenuId + "-button",
  79              menu: selectmenuId + "-menu"
  80          };
  81  
  82          this._drawButton();
  83          this._drawMenu();
  84          this._bindFormResetHandler();
  85  
  86          this._rendered = false;
  87          this.menuItems = $();
  88      },
  89  
  90      _drawButton: function() {
  91          var icon,
  92              that = this,
  93              item = this._parseOption(
  94                  this.element.find( "option:selected" ),
  95                  this.element[ 0 ].selectedIndex
  96              );
  97  
  98          // Associate existing label with the new button
  99          this.labels = this.element.labels().attr( "for", this.ids.button );
 100          this._on( this.labels, {
 101              click: function( event ) {
 102                  this.button.trigger( "focus" );
 103                  event.preventDefault();
 104              }
 105          } );
 106  
 107          // Hide original select element
 108          this.element.hide();
 109  
 110          // Create button
 111          this.button = $( "<span>", {
 112              tabindex: this.options.disabled ? -1 : 0,
 113              id: this.ids.button,
 114              role: "combobox",
 115              "aria-expanded": "false",
 116              "aria-autocomplete": "list",
 117              "aria-owns": this.ids.menu,
 118              "aria-haspopup": "true",
 119              title: this.element.attr( "title" )
 120          } )
 121              .insertAfter( this.element );
 122  
 123          this._addClass( this.button, "ui-selectmenu-button ui-selectmenu-button-closed",
 124              "ui-button ui-widget" );
 125  
 126          icon = $( "<span>" ).appendTo( this.button );
 127          this._addClass( icon, "ui-selectmenu-icon", "ui-icon " + this.options.icons.button );
 128          this.buttonItem = this._renderButtonItem( item )
 129              .appendTo( this.button );
 130  
 131          if ( this.options.width !== false ) {
 132              this._resizeButton();
 133          }
 134  
 135          this._on( this.button, this._buttonEvents );
 136          this.button.one( "focusin", function() {
 137  
 138              // Delay rendering the menu items until the button receives focus.
 139              // The menu may have already been rendered via a programmatic open.
 140              if ( !that._rendered ) {
 141                  that._refreshMenu();
 142              }
 143          } );
 144      },
 145  
 146      _drawMenu: function() {
 147          var that = this;
 148  
 149          // Create menu
 150          this.menu = $( "<ul>", {
 151              "aria-hidden": "true",
 152              "aria-labelledby": this.ids.button,
 153              id: this.ids.menu
 154          } );
 155  
 156          // Wrap menu
 157          this.menuWrap = $( "<div>" ).append( this.menu );
 158          this._addClass( this.menuWrap, "ui-selectmenu-menu", "ui-front" );
 159          this.menuWrap.appendTo( this._appendTo() );
 160  
 161          // Initialize menu widget
 162          this.menuInstance = this.menu
 163              .menu( {
 164                  classes: {
 165                      "ui-menu": "ui-corner-bottom"
 166                  },
 167                  role: "listbox",
 168                  select: function( event, ui ) {
 169                      event.preventDefault();
 170  
 171                      // Support: IE8
 172                      // If the item was selected via a click, the text selection
 173                      // will be destroyed in IE
 174                      that._setSelection();
 175  
 176                      that._select( ui.item.data( "ui-selectmenu-item" ), event );
 177                  },
 178                  focus: function( event, ui ) {
 179                      var item = ui.item.data( "ui-selectmenu-item" );
 180  
 181                      // Prevent inital focus from firing and check if its a newly focused item
 182                      if ( that.focusIndex != null && item.index !== that.focusIndex ) {
 183                          that._trigger( "focus", event, { item: item } );
 184                          if ( !that.isOpen ) {
 185                              that._select( item, event );
 186                          }
 187                      }
 188                      that.focusIndex = item.index;
 189  
 190                      that.button.attr( "aria-activedescendant",
 191                          that.menuItems.eq( item.index ).attr( "id" ) );
 192                  }
 193              } )
 194              .menu( "instance" );
 195  
 196          // Don't close the menu on mouseleave
 197          this.menuInstance._off( this.menu, "mouseleave" );
 198  
 199          // Cancel the menu's collapseAll on document click
 200          this.menuInstance._closeOnDocumentClick = function() {
 201              return false;
 202          };
 203  
 204          // Selects often contain empty items, but never contain dividers
 205          this.menuInstance._isDivider = function() {
 206              return false;
 207          };
 208      },
 209  
 210      refresh: function() {
 211          this._refreshMenu();
 212          this.buttonItem.replaceWith(
 213              this.buttonItem = this._renderButtonItem(
 214  
 215                  // Fall back to an empty object in case there are no options
 216                  this._getSelectedItem().data( "ui-selectmenu-item" ) || {}
 217              )
 218          );
 219          if ( this.options.width === null ) {
 220              this._resizeButton();
 221          }
 222      },
 223  
 224      _refreshMenu: function() {
 225          var item,
 226              options = this.element.find( "option" );
 227  
 228          this.menu.empty();
 229  
 230          this._parseOptions( options );
 231          this._renderMenu( this.menu, this.items );
 232  
 233          this.menuInstance.refresh();
 234          this.menuItems = this.menu.find( "li" )
 235              .not( ".ui-selectmenu-optgroup" )
 236                  .find( ".ui-menu-item-wrapper" );
 237  
 238          this._rendered = true;
 239  
 240          if ( !options.length ) {
 241              return;
 242          }
 243  
 244          item = this._getSelectedItem();
 245  
 246          // Update the menu to have the correct item focused
 247          this.menuInstance.focus( null, item );
 248          this._setAria( item.data( "ui-selectmenu-item" ) );
 249  
 250          // Set disabled state
 251          this._setOption( "disabled", this.element.prop( "disabled" ) );
 252      },
 253  
 254      open: function( event ) {
 255          if ( this.options.disabled ) {
 256              return;
 257          }
 258  
 259          // If this is the first time the menu is being opened, render the items
 260          if ( !this._rendered ) {
 261              this._refreshMenu();
 262          } else {
 263  
 264              // Menu clears focus on close, reset focus to selected item
 265              this._removeClass( this.menu.find( ".ui-state-active" ), null, "ui-state-active" );
 266              this.menuInstance.focus( null, this._getSelectedItem() );
 267          }
 268  
 269          // If there are no options, don't open the menu
 270          if ( !this.menuItems.length ) {
 271              return;
 272          }
 273  
 274          this.isOpen = true;
 275          this._toggleAttr();
 276          this._resizeMenu();
 277          this._position();
 278  
 279          this._on( this.document, this._documentClick );
 280  
 281          this._trigger( "open", event );
 282      },
 283  
 284      _position: function() {
 285          this.menuWrap.position( $.extend( { of: this.button }, this.options.position ) );
 286      },
 287  
 288      close: function( event ) {
 289          if ( !this.isOpen ) {
 290              return;
 291          }
 292  
 293          this.isOpen = false;
 294          this._toggleAttr();
 295  
 296          this.range = null;
 297          this._off( this.document );
 298  
 299          this._trigger( "close", event );
 300      },
 301  
 302      widget: function() {
 303          return this.button;
 304      },
 305  
 306      menuWidget: function() {
 307          return this.menu;
 308      },
 309  
 310      _renderButtonItem: function( item ) {
 311          var buttonItem = $( "<span>" );
 312  
 313          this._setText( buttonItem, item.label );
 314          this._addClass( buttonItem, "ui-selectmenu-text" );
 315  
 316          return buttonItem;
 317      },
 318  
 319      _renderMenu: function( ul, items ) {
 320          var that = this,
 321              currentOptgroup = "";
 322  
 323          $.each( items, function( index, item ) {
 324              var li;
 325  
 326              if ( item.optgroup !== currentOptgroup ) {
 327                  li = $( "<li>", {
 328                      text: item.optgroup
 329                  } );
 330                  that._addClass( li, "ui-selectmenu-optgroup", "ui-menu-divider" +
 331                      ( item.element.parent( "optgroup" ).prop( "disabled" ) ?
 332                          " ui-state-disabled" :
 333                          "" ) );
 334  
 335                  li.appendTo( ul );
 336  
 337                  currentOptgroup = item.optgroup;
 338              }
 339  
 340              that._renderItemData( ul, item );
 341          } );
 342      },
 343  
 344      _renderItemData: function( ul, item ) {
 345          return this._renderItem( ul, item ).data( "ui-selectmenu-item", item );
 346      },
 347  
 348      _renderItem: function( ul, item ) {
 349          var li = $( "<li>" ),
 350              wrapper = $( "<div>", {
 351                  title: item.element.attr( "title" )
 352              } );
 353  
 354          if ( item.disabled ) {
 355              this._addClass( li, null, "ui-state-disabled" );
 356          }
 357  
 358          if ( item.hidden ) {
 359              li.prop( "hidden", true );
 360          } else {
 361              this._setText( wrapper, item.label );
 362          }
 363  
 364          return li.append( wrapper ).appendTo( ul );
 365      },
 366  
 367      _setText: function( element, value ) {
 368          if ( value ) {
 369              element.text( value );
 370          } else {
 371              element.html( "&#160;" );
 372          }
 373      },
 374  
 375      _move: function( direction, event ) {
 376          var item, next,
 377              filter = ".ui-menu-item";
 378  
 379          if ( this.isOpen ) {
 380              item = this.menuItems.eq( this.focusIndex ).parent( "li" );
 381          } else {
 382              item = this.menuItems.eq( this.element[ 0 ].selectedIndex ).parent( "li" );
 383              filter += ":not(.ui-state-disabled)";
 384          }
 385  
 386          if ( direction === "first" || direction === "last" ) {
 387              next = item[ direction === "first" ? "prevAll" : "nextAll" ]( filter ).eq( -1 );
 388          } else {
 389              next = item[ direction + "All" ]( filter ).eq( 0 );
 390          }
 391  
 392          if ( next.length ) {
 393              this.menuInstance.focus( event, next );
 394          }
 395      },
 396  
 397      _getSelectedItem: function() {
 398          return this.menuItems.eq( this.element[ 0 ].selectedIndex ).parent( "li" );
 399      },
 400  
 401      _toggle: function( event ) {
 402          this[ this.isOpen ? "close" : "open" ]( event );
 403      },
 404  
 405      _setSelection: function() {
 406          var selection;
 407  
 408          if ( !this.range ) {
 409              return;
 410          }
 411  
 412          if ( window.getSelection ) {
 413              selection = window.getSelection();
 414              selection.removeAllRanges();
 415              selection.addRange( this.range );
 416  
 417          // Support: IE8
 418          } else {
 419              this.range.select();
 420          }
 421  
 422          // Support: IE
 423          // Setting the text selection kills the button focus in IE, but
 424          // restoring the focus doesn't kill the selection.
 425          this.button.trigger( "focus" );
 426      },
 427  
 428      _documentClick: {
 429          mousedown: function( event ) {
 430              if ( !this.isOpen ) {
 431                  return;
 432              }
 433  
 434              if ( !$( event.target ).closest( ".ui-selectmenu-menu, #" +
 435                  $.escapeSelector( this.ids.button ) ).length ) {
 436                  this.close( event );
 437              }
 438          }
 439      },
 440  
 441      _buttonEvents: {
 442  
 443          // Prevent text selection from being reset when interacting with the selectmenu (#10144)
 444          mousedown: function() {
 445              var selection;
 446  
 447              if ( window.getSelection ) {
 448                  selection = window.getSelection();
 449                  if ( selection.rangeCount ) {
 450                      this.range = selection.getRangeAt( 0 );
 451                  }
 452  
 453              // Support: IE8
 454              } else {
 455                  this.range = document.selection.createRange();
 456              }
 457          },
 458  
 459          click: function( event ) {
 460              this._setSelection();
 461              this._toggle( event );
 462          },
 463  
 464          keydown: function( event ) {
 465              var preventDefault = true;
 466              switch ( event.keyCode ) {
 467              case $.ui.keyCode.TAB:
 468              case $.ui.keyCode.ESCAPE:
 469                  this.close( event );
 470                  preventDefault = false;
 471                  break;
 472              case $.ui.keyCode.ENTER:
 473                  if ( this.isOpen ) {
 474                      this._selectFocusedItem( event );
 475                  }
 476                  break;
 477              case $.ui.keyCode.UP:
 478                  if ( event.altKey ) {
 479                      this._toggle( event );
 480                  } else {
 481                      this._move( "prev", event );
 482                  }
 483                  break;
 484              case $.ui.keyCode.DOWN:
 485                  if ( event.altKey ) {
 486                      this._toggle( event );
 487                  } else {
 488                      this._move( "next", event );
 489                  }
 490                  break;
 491              case $.ui.keyCode.SPACE:
 492                  if ( this.isOpen ) {
 493                      this._selectFocusedItem( event );
 494                  } else {
 495                      this._toggle( event );
 496                  }
 497                  break;
 498              case $.ui.keyCode.LEFT:
 499                  this._move( "prev", event );
 500                  break;
 501              case $.ui.keyCode.RIGHT:
 502                  this._move( "next", event );
 503                  break;
 504              case $.ui.keyCode.HOME:
 505              case $.ui.keyCode.PAGE_UP:
 506                  this._move( "first", event );
 507                  break;
 508              case $.ui.keyCode.END:
 509              case $.ui.keyCode.PAGE_DOWN:
 510                  this._move( "last", event );
 511                  break;
 512              default:
 513                  this.menu.trigger( event );
 514                  preventDefault = false;
 515              }
 516  
 517              if ( preventDefault ) {
 518                  event.preventDefault();
 519              }
 520          }
 521      },
 522  
 523      _selectFocusedItem: function( event ) {
 524          var item = this.menuItems.eq( this.focusIndex ).parent( "li" );
 525          if ( !item.hasClass( "ui-state-disabled" ) ) {
 526              this._select( item.data( "ui-selectmenu-item" ), event );
 527          }
 528      },
 529  
 530      _select: function( item, event ) {
 531          var oldIndex = this.element[ 0 ].selectedIndex;
 532  
 533          // Change native select element
 534          this.element[ 0 ].selectedIndex = item.index;
 535          this.buttonItem.replaceWith( this.buttonItem = this._renderButtonItem( item ) );
 536          this._setAria( item );
 537          this._trigger( "select", event, { item: item } );
 538  
 539          if ( item.index !== oldIndex ) {
 540              this._trigger( "change", event, { item: item } );
 541          }
 542  
 543          this.close( event );
 544      },
 545  
 546      _setAria: function( item ) {
 547          var id = this.menuItems.eq( item.index ).attr( "id" );
 548  
 549          this.button.attr( {
 550              "aria-labelledby": id,
 551              "aria-activedescendant": id
 552          } );
 553          this.menu.attr( "aria-activedescendant", id );
 554      },
 555  
 556      _setOption: function( key, value ) {
 557          if ( key === "icons" ) {
 558              var icon = this.button.find( "span.ui-icon" );
 559              this._removeClass( icon, null, this.options.icons.button )
 560                  ._addClass( icon, null, value.button );
 561          }
 562  
 563          this._super( key, value );
 564  
 565          if ( key === "appendTo" ) {
 566              this.menuWrap.appendTo( this._appendTo() );
 567          }
 568  
 569          if ( key === "width" ) {
 570              this._resizeButton();
 571          }
 572      },
 573  
 574      _setOptionDisabled: function( value ) {
 575          this._super( value );
 576  
 577          this.menuInstance.option( "disabled", value );
 578          this.button.attr( "aria-disabled", value );
 579          this._toggleClass( this.button, null, "ui-state-disabled", value );
 580  
 581          this.element.prop( "disabled", value );
 582          if ( value ) {
 583              this.button.attr( "tabindex", -1 );
 584              this.close();
 585          } else {
 586              this.button.attr( "tabindex", 0 );
 587          }
 588      },
 589  
 590      _appendTo: function() {
 591          var element = this.options.appendTo;
 592  
 593          if ( element ) {
 594              element = element.jquery || element.nodeType ?
 595                  $( element ) :
 596                  this.document.find( element ).eq( 0 );
 597          }
 598  
 599          if ( !element || !element[ 0 ] ) {
 600              element = this.element.closest( ".ui-front, dialog" );
 601          }
 602  
 603          if ( !element.length ) {
 604              element = this.document[ 0 ].body;
 605          }
 606  
 607          return element;
 608      },
 609  
 610      _toggleAttr: function() {
 611          this.button.attr( "aria-expanded", this.isOpen );
 612  
 613          // We can't use two _toggleClass() calls here, because we need to make sure
 614          // we always remove classes first and add them second, otherwise if both classes have the
 615          // same theme class, it will be removed after we add it.
 616          this._removeClass( this.button, "ui-selectmenu-button-" +
 617              ( this.isOpen ? "closed" : "open" ) )
 618              ._addClass( this.button, "ui-selectmenu-button-" +
 619                  ( this.isOpen ? "open" : "closed" ) )
 620              ._toggleClass( this.menuWrap, "ui-selectmenu-open", null, this.isOpen );
 621  
 622          this.menu.attr( "aria-hidden", !this.isOpen );
 623      },
 624  
 625      _resizeButton: function() {
 626          var width = this.options.width;
 627  
 628          // For `width: false`, just remove inline style and stop
 629          if ( width === false ) {
 630              this.button.css( "width", "" );
 631              return;
 632          }
 633  
 634          // For `width: null`, match the width of the original element
 635          if ( width === null ) {
 636              width = this.element.show().outerWidth();
 637              this.element.hide();
 638          }
 639  
 640          this.button.outerWidth( width );
 641      },
 642  
 643      _resizeMenu: function() {
 644          this.menu.outerWidth( Math.max(
 645              this.button.outerWidth(),
 646  
 647              // Support: IE10
 648              // IE10 wraps long text (possibly a rounding bug)
 649              // so we add 1px to avoid the wrapping
 650              this.menu.width( "" ).outerWidth() + 1
 651          ) );
 652      },
 653  
 654      _getCreateOptions: function() {
 655          var options = this._super();
 656  
 657          options.disabled = this.element.prop( "disabled" );
 658  
 659          return options;
 660      },
 661  
 662      _parseOptions: function( options ) {
 663          var that = this,
 664              data = [];
 665          options.each( function( index, item ) {
 666              data.push( that._parseOption( $( item ), index ) );
 667          } );
 668          this.items = data;
 669      },
 670  
 671      _parseOption: function( option, index ) {
 672          var optgroup = option.parent( "optgroup" );
 673  
 674          return {
 675              element: option,
 676              index: index,
 677              value: option.val(),
 678              label: option.text(),
 679              hidden: optgroup.prop( "hidden" ) || option.prop( "hidden" ),
 680              optgroup: optgroup.attr( "label" ) || "",
 681              disabled: optgroup.prop( "disabled" ) || option.prop( "disabled" )
 682          };
 683      },
 684  
 685      _destroy: function() {
 686          this._unbindFormResetHandler();
 687          this.menuWrap.remove();
 688          this.button.remove();
 689          this.element.show();
 690          this.element.removeUniqueId();
 691          this.labels.attr( "for", this.ids.element );
 692      }
 693  } ] );
 694  
 695  } );


Generated : Thu Dec 26 08:20:01 2024 Cross-referenced by PHPXref