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


Generated : Mon Jul 27 08:20:18 2026 Cross-referenced by PHPXref