mediawiki-extensions-Tabber.../modules/ext.tabberNeue.js

243 lines
6.9 KiB
JavaScript
Raw Normal View History

2021-06-21 17:49:47 +00:00
/**
2021-06-21 21:14:33 +00:00
* Initialize Tabber
*
* @param {HTMLElement} tabber
* @param {number} count
2021-06-21 17:49:47 +00:00
*/
function initTabber( tabber, count ) {
var tabPanels = tabber.querySelectorAll( ':scope > .tabber__section > .tabber__panel' );
2021-06-21 21:14:33 +00:00
var container = document.createElement( 'header' ),
2021-06-21 23:32:15 +00:00
tabList = document.createElement( 'nav' ),
2021-06-21 21:14:33 +00:00
prevButton = document.createElement( 'div' ),
nextButton = document.createElement( 'div' );
var buildTabs = function() {
var fragment = new DocumentFragment();
2021-06-21 21:14:33 +00:00
Array.prototype.forEach.call( tabPanels, function( tabPanel ) {
var hash = mw.util.escapeIdForAttribute( tabPanel.title ) + '-' + count,
2021-06-21 21:14:33 +00:00
tab = document.createElement( 'a' );
tabPanel.setAttribute( 'id', hash );
tabPanel.setAttribute( 'role', 'tabpanel' );
tabPanel.setAttribute( 'aria-labelledby', 'tab-' + hash );
tabPanel.setAttribute( 'aria-hidden', true );
2021-06-21 21:14:33 +00:00
tab.innerText = tabPanel.title;
tab.classList.add( 'tabber__tab' );
2021-06-21 21:14:33 +00:00
tab.setAttribute( 'title', tabPanel.title );
tab.setAttribute( 'role', 'tab' );
tab.setAttribute( 'href', '#' + hash );
tab.setAttribute( 'id', 'tab-' + hash );
tab.setAttribute( 'aria-select', false );
2021-06-21 21:14:33 +00:00
tab.setAttribute( 'aria-controls', hash );
fragment.append( tab );
2021-06-21 21:14:33 +00:00
} );
2021-06-21 23:32:15 +00:00
tabList.append( fragment );
container.classList.add( 'tabber__header' );
tabList.classList.add( 'tabber__tabs' );
2021-06-21 23:32:15 +00:00
tabList.setAttribute( 'role', 'tablist' );
prevButton.classList.add( 'tabber__header__prev' );
nextButton.classList.add( 'tabber__header__next' );
2021-06-21 23:32:15 +00:00
container.append( prevButton, tabList, nextButton );
2021-06-21 21:14:33 +00:00
};
2021-06-21 17:49:47 +00:00
2021-06-21 21:14:33 +00:00
buildTabs();
tabber.prepend( container );
// Initalize previous and next buttons
var initButtons = function() {
var PREVCLASS = 'tabber__header--prev-visible',
2021-06-21 21:14:33 +00:00
NEXTCLASS = 'tabber__header--next-visible';
/* eslint-disable mediawiki/class-doc */
var scrollTabs = function( offset ) {
var scrollLeft = tabList.scrollLeft + offset;
2021-06-21 21:14:33 +00:00
// Scroll to the start
if ( scrollLeft <= 0 ) {
2021-06-21 23:32:15 +00:00
tabList.scrollLeft = 0;
} else {
tabList.scrollLeft = scrollLeft;
}
};
var updateButtons = function() {
var scrollLeft = tabList.scrollLeft;
// Scroll to the start
if ( scrollLeft <= 0 ) {
2021-06-21 21:14:33 +00:00
container.classList.remove( PREVCLASS );
container.classList.add( NEXTCLASS );
} else {
// Scroll to the end
2021-06-21 23:32:15 +00:00
if ( scrollLeft + tabList.offsetWidth >= tabList.scrollWidth ) {
2021-06-21 21:14:33 +00:00
container.classList.remove( NEXTCLASS );
container.classList.add( PREVCLASS );
2021-06-21 17:49:47 +00:00
} else {
2021-06-21 21:14:33 +00:00
container.classList.add( NEXTCLASS );
container.classList.add( PREVCLASS );
}
}
2021-06-21 21:14:33 +00:00
};
var setupButtons = function() {
var isScrollable = ( tabList.scrollWidth > container.offsetWidth );
2021-06-21 21:14:33 +00:00
if ( isScrollable ) {
var scrollOffset = container.offsetWidth / 2;
2021-06-21 21:14:33 +00:00
// Just to add the right classes
updateButtons();
prevButton.addEventListener( 'click', function() {
2021-06-21 21:14:33 +00:00
scrollTabs( -scrollOffset );
}, false );
nextButton.addEventListener( 'click', function() {
2021-06-21 21:14:33 +00:00
scrollTabs( scrollOffset );
}, false );
} else {
container.classList.remove( NEXTCLASS );
container.classList.remove( PREVCLASS );
}
};
/* eslint-enable mediawiki/class-doc */
2021-06-21 21:14:33 +00:00
setupButtons();
// Listen for scroll event on header
// Also triggered by side-scrolling using other means other than the buttons
tabList.addEventListener( 'scroll', function() {
updateButtons();
} );
2021-06-21 21:14:33 +00:00
// Listen for window resize
window.addEventListener( 'resize', function() {
2021-06-21 21:14:33 +00:00
mw.util.debounce( 250, setupButtons() );
} );
};
2021-06-21 21:14:33 +00:00
/**
* Show panel based on target hash
*
* @param {string} targetHash
*/
function showPanel( targetHash ) {
var ACTIVETABCLASS = 'tabber__tab--active',
2021-06-21 21:14:33 +00:00
ACTIVEPANELCLASS = 'tabber__panel--active',
targetPanel = document.getElementById( targetHash ),
targetTab = document.getElementById( 'tab-' + targetHash ),
section = targetPanel.parentElement,
activePanel = section.querySelector( ':scope > .' + ACTIVEPANELCLASS );
2021-06-21 21:14:33 +00:00
var getHeight = function( el ) {
if ( el.offsetHeight !== 0 ) {
return el.offsetHeight;
}
// Sometimes the tab is hidden by one of its parent elements
// and you can only get the actual height by cloning the element
var clone = el.cloneNode( true );
// Hide the cloned element
clone.style.cssText = 'position:absolute;visibility:hidden;';
// Add cloned element to body
document.body.appendChild( clone );
// Measure the height of the clone
var height = clone.clientHeight;
// Remove the cloned element
clone.parentNode.removeChild( clone );
return height;
};
2021-06-21 21:14:33 +00:00
/* eslint-disable mediawiki/class-doc */
if ( activePanel ) {
// Just to be safe since there can be multiple active classes
// even if there shouldn't be
var activeTabs = tabList.querySelectorAll( '.' + ACTIVETABCLASS );
if ( activeTabs.length > 0 ) {
Array.prototype.forEach.call( activeTabs, function( activeTab ) {
activeTab.classList.remove( ACTIVETABCLASS );
activeTab.setAttribute( 'aria-selected', false );
} );
2021-06-21 17:49:47 +00:00
}
2021-06-21 21:14:33 +00:00
activePanel.classList.remove( ACTIVEPANELCLASS );
activePanel.setAttribute( 'aria-hidden', true );
section.style.height = getHeight( activePanel ) + 'px';
section.style.height = getHeight( targetPanel ) + 'px';
2021-06-21 21:14:33 +00:00
} else {
section.style.height = getHeight( targetPanel ) + 'px';
2021-06-21 21:14:33 +00:00
}
// Add active class to the tab
2021-06-21 21:14:33 +00:00
targetTab.classList.add( ACTIVETABCLASS );
targetTab.setAttribute( 'aria-selected', true );
2021-06-21 21:14:33 +00:00
targetPanel.classList.add( ACTIVEPANELCLASS );
targetPanel.setAttribute( 'aria-hidden', false );
2021-06-21 21:14:33 +00:00
// Scroll to tab
section.scrollLeft = targetPanel.offsetLeft;
/* eslint-enable mediawiki/class-doc */
}
/**
* Retrieve target hash and trigger show panel
* If no targetHash is invalid, use the first panel
*
* @param {HTMLElement} tabber
*/
function switchTab() {
var targetHash = new mw.Uri( location.href ).fragment;
2021-06-21 21:14:33 +00:00
// Switch to the first tab if no targetHash or no tab is detected
if ( !targetHash || !tabList.querySelector( '#tab-' + CSS.escape( targetHash ) ) ) {
2021-06-21 23:32:15 +00:00
targetHash = tabList.firstElementChild.getAttribute( 'id' ).substring( 4 );
2021-06-21 21:14:33 +00:00
}
showPanel( targetHash );
}
switchTab();
// Only run if client is not a touch device
if ( matchMedia( '(hover: hover)' ).matches ) {
initButtons();
}
// window.addEventListener( 'hashchange', switchTab, false );
// Respond to clicks on the nav tabs
Array.prototype.forEach.call( tabList.children, function( tab ) {
tab.addEventListener( 'click', function( event ) {
var targetHash = tab.getAttribute( 'href' ).substring( 1 );
2021-06-21 21:14:33 +00:00
event.preventDefault();
// Add hash to the end of the URL
history.pushState( null, null, '#' + targetHash );
showPanel( targetHash );
} );
} );
2021-06-21 17:49:47 +00:00
2021-06-21 21:14:33 +00:00
tabber.classList.add( 'tabber--live' );
}
2021-06-21 17:49:47 +00:00
2021-06-21 21:14:33 +00:00
function main() {
var tabbers = document.querySelectorAll( '.tabber' );
2021-06-21 17:49:47 +00:00
2021-06-21 21:14:33 +00:00
if ( tabbers ) {
var count = 0;
2021-06-21 21:14:33 +00:00
mw.loader.load( 'ext.tabberNeue.icons' );
Array.prototype.forEach.call( tabbers, function( tabber ) {
initTabber( tabber, count );
count++;
2021-06-21 17:49:47 +00:00
} );
2021-06-21 21:14:33 +00:00
}
}
2021-06-21 17:49:47 +00:00
2021-06-21 21:14:33 +00:00
main();