[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

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

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


Generated: Tue Sep 21 01:00:05 2021 Cross-referenced by PHPXref 0.7.1