/** Story-only alignment of the entire transformed Workdesk stage in the visible * Content Wrapper's content box. Core retains its proportional width/height fit. */ export function centerCompositionPreview(host: HTMLElement): () => void { let frame: number | undefined; let shadow: ShadowRoot | undefined; let stage: HTMLElement | undefined; const schedule = () => { frame ??= requestAnimationFrame(align); }; const resize = new ResizeObserver(schedule); const mutation = new MutationObserver(schedule); const outer = new MutationObserver(schedule); const align = () => { frame = undefined; const nextShadow = host.querySelector('.hm-orgchart-host')?.shadowRoot; if (!nextShadow) return; if (shadow !== nextShadow) { mutation.disconnect(); shadow = nextShadow; mutation.observe(shadow, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'data-layout'] }); } const wrapper = shadow.querySelector('.hm-orgchart-composition'); const nextStage = shadow.querySelector('.hm-orgchart-stage[data-workdesk-artwork]'); if (!wrapper || !nextStage) return; if (stage !== nextStage) { if (stage) resize.unobserve(stage); stage = nextStage; resize.observe(stage); } const container = host.getBoundingClientRect(); const css = getComputedStyle(host); const inset = (key: 'borderLeftWidth' | 'borderRightWidth' | 'paddingLeft' | 'paddingRight') => parseFloat(css[key]) || 0; const left = container.left + inset('borderLeftWidth') + inset('paddingLeft'); const right = container.right - inset('borderRightWidth') - inset('paddingRight'); const bounds = stage.getBoundingClientRect(); if (right <= left || !bounds.width) return; // The stage keeps its unscaled layout width, so centering that width with // flex/grid would misplace Core's transformed rectangle. Measure the actual // rendered rectangle instead; translate it as one unit without changing the // transform, scale, 1920 x 914.5 canvas or any stage-relative coordinates. const difference = (left + right - bounds.left - bounds.right) / 2; // Fractional proportional scales can alternate the rounded correction by // 0.0001px. Keep the same content-box calculation without a feedback loop. if (Math.abs(difference) < 0.001) return; const translation = `${Number(((parseFloat(stage.style.translate) || 0) + difference).toFixed(4))}px`; if (stage.style.translate !== translation) stage.style.translate = translation; }; resize.observe(host); // A padding redistribution can move the content center without resizing it. outer.observe(host, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); window.addEventListener('resize', schedule); document.fonts.addEventListener('loadingdone', schedule); schedule(); return () => { resize.disconnect(); mutation.disconnect(); outer.disconnect(); window.removeEventListener('resize', schedule); document.fonts.removeEventListener('loadingdone', schedule); if (frame !== undefined) cancelAnimationFrame(frame); }; }