UNPKG

@itwin/core-react

Version:

A react component library of iTwin.js UI general purpose components

201 lines 9.91 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module Utilities */ import * as React from "react"; import { useRefEffect } from "./useRefEffect.js"; import { useRefs } from "./useRefs.js"; /* eslint-disable @typescript-eslint/no-deprecated */ /** Uses ResizeObserver API to notify about element bound changes. * @internal */ export function useResizeObserver(onResize) { const bounds = React.useRef({ width: 0, height: 0 }); const resizeObserver = React.useRef(null); const rafRef = React.useRef(0); // set to non-zero when requestAnimationFrame processing is active const isMountedRef = React.useRef(false); const owningWindowRef = React.useRef(null); const resizeObserverCleanup = () => { if (rafRef.current && owningWindowRef.current) owningWindowRef.current.cancelAnimationFrame(rafRef.current); resizeObserver.current?.disconnect(); resizeObserver.current = null; }; React.useEffect(() => { isMountedRef.current = true; return () => { isMountedRef.current = false; if (rafRef.current && owningWindowRef.current) owningWindowRef.current.cancelAnimationFrame(rafRef.current); owningWindowRef.current?.removeEventListener("beforeunload", resizeObserverCleanup); }; }, []); const processResize = React.useCallback((target) => { const newBounds = target.getBoundingClientRect(); if (isMountedRef.current && (bounds.current.width !== newBounds.width || bounds.current.height !== newBounds.height)) { bounds.current = newBounds; onResize && onResize(newBounds.width, newBounds.height); } }, [onResize]); const handleResize = React.useCallback((entries) => { if (!isMountedRef.current || !Array.isArray(entries) || 0 === entries.length) { return; } const target = entries[0].target; owningWindowRef.current && owningWindowRef.current.removeEventListener("beforeunload", resizeObserverCleanup); owningWindowRef.current = target.ownerDocument.defaultView; if (owningWindowRef.current) { owningWindowRef.current.addEventListener("unload", resizeObserverCleanup); // using requestAnimationFrame to stop the "ResizeObserver loop completed with undelivered notifications." and // "ResizeObserver loop limit exceeded" messages reported to window.onError rafRef.current = owningWindowRef.current.requestAnimationFrame(() => processResize(target)); } }, [processResize]); const observerRef = useRefEffect((instance) => { resizeObserver.current = new ResizeObserver(handleResize); if (instance) { resizeObserver.current.observe(instance); const newBounds = instance.getBoundingClientRect(); onResize && onResize(newBounds.width, newBounds.height); bounds.current = newBounds; } return () => { instance && resizeObserver.current?.unobserve(instance); resizeObserver.current = null; }; }, [handleResize, onResize]); const handleRef = React.useCallback((instance) => { const newBounds = instance && instance.getBoundingClientRect(); if (newBounds && (bounds.current.width !== newBounds.width || bounds.current.height !== newBounds.height)) { bounds.current = newBounds; onResize && onResize(newBounds.width, newBounds.height); } }, [onResize]); const ref = useRefs(handleRef, observerRef); return ref; } /** React hook that uses ResizeObserver API to notify about element bound changes. Note: to work similar to ReactResizeDetector * width and height are initialized as undefined and only set during after mount if bounds are non-zero. This implementation properly * handles observing element in pop-out/child windows. * @internal */ export function useLayoutResizeObserver(inElement, onResize) { const inBoundingRect = inElement?.getBoundingClientRect(); const [bounds, setBounds] = React.useState({ width: inBoundingRect?.width, height: inBoundingRect?.height }); const [watchedElement, setWatchedElement] = React.useState(inElement); const resizeObserver = React.useRef(null); const rafRef = React.useRef(0); // set to non-zero when requestAnimationFrame processing is active const isMountedRef = React.useRef(false); const owningWindowRef = React.useRef(null); const resizeObserverCleanup = () => { if (rafRef.current) owningWindowRef.current.cancelAnimationFrame(rafRef.current); resizeObserver.current?.disconnect(); resizeObserver.current = null; }; React.useEffect(() => { isMountedRef.current = true; setWatchedElement(inElement); if (inElement) { const newBounds = inElement.getBoundingClientRect(); onResize && onResize(newBounds.width, newBounds.height); setBounds(newBounds); } return () => { isMountedRef.current = false; rafRef.current && owningWindowRef.current && owningWindowRef.current.cancelAnimationFrame(rafRef.current); owningWindowRef.current && owningWindowRef.current.removeEventListener("beforeunload", resizeObserverCleanup); }; }, [onResize, inElement]); const processResize = React.useCallback((target) => { const newBounds = target.getBoundingClientRect(); if (isMountedRef.current && (bounds.width !== newBounds.width || bounds.height !== newBounds.height)) { onResize && onResize(newBounds.width, newBounds.height); setBounds(newBounds); } }, [bounds.height, bounds.width, onResize]); const handleResize = React.useCallback((entries) => { if (!isMountedRef.current || !Array.isArray(entries) || 0 === entries.length) { return; } const target = entries[0].target; if (owningWindowRef.current) owningWindowRef.current.removeEventListener("beforeunload", resizeObserverCleanup); owningWindowRef.current = target.ownerDocument.defaultView; if (owningWindowRef.current) { owningWindowRef.current.addEventListener("unload", resizeObserverCleanup); // using requestAnimationFrame to stop the "ResizeObserver loop completed with undelivered notifications." and // "ResizeObserver loop limit exceeded" messages reported to window.onError rafRef.current = owningWindowRef.current.requestAnimationFrame(() => processResize(target)); } }, [processResize]); React.useLayoutEffect(() => { if (!watchedElement) { return; } resizeObserver.current = new ResizeObserver(handleResize); resizeObserver.current.observe(watchedElement); return () => { resizeObserver.current?.disconnect(); resizeObserver.current = null; }; }, [handleResize, watchedElement]); return [bounds.width, bounds.height]; } /** ElementResizeObserver provides functionality similar to ReactResizeDetector when a render function is specified. This implementation properly handles * observing element in pop-out/child windows. * @public * @deprecated in 4.15.0. Used internally. */ export function ElementResizeObserver({ watchedElement, render, }) { const [width, height] = useLayoutResizeObserver(watchedElement); return render({ width, height }); } /** ResizableContainerObserver is a component that provides the functionality similar to the ReactResizeDetector option that call a function when * the observed element is resized. This implementation properly handles observing element in pop-out/child windows. If children nodes are defined then * the div added by the component is considered the container whose size will be observed. If no children are provided then the component will report * size changes from its parent container. * @public * @deprecated in 4.15.0. Please use third party packages. */ export function ResizableContainerObserver({ onResize, children, }) { const containerRef = React.useRef(null); // set to non-zero when requestAnimationFrame processing is active const [containerElement, setContainerElement] = React.useState(null); const isMountedRef = React.useRef(false); const hasChildren = React.Children.count(children) !== 0; // if no children are specified then monitor size of parent element. React.useEffect(() => { if (!isMountedRef.current && containerRef.current) { isMountedRef.current = true; const hasParent = !!containerRef.current.parentElement; setContainerElement(hasChildren && hasParent ? containerRef.current : containerRef.current.parentElement); } }, [hasChildren]); const processResize = React.useCallback((width, height) => { onResize(width ?? 0, height ?? 0); }, [onResize]); useLayoutResizeObserver(containerElement, processResize); const style = hasChildren ? { width: "100%", height: "100%" } : { display: "none" }; return (React.createElement("div", { ref: containerRef, className: "uicore-resizable-container", style: style }, hasChildren && children)); } //# sourceMappingURL=useResizeObserver.js.map