import throttle from 'lodash.throttle' import { useLayoutEffect } from 'react' import { Box } from '../primitives/Box' import { useEditor } from './useEditor' export function useScreenBounds(ref: React.RefObject) { const editor = useEditor() useLayoutEffect(() => { function updateScreenBounds() { const container = ref.current if (!container) return null const rect = container.getBoundingClientRect() editor.updateViewportScreenBounds( new Box( rect.left || rect.x, rect.top || rect.y, Math.max(rect.width, 1), Math.max(rect.height, 1) ) ) } // Set the initial bounds updateScreenBounds() // Everything else uses a debounced update... const updateBounds = throttle(updateScreenBounds, 200, { trailing: true, }) // Rather than running getClientRects on every frame, we'll // run it once a second or when the window resizes. const interval = setInterval(updateBounds, 1000) window.addEventListener('resize', updateBounds) const resizeObserver = new ResizeObserver((entries) => { if (!entries[0].contentRect) return updateBounds() }) const container = ref.current let scrollingParent: HTMLElement | Document | null = null if (container) { // When the container's size changes, update the bounds resizeObserver.observe(container) // When the container's nearest scrollable parent scrolls, update the bounds scrollingParent = getNearestScrollableContainer(container) scrollingParent.addEventListener('scroll', updateBounds) } return () => { clearInterval(interval) window.removeEventListener('resize', updateBounds) resizeObserver.disconnect() scrollingParent?.removeEventListener('scroll', updateBounds) } }, [editor, ref]) } /*! * Author: excalidraw * MIT License: https://github.com/excalidraw/excalidraw/blob/master/LICENSE * https://github.com/excalidraw/excalidraw/blob/48c3465b19f10ec755b3eb84e21a01a468e96e43/packages/excalidraw/utils.ts#L600 */ const getNearestScrollableContainer = (element: HTMLElement): HTMLElement | Document => { let parent = element.parentElement while (parent) { if (parent === document.body) { return document } const { overflowY } = window.getComputedStyle(parent) const hasScrollableContent = parent.scrollHeight > parent.clientHeight if ( hasScrollableContent && (overflowY === 'auto' || overflowY === 'scroll' || overflowY === 'overlay') ) { return parent } parent = parent.parentElement } return document }