2014-08-07 11:38:34 +00:00
|
|
|
/**
|
2016-11-28 23:14:33 +00:00
|
|
|
* Collapsible Tabs for the Vector skin.
|
|
|
|
*
|
|
|
|
* @class jQuery.plugin.collapsibleTabs
|
2014-08-07 11:38:34 +00:00
|
|
|
*/
|
2018-11-16 15:35:52 +00:00
|
|
|
( function () {
|
2019-09-11 19:26:59 +00:00
|
|
|
var boundEvent,
|
|
|
|
isRTL = document.documentElement.dir === 'rtl',
|
2015-11-19 23:53:05 +00:00
|
|
|
rAF = window.requestAnimationFrame || setTimeout;
|
|
|
|
|
2016-11-28 23:14:33 +00:00
|
|
|
/**
|
|
|
|
* @event beforeTabCollapse
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @event afterTabCollapse
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {Object} [options]
|
|
|
|
* @param {string} [options.expandedContainer="#p-views ul"] List of tabs
|
|
|
|
* @param {string} [options.collapsedContainer="#p-cactions ul"] List of menu items
|
|
|
|
* @param {string} [options.collapsible="li.collapsible"] Match tabs that are collapsible
|
|
|
|
* @param {Function} [options.expandCondition]
|
|
|
|
* @param {Function} [options.collapseCondition]
|
|
|
|
* @return {jQuery}
|
|
|
|
* @chainable
|
|
|
|
*/
|
2014-08-07 11:38:34 +00:00
|
|
|
$.fn.collapsibleTabs = function ( options ) {
|
2015-07-26 19:17:11 +00:00
|
|
|
// Merge options into the defaults
|
|
|
|
var settings = $.extend( {}, $.collapsibleTabs.defaults, options );
|
|
|
|
|
2014-08-07 11:38:34 +00:00
|
|
|
// return if the function is called on an empty jquery object
|
|
|
|
if ( !this.length ) {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.each( function () {
|
|
|
|
var $el = $( this );
|
|
|
|
// add the element to our array of collapsible managers
|
2016-11-28 23:14:33 +00:00
|
|
|
$.collapsibleTabs.instances.push( $el );
|
2014-08-07 11:38:34 +00:00
|
|
|
// attach the settings to the elements
|
|
|
|
$el.data( 'collapsibleTabsSettings', settings );
|
|
|
|
// attach data to our collapsible elements
|
|
|
|
$el.children( settings.collapsible ).each( function () {
|
|
|
|
$.collapsibleTabs.addData( $( this ) );
|
|
|
|
} );
|
|
|
|
} );
|
|
|
|
|
|
|
|
// if we haven't already bound our resize handler, bind it now
|
2016-11-28 23:14:33 +00:00
|
|
|
if ( !boundEvent ) {
|
|
|
|
boundEvent = true;
|
2019-09-11 19:27:53 +00:00
|
|
|
$( window ).on( 'resize', mw.util.debounce( 10, function () {
|
2015-11-19 23:53:05 +00:00
|
|
|
rAF( $.collapsibleTabs.handleResize );
|
2014-08-07 11:38:34 +00:00
|
|
|
} ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
// call our resize handler to setup the page
|
2015-11-19 23:53:05 +00:00
|
|
|
rAF( $.collapsibleTabs.handleResize );
|
2014-08-07 11:38:34 +00:00
|
|
|
return this;
|
|
|
|
};
|
|
|
|
$.collapsibleTabs = {
|
2016-11-28 23:14:33 +00:00
|
|
|
instances: [],
|
2014-08-07 11:38:34 +00:00
|
|
|
defaults: {
|
|
|
|
expandedContainer: '#p-views ul',
|
|
|
|
collapsedContainer: '#p-cactions ul',
|
|
|
|
collapsible: 'li.collapsible',
|
|
|
|
shifting: false,
|
|
|
|
expandCondition: function ( eleWidth ) {
|
|
|
|
// If there are at least eleWidth + 1 pixels of free space, expand.
|
|
|
|
// We add 1 because .width() will truncate fractional values but .offset() will not.
|
|
|
|
return $.collapsibleTabs.calculateTabDistance() >= eleWidth + 1;
|
|
|
|
},
|
|
|
|
collapseCondition: function () {
|
|
|
|
// If there's an overlap, collapse.
|
|
|
|
return $.collapsibleTabs.calculateTabDistance() < 0;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
addData: function ( $collapsible ) {
|
|
|
|
var settings = $collapsible.parent().data( 'collapsibleTabsSettings' );
|
|
|
|
if ( settings ) {
|
|
|
|
$collapsible.data( 'collapsibleTabsSettings', {
|
|
|
|
expandedContainer: settings.expandedContainer,
|
|
|
|
collapsedContainer: settings.collapsedContainer,
|
2016-11-28 23:14:33 +00:00
|
|
|
expandedWidth: $collapsible.width()
|
2014-08-07 11:38:34 +00:00
|
|
|
} );
|
|
|
|
}
|
|
|
|
},
|
|
|
|
getSettings: function ( $collapsible ) {
|
|
|
|
var settings = $collapsible.data( 'collapsibleTabsSettings' );
|
|
|
|
if ( !settings ) {
|
|
|
|
$.collapsibleTabs.addData( $collapsible );
|
|
|
|
settings = $collapsible.data( 'collapsibleTabsSettings' );
|
|
|
|
}
|
|
|
|
return settings;
|
|
|
|
},
|
|
|
|
handleResize: function () {
|
2018-11-26 23:39:26 +00:00
|
|
|
$.collapsibleTabs.instances.forEach( function ( $el ) {
|
2019-07-01 15:02:07 +00:00
|
|
|
var $tab,
|
|
|
|
data = $.collapsibleTabs.getSettings( $el );
|
|
|
|
|
2014-08-07 11:38:34 +00:00
|
|
|
if ( data.shifting ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if the two navigations are colliding
|
2016-11-28 23:14:33 +00:00
|
|
|
if ( $el.children( data.collapsible ).length && data.collapseCondition() ) {
|
2014-08-07 11:38:34 +00:00
|
|
|
$el.trigger( 'beforeTabCollapse' );
|
|
|
|
// move the element to the dropdown menu
|
2019-07-01 15:02:07 +00:00
|
|
|
$.collapsibleTabs.moveToCollapsed( $el.children( data.collapsible ).last() );
|
2014-08-07 11:38:34 +00:00
|
|
|
}
|
|
|
|
|
2019-07-01 15:02:07 +00:00
|
|
|
$tab = $( data.collapsedContainer ).children( data.collapsible ).first();
|
2014-08-07 11:38:34 +00:00
|
|
|
// if there are still moveable items in the dropdown menu,
|
|
|
|
// and there is sufficient space to place them in the tab container
|
2018-09-09 17:05:47 +00:00
|
|
|
if (
|
|
|
|
$( data.collapsedContainer + ' ' + data.collapsible ).length &&
|
|
|
|
data.expandCondition(
|
2019-07-01 15:02:07 +00:00
|
|
|
$.collapsibleTabs.getSettings( $tab ).expandedWidth
|
2018-09-09 17:05:47 +00:00
|
|
|
)
|
|
|
|
) {
|
2015-02-14 20:44:54 +00:00
|
|
|
// move the element from the dropdown to the tab
|
2014-08-07 11:38:34 +00:00
|
|
|
$el.trigger( 'beforeTabExpand' );
|
2019-07-01 15:02:07 +00:00
|
|
|
$.collapsibleTabs.moveToExpanded( $tab );
|
2014-08-07 11:38:34 +00:00
|
|
|
}
|
|
|
|
} );
|
|
|
|
},
|
2016-11-28 23:14:33 +00:00
|
|
|
moveToCollapsed: function ( $moving ) {
|
|
|
|
var outerData, expContainerSettings, target;
|
2014-08-07 11:38:34 +00:00
|
|
|
|
|
|
|
outerData = $.collapsibleTabs.getSettings( $moving );
|
|
|
|
if ( !outerData ) {
|
|
|
|
return;
|
|
|
|
}
|
2018-09-09 17:05:47 +00:00
|
|
|
expContainerSettings = $.collapsibleTabs.getSettings(
|
|
|
|
$( outerData.expandedContainer )
|
|
|
|
);
|
2014-08-07 11:38:34 +00:00
|
|
|
if ( !expContainerSettings ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
expContainerSettings.shifting = true;
|
|
|
|
|
|
|
|
// Remove the element from where it's at and put it in the dropdown menu
|
|
|
|
target = outerData.collapsedContainer;
|
2019-03-13 23:00:40 +00:00
|
|
|
// eslint-disable-next-line no-jquery/no-animate
|
2014-08-07 11:38:34 +00:00
|
|
|
$moving.css( 'position', 'relative' )
|
2016-11-28 23:14:33 +00:00
|
|
|
.css( ( isRTL ? 'left' : 'right' ), 0 )
|
2014-08-07 11:38:34 +00:00
|
|
|
.animate( { width: '1px' }, 'normal', function () {
|
|
|
|
$( this ).hide();
|
|
|
|
// add the placeholder
|
2019-01-11 13:03:15 +00:00
|
|
|
$( '<span>' ).addClass( 'placeholder' ).css( 'display', 'none' ).insertAfter( this );
|
2014-08-07 11:38:34 +00:00
|
|
|
$( this ).detach().prependTo( target ).data( 'collapsibleTabsSettings', outerData );
|
|
|
|
$( this ).attr( 'style', 'display: list-item;' );
|
2016-11-28 23:14:33 +00:00
|
|
|
expContainerSettings.shifting = false;
|
|
|
|
rAF( $.collapsibleTabs.handleResize );
|
2014-08-07 11:38:34 +00:00
|
|
|
} );
|
|
|
|
},
|
2019-07-01 15:02:07 +00:00
|
|
|
moveToExpanded: function ( $moving ) {
|
|
|
|
var data, expContainerSettings, $target, expandedWidth;
|
2014-08-07 11:38:34 +00:00
|
|
|
|
|
|
|
data = $.collapsibleTabs.getSettings( $moving );
|
|
|
|
if ( !data ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
expContainerSettings = $.collapsibleTabs.getSettings( $( data.expandedContainer ) );
|
|
|
|
if ( !expContainerSettings ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
expContainerSettings.shifting = true;
|
|
|
|
|
|
|
|
// grab the next appearing placeholder so we can use it for replacing
|
2019-07-01 15:02:07 +00:00
|
|
|
$target = $( data.expandedContainer ).find( 'span.placeholder' ).first();
|
2014-08-07 11:38:34 +00:00
|
|
|
expandedWidth = data.expandedWidth;
|
2016-11-28 23:14:33 +00:00
|
|
|
$moving.css( 'position', 'relative' ).css( ( isRTL ? 'right' : 'left' ), 0 ).css( 'width', '1px' );
|
2014-08-07 11:38:34 +00:00
|
|
|
$target.replaceWith(
|
2019-03-13 23:00:40 +00:00
|
|
|
// eslint-disable-next-line no-jquery/no-animate
|
2014-08-07 11:38:34 +00:00
|
|
|
$moving
|
2017-08-25 15:21:21 +00:00
|
|
|
.detach()
|
|
|
|
.css( 'width', '1px' )
|
|
|
|
.data( 'collapsibleTabsSettings', data )
|
|
|
|
.animate( { width: expandedWidth + 'px' }, 'normal', function () {
|
|
|
|
$( this ).attr( 'style', 'display: block;' );
|
|
|
|
rAF( function () {
|
2018-09-09 17:05:47 +00:00
|
|
|
// Update the 'expandedWidth' in case someone was brazen enough to
|
|
|
|
// change the tab's contents after the page load *gasp* (T71729). This
|
|
|
|
// doesn't prevent a tab from collapsing back and forth once, but at
|
|
|
|
// least it won't continue to do that forever.
|
2017-08-25 15:21:21 +00:00
|
|
|
data.expandedWidth = $moving.width();
|
|
|
|
$moving.data( 'collapsibleTabsSettings', data );
|
|
|
|
expContainerSettings.shifting = false;
|
|
|
|
$.collapsibleTabs.handleResize();
|
|
|
|
} );
|
|
|
|
} )
|
2014-08-07 11:38:34 +00:00
|
|
|
);
|
|
|
|
},
|
|
|
|
/**
|
2016-11-28 23:14:33 +00:00
|
|
|
* Get the amount of horizontal distance between the two tabs groups in pixels.
|
|
|
|
*
|
|
|
|
* Uses `#left-navigation` and `#right-navigation`. If negative, this
|
2014-08-07 11:38:34 +00:00
|
|
|
* means that the tabs overlap, and the value is the width of overlapping
|
|
|
|
* parts.
|
|
|
|
*
|
2016-11-28 23:14:33 +00:00
|
|
|
* Used in default `expandCondition` and `collapseCondition` options.
|
2014-08-07 11:38:34 +00:00
|
|
|
*
|
2016-11-28 23:14:33 +00:00
|
|
|
* @return {number} distance/overlap in pixels
|
2014-08-07 11:38:34 +00:00
|
|
|
*/
|
|
|
|
calculateTabDistance: function () {
|
2016-11-28 23:14:33 +00:00
|
|
|
var leftTab, rightTab, leftEnd, rightStart;
|
2014-08-07 11:38:34 +00:00
|
|
|
|
|
|
|
// In RTL, #right-navigation is actually on the left and vice versa.
|
|
|
|
// Hooray for descriptive naming.
|
2016-11-28 23:14:33 +00:00
|
|
|
if ( !isRTL ) {
|
|
|
|
leftTab = document.getElementById( 'left-navigation' );
|
|
|
|
rightTab = document.getElementById( 'right-navigation' );
|
2014-08-07 11:38:34 +00:00
|
|
|
} else {
|
2016-11-28 23:14:33 +00:00
|
|
|
leftTab = document.getElementById( 'right-navigation' );
|
|
|
|
rightTab = document.getElementById( 'left-navigation' );
|
2014-08-07 11:38:34 +00:00
|
|
|
}
|
|
|
|
|
2016-11-28 23:14:33 +00:00
|
|
|
leftEnd = leftTab.getBoundingClientRect().right;
|
|
|
|
rightStart = rightTab.getBoundingClientRect().left;
|
2014-08-07 11:38:34 +00:00
|
|
|
return rightStart - leftEnd;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-11-28 23:14:33 +00:00
|
|
|
/**
|
|
|
|
* @class jQuery
|
|
|
|
* @mixins jQuery.plugin.collapsibleTabs
|
|
|
|
*/
|
|
|
|
|
2018-11-16 15:35:52 +00:00
|
|
|
}() );
|