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