[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

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

   1  /*!
   2   * jQuery UI Tabs 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: Tabs
  11  //>>group: Widgets
  12  //>>description: Transforms a set of container elements into a tab structure.
  13  //>>docs: https://api.jqueryui.com/tabs/
  14  //>>demos: https://jqueryui.com/tabs/
  15  //>>css.structure: ../../themes/base/core.css
  16  //>>css.structure: ../../themes/base/tabs.css
  17  //>>css.theme: ../../themes/base/theme.css
  18  
  19  ( function( factory ) {
  20      "use strict";
  21  
  22      if ( typeof define === "function" && define.amd ) {
  23  
  24          // AMD. Register as an anonymous module.
  25          define( [
  26              "jquery",
  27              "../keycode",
  28              "../unique-id",
  29              "../version",
  30              "../widget"
  31          ], factory );
  32      } else {
  33  
  34          // Browser globals
  35          factory( jQuery );
  36      }
  37  } )( function( $ ) {
  38  "use strict";
  39  
  40  $.widget( "ui.tabs", {
  41      version: "1.14.2",
  42      delay: 300,
  43      options: {
  44          active: null,
  45          classes: {
  46              "ui-tabs": "ui-corner-all",
  47              "ui-tabs-nav": "ui-corner-all",
  48              "ui-tabs-panel": "ui-corner-bottom",
  49              "ui-tabs-tab": "ui-corner-top"
  50          },
  51          collapsible: false,
  52          event: "click",
  53          heightStyle: "content",
  54          hide: null,
  55          show: null,
  56  
  57          // Callbacks
  58          activate: null,
  59          beforeActivate: null,
  60          beforeLoad: null,
  61          load: null
  62      },
  63  
  64      _isLocal: function( anchor ) {
  65          var anchorUrl = new URL( anchor.href ),
  66              locationUrl = new URL( location.href );
  67  
  68          return anchor.hash.length > 1 &&
  69  
  70              // `href` may contain a hash but also username & password;
  71              // we want to ignore them, so we check the three fields
  72              // below instead.
  73              anchorUrl.origin === locationUrl.origin &&
  74              anchorUrl.pathname === locationUrl.pathname &&
  75              anchorUrl.search === locationUrl.search;
  76      },
  77  
  78      _create: function() {
  79          var that = this,
  80              options = this.options;
  81  
  82          this.running = false;
  83  
  84          this._addClass( "ui-tabs", "ui-widget ui-widget-content" );
  85          this._toggleClass( "ui-tabs-collapsible", null, options.collapsible );
  86  
  87          this._processTabs();
  88          options.active = this._initialActive();
  89  
  90          // Take disabling tabs via class attribute from HTML
  91          // into account and update option properly.
  92          if ( Array.isArray( options.disabled ) ) {
  93              options.disabled = $.uniqueSort( options.disabled.concat(
  94                  $.map( this.tabs.filter( ".ui-state-disabled" ), function( li ) {
  95                      return that.tabs.index( li );
  96                  } )
  97              ) ).sort();
  98          }
  99  
 100          // Check for length avoids error when initializing empty list
 101          if ( this.options.active !== false && this.anchors.length ) {
 102              this.active = this._findActive( options.active );
 103          } else {
 104              this.active = $();
 105          }
 106  
 107          this._refresh();
 108  
 109          if ( this.active.length ) {
 110              this.load( options.active );
 111          }
 112      },
 113  
 114      _initialActive: function() {
 115          var active = this.options.active,
 116              collapsible = this.options.collapsible,
 117              locationHash = location.hash.substring( 1 ),
 118              locationHashDecoded = decodeURIComponent( locationHash );
 119  
 120          if ( active === null ) {
 121  
 122              // check the fragment identifier in the URL
 123              if ( locationHash ) {
 124                  this.tabs.each( function( i, tab ) {
 125                      if ( $( tab ).attr( "aria-controls" ) === locationHash ) {
 126                          active = i;
 127                          return false;
 128                      }
 129                  } );
 130  
 131                  // If not found, decode the hash & try again.
 132                  // See the comment in `_processTabs` under the `_isLocal` check
 133                  // for more information.
 134                  if ( active === null ) {
 135                      this.tabs.each( function( i, tab ) {
 136                          if ( $( tab ).attr( "aria-controls" ) === locationHashDecoded ) {
 137                              active = i;
 138                              return false;
 139                          }
 140                      } );
 141                  }
 142              }
 143  
 144              // Check for a tab marked active via a class
 145              if ( active === null ) {
 146                  active = this.tabs.index( this.tabs.filter( ".ui-tabs-active" ) );
 147              }
 148  
 149              // No active tab, set to false
 150              if ( active === null || active === -1 ) {
 151                  active = this.tabs.length ? 0 : false;
 152              }
 153          }
 154  
 155          // Handle numbers: negative, out of range
 156          if ( active !== false ) {
 157              active = this.tabs.index( this.tabs.eq( active ) );
 158              if ( active === -1 ) {
 159                  active = collapsible ? false : 0;
 160              }
 161          }
 162  
 163          // Don't allow collapsible: false and active: false
 164          if ( !collapsible && active === false && this.anchors.length ) {
 165              active = 0;
 166          }
 167  
 168          return active;
 169      },
 170  
 171      _getCreateEventData: function() {
 172          return {
 173              tab: this.active,
 174              panel: !this.active.length ? $() : this._getPanelForTab( this.active )
 175          };
 176      },
 177  
 178      _tabKeydown: function( event ) {
 179          var focusedTab = $( this.document[ 0 ].activeElement ).closest( "li" ),
 180              selectedIndex = this.tabs.index( focusedTab ),
 181              goingForward = true;
 182  
 183          if ( this._handlePageNav( event ) ) {
 184              return;
 185          }
 186  
 187          switch ( event.keyCode ) {
 188          case $.ui.keyCode.RIGHT:
 189          case $.ui.keyCode.DOWN:
 190              selectedIndex++;
 191              break;
 192          case $.ui.keyCode.UP:
 193          case $.ui.keyCode.LEFT:
 194              goingForward = false;
 195              selectedIndex--;
 196              break;
 197          case $.ui.keyCode.END:
 198              selectedIndex = this.anchors.length - 1;
 199              break;
 200          case $.ui.keyCode.HOME:
 201              selectedIndex = 0;
 202              break;
 203          case $.ui.keyCode.SPACE:
 204  
 205              // Activate only, no collapsing
 206              event.preventDefault();
 207              clearTimeout( this.activating );
 208              this._activate( selectedIndex );
 209              return;
 210          case $.ui.keyCode.ENTER:
 211  
 212              // Toggle (cancel delayed activation, allow collapsing)
 213              event.preventDefault();
 214              clearTimeout( this.activating );
 215  
 216              // Determine if we should collapse or activate
 217              this._activate( selectedIndex === this.options.active ? false : selectedIndex );
 218              return;
 219          default:
 220              return;
 221          }
 222  
 223          // Focus the appropriate tab, based on which key was pressed
 224          event.preventDefault();
 225          clearTimeout( this.activating );
 226          selectedIndex = this._focusNextTab( selectedIndex, goingForward );
 227  
 228          // Navigating with control/command key will prevent automatic activation
 229          if ( !event.ctrlKey && !event.metaKey ) {
 230  
 231              // Update aria-selected immediately so that AT think the tab is already selected.
 232              // Otherwise AT may confuse the user by stating that they need to activate the tab,
 233              // but the tab will already be activated by the time the announcement finishes.
 234              focusedTab.attr( "aria-selected", "false" );
 235              this.tabs.eq( selectedIndex ).attr( "aria-selected", "true" );
 236  
 237              this.activating = this._delay( function() {
 238                  this.option( "active", selectedIndex );
 239              }, this.delay );
 240          }
 241      },
 242  
 243      _panelKeydown: function( event ) {
 244          if ( this._handlePageNav( event ) ) {
 245              return;
 246          }
 247  
 248          // Ctrl+up moves focus to the current tab
 249          if ( event.ctrlKey && event.keyCode === $.ui.keyCode.UP ) {
 250              event.preventDefault();
 251              this.active.trigger( "focus" );
 252          }
 253      },
 254  
 255      // Alt+page up/down moves focus to the previous/next tab (and activates)
 256      _handlePageNav: function( event ) {
 257          if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_UP ) {
 258              this._activate( this._focusNextTab( this.options.active - 1, false ) );
 259              return true;
 260          }
 261          if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_DOWN ) {
 262              this._activate( this._focusNextTab( this.options.active + 1, true ) );
 263              return true;
 264          }
 265      },
 266  
 267      _findNextTab: function( index, goingForward ) {
 268          var lastTabIndex = this.tabs.length - 1;
 269  
 270  		function constrain() {
 271              if ( index > lastTabIndex ) {
 272                  index = 0;
 273              }
 274              if ( index < 0 ) {
 275                  index = lastTabIndex;
 276              }
 277              return index;
 278          }
 279  
 280          while ( $.inArray( constrain(), this.options.disabled ) !== -1 ) {
 281              index = goingForward ? index + 1 : index - 1;
 282          }
 283  
 284          return index;
 285      },
 286  
 287      _focusNextTab: function( index, goingForward ) {
 288          index = this._findNextTab( index, goingForward );
 289          this.tabs.eq( index ).trigger( "focus" );
 290          return index;
 291      },
 292  
 293      _setOption: function( key, value ) {
 294          if ( key === "active" ) {
 295  
 296              // _activate() will handle invalid values and update this.options
 297              this._activate( value );
 298              return;
 299          }
 300  
 301          this._super( key, value );
 302  
 303          if ( key === "collapsible" ) {
 304              this._toggleClass( "ui-tabs-collapsible", null, value );
 305  
 306              // Setting collapsible: false while collapsed; open first panel
 307              if ( !value && this.options.active === false ) {
 308                  this._activate( 0 );
 309              }
 310          }
 311  
 312          if ( key === "event" ) {
 313              this._setupEvents( value );
 314          }
 315  
 316          if ( key === "heightStyle" ) {
 317              this._setupHeightStyle( value );
 318          }
 319      },
 320  
 321      refresh: function() {
 322          var options = this.options,
 323              lis = this.tablist.children( ":has(a[href])" );
 324  
 325          // Get disabled tabs from class attribute from HTML
 326          // this will get converted to a boolean if needed in _refresh()
 327          options.disabled = $.map( lis.filter( ".ui-state-disabled" ), function( tab ) {
 328              return lis.index( tab );
 329          } );
 330  
 331          this._processTabs();
 332  
 333          // Was collapsed or no tabs
 334          if ( options.active === false || !this.anchors.length ) {
 335              options.active = false;
 336              this.active = $();
 337  
 338          // was active, but active tab is gone
 339          } else if ( this.active.length && !$.contains( this.tablist[ 0 ], this.active[ 0 ] ) ) {
 340  
 341              // all remaining tabs are disabled
 342              if ( this.tabs.length === options.disabled.length ) {
 343                  options.active = false;
 344                  this.active = $();
 345  
 346              // activate previous tab
 347              } else {
 348                  this._activate( this._findNextTab( Math.max( 0, options.active - 1 ), false ) );
 349              }
 350  
 351          // was active, active tab still exists
 352          } else {
 353  
 354              // make sure active index is correct
 355              options.active = this.tabs.index( this.active );
 356          }
 357  
 358          this._refresh();
 359      },
 360  
 361      _refresh: function() {
 362          this._setOptionDisabled( this.options.disabled );
 363          this._setupEvents( this.options.event );
 364          this._setupHeightStyle( this.options.heightStyle );
 365  
 366          this.tabs.not( this.active ).attr( {
 367              "aria-selected": "false",
 368              "aria-expanded": "false",
 369              tabIndex: -1
 370          } );
 371          this.panels.not( this._getPanelForTab( this.active ) )
 372              .hide()
 373              .attr( {
 374                  "aria-hidden": "true"
 375              } );
 376  
 377          // Make sure one tab is in the tab order
 378          if ( !this.active.length ) {
 379              this.tabs.eq( 0 ).attr( "tabIndex", 0 );
 380          } else {
 381              this.active
 382                  .attr( {
 383                      "aria-selected": "true",
 384                      "aria-expanded": "true",
 385                      tabIndex: 0
 386                  } );
 387              this._addClass( this.active, "ui-tabs-active", "ui-state-active" );
 388              this._getPanelForTab( this.active )
 389                  .show()
 390                  .attr( {
 391                      "aria-hidden": "false"
 392                  } );
 393          }
 394      },
 395  
 396      _processTabs: function() {
 397          var that = this,
 398              prevTabs = this.tabs,
 399              prevAnchors = this.anchors,
 400              prevPanels = this.panels;
 401  
 402          this.tablist = this._getList().attr( "role", "tablist" );
 403          this._addClass( this.tablist, "ui-tabs-nav",
 404              "ui-helper-reset ui-helper-clearfix ui-widget-header" );
 405  
 406          // Prevent users from focusing disabled tabs via click
 407          this.tablist
 408              .on( "mousedown" + this.eventNamespace, "> li", function( event ) {
 409                  if ( $( this ).is( ".ui-state-disabled" ) ) {
 410                      event.preventDefault();
 411                  }
 412              } );
 413  
 414          this.tabs = this.tablist.find( "> li:has(a[href])" )
 415              .attr( {
 416                  role: "tab",
 417                  tabIndex: -1
 418              } );
 419          this._addClass( this.tabs, "ui-tabs-tab", "ui-state-default" );
 420  
 421          this.anchors = this.tabs.map( function() {
 422              return $( "a", this )[ 0 ];
 423          } )
 424              .attr( {
 425                  tabIndex: -1
 426              } );
 427          this._addClass( this.anchors, "ui-tabs-anchor" );
 428  
 429          this.panels = $();
 430  
 431          this.anchors.each( function( i, anchor ) {
 432              var selector, panel, panelId,
 433                  anchorId = $( anchor ).uniqueId().attr( "id" ),
 434                  tab = $( anchor ).closest( "li" ),
 435                  originalAriaControls = tab.attr( "aria-controls" );
 436  
 437              // Inline tab
 438              if ( that._isLocal( anchor ) ) {
 439  
 440                  // The "scrolling to a fragment" section of the HTML spec:
 441                  // https://html.spec.whatwg.org/#scrolling-to-a-fragment
 442                  // uses a concept of document's indicated part:
 443                  // https://html.spec.whatwg.org/#the-indicated-part-of-the-document
 444                  // Slightly below there's an algorithm to compute the indicated
 445                  // part:
 446                  // https://html.spec.whatwg.org/#the-indicated-part-of-the-document
 447                  // First, the algorithm tries the hash as-is, without decoding.
 448                  // Then, if one is not found, the same is attempted with a decoded
 449                  // hash. Replicate this logic.
 450                  selector = anchor.hash;
 451                  panelId = selector.substring( 1 );
 452                  panel = that.element.find( "#" + CSS.escape( panelId ) );
 453                  if ( !panel.length ) {
 454                      panelId = decodeURIComponent( panelId );
 455                      panel = that.element.find( "#" + CSS.escape( panelId ) );
 456                  }
 457  
 458              // remote tab
 459              } else {
 460  
 461                  // If the tab doesn't already have aria-controls,
 462                  // generate an id by using a throw-away element
 463                  panelId = tab.attr( "aria-controls" ) || $( {} ).uniqueId()[ 0 ].id;
 464                  selector = "#" + panelId;
 465                  panel = that.element.find( selector );
 466                  if ( !panel.length ) {
 467                      panel = that._createPanel( panelId );
 468                      panel.insertAfter( that.panels[ i - 1 ] || that.tablist );
 469                  }
 470                  panel.attr( "aria-live", "polite" );
 471              }
 472  
 473              if ( panel.length ) {
 474                  that.panels = that.panels.add( panel );
 475              }
 476              if ( originalAriaControls ) {
 477                  tab.data( "ui-tabs-aria-controls", originalAriaControls );
 478              }
 479              tab.attr( {
 480                  "aria-controls": panelId,
 481                  "aria-labelledby": anchorId
 482              } );
 483              panel.attr( "aria-labelledby", anchorId );
 484          } );
 485  
 486          this.panels.attr( "role", "tabpanel" );
 487          this._addClass( this.panels, "ui-tabs-panel", "ui-widget-content" );
 488  
 489          // Avoid memory leaks (#10056)
 490          if ( prevTabs ) {
 491              this._off( prevTabs.not( this.tabs ) );
 492              this._off( prevAnchors.not( this.anchors ) );
 493              this._off( prevPanels.not( this.panels ) );
 494          }
 495      },
 496  
 497      // Allow overriding how to find the list for rare usage scenarios (#7715)
 498      _getList: function() {
 499          return this.tablist || this.element.find( "ol, ul" ).eq( 0 );
 500      },
 501  
 502      _createPanel: function( id ) {
 503          return $( "<div>" )
 504              .attr( "id", id )
 505              .data( "ui-tabs-destroy", true );
 506      },
 507  
 508      _setOptionDisabled: function( disabled ) {
 509          var currentItem, li, i;
 510  
 511          if ( Array.isArray( disabled ) ) {
 512              if ( !disabled.length ) {
 513                  disabled = false;
 514              } else if ( disabled.length === this.anchors.length ) {
 515                  disabled = true;
 516              }
 517          }
 518  
 519          // Disable tabs
 520          for ( i = 0; ( li = this.tabs[ i ] ); i++ ) {
 521              currentItem = $( li );
 522              if ( disabled === true || $.inArray( i, disabled ) !== -1 ) {
 523                  currentItem.attr( "aria-disabled", "true" );
 524                  this._addClass( currentItem, null, "ui-state-disabled" );
 525              } else {
 526                  currentItem.removeAttr( "aria-disabled" );
 527                  this._removeClass( currentItem, null, "ui-state-disabled" );
 528              }
 529          }
 530  
 531          this.options.disabled = disabled;
 532  
 533          this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null,
 534              disabled === true );
 535      },
 536  
 537      _setupEvents: function( event ) {
 538          var events = {};
 539          if ( event ) {
 540              $.each( event.split( " " ), function( index, eventName ) {
 541                  events[ eventName ] = "_eventHandler";
 542              } );
 543          }
 544  
 545          this._off( this.anchors.add( this.tabs ).add( this.panels ) );
 546  
 547          // Always prevent the default action, even when disabled
 548          this._on( true, this.anchors, {
 549              click: function( event ) {
 550                  event.preventDefault();
 551              }
 552          } );
 553          this._on( this.anchors, events );
 554          this._on( this.tabs, { keydown: "_tabKeydown" } );
 555          this._on( this.panels, { keydown: "_panelKeydown" } );
 556  
 557          this._focusable( this.tabs );
 558          this._hoverable( this.tabs );
 559      },
 560  
 561      _setupHeightStyle: function( heightStyle ) {
 562          var maxHeight,
 563              parent = this.element.parent();
 564  
 565          if ( heightStyle === "fill" ) {
 566              maxHeight = parent.height();
 567              maxHeight -= this.element.outerHeight() - this.element.height();
 568  
 569              this.element.siblings( ":visible" ).each( function() {
 570                  var elem = $( this ),
 571                      position = elem.css( "position" );
 572  
 573                  if ( position === "absolute" || position === "fixed" ) {
 574                      return;
 575                  }
 576                  maxHeight -= elem.outerHeight( true );
 577              } );
 578  
 579              this.element.children().not( this.panels ).each( function() {
 580                  maxHeight -= $( this ).outerHeight( true );
 581              } );
 582  
 583              this.panels.each( function() {
 584                  $( this ).height( Math.max( 0, maxHeight -
 585                      $( this ).innerHeight() + $( this ).height() ) );
 586              } )
 587                  .css( "overflow", "auto" );
 588          } else if ( heightStyle === "auto" ) {
 589              maxHeight = 0;
 590              this.panels.each( function() {
 591                  maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() );
 592              } ).height( maxHeight );
 593          }
 594      },
 595  
 596      _eventHandler: function( event ) {
 597          var options = this.options,
 598              active = this.active,
 599              anchor = $( event.currentTarget ),
 600              tab = anchor.closest( "li" ),
 601              clickedIsActive = tab[ 0 ] === active[ 0 ],
 602              collapsing = clickedIsActive && options.collapsible,
 603              toShow = collapsing ? $() : this._getPanelForTab( tab ),
 604              toHide = !active.length ? $() : this._getPanelForTab( active ),
 605              eventData = {
 606                  oldTab: active,
 607                  oldPanel: toHide,
 608                  newTab: collapsing ? $() : tab,
 609                  newPanel: toShow
 610              };
 611  
 612          event.preventDefault();
 613  
 614          if ( tab.hasClass( "ui-state-disabled" ) ||
 615  
 616                  // tab is already loading
 617                  tab.hasClass( "ui-tabs-loading" ) ||
 618  
 619                  // can't switch durning an animation
 620                  this.running ||
 621  
 622                  // click on active header, but not collapsible
 623                  ( clickedIsActive && !options.collapsible ) ||
 624  
 625                  // allow canceling activation
 626                  ( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
 627              return;
 628          }
 629  
 630          options.active = collapsing ? false : this.tabs.index( tab );
 631  
 632          this.active = clickedIsActive ? $() : tab;
 633          if ( this.xhr ) {
 634              this.xhr.abort();
 635          }
 636  
 637          if ( !toHide.length && !toShow.length ) {
 638              $.error( "jQuery UI Tabs: Mismatching fragment identifier." );
 639          }
 640  
 641          if ( toShow.length ) {
 642              this.load( this.tabs.index( tab ), event );
 643          }
 644          this._toggle( event, eventData );
 645      },
 646  
 647      // Handles show/hide for selecting tabs
 648      _toggle: function( event, eventData ) {
 649          var that = this,
 650              toShow = eventData.newPanel,
 651              toHide = eventData.oldPanel;
 652  
 653          this.running = true;
 654  
 655  		function complete() {
 656              that.running = false;
 657              that._trigger( "activate", event, eventData );
 658          }
 659  
 660  		function show() {
 661              that._addClass( eventData.newTab.closest( "li" ), "ui-tabs-active", "ui-state-active" );
 662  
 663              if ( toShow.length && that.options.show ) {
 664                  that._show( toShow, that.options.show, complete );
 665              } else {
 666                  toShow.show();
 667                  complete();
 668              }
 669          }
 670  
 671          // Start out by hiding, then showing, then completing
 672          if ( toHide.length && this.options.hide ) {
 673              this._hide( toHide, this.options.hide, function() {
 674                  that._removeClass( eventData.oldTab.closest( "li" ),
 675                      "ui-tabs-active", "ui-state-active" );
 676                  show();
 677              } );
 678          } else {
 679              this._removeClass( eventData.oldTab.closest( "li" ),
 680                  "ui-tabs-active", "ui-state-active" );
 681              toHide.hide();
 682              show();
 683          }
 684  
 685          toHide.attr( "aria-hidden", "true" );
 686          eventData.oldTab.attr( {
 687              "aria-selected": "false",
 688              "aria-expanded": "false"
 689          } );
 690  
 691          // If we're switching tabs, remove the old tab from the tab order.
 692          // If we're opening from collapsed state, remove the previous tab from the tab order.
 693          // If we're collapsing, then keep the collapsing tab in the tab order.
 694          if ( toShow.length && toHide.length ) {
 695              eventData.oldTab.attr( "tabIndex", -1 );
 696          } else if ( toShow.length ) {
 697              this.tabs.filter( function() {
 698                  return $( this ).attr( "tabIndex" ) === 0;
 699              } )
 700                  .attr( "tabIndex", -1 );
 701          }
 702  
 703          toShow.attr( "aria-hidden", "false" );
 704          eventData.newTab.attr( {
 705              "aria-selected": "true",
 706              "aria-expanded": "true",
 707              tabIndex: 0
 708          } );
 709      },
 710  
 711      _activate: function( index ) {
 712          var anchor,
 713              active = this._findActive( index );
 714  
 715          // Trying to activate the already active panel
 716          if ( active[ 0 ] === this.active[ 0 ] ) {
 717              return;
 718          }
 719  
 720          // Trying to collapse, simulate a click on the current active header
 721          if ( !active.length ) {
 722              active = this.active;
 723          }
 724  
 725          anchor = active.find( ".ui-tabs-anchor" )[ 0 ];
 726          this._eventHandler( {
 727              target: anchor,
 728              currentTarget: anchor,
 729              preventDefault: $.noop
 730          } );
 731      },
 732  
 733      _findActive: function( index ) {
 734          return index === false ? $() : this.tabs.eq( index );
 735      },
 736  
 737      _getIndex: function( index ) {
 738  
 739          // meta-function to give users option to provide a href string instead of a numerical index.
 740          if ( typeof index === "string" ) {
 741              index = this.anchors.index( this.anchors.filter( "[href$='" +
 742                  CSS.escape( index ) + "']" ) );
 743          }
 744  
 745          return index;
 746      },
 747  
 748      _destroy: function() {
 749          if ( this.xhr ) {
 750              this.xhr.abort();
 751          }
 752  
 753          this.tablist
 754              .removeAttr( "role" )
 755              .off( this.eventNamespace );
 756  
 757          this.anchors
 758              .removeAttr( "role tabIndex" )
 759              .removeUniqueId();
 760  
 761          this.tabs.add( this.panels ).each( function() {
 762              if ( $.data( this, "ui-tabs-destroy" ) ) {
 763                  $( this ).remove();
 764              } else {
 765                  $( this ).removeAttr( "role tabIndex " +
 766                      "aria-live aria-busy aria-selected aria-labelledby aria-hidden aria-expanded" );
 767              }
 768          } );
 769  
 770          this.tabs.each( function() {
 771              var li = $( this ),
 772                  prev = li.data( "ui-tabs-aria-controls" );
 773              if ( prev ) {
 774                  li
 775                      .attr( "aria-controls", prev )
 776                      .removeData( "ui-tabs-aria-controls" );
 777              } else {
 778                  li.removeAttr( "aria-controls" );
 779              }
 780          } );
 781  
 782          this.panels.show();
 783  
 784          if ( this.options.heightStyle !== "content" ) {
 785              this.panels.css( "height", "" );
 786          }
 787      },
 788  
 789      enable: function( index ) {
 790          var disabled = this.options.disabled;
 791          if ( disabled === false ) {
 792              return;
 793          }
 794  
 795          if ( index === undefined ) {
 796              disabled = false;
 797          } else {
 798              index = this._getIndex( index );
 799              if ( Array.isArray( disabled ) ) {
 800                  disabled = $.map( disabled, function( num ) {
 801                      return num !== index ? num : null;
 802                  } );
 803              } else {
 804                  disabled = $.map( this.tabs, function( li, num ) {
 805                      return num !== index ? num : null;
 806                  } );
 807              }
 808          }
 809          this._setOptionDisabled( disabled );
 810      },
 811  
 812      disable: function( index ) {
 813          var disabled = this.options.disabled;
 814          if ( disabled === true ) {
 815              return;
 816          }
 817  
 818          if ( index === undefined ) {
 819              disabled = true;
 820          } else {
 821              index = this._getIndex( index );
 822              if ( $.inArray( index, disabled ) !== -1 ) {
 823                  return;
 824              }
 825              if ( Array.isArray( disabled ) ) {
 826                  disabled = $.merge( [ index ], disabled ).sort();
 827              } else {
 828                  disabled = [ index ];
 829              }
 830          }
 831          this._setOptionDisabled( disabled );
 832      },
 833  
 834      load: function( index, event ) {
 835          index = this._getIndex( index );
 836          var that = this,
 837              tab = this.tabs.eq( index ),
 838              anchor = tab.find( ".ui-tabs-anchor" ),
 839              panel = this._getPanelForTab( tab ),
 840              eventData = {
 841                  tab: tab,
 842                  panel: panel
 843              },
 844              complete = function( jqXHR, status ) {
 845                  if ( status === "abort" ) {
 846                      that.panels.stop( false, true );
 847                  }
 848  
 849                  that._removeClass( tab, "ui-tabs-loading" );
 850                  panel.removeAttr( "aria-busy" );
 851  
 852                  if ( jqXHR === that.xhr ) {
 853                      delete that.xhr;
 854                  }
 855              };
 856  
 857          // Not remote
 858          if ( this._isLocal( anchor[ 0 ] ) ) {
 859              return;
 860          }
 861  
 862          this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) );
 863  
 864          if ( this.xhr.statusText !== "canceled" ) {
 865              this._addClass( tab, "ui-tabs-loading" );
 866              panel.attr( "aria-busy", "true" );
 867  
 868              this.xhr
 869                  .done( function( response, status, jqXHR ) {
 870                      panel.html( response );
 871                      that._trigger( "load", event, eventData );
 872  
 873                      complete( jqXHR, status );
 874                  } )
 875                  .fail( function( jqXHR, status ) {
 876                      complete( jqXHR, status );
 877                  } );
 878          }
 879      },
 880  
 881      _ajaxSettings: function( anchor, event, eventData ) {
 882          var that = this;
 883          return {
 884              url: anchor.attr( "href" ),
 885              beforeSend: function( jqXHR, settings ) {
 886                  return that._trigger( "beforeLoad", event,
 887                      $.extend( { jqXHR: jqXHR, ajaxSettings: settings }, eventData ) );
 888              }
 889          };
 890      },
 891  
 892      _getPanelForTab: function( tab ) {
 893          var id = $( tab ).attr( "aria-controls" );
 894          return this.element.find( "#" + CSS.escape( id ) );
 895      }
 896  } );
 897  
 898  // DEPRECATED
 899  // TODO: Switch return back to widget declaration at top of file when this is removed
 900  if ( $.uiBackCompat === true ) {
 901  
 902      // Backcompat for ui-tab class (now ui-tabs-tab)
 903      $.widget( "ui.tabs", $.ui.tabs, {
 904          _processTabs: function() {
 905              this._superApply( arguments );
 906              this._addClass( this.tabs, "ui-tab" );
 907          }
 908      } );
 909  }
 910  
 911  return $.ui.tabs;
 912  
 913  } );


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