import { CSSProperties, ReactNode, useInsertionEffect, useLayoutEffect, useRef } from "react"; import { CurvesUnit, MeasuredSize } from "../types"; import { Box } from "./Box"; import { ElementUtil } from "@web-package/utility"; import { ReactWidgetsBinding } from "../modules/react_widgets_binding"; export interface AnimatedSizeOption { autoMeasureUniqueSize: boolean, sizeTolerance: number, } export function AnimatedSize({children, overflow = "clip", duration, curve}: { children: ReactNode, overflow?: CSSProperties["overflow"], duration: string, curve?: CurvesUnit, }) { const wrapperRef = useRef(null); const lowerSizeRef = useRef(null); const upperSizeRef = useRef(null); const willChange = ReactWidgetsBinding.instance.optionValueOf("useWillChange") ? "width, height" : undefined; const getOuter = () => wrapperRef.current; const getInner = () => getOuter().firstElementChild as HTMLElement; useLayoutEffect(() => { // Using resize observer because the intrinsic size of the previous element // cannot be accurately obtained when recomponented after. const observer = new ResizeObserver(() => { // Defines the previous intrinsic size when recomponented // for a size transition animation. lowerSizeRef.current = ElementUtil.intrinsicSizeOf(getOuter()); }); observer.observe(getOuter(), {"box": "border-box"}); return () => observer.disconnect(); }, []); useLayoutEffect(() => { const outer = getOuter(); const inner = getInner(); inner.ontransitionend = event => { event.stopPropagation(); } // Animates because the children elements has changed, // i.e. This can be thought that the sub-tree elements has size can be changed. // // See Also, using MutationObserver to detect changes in the size // of child elements is not considered a best practice in React. { if (!lowerSizeRef.current) return; outer.style.display = "contents"; inner.style.display = "contents"; outer.style.width = null; inner.style.width = null; outer.style.height = null; inner.style.height = null; const lowerSize = lowerSizeRef.current; const upperSize = ElementUtil.intrinsicSizeOf(inner); // reflowed // Is not the children in this element has resized. if (lowerSize.width == upperSize.width && lowerSize.height == upperSize.height) { return; } upperSizeRef.current = upperSize; outer.style.display = null; outer.style.width = `${lowerSize.width}px`; outer.style.height = `${lowerSize.height}px`; ElementUtil.reflow(inner); outer.style.width = `${upperSize.width}px`; outer.style.height = `${upperSize.height}px`; outer.ontransitionend = () => { outer.style.display = "contents"; inner.style.display = "contents"; outer.style.width = null; outer.style.height = null; inner.style.minWidth = null; inner.style.minWidth = null; } inner.style.display = null; inner.style.minWidth = `${upperSize.width}px`; inner.style.minHeight = `${upperSize.height}px`; } }, [children]); return ( {children}} /> ) }