diff --git a/extension.json b/extension.json index 8027a1d..b32d5f8 100644 --- a/extension.json +++ b/extension.json @@ -48,7 +48,6 @@ "useLegacyTabIds": "TabberNeueUseLegacyTabIds" } }, - "ext.tabberNeue/Hash.js", "ext.tabberNeue/Transclude.js", "ext.tabberNeue/Util.js" ], diff --git a/modules/ext.tabberNeue.init/ext.tabberNeue.init.less b/modules/ext.tabberNeue.init/ext.tabberNeue.init.less index f1ae659..d1820b4 100644 --- a/modules/ext.tabberNeue.init/ext.tabberNeue.init.less +++ b/modules/ext.tabberNeue.init/ext.tabberNeue.init.less @@ -1,4 +1,7 @@ .tabber { + position: relative; + overflow: hidden; + &__header { box-shadow: inset 0 -1px 0 0 var( --border-color-base, #a2a9b1 ); @@ -10,7 +13,8 @@ &__tabs { display: flex; - overflow: auto hidden; + overflow-x: auto; + overflow-y: hidden; } &__tab { diff --git a/modules/ext.tabberNeue/Hash.js b/modules/ext.tabberNeue/Hash.js deleted file mode 100644 index 0013a7d..0000000 --- a/modules/ext.tabberNeue/Hash.js +++ /dev/null @@ -1,77 +0,0 @@ -let uniqueHashes; - -/** - * Class representing a Hash utility for generating unique hash values. - * - * @class Hash - */ -class Hash { - /** - * Initializes the Hash class by creating a new Set to store unique hashes. - */ - static init() { - uniqueHashes = new Set(); - } - - /** - * Checks if a given hash is not unique by verifying if it exists in the Set of unique hashes. - * - * @param {string} hash - The hash to check for uniqueness. - * @return {boolean} - Returns true if the hash is not unique, false otherwise. - */ - static exists( hash ) { - return uniqueHashes.has( hash ); - } - - /** - * Generates a unique hash based on the input hash by appending a suffix if necessary. - * - * @param {string} hash - The base hash to make unique. - * @return {string} - A unique hash derived from the input hash. - */ - static makeUnique( hash ) { - const match = hash.match( /^(.+)_([0-9]+)$/ ); - let suffix = match ? parseInt( match[ 2 ], 10 ) + 1 : 1; - - const initialHash = hash; - - let uniqueHash = `${ initialHash }_${ suffix }`; - // Increment suffix and generate a new unique hash until a unique one is found - while ( Hash.exists( uniqueHash ) ) { - suffix++; - uniqueHash = `${ initialHash }_${ suffix }`; - } - - return uniqueHash; - } - - /** - * Builds a unique hash based on the provided title text. - * - * @param {string} titleText - The title text to generate the hash from. - * @param {boolean} useLegacyTabIds - Whether to use the legacy tab ID format. - * @return {string} - A unique hash created from the title text. - */ - static build( titleText, useLegacyTabIds ) { - let hash = mw.util.escapeIdForAttribute( titleText ); - if ( !useLegacyTabIds ) { - hash = `tabber-${ hash }`; - } - - if ( Hash.exists( hash ) ) { - hash = Hash.makeUnique( hash ); - } - - uniqueHashes.add( hash ); - return hash; - } - - /** - * Clears the Set of unique hashes, removing all stored hashes. - */ - static clear() { - uniqueHashes.clear(); - } -} - -module.exports = Hash; diff --git a/modules/ext.tabberNeue/ext.tabberNeue.js b/modules/ext.tabberNeue/ext.tabberNeue.js index 540648a..bff36f0 100644 --- a/modules/ext.tabberNeue/ext.tabberNeue.js +++ b/modules/ext.tabberNeue/ext.tabberNeue.js @@ -6,12 +6,12 @@ * TODO: Split classes into different modules */ const config = require( './config.json' ); -const Hash = require( './Hash.js' ); const Transclude = require( './Transclude.js' ); const Util = require( './Util.js' ); let resizeObserver; +const previousWidths = new WeakMap(); /** * Class representing TabberAction functionality for handling tab events and animations. * @@ -98,9 +98,8 @@ class TabberAction { * Scrolls the section to make the active tab panel visible. * * @param {Element} activeTabpanel - The active tab panel element to be set. - * @param {Element|null} currentActiveTabpanel - The current active tab panel element */ - static setActiveTabpanel( activeTabpanel, currentActiveTabpanel = null ) { + static setActiveTabpanel( activeTabpanel ) { const section = activeTabpanel.closest( '.tabber__section' ); if ( activeTabpanel.dataset.mwTabberLoadUrl ) { @@ -118,11 +117,6 @@ class TabberAction { // Scroll to tab section.scrollLeft = activeTabpanel.offsetLeft; } ); - - if ( currentActiveTabpanel ) { - resizeObserver.unobserve( currentActiveTabpanel ); - } - resizeObserver.observe( activeTabpanel ); } /** @@ -138,11 +132,6 @@ class TabberAction { const tabberEl = activeTabpanel.closest( '.tabber' ); const currentActiveTab = tabberEl.querySelector( ':scope > .tabber__header > .tabber__tabs > .tabber__tab[aria-selected="true"]' ); - let currentActiveTabpanel; - - if ( currentActiveTab ) { - currentActiveTabpanel = TabberAction.getTabpanel( currentActiveTab ); - } if ( currentActiveTab ) { const currentActiveTabAttributes = { @@ -150,11 +139,6 @@ class TabberAction { 'aria-selected': 'false' }; Util.setAttributes( currentActiveTab, currentActiveTabAttributes ); - - if ( currentActiveTabpanel ) { - const currentActiveTabpanelAttributes = { tabindex: -1 }; - Util.setAttributes( currentActiveTabpanel, currentActiveTabpanelAttributes ); - } } const activeTabAttributes = { @@ -162,11 +146,8 @@ class TabberAction { 'aria-selected': 'true' }; - const activeTabpanelAttributes = { tabindex: 0 }; - Util.setAttributes( activeTab, activeTabAttributes ); - Util.setAttributes( activeTabpanel, activeTabpanelAttributes ); - TabberAction.setActiveTabpanel( activeTabpanel, currentActiveTabpanel ); + TabberAction.setActiveTabpanel( activeTabpanel ); resolve(); } ); @@ -213,17 +194,19 @@ class TabberAction { /** * Handles the resize event for tabber elements. - * Updates the header overflow if the resized element is a tab list, - * or sets the active tab panel if the resized element is a tab panel. + * Updates the header overflow if the resized element is a tablist * * @param {ResizeObserverEntry[]} entries - An array of ResizeObserverEntry objects. */ static onResize( entries ) { for ( const { target } of entries ) { - if ( target.classList.contains( 'tabber__tabs' ) ) { - TabberAction.updateHeaderOverflow( target ); - } else if ( target.classList.contains( 'tabber__panel' ) ) { - TabberAction.setActiveTabpanel( target ); + switch ( true ) { + case target.classList.contains( 'tabber__tabs' ): + TabberAction.updateHeaderOverflow( target ); + break; + case target.classList.contains( 'tabber__panel' ): + TabberAction.setActiveTabpanel( target ); + break; } } } @@ -432,6 +415,31 @@ class TabberBuilder { } } + /** + * Get the active tab in init state + * + * @param {string} urlHash - The URL hash used to set the active tab. + * @return {HTMLElement} + */ + getActiveTab( urlHash ) { + const activeTab = this.tablist.firstElementChild; + if ( !urlHash ) { + return activeTab; + } + const idFromUrlHash = urlHash.replace( 'tabber-tabpanel-', 'tabber-tab-' ); + if ( idFromUrlHash === urlHash ) { + return activeTab; + } + const activeTabFromUrlHash = document.getElementById( idFromUrlHash ); + if ( !activeTabFromUrlHash ) { + return activeTab; + } + if ( activeTabFromUrlHash.closest( '.tabber__tabs' ) !== this.tablist ) { + return activeTab; + } + return activeTabFromUrlHash; + } + /** * Sets the tabs attributes * Sets the active tab based on the URL hash, and updates the header overflow. @@ -441,7 +449,7 @@ class TabberBuilder { * @return {void} */ async init( urlHash ) { - const activeTab = this.tablist.querySelector( `#tabber-tab-${ CSS.escape( urlHash ) }` ) || this.tablist.firstElementChild; + const activeTab = this.getActiveTab( urlHash ); this.setTabsAttributes(); await TabberAction.setActiveTab( activeTab ); TabberAction.updateHeaderOverflow( this.tablist ); @@ -451,6 +459,7 @@ class TabberBuilder { tabberEvent.init(); this.tabber.classList.remove( 'tabber--init' ); this.tabber.classList.add( 'tabber--live' ); + TabberAction.setActiveTab( activeTab ); } } @@ -465,8 +474,6 @@ async function load( tabberEls ) { mw.loader.load( 'ext.tabberNeue.icons' ); - Hash.init(); - // eslint-disable-next-line compat/compat resizeObserver = new ResizeObserver( TabberAction.onResize ); @@ -479,8 +486,8 @@ async function load( tabberEls ) { // Delay animation execution so it doesn't not animate the tab gets into position on load TabberAction.toggleAnimation( true ); window.addEventListener( 'hashchange', ( event ) => { - const newHash = window.location.hash.slice( 1 ); - const tab = document.getElementById( `tabber-tab-${ CSS.escape( newHash ) }` ); + const hash = window.location.hash.slice( 1 ); + const tab = document.getElementById( `tabber-tab-${ CSS.escape( hash ) }` ); if ( tab ) { event.preventDefault(); tab.click();