import { MutableRefObject, useLayoutEffect, useState } from 'react'; export const DOCUMENT_VIEWER_PORTRAIT_HEIGHT_RATIO = 0.4; const PORTRAIT_RATIO_STYLE_ID = 'document-viewer-portrait-ratio-style'; const ensurePortraitRatioStyle = () => { const cssValue = String(DOCUMENT_VIEWER_PORTRAIT_HEIGHT_RATIO); const cssText = `:root { --document-viewer-portrait-height-ratio: ${cssValue}; }`; let styleEl = document.getElementById(PORTRAIT_RATIO_STYLE_ID); if (!styleEl) { styleEl = document.createElement('style'); styleEl.id = PORTRAIT_RATIO_STYLE_ID; document.head.appendChild(styleEl); } if (styleEl.textContent !== cssText) { styleEl.textContent = cssText; } }; const MIN_STACKED_BREAKPOINT = 480; const computeStackedLayoutBreakpoint = () => Math.max(window.innerWidth / 2, MIN_STACKED_BREAKPOINT); export const useViewerLayoutMode = ( ref: MutableRefObject | null, dependency?: unknown, ) => { const [isStacked, setIsStacked] = useState(false); useLayoutEffect(() => { ensurePortraitRatioStyle(); }, []); useLayoutEffect(() => { const node = ref?.current; if (!node) { setIsStacked(false); return undefined; } let frame: number | null = null; const commitMeasure = (width: number) => { if (frame) { cancelAnimationFrame(frame); } frame = requestAnimationFrame(() => { const breakpoint = computeStackedLayoutBreakpoint(); setIsStacked(width < breakpoint); }); }; const measure = () => { commitMeasure(node.getBoundingClientRect().width); }; measure(); if (!('ResizeObserver' in window)) { window.addEventListener('resize', measure); return () => { if (frame) { cancelAnimationFrame(frame); } window.removeEventListener('resize', measure); }; } const observer = new ResizeObserver((entries) => { if (!entries.length) { return; } commitMeasure(entries[0].contentRect.width); }); observer.observe(node); return () => { observer.disconnect(); if (frame) { cancelAnimationFrame(frame); } }; }, [ref, dependency]); return isStacked; }; export default useViewerLayoutMode;