/** Hauteur utile du corps d’un iframe d’aperçu mail (évite le scrollHeight fantôme). */ export const EMAIL_PREVIEW_MIN_IFRAME_HEIGHT = 120 export const EMAIL_PREVIEW_HEIGHT_BUFFER = 8 export const EMAIL_PREVIEW_MEASURE_ROOT_SELECTOR = "[data-ultimail-measure-root]" const CONTENT_MEASURE_SELECTORS = "p,div,span,td,th,li,h1,h2,h3,h4,h5,h6,table,img,blockquote,pre,hr,a,section,article,footer,header" function roundPreviewHeight(raw: number): number { return Math.max( EMAIL_PREVIEW_MIN_IFRAME_HEIGHT, Math.ceil(raw / 4) * 4 + EMAIL_PREVIEW_HEIGHT_BUFFER ) } export function resolveEmailPreviewMeasureRoot(doc: Document): HTMLElement { return ( doc.querySelector(EMAIL_PREVIEW_MEASURE_ROOT_SELECTOR) ?? doc.body ) } export function measureEmailPreviewIframeHeight(doc: Document): number { const root = resolveEmailPreviewMeasureRoot(doc) const view = doc.defaultView if (!root || !view) return EMAIL_PREVIEW_MIN_IFRAME_HEIGHT const scrollEstimate = Math.max( root.scrollHeight, root.offsetHeight, doc.body?.scrollHeight ?? 0, doc.body?.offsetHeight ?? 0, doc.documentElement?.scrollHeight ?? 0, doc.documentElement?.offsetHeight ?? 0 ) const rootTop = root.getBoundingClientRect().top let contentBottom = rootTop for (const el of root.querySelectorAll(CONTENT_MEASURE_SELECTORS)) { const cs = view.getComputedStyle(el) if (cs.display === "none" || cs.visibility === "hidden") continue const rect = el.getBoundingClientRect() if (rect.height < 1 && rect.width < 1) continue contentBottom = Math.max(contentBottom, rect.bottom) } const hasVisibleLayout = contentBottom > rootTop + 8 if (hasVisibleLayout) { const boundEstimate = Math.ceil(contentBottom - rootTop) + 4 // Prefer bounds over scrollHeight (phantom tail in newsletters), but never // shrink below scroll when inline/absolute content extends past last block. const contentHeight = Math.max( boundEstimate, Math.min(scrollEstimate, boundEstimate + 48) ) return roundPreviewHeight(contentHeight) } return roundPreviewHeight(scrollEstimate + 2) }