109 lines
3.1 KiB
JavaScript
109 lines
3.1 KiB
JavaScript
/**
|
|
* Team Grid block — bio expand/collapse animation.
|
|
*
|
|
* Native <details>/<summary> handles the toggle, but CSS transitions on
|
|
* max-height need a concrete target value to animate to. This module
|
|
* measures each bio's natural height on open and animates max-height
|
|
* from current → measured (open) or current → 0 (close) so the
|
|
* transition fires in both directions.
|
|
*
|
|
* The module is enqueued per-block by the team-grid PHP template. It
|
|
* auto-initializes on import: queries the DOM for any existing
|
|
* .team-grid__details elements and attaches a toggle handler to each.
|
|
*/
|
|
|
|
const DETAILS_SELECTOR = '.team-grid__details';
|
|
const BIO_WRAP_SELECTOR = '.team-grid__bio-wrap';
|
|
|
|
const setMaxHeight = (el, value) => {
|
|
el.style.maxHeight = typeof value === 'number' ? `${value}px` : value;
|
|
};
|
|
|
|
const measureBioHeight = (bioWrap) => {
|
|
// Temporarily allow the inner content to render at its natural size so
|
|
// scrollHeight reports the real height rather than the clipped height.
|
|
const previousMax = bioWrap.style.maxHeight;
|
|
const previousOverflow = bioWrap.style.overflow;
|
|
|
|
setMaxHeight(bioWrap, 'none');
|
|
bioWrap.style.overflow = 'visible';
|
|
|
|
const height = bioWrap.scrollHeight;
|
|
|
|
bioWrap.style.overflow = previousOverflow;
|
|
setMaxHeight(bioWrap, previousMax || '');
|
|
|
|
return height;
|
|
};
|
|
|
|
const animateOpen = (bioWrap) => {
|
|
const target = measureBioHeight(bioWrap);
|
|
|
|
setMaxHeight(bioWrap, '0px');
|
|
|
|
// Force a reflow so the browser registers the starting value before
|
|
// we change it to the target — otherwise the transition is skipped.
|
|
// eslint-disable-next-line no-unused-expressions
|
|
bioWrap.offsetHeight;
|
|
|
|
requestAnimationFrame(() => {
|
|
setMaxHeight(bioWrap, `${target}px`);
|
|
});
|
|
|
|
// After the transition completes, set max-height back to 'none' so
|
|
// dynamic content (e.g. images loading late) can grow the bio
|
|
// without being clipped.
|
|
const onEnd = (event) => {
|
|
if (event.propertyName !== 'max-height' || event.target !== bioWrap) {
|
|
return;
|
|
}
|
|
bioWrap.removeEventListener('transitionend', onEnd);
|
|
setMaxHeight(bioWrap, 'none');
|
|
};
|
|
bioWrap.addEventListener('transitionend', onEnd);
|
|
};
|
|
|
|
const animateClose = (bioWrap) => {
|
|
// Capture the current rendered height so the browser has a concrete
|
|
// starting value for the transition. Without this, going from
|
|
// 'none' to '0px' snaps instead of animating.
|
|
const current = bioWrap.getBoundingClientRect().height;
|
|
|
|
setMaxHeight(bioWrap, `${current}px`);
|
|
|
|
// Force a reflow so the browser registers the starting value.
|
|
// eslint-disable-next-line no-unused-expressions
|
|
bioWrap.offsetHeight;
|
|
|
|
requestAnimationFrame(() => {
|
|
setMaxHeight(bioWrap, '0px');
|
|
});
|
|
};
|
|
|
|
const attach = (details) => {
|
|
if (details.dataset.teamGridBound === 'true') {
|
|
return;
|
|
}
|
|
details.dataset.teamGridBound = 'true';
|
|
|
|
const bioWrap = details.querySelector(BIO_WRAP_SELECTOR);
|
|
|
|
if (!bioWrap) {
|
|
return;
|
|
}
|
|
|
|
details.addEventListener('toggle', () => {
|
|
if (details.open) {
|
|
animateOpen(bioWrap);
|
|
} else {
|
|
animateClose(bioWrap);
|
|
}
|
|
});
|
|
};
|
|
|
|
const init = () => {
|
|
document.querySelectorAll(DETAILS_SELECTOR).forEach(attach);
|
|
};
|
|
|
|
init();
|