/** * Team Grid block — bio expand/collapse animation. * * Native
/ 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();