Files
HMDesign/src/org-chart/center-composition-preview.ts
T

59 lines
3.1 KiB
TypeScript

/** 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<HTMLElement>('.hm-orgchart-composition');
const nextStage = shadow.querySelector<HTMLElement>('.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);
};
}