UPDATE 4.4.0.0
[phpmyadmin.git] / js / jquery / src / jquery-ui / accordion.js
blob5f799ab503e1f8fcaeabc1e7dd6db56e33cc00d7
1 /*!
2  * jQuery UI Accordion 1.11.2
3  * http://jqueryui.com
4  *
5  * Copyright 2014 jQuery Foundation and other contributors
6  * Released under the MIT license.
7  * http://jquery.org/license
8  *
9  * http://api.jqueryui.com/accordion/
10  */
11 (function( factory ) {
12         if ( typeof define === "function" && define.amd ) {
14                 // AMD. Register as an anonymous module.
15                 define([
16                         "jquery",
17                         "./core",
18                         "./widget"
19                 ], factory );
20         } else {
22                 // Browser globals
23                 factory( jQuery );
24         }
25 }(function( $ ) {
27 return $.widget( "ui.accordion", {
28         version: "1.11.2",
29         options: {
30                 active: 0,
31                 animate: {},
32                 collapsible: false,
33                 event: "click",
34                 header: "> li > :first-child,> :not(li):even",
35                 heightStyle: "auto",
36                 icons: {
37                         activeHeader: "ui-icon-triangle-1-s",
38                         header: "ui-icon-triangle-1-e"
39                 },
41                 // callbacks
42                 activate: null,
43                 beforeActivate: null
44         },
46         hideProps: {
47                 borderTopWidth: "hide",
48                 borderBottomWidth: "hide",
49                 paddingTop: "hide",
50                 paddingBottom: "hide",
51                 height: "hide"
52         },
54         showProps: {
55                 borderTopWidth: "show",
56                 borderBottomWidth: "show",
57                 paddingTop: "show",
58                 paddingBottom: "show",
59                 height: "show"
60         },
62         _create: function() {
63                 var options = this.options;
64                 this.prevShow = this.prevHide = $();
65                 this.element.addClass( "ui-accordion ui-widget ui-helper-reset" )
66                         // ARIA
67                         .attr( "role", "tablist" );
69                 // don't allow collapsible: false and active: false / null
70                 if ( !options.collapsible && (options.active === false || options.active == null) ) {
71                         options.active = 0;
72                 }
74                 this._processPanels();
75                 // handle negative values
76                 if ( options.active < 0 ) {
77                         options.active += this.headers.length;
78                 }
79                 this._refresh();
80         },
82         _getCreateEventData: function() {
83                 return {
84                         header: this.active,
85                         panel: !this.active.length ? $() : this.active.next()
86                 };
87         },
89         _createIcons: function() {
90                 var icons = this.options.icons;
91                 if ( icons ) {
92                         $( "<span>" )
93                                 .addClass( "ui-accordion-header-icon ui-icon " + icons.header )
94                                 .prependTo( this.headers );
95                         this.active.children( ".ui-accordion-header-icon" )
96                                 .removeClass( icons.header )
97                                 .addClass( icons.activeHeader );
98                         this.headers.addClass( "ui-accordion-icons" );
99                 }
100         },
102         _destroyIcons: function() {
103                 this.headers
104                         .removeClass( "ui-accordion-icons" )
105                         .children( ".ui-accordion-header-icon" )
106                                 .remove();
107         },
109         _destroy: function() {
110                 var contents;
112                 // clean up main element
113                 this.element
114                         .removeClass( "ui-accordion ui-widget ui-helper-reset" )
115                         .removeAttr( "role" );
117                 // clean up headers
118                 this.headers
119                         .removeClass( "ui-accordion-header ui-accordion-header-active ui-state-default " +
120                                 "ui-corner-all ui-state-active ui-state-disabled ui-corner-top" )
121                         .removeAttr( "role" )
122                         .removeAttr( "aria-expanded" )
123                         .removeAttr( "aria-selected" )
124                         .removeAttr( "aria-controls" )
125                         .removeAttr( "tabIndex" )
126                         .removeUniqueId();
128                 this._destroyIcons();
130                 // clean up content panels
131                 contents = this.headers.next()
132                         .removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom " +
133                                 "ui-accordion-content ui-accordion-content-active ui-state-disabled" )
134                         .css( "display", "" )
135                         .removeAttr( "role" )
136                         .removeAttr( "aria-hidden" )
137                         .removeAttr( "aria-labelledby" )
138                         .removeUniqueId();
140                 if ( this.options.heightStyle !== "content" ) {
141                         contents.css( "height", "" );
142                 }
143         },
145         _setOption: function( key, value ) {
146                 if ( key === "active" ) {
147                         // _activate() will handle invalid values and update this.options
148                         this._activate( value );
149                         return;
150                 }
152                 if ( key === "event" ) {
153                         if ( this.options.event ) {
154                                 this._off( this.headers, this.options.event );
155                         }
156                         this._setupEvents( value );
157                 }
159                 this._super( key, value );
161                 // setting collapsible: false while collapsed; open first panel
162                 if ( key === "collapsible" && !value && this.options.active === false ) {
163                         this._activate( 0 );
164                 }
166                 if ( key === "icons" ) {
167                         this._destroyIcons();
168                         if ( value ) {
169                                 this._createIcons();
170                         }
171                 }
173                 // #5332 - opacity doesn't cascade to positioned elements in IE
174                 // so we need to add the disabled class to the headers and panels
175                 if ( key === "disabled" ) {
176                         this.element
177                                 .toggleClass( "ui-state-disabled", !!value )
178                                 .attr( "aria-disabled", value );
179                         this.headers.add( this.headers.next() )
180                                 .toggleClass( "ui-state-disabled", !!value );
181                 }
182         },
184         _keydown: function( event ) {
185                 if ( event.altKey || event.ctrlKey ) {
186                         return;
187                 }
189                 var keyCode = $.ui.keyCode,
190                         length = this.headers.length,
191                         currentIndex = this.headers.index( event.target ),
192                         toFocus = false;
194                 switch ( event.keyCode ) {
195                         case keyCode.RIGHT:
196                         case keyCode.DOWN:
197                                 toFocus = this.headers[ ( currentIndex + 1 ) % length ];
198                                 break;
199                         case keyCode.LEFT:
200                         case keyCode.UP:
201                                 toFocus = this.headers[ ( currentIndex - 1 + length ) % length ];
202                                 break;
203                         case keyCode.SPACE:
204                         case keyCode.ENTER:
205                                 this._eventHandler( event );
206                                 break;
207                         case keyCode.HOME:
208                                 toFocus = this.headers[ 0 ];
209                                 break;
210                         case keyCode.END:
211                                 toFocus = this.headers[ length - 1 ];
212                                 break;
213                 }
215                 if ( toFocus ) {
216                         $( event.target ).attr( "tabIndex", -1 );
217                         $( toFocus ).attr( "tabIndex", 0 );
218                         toFocus.focus();
219                         event.preventDefault();
220                 }
221         },
223         _panelKeyDown: function( event ) {
224                 if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) {
225                         $( event.currentTarget ).prev().focus();
226                 }
227         },
229         refresh: function() {
230                 var options = this.options;
231                 this._processPanels();
233                 // was collapsed or no panel
234                 if ( ( options.active === false && options.collapsible === true ) || !this.headers.length ) {
235                         options.active = false;
236                         this.active = $();
237                 // active false only when collapsible is true
238                 } else if ( options.active === false ) {
239                         this._activate( 0 );
240                 // was active, but active panel is gone
241                 } else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
242                         // all remaining panel are disabled
243                         if ( this.headers.length === this.headers.find(".ui-state-disabled").length ) {
244                                 options.active = false;
245                                 this.active = $();
246                         // activate previous panel
247                         } else {
248                                 this._activate( Math.max( 0, options.active - 1 ) );
249                         }
250                 // was active, active panel still exists
251                 } else {
252                         // make sure active index is correct
253                         options.active = this.headers.index( this.active );
254                 }
256                 this._destroyIcons();
258                 this._refresh();
259         },
261         _processPanels: function() {
262                 var prevHeaders = this.headers,
263                         prevPanels = this.panels;
265                 this.headers = this.element.find( this.options.header )
266                         .addClass( "ui-accordion-header ui-state-default ui-corner-all" );
268                 this.panels = this.headers.next()
269                         .addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" )
270                         .filter( ":not(.ui-accordion-content-active)" )
271                         .hide();
273                 // Avoid memory leaks (#10056)
274                 if ( prevPanels ) {
275                         this._off( prevHeaders.not( this.headers ) );
276                         this._off( prevPanels.not( this.panels ) );
277                 }
278         },
280         _refresh: function() {
281                 var maxHeight,
282                         options = this.options,
283                         heightStyle = options.heightStyle,
284                         parent = this.element.parent();
286                 this.active = this._findActive( options.active )
287                         .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" )
288                         .removeClass( "ui-corner-all" );
289                 this.active.next()
290                         .addClass( "ui-accordion-content-active" )
291                         .show();
293                 this.headers
294                         .attr( "role", "tab" )
295                         .each(function() {
296                                 var header = $( this ),
297                                         headerId = header.uniqueId().attr( "id" ),
298                                         panel = header.next(),
299                                         panelId = panel.uniqueId().attr( "id" );
300                                 header.attr( "aria-controls", panelId );
301                                 panel.attr( "aria-labelledby", headerId );
302                         })
303                         .next()
304                                 .attr( "role", "tabpanel" );
306                 this.headers
307                         .not( this.active )
308                         .attr({
309                                 "aria-selected": "false",
310                                 "aria-expanded": "false",
311                                 tabIndex: -1
312                         })
313                         .next()
314                                 .attr({
315                                         "aria-hidden": "true"
316                                 })
317                                 .hide();
319                 // make sure at least one header is in the tab order
320                 if ( !this.active.length ) {
321                         this.headers.eq( 0 ).attr( "tabIndex", 0 );
322                 } else {
323                         this.active.attr({
324                                 "aria-selected": "true",
325                                 "aria-expanded": "true",
326                                 tabIndex: 0
327                         })
328                         .next()
329                                 .attr({
330                                         "aria-hidden": "false"
331                                 });
332                 }
334                 this._createIcons();
336                 this._setupEvents( options.event );
338                 if ( heightStyle === "fill" ) {
339                         maxHeight = parent.height();
340                         this.element.siblings( ":visible" ).each(function() {
341                                 var elem = $( this ),
342                                         position = elem.css( "position" );
344                                 if ( position === "absolute" || position === "fixed" ) {
345                                         return;
346                                 }
347                                 maxHeight -= elem.outerHeight( true );
348                         });
350                         this.headers.each(function() {
351                                 maxHeight -= $( this ).outerHeight( true );
352                         });
354                         this.headers.next()
355                                 .each(function() {
356                                         $( this ).height( Math.max( 0, maxHeight -
357                                                 $( this ).innerHeight() + $( this ).height() ) );
358                                 })
359                                 .css( "overflow", "auto" );
360                 } else if ( heightStyle === "auto" ) {
361                         maxHeight = 0;
362                         this.headers.next()
363                                 .each(function() {
364                                         maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() );
365                                 })
366                                 .height( maxHeight );
367                 }
368         },
370         _activate: function( index ) {
371                 var active = this._findActive( index )[ 0 ];
373                 // trying to activate the already active panel
374                 if ( active === this.active[ 0 ] ) {
375                         return;
376                 }
378                 // trying to collapse, simulate a click on the currently active header
379                 active = active || this.active[ 0 ];
381                 this._eventHandler({
382                         target: active,
383                         currentTarget: active,
384                         preventDefault: $.noop
385                 });
386         },
388         _findActive: function( selector ) {
389                 return typeof selector === "number" ? this.headers.eq( selector ) : $();
390         },
392         _setupEvents: function( event ) {
393                 var events = {
394                         keydown: "_keydown"
395                 };
396                 if ( event ) {
397                         $.each( event.split( " " ), function( index, eventName ) {
398                                 events[ eventName ] = "_eventHandler";
399                         });
400                 }
402                 this._off( this.headers.add( this.headers.next() ) );
403                 this._on( this.headers, events );
404                 this._on( this.headers.next(), { keydown: "_panelKeyDown" });
405                 this._hoverable( this.headers );
406                 this._focusable( this.headers );
407         },
409         _eventHandler: function( event ) {
410                 var options = this.options,
411                         active = this.active,
412                         clicked = $( event.currentTarget ),
413                         clickedIsActive = clicked[ 0 ] === active[ 0 ],
414                         collapsing = clickedIsActive && options.collapsible,
415                         toShow = collapsing ? $() : clicked.next(),
416                         toHide = active.next(),
417                         eventData = {
418                                 oldHeader: active,
419                                 oldPanel: toHide,
420                                 newHeader: collapsing ? $() : clicked,
421                                 newPanel: toShow
422                         };
424                 event.preventDefault();
426                 if (
427                                 // click on active header, but not collapsible
428                                 ( clickedIsActive && !options.collapsible ) ||
429                                 // allow canceling activation
430                                 ( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
431                         return;
432                 }
434                 options.active = collapsing ? false : this.headers.index( clicked );
436                 // when the call to ._toggle() comes after the class changes
437                 // it causes a very odd bug in IE 8 (see #6720)
438                 this.active = clickedIsActive ? $() : clicked;
439                 this._toggle( eventData );
441                 // switch classes
442                 // corner classes on the previously active header stay after the animation
443                 active.removeClass( "ui-accordion-header-active ui-state-active" );
444                 if ( options.icons ) {
445                         active.children( ".ui-accordion-header-icon" )
446                                 .removeClass( options.icons.activeHeader )
447                                 .addClass( options.icons.header );
448                 }
450                 if ( !clickedIsActive ) {
451                         clicked
452                                 .removeClass( "ui-corner-all" )
453                                 .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" );
454                         if ( options.icons ) {
455                                 clicked.children( ".ui-accordion-header-icon" )
456                                         .removeClass( options.icons.header )
457                                         .addClass( options.icons.activeHeader );
458                         }
460                         clicked
461                                 .next()
462                                 .addClass( "ui-accordion-content-active" );
463                 }
464         },
466         _toggle: function( data ) {
467                 var toShow = data.newPanel,
468                         toHide = this.prevShow.length ? this.prevShow : data.oldPanel;
470                 // handle activating a panel during the animation for another activation
471                 this.prevShow.add( this.prevHide ).stop( true, true );
472                 this.prevShow = toShow;
473                 this.prevHide = toHide;
475                 if ( this.options.animate ) {
476                         this._animate( toShow, toHide, data );
477                 } else {
478                         toHide.hide();
479                         toShow.show();
480                         this._toggleComplete( data );
481                 }
483                 toHide.attr({
484                         "aria-hidden": "true"
485                 });
486                 toHide.prev().attr( "aria-selected", "false" );
487                 // if we're switching panels, remove the old header from the tab order
488                 // if we're opening from collapsed state, remove the previous header from the tab order
489                 // if we're collapsing, then keep the collapsing header in the tab order
490                 if ( toShow.length && toHide.length ) {
491                         toHide.prev().attr({
492                                 "tabIndex": -1,
493                                 "aria-expanded": "false"
494                         });
495                 } else if ( toShow.length ) {
496                         this.headers.filter(function() {
497                                 return $( this ).attr( "tabIndex" ) === 0;
498                         })
499                         .attr( "tabIndex", -1 );
500                 }
502                 toShow
503                         .attr( "aria-hidden", "false" )
504                         .prev()
505                                 .attr({
506                                         "aria-selected": "true",
507                                         tabIndex: 0,
508                                         "aria-expanded": "true"
509                                 });
510         },
512         _animate: function( toShow, toHide, data ) {
513                 var total, easing, duration,
514                         that = this,
515                         adjust = 0,
516                         down = toShow.length &&
517                                 ( !toHide.length || ( toShow.index() < toHide.index() ) ),
518                         animate = this.options.animate || {},
519                         options = down && animate.down || animate,
520                         complete = function() {
521                                 that._toggleComplete( data );
522                         };
524                 if ( typeof options === "number" ) {
525                         duration = options;
526                 }
527                 if ( typeof options === "string" ) {
528                         easing = options;
529                 }
530                 // fall back from options to animation in case of partial down settings
531                 easing = easing || options.easing || animate.easing;
532                 duration = duration || options.duration || animate.duration;
534                 if ( !toHide.length ) {
535                         return toShow.animate( this.showProps, duration, easing, complete );
536                 }
537                 if ( !toShow.length ) {
538                         return toHide.animate( this.hideProps, duration, easing, complete );
539                 }
541                 total = toShow.show().outerHeight();
542                 toHide.animate( this.hideProps, {
543                         duration: duration,
544                         easing: easing,
545                         step: function( now, fx ) {
546                                 fx.now = Math.round( now );
547                         }
548                 });
549                 toShow
550                         .hide()
551                         .animate( this.showProps, {
552                                 duration: duration,
553                                 easing: easing,
554                                 complete: complete,
555                                 step: function( now, fx ) {
556                                         fx.now = Math.round( now );
557                                         if ( fx.prop !== "height" ) {
558                                                 adjust += fx.now;
559                                         } else if ( that.options.heightStyle !== "content" ) {
560                                                 fx.now = Math.round( total - toHide.outerHeight() - adjust );
561                                                 adjust = 0;
562                                         }
563                                 }
564                         });
565         },
567         _toggleComplete: function( data ) {
568                 var toHide = data.oldPanel;
570                 toHide
571                         .removeClass( "ui-accordion-content-active" )
572                         .prev()
573                                 .removeClass( "ui-corner-top" )
574                                 .addClass( "ui-corner-all" );
576                 // Work around for rendering bug in IE (#5421)
577                 if ( toHide.length ) {
578                         toHide.parent()[ 0 ].className = toHide.parent()[ 0 ].className;
579                 }
580                 this._trigger( "activate", null, data );
581         }
584 }));