fix: use resizeObserver on active tabpanel instead

This commit is contained in:
alistair3149 2024-06-06 16:54:31 -04:00
parent 8b39e9e970
commit 0177715f2b
No known key found for this signature in database

View file

@ -10,8 +10,6 @@ const Hash = require( './Hash.js' );
const Transclude = require( './Transclude.js' ); const Transclude = require( './Transclude.js' );
const Util = require( './Util.js' ); const Util = require( './Util.js' );
let resizeObserver;
/** /**
* Class representing TabberAction functionality for handling tab events and animations. * Class representing TabberAction functionality for handling tab events and animations.
* *
@ -168,9 +166,6 @@ class TabberAction {
tabindex: '0' tabindex: '0'
} }
} ); } );
if ( typeof resizeObserver !== 'undefined' && resizeObserver ) {
resizeObserver.observe( activeTabpanel );
}
} else { } else {
tabpanelVisibilityUpdates.push( { tabpanelVisibilityUpdates.push( {
element: tabpanel, element: tabpanel,
@ -179,9 +174,6 @@ class TabberAction {
tabindex: '-1' tabindex: '-1'
} }
} ); } );
if ( typeof resizeObserver !== 'undefined' && resizeObserver ) {
resizeObserver.unobserve( tabpanel );
}
} }
} ); } );
@ -251,35 +243,6 @@ class TabberAction {
const scrollOffset = type === 'prev' ? -tablistWidth / 2 : tablistWidth / 2; const scrollOffset = type === 'prev' ? -tablistWidth / 2 : tablistWidth / 2;
TabberAction.scrollTablist( scrollOffset, tablist ); TabberAction.scrollTablist( scrollOffset, tablist );
} }
/**
* Checks if there are entries and the first entry has a target element
* that is an instance of Element.
* If true, calls the setActiveTabpanel method of the TabberAction class
* with the activeTabpanel as the argument.
*
* @param {ResizeObserverEntry[]} entries
*/
static handleElementResize( entries ) {
if ( entries && entries.length > 0 ) {
const activeTabpanel = entries[ 0 ].target;
if ( activeTabpanel instanceof Element ) {
TabberAction.setActiveTabpanel( activeTabpanel );
}
}
}
/**
* Sets up event listeners for tab elements.
* Attaches a click event listener to the body content element,
* delegating the click event to the tab elements.
* When a tab element is clicked, it triggers the handleClick method of the TabberAction class.
*/
static attachEvents() {
if ( window.ResizeObserver ) {
resizeObserver = new ResizeObserver( TabberAction.handleElementResize );
}
}
} }
/** /**
@ -298,11 +261,34 @@ class TabberEvent {
this.activeTab = this.tablist.querySelector( '[aria-selected="true"]' ); this.activeTab = this.tablist.querySelector( '[aria-selected="true"]' );
this.indicator = this.tabber.querySelector( ':scope > .tabber__header > .tabber__indicator' ); this.indicator = this.tabber.querySelector( ':scope > .tabber__header > .tabber__indicator' );
this.tabFocus = 0; this.tabFocus = 0;
this.debouncedUpdateHeaderOverflow = mw.util.debounce( () => TabberAction.updateHeaderOverflow( this.tabber ), 250 ); this.debouncedUpdateHeaderOverflow = mw.util.debounce( () => TabberAction.updateHeaderOverflow( this.tabber ), 100 );
this.debouncedSetActiveTabpanel = mw.util.debounce( () => TabberAction.setActiveTabpanel( this.getActiveTabpanel() ), 100 );
this.handleTabFocusChange = this.handleTabFocusChange.bind( this ); this.handleTabFocusChange = this.handleTabFocusChange.bind( this );
this.onHeaderClick = this.onHeaderClick.bind( this ); this.onHeaderClick = this.onHeaderClick.bind( this );
this.onTablistScroll = this.onTablistScroll.bind( this ); this.onTablistScroll = this.onTablistScroll.bind( this );
this.onTablistKeydown = this.onTablistKeydown.bind( this ); this.onTablistKeydown = this.onTablistKeydown.bind( this );
// eslint-disable-next-line compat/compat
this.activeTabpanelObserver = new ResizeObserver( this.debounceSetActiveTabpanel() );
// eslint-disable-next-line compat/compat
this.headerOverflowObserver = new ResizeObserver( this.debounceUpdateHeaderOverflow() );
}
/**
* Returns the active tab panel element based on the currently active tab.
*
* @return {Element} The active tab panel element.
*/
getActiveTabpanel() {
return document.getElementById( this.activeTab.getAttribute( 'aria-controls' ) );
}
/**
* Returns a debounced function that updates the height of the active tabpanel
*
* @return {Function} A debounced function that updates the height of the active tabpanel.
*/
debounceSetActiveTabpanel() {
return this.debouncedSetActiveTabpanel;
} }
/** /**
@ -409,36 +395,31 @@ class TabberEvent {
} }
/** /**
* Adds event listeners for header click, tablist scroll, and tablist keydown. * Adds listeners for header click, tablist scroll, tablist keydown, and activeTabpanel resize.
*/ */
resume() { resume() {
this.header.addEventListener( 'click', this.onHeaderClick ); this.header.addEventListener( 'click', this.onHeaderClick );
this.tablist.addEventListener( 'scroll', this.onTablistScroll ); this.tablist.addEventListener( 'scroll', this.onTablistScroll );
this.tablist.addEventListener( 'keydown', this.onTablistKeydown ); this.tablist.addEventListener( 'keydown', this.onTablistKeydown );
this.activeTabpanelObserver.observe( this.getActiveTabpanel() );
if ( window.ResizeObserver ) { this.headerOverflowObserver.observe( this.tablist );
const headerOverflowObserver = new ResizeObserver( this.debounceUpdateHeaderOverflow() );
headerOverflowObserver.observe( this.tablist );
}
// Refresh tabber height after it comes into viewport (#137)
const activeTabpanel = document.getElementById( this.activeTab.getAttribute( 'aria-controls' ) );
TabberAction.setActiveTabpanel( activeTabpanel );
} }
/** /**
* Removes event listeners for header click, tablist scroll, and tablist keydown. * Removes listeners for header click, tablist scroll, tablist keydown, and activeTabpanel resize.
*/ */
pause() { pause() {
this.header.removeEventListener( 'click', this.onHeaderClick ); this.header.removeEventListener( 'click', this.onHeaderClick );
this.tablist.removeEventListener( 'scroll', this.onTablistScroll ); this.tablist.removeEventListener( 'scroll', this.onTablistScroll );
this.tablist.removeEventListener( 'keydown', this.onTablistKeydown ); this.tablist.removeEventListener( 'keydown', this.onTablistKeydown );
this.activeTabpanelObserver.unobserve( this.getActiveTabpanel() );
this.headerOverflowObserver.unobserve( this.tablist );
} }
/** /**
* Initializes the TabberEvent instance by creating an IntersectionObserver to handle tabber visibility. * Initializes the TabberEvent instance by creating an IntersectionObserver to handle tabber visibility.
* When the tabber intersects with the viewport, it resumes event listeners for header click, tablist scroll, and tablist keydown. * When the tabber intersects with the viewport, it resumes all event listeners and observers.
* Otherwise, it pauses the event listeners. * Otherwise, it pauses the event listeners and observers.
*/ */
init() { init() {
// eslint-disable-next-line compat/compat // eslint-disable-next-line compat/compat
@ -657,7 +638,6 @@ function load( tabberEls ) {
} ); } );
} }
TabberAction.attachEvents();
// Delay animation execution so it doesn't not animate the tab gets into position on load // Delay animation execution so it doesn't not animate the tab gets into position on load
setTimeout( () => { setTimeout( () => {
TabberAction.toggleAnimation( true ); TabberAction.toggleAnimation( true );