diff --git a/components/StaffReportView.tsx b/components/StaffReportView.tsx index 3c610e7..2ff795d 100644 --- a/components/StaffReportView.tsx +++ b/components/StaffReportView.tsx @@ -76,22 +76,39 @@ export function StaffReportView({ // When embedded, post our content height to the parent so the Civi tab's // iframe can resize to fit (no nested scrollbars). The receiving script // lives in the WebForm-mw Civi extension's tab template. + // + // The root layout sets `html.h-full` and `body.min-h-full`, which tie + // document height to the iframe's viewport height. Combined with the + // parent setting `iframe.height = postedHeight + 24` on every message, + // that creates an unbounded feedback loop (viewport grows -> measured + // height grows -> parent grows the iframe -> repeat). Inside the iframe + // we decouple html/body from the viewport, measure `body.scrollHeight` + // (the actual content), observe the body, and skip duplicate posts. useEffect(() => { if (!framed || typeof window === "undefined") return; if (window.parent === window) return; + const html = document.documentElement; + const body = document.body; + const prevHtmlHeight = html.style.height; + const prevBodyMinHeight = body.style.minHeight; + html.style.height = "auto"; + body.style.minHeight = "0"; + let lastHeight = -1; const post = () => { - window.parent.postMessage( - { type: "webform-mw-height", height: document.documentElement.scrollHeight }, - "*", - ); + const h = body.scrollHeight; + if (h === lastHeight) return; + lastHeight = h; + window.parent.postMessage({ type: "webform-mw-height", height: h }, "*"); }; post(); const ro = new ResizeObserver(post); - ro.observe(document.documentElement); + ro.observe(body); window.addEventListener("load", post); return () => { ro.disconnect(); window.removeEventListener("load", post); + html.style.height = prevHtmlHeight; + body.style.minHeight = prevBodyMinHeight; }; }, [framed, load]);