[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

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

   1  /*!
   2   * jQuery UI Accordion 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: Accordion
  11  //>>group: Widgets
  12  //>>description: Displays collapsible content panels for presenting information in a limited amount of space.
  13  //>>docs: https://api.jqueryui.com/accordion/
  14  //>>demos: https://jqueryui.com/accordion/
  15  //>>css.structure: ../../themes/base/core.css
  16  //>>css.structure: ../../themes/base/accordion.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              "../version",
  28              "../keycode",
  29              "../unique-id",
  30              "../widget"
  31          ], factory );
  32      } else {
  33  
  34          // Browser globals
  35          factory( jQuery );
  36      }
  37  } )( function( $ ) {
  38  "use strict";
  39  
  40  return $.widget( "ui.accordion", {
  41      version: "1.14.2",
  42      options: {
  43          active: 0,
  44          animate: {},
  45          classes: {
  46              "ui-accordion-header": "ui-corner-top",
  47              "ui-accordion-header-collapsed": "ui-corner-all",
  48              "ui-accordion-content": "ui-corner-bottom"
  49          },
  50          collapsible: false,
  51          event: "click",
  52          header: function( elem ) {
  53              return elem
  54                  .find( "> li > :first-child" )
  55                  .add(
  56                      elem.find( "> :not(li)" )
  57  
  58                          // Support: jQuery <3.5 only
  59                          // We could use `.even()` but that's unavailable in older jQuery.
  60                          .filter( function( i ) {
  61                              return i % 2 === 0;
  62                          } )
  63                  );
  64          },
  65          heightStyle: "auto",
  66          icons: {
  67              activeHeader: "ui-icon-triangle-1-s",
  68              header: "ui-icon-triangle-1-e"
  69          },
  70  
  71          // Callbacks
  72          activate: null,
  73          beforeActivate: null
  74      },
  75  
  76      hideProps: {
  77          borderTopWidth: "hide",
  78          borderBottomWidth: "hide",
  79          paddingTop: "hide",
  80          paddingBottom: "hide",
  81          height: "hide"
  82      },
  83  
  84      showProps: {
  85          borderTopWidth: "show",
  86          borderBottomWidth: "show",
  87          paddingTop: "show",
  88          paddingBottom: "show",
  89          height: "show"
  90      },
  91  
  92      _create: function() {
  93          var options = this.options;
  94  
  95          this.prevShow = this.prevHide = $();
  96          this._addClass( "ui-accordion", "ui-widget ui-helper-reset" );
  97          this.element.attr( "role", "tablist" );
  98  
  99          // Don't allow collapsible: false and active: false / null
 100          if ( !options.collapsible && ( options.active === false || options.active == null ) ) {
 101              options.active = 0;
 102          }
 103  
 104          this._processPanels();
 105  
 106          // handle negative values
 107          if ( options.active < 0 ) {
 108              options.active += this.headers.length;
 109          }
 110          this._refresh();
 111      },
 112  
 113      _getCreateEventData: function() {
 114          return {
 115              header: this.active,
 116              panel: !this.active.length ? $() : this.active.next()
 117          };
 118      },
 119  
 120      _createIcons: function() {
 121          var icon, children,
 122              icons = this.options.icons;
 123  
 124          if ( icons ) {
 125              icon = $( "<span>" );
 126              this._addClass( icon, "ui-accordion-header-icon", "ui-icon " + icons.header );
 127              icon.prependTo( this.headers );
 128              children = this.active.children( ".ui-accordion-header-icon" );
 129              this._removeClass( children, icons.header )
 130                  ._addClass( children, null, icons.activeHeader )
 131                  ._addClass( this.headers, "ui-accordion-icons" );
 132          }
 133      },
 134  
 135      _destroyIcons: function() {
 136          this._removeClass( this.headers, "ui-accordion-icons" );
 137          this.headers.children( ".ui-accordion-header-icon" ).remove();
 138      },
 139  
 140      _destroy: function() {
 141          var contents;
 142  
 143          // Clean up main element
 144          this.element.removeAttr( "role" );
 145  
 146          // Clean up headers
 147          this.headers
 148              .removeAttr( "role aria-expanded aria-selected aria-controls tabIndex" )
 149              .removeUniqueId();
 150  
 151          this._destroyIcons();
 152  
 153          // Clean up content panels
 154          contents = this.headers.next()
 155              .css( "display", "" )
 156              .removeAttr( "role aria-hidden aria-labelledby" )
 157              .removeUniqueId();
 158  
 159          if ( this.options.heightStyle !== "content" ) {
 160              contents.css( "height", "" );
 161          }
 162      },
 163  
 164      _setOption: function( key, value ) {
 165          if ( key === "active" ) {
 166  
 167              // _activate() will handle invalid values and update this.options
 168              this._activate( value );
 169              return;
 170          }
 171  
 172          if ( key === "event" ) {
 173              if ( this.options.event ) {
 174                  this._off( this.headers, this.options.event );
 175              }
 176              this._setupEvents( value );
 177          }
 178  
 179          this._super( key, value );
 180  
 181          // Setting collapsible: false while collapsed; open first panel
 182          if ( key === "collapsible" && !value && this.options.active === false ) {
 183              this._activate( 0 );
 184          }
 185  
 186          if ( key === "icons" ) {
 187              this._destroyIcons();
 188              if ( value ) {
 189                  this._createIcons();
 190              }
 191          }
 192      },
 193  
 194      _setOptionDisabled: function( value ) {
 195          this._super( value );
 196  
 197          this.element.attr( "aria-disabled", value );
 198          this._toggleClass( null, "ui-state-disabled", !!value );
 199      },
 200  
 201      _keydown: function( event ) {
 202          if ( event.altKey || event.ctrlKey ) {
 203              return;
 204          }
 205  
 206          var keyCode = $.ui.keyCode,
 207              length = this.headers.length,
 208              currentIndex = this.headers.index( event.target ),
 209              toFocus = false;
 210  
 211          switch ( event.keyCode ) {
 212          case keyCode.RIGHT:
 213          case keyCode.DOWN:
 214              toFocus = this.headers[ ( currentIndex + 1 ) % length ];
 215              break;
 216          case keyCode.LEFT:
 217          case keyCode.UP:
 218              toFocus = this.headers[ ( currentIndex - 1 + length ) % length ];
 219              break;
 220          case keyCode.SPACE:
 221          case keyCode.ENTER:
 222              this._eventHandler( event );
 223              break;
 224          case keyCode.HOME:
 225              toFocus = this.headers[ 0 ];
 226              break;
 227          case keyCode.END:
 228              toFocus = this.headers[ length - 1 ];
 229              break;
 230          }
 231  
 232          if ( toFocus ) {
 233              $( event.target ).attr( "tabIndex", -1 );
 234              $( toFocus ).attr( "tabIndex", 0 );
 235              $( toFocus ).trigger( "focus" );
 236              event.preventDefault();
 237          }
 238      },
 239  
 240      _panelKeyDown: function( event ) {
 241          if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) {
 242              $( event.currentTarget ).prev().trigger( "focus" );
 243          }
 244      },
 245  
 246      refresh: function() {
 247          var options = this.options;
 248          this._processPanels();
 249  
 250          // Was collapsed or no panel
 251          if ( ( options.active === false && options.collapsible === true ) ||
 252                  !this.headers.length ) {
 253              options.active = false;
 254              this.active = $();
 255  
 256          // active false only when collapsible is true
 257          } else if ( options.active === false ) {
 258              this._activate( 0 );
 259  
 260          // was active, but active panel is gone
 261          } else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
 262  
 263              // all remaining panel are disabled
 264              if ( this.headers.length === this.headers.find( ".ui-state-disabled" ).length ) {
 265                  options.active = false;
 266                  this.active = $();
 267  
 268              // activate previous panel
 269              } else {
 270                  this._activate( Math.max( 0, options.active - 1 ) );
 271              }
 272  
 273          // was active, active panel still exists
 274          } else {
 275  
 276              // make sure active index is correct
 277              options.active = this.headers.index( this.active );
 278          }
 279  
 280          this._destroyIcons();
 281  
 282          this._refresh();
 283      },
 284  
 285      _processPanels: function() {
 286          var prevHeaders = this.headers,
 287              prevPanels = this.panels;
 288  
 289          if ( typeof this.options.header === "function" ) {
 290              this.headers = this.options.header( this.element );
 291          } else {
 292              this.headers = this.element.find( this.options.header );
 293          }
 294          this._addClass( this.headers, "ui-accordion-header ui-accordion-header-collapsed",
 295              "ui-state-default" );
 296  
 297          this.panels = this.headers.next().filter( ":not(.ui-accordion-content-active)" ).hide();
 298          this._addClass( this.panels, "ui-accordion-content", "ui-helper-reset ui-widget-content" );
 299  
 300          // Avoid memory leaks (#10056)
 301          if ( prevPanels ) {
 302              this._off( prevHeaders.not( this.headers ) );
 303              this._off( prevPanels.not( this.panels ) );
 304          }
 305      },
 306  
 307      _refresh: function() {
 308          var maxHeight,
 309              options = this.options,
 310              heightStyle = options.heightStyle,
 311              parent = this.element.parent();
 312  
 313          this.active = this._findActive( options.active );
 314          this._addClass( this.active, "ui-accordion-header-active", "ui-state-active" )
 315              ._removeClass( this.active, "ui-accordion-header-collapsed" );
 316          this._addClass( this.active.next(), "ui-accordion-content-active" );
 317          this.active.next().show();
 318  
 319          this.headers
 320              .attr( "role", "tab" )
 321              .each( function() {
 322                  var header = $( this ),
 323                      headerId = header.uniqueId().attr( "id" ),
 324                      panel = header.next(),
 325                      panelId = panel.uniqueId().attr( "id" );
 326                  header.attr( "aria-controls", panelId );
 327                  panel.attr( "aria-labelledby", headerId );
 328              } )
 329              .next()
 330                  .attr( "role", "tabpanel" );
 331  
 332          this.headers
 333              .not( this.active )
 334                  .attr( {
 335                      "aria-selected": "false",
 336                      "aria-expanded": "false",
 337                      tabIndex: -1
 338                  } )
 339                  .next()
 340                      .attr( {
 341                          "aria-hidden": "true"
 342                      } )
 343                      .hide();
 344  
 345          // Make sure at least one header is in the tab order
 346          if ( !this.active.length ) {
 347              this.headers.eq( 0 ).attr( "tabIndex", 0 );
 348          } else {
 349              this.active.attr( {
 350                  "aria-selected": "true",
 351                  "aria-expanded": "true",
 352                  tabIndex: 0
 353              } )
 354                  .next()
 355                      .attr( {
 356                          "aria-hidden": "false"
 357                      } );
 358          }
 359  
 360          this._createIcons();
 361  
 362          this._setupEvents( options.event );
 363  
 364          if ( heightStyle === "fill" ) {
 365              maxHeight = parent.height();
 366              this.element.siblings( ":visible" ).each( function() {
 367                  var elem = $( this ),
 368                      position = elem.css( "position" );
 369  
 370                  if ( position === "absolute" || position === "fixed" ) {
 371                      return;
 372                  }
 373                  maxHeight -= elem.outerHeight( true );
 374              } );
 375  
 376              this.headers.each( function() {
 377                  maxHeight -= $( this ).outerHeight( true );
 378              } );
 379  
 380              this.headers.next()
 381                  .each( function() {
 382                      $( this ).height( Math.max( 0, maxHeight -
 383                          $( this ).innerHeight() + $( this ).height() ) );
 384                  } )
 385                  .css( "overflow", "auto" );
 386          } else if ( heightStyle === "auto" ) {
 387              maxHeight = 0;
 388              this.headers.next()
 389                  .each( function() {
 390                      var isVisible = $( this ).is( ":visible" );
 391                      if ( !isVisible ) {
 392                          $( this ).show();
 393                      }
 394                      maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() );
 395                      if ( !isVisible ) {
 396                          $( this ).hide();
 397                      }
 398                  } )
 399                  .height( maxHeight );
 400          }
 401      },
 402  
 403      _activate: function( index ) {
 404          var active = this._findActive( index )[ 0 ];
 405  
 406          // Trying to activate the already active panel
 407          if ( active === this.active[ 0 ] ) {
 408              return;
 409          }
 410  
 411          // Trying to collapse, simulate a click on the currently active header
 412          active = active || this.active[ 0 ];
 413  
 414          this._eventHandler( {
 415              target: active,
 416              currentTarget: active,
 417              preventDefault: $.noop
 418          } );
 419      },
 420  
 421      _findActive: function( selector ) {
 422          return typeof selector === "number" ? this.headers.eq( selector ) : $();
 423      },
 424  
 425      _setupEvents: function( event ) {
 426          var events = {
 427              keydown: "_keydown"
 428          };
 429          if ( event ) {
 430              $.each( event.split( " " ), function( index, eventName ) {
 431                  events[ eventName ] = "_eventHandler";
 432              } );
 433          }
 434  
 435          this._off( this.headers.add( this.headers.next() ) );
 436          this._on( this.headers, events );
 437          this._on( this.headers.next(), { keydown: "_panelKeyDown" } );
 438          this._hoverable( this.headers );
 439          this._focusable( this.headers );
 440      },
 441  
 442      _eventHandler: function( event ) {
 443          var activeChildren, clickedChildren,
 444              options = this.options,
 445              active = this.active,
 446              clicked = $( event.currentTarget ),
 447              clickedIsActive = clicked[ 0 ] === active[ 0 ],
 448              collapsing = clickedIsActive && options.collapsible,
 449              toShow = collapsing ? $() : clicked.next(),
 450              toHide = active.next(),
 451              eventData = {
 452                  oldHeader: active,
 453                  oldPanel: toHide,
 454                  newHeader: collapsing ? $() : clicked,
 455                  newPanel: toShow
 456              };
 457  
 458          event.preventDefault();
 459  
 460          if (
 461  
 462                  // click on active header, but not collapsible
 463                  ( clickedIsActive && !options.collapsible ) ||
 464  
 465                  // allow canceling activation
 466                  ( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
 467              return;
 468          }
 469  
 470          options.active = collapsing ? false : this.headers.index( clicked );
 471  
 472          // When the call to ._toggle() comes after the class changes
 473          // it causes a very odd bug in IE 8 (see #6720)
 474          this.active = clickedIsActive ? $() : clicked;
 475          this._toggle( eventData );
 476  
 477          // Switch classes
 478          // corner classes on the previously active header stay after the animation
 479          this._removeClass( active, "ui-accordion-header-active", "ui-state-active" );
 480          if ( options.icons ) {
 481              activeChildren = active.children( ".ui-accordion-header-icon" );
 482              this._removeClass( activeChildren, null, options.icons.activeHeader )
 483                  ._addClass( activeChildren, null, options.icons.header );
 484          }
 485  
 486          if ( !clickedIsActive ) {
 487              this._removeClass( clicked, "ui-accordion-header-collapsed" )
 488                  ._addClass( clicked, "ui-accordion-header-active", "ui-state-active" );
 489              if ( options.icons ) {
 490                  clickedChildren = clicked.children( ".ui-accordion-header-icon" );
 491                  this._removeClass( clickedChildren, null, options.icons.header )
 492                      ._addClass( clickedChildren, null, options.icons.activeHeader );
 493              }
 494  
 495              this._addClass( clicked.next(), "ui-accordion-content-active" );
 496          }
 497      },
 498  
 499      _toggle: function( data ) {
 500          var toShow = data.newPanel,
 501              toHide = this.prevShow.length ? this.prevShow : data.oldPanel;
 502  
 503          // Handle activating a panel during the animation for another activation
 504          this.prevShow.add( this.prevHide ).stop( true, true );
 505          this.prevShow = toShow;
 506          this.prevHide = toHide;
 507  
 508          if ( this.options.animate ) {
 509              this._animate( toShow, toHide, data );
 510          } else {
 511              toHide.hide();
 512              toShow.show();
 513              this._toggleComplete( data );
 514          }
 515  
 516          toHide.attr( {
 517              "aria-hidden": "true"
 518          } );
 519          toHide.prev().attr( {
 520              "aria-selected": "false",
 521              "aria-expanded": "false"
 522          } );
 523  
 524          // if we're switching panels, remove the old header from the tab order
 525          // if we're opening from collapsed state, remove the previous header from the tab order
 526          // if we're collapsing, then keep the collapsing header in the tab order
 527          if ( toShow.length && toHide.length ) {
 528              toHide.prev().attr( {
 529                  "tabIndex": -1,
 530                  "aria-expanded": "false"
 531              } );
 532          } else if ( toShow.length ) {
 533              this.headers.filter( function() {
 534                  return parseInt( $( this ).attr( "tabIndex" ), 10 ) === 0;
 535              } )
 536                  .attr( "tabIndex", -1 );
 537          }
 538  
 539          toShow
 540              .attr( "aria-hidden", "false" )
 541              .prev()
 542                  .attr( {
 543                      "aria-selected": "true",
 544                      "aria-expanded": "true",
 545                      tabIndex: 0
 546                  } );
 547      },
 548  
 549      _animate: function( toShow, toHide, data ) {
 550          var total, easing, duration,
 551              that = this,
 552              adjust = 0,
 553              boxSizing = toShow.css( "box-sizing" ),
 554              down = toShow.length &&
 555                  ( !toHide.length || ( toShow.index() < toHide.index() ) ),
 556              animate = this.options.animate || {},
 557              options = down && animate.down || animate,
 558              complete = function() {
 559                  that._toggleComplete( data );
 560              };
 561  
 562          if ( typeof options === "number" ) {
 563              duration = options;
 564          }
 565          if ( typeof options === "string" ) {
 566              easing = options;
 567          }
 568  
 569          // fall back from options to animation in case of partial down settings
 570          easing = easing || options.easing || animate.easing;
 571          duration = duration || options.duration || animate.duration;
 572  
 573          if ( !toHide.length ) {
 574              return toShow.animate( this.showProps, duration, easing, complete );
 575          }
 576          if ( !toShow.length ) {
 577              return toHide.animate( this.hideProps, duration, easing, complete );
 578          }
 579  
 580          total = toShow.show().outerHeight();
 581          toHide.animate( this.hideProps, {
 582              duration: duration,
 583              easing: easing,
 584              step: function( now, fx ) {
 585                  fx.now = Math.round( now );
 586              }
 587          } );
 588          toShow
 589              .hide()
 590              .animate( this.showProps, {
 591                  duration: duration,
 592                  easing: easing,
 593                  complete: complete,
 594                  step: function( now, fx ) {
 595                      fx.now = Math.round( now );
 596                      if ( fx.prop !== "height" ) {
 597                          if ( boxSizing === "content-box" ) {
 598                              adjust += fx.now;
 599                          }
 600                      } else if ( that.options.heightStyle !== "content" ) {
 601                          fx.now = Math.round( total - toHide.outerHeight() - adjust );
 602                          adjust = 0;
 603                      }
 604                  }
 605              } );
 606      },
 607  
 608      _toggleComplete: function( data ) {
 609          var toHide = data.oldPanel,
 610              prev = toHide.prev();
 611  
 612          this._removeClass( toHide, "ui-accordion-content-active" );
 613          this._removeClass( prev, "ui-accordion-header-active" )
 614              ._addClass( prev, "ui-accordion-header-collapsed" );
 615  
 616          this._trigger( "activate", null, data );
 617      }
 618  } );
 619  
 620  } );


Generated : Wed Jul 15 08:20:16 2026 Cross-referenced by PHPXref