{"version":3,"file":"PopChild.mjs","sources":["../../../../src/components/AnimatePresence/PopChild.tsx"],"sourcesContent":["\"use client\"\n\nimport { isHTMLElement, microtask, type VisualElement } from \"motion-dom\"\nimport * as React from \"react\"\nimport { useContext, useId, useInsertionEffect, useRef } from \"react\"\n\nimport { MotionConfigContext } from \"../../context/MotionConfigContext\"\nimport { useComposedRefs } from \"../../utils/use-composed-ref\"\n\ninterface Size {\n    width: number\n    height: number\n    top: number\n    left: number\n    right: number\n    bottom: number\n    direction: string\n    point?: number[]\n}\n\ninterface Props {\n    children: React.ReactElement\n    isPresent: boolean\n    anchorX?: \"left\" | \"right\"\n    anchorY?: \"top\" | \"bottom\"\n    root?: HTMLElement | ShadowRoot\n    pop?: boolean\n    popNode?: React.RefObject<VisualElement | undefined>\n}\n\ninterface MeasureProps extends Props {\n    getElement: () => unknown\n    sizeRef: React.RefObject<Size>\n}\n\n/**\n * How much an element's transforms, and its ancestors', scale it on screen.\n */\nconst measureScale = (element?: HTMLElement | null) => {\n    const { width, height } =\n        element?.getBoundingClientRect() || ({} as DOMRect)\n    return [\n        width / element?.offsetWidth! || 1,\n        height / element?.offsetHeight! || 1,\n    ]\n}\n\n/**\n * The element's position in the space it scrolls in, as the projection\n * system measures it (see measurePageBox and removeElementScroll): its\n * viewport position, plus its scroll containers' scroll (across shadow\n * roots), plus the window's (the root's) scroll. Like checkIsScrollRoot, a\n * fixed ancestor is a scroll root: it doesn't scroll with anything above it,\n * so a fixed container is measured relative to the viewport.\n *\n * So a scroll position clamped by content shrinking reads as the element\n * moving only if it doesn't scroll with that content.\n *\n * Scroll offsets are in their container's coordinate space, so are scaled\n * onto the screen, and the result is returned in the offsetParent's\n * coordinate space, which the pop rule's offsets are in.\n */\nconst measurePoint = (element: HTMLElement) => {\n    let { left, top } = element.getBoundingClientRect()\n    let node: HTMLElement | null | undefined = element\n    while (\n        (node = (node.parentElement ||\n            (node.parentNode as ShadowRoot)?.host) as HTMLElement | null)\n    ) {\n        const { scrollLeft, scrollTop } = node\n        if (scrollLeft || scrollTop) {\n            const [x, y] = measureScale(node)\n            left += scrollLeft * x\n            top += scrollTop * y\n        }\n        if (getComputedStyle(node).position === \"fixed\") break\n    }\n    const [x, y] = measureScale(element.offsetParent as HTMLElement | null)\n    return [left / x, top / y]\n}\n\n/**\n * Measurement functionality has to be within a separate component\n * to leverage snapshot lifecycle.\n */\nclass PopChildMeasure extends React.Component<MeasureProps> {\n    getSnapshotBeforeUpdate(prevProps: MeasureProps) {\n        const element = this.props.getElement()\n        if (isHTMLElement(element) && prevProps.isPresent && !this.props.isPresent && this.props.pop !== false) {\n            const parent = element.offsetParent\n            const parentWidth = isHTMLElement(parent)\n                ? parent.offsetWidth || 0\n                : 0\n            const parentHeight = isHTMLElement(parent)\n                ? parent.offsetHeight || 0\n                : 0\n\n            const computedStyle = getComputedStyle(element)\n            const size = this.props.sizeRef.current!\n            size.height = parseFloat(computedStyle.height)\n            size.width = parseFloat(computedStyle.width)\n            size.top = element.offsetTop\n            size.left = element.offsetLeft\n            size.right = parentWidth - size.width - size.left\n            size.bottom = parentHeight - size.height - size.top\n            size.direction = computedStyle.direction\n            size.point = measurePoint(element)\n        }\n\n        return null\n    }\n\n    /**\n     * Required with getSnapshotBeforeUpdate to stop React complaining.\n     */\n    componentDidUpdate() {}\n\n    render() {\n        return this.props.children\n    }\n}\n\nexport function PopChild({ children, isPresent, anchorX, anchorY, root, pop, popNode }: Props) {\n    const id = useId()\n    const ref = useRef<HTMLElement>(null)\n    const size = useRef<Size>({\n        width: 0,\n        height: 0,\n        top: 0,\n        left: 0,\n        right: 0,\n        bottom: 0,\n        direction: \"ltr\",\n    })\n    const { nonce } = useContext(MotionConfigContext)\n    /**\n     * In React 19, refs are passed via props.ref instead of element.ref.\n     * We check props.ref first (React 19) and fall back to element.ref (React 18).\n     *\n     * The composed ref is only used when popping the child out (pop !== false),\n     * so we avoid reading children.props.ref otherwise. In React 18.3 that read\n     * triggers a spurious \"`ref` is not a prop\" warning getter (#3745).\n     */\n    const childRef =\n        pop !== false\n            ? ((children.props as { ref?: React.Ref<HTMLElement> })?.ref ??\n              (children as unknown as { ref?: React.Ref<HTMLElement> })?.ref)\n            : undefined\n    const composedRef = useComposedRefs(ref, childRef)\n\n    /**\n     * The child's element: the one our ref reached or, if the child can't\n     * be given a ref (for instance a function component that doesn't\n     * forward it), its outermost motion component's element (#2503).\n     */\n    const getElement = () =>\n        ref.current || (popNode?.current?.current as HTMLElement | undefined)\n\n    const insertPopRule = (sheet: CSSStyleSheet | null) => {\n        const { width, height, top, left, right, bottom, direction } = size.current\n        const isRTL = direction === \"rtl\"\n        const x = anchorX === \"left\"\n            ? (isRTL ? `right:${right}` : `left:${left}`)\n            : (isRTL ? `left:${left}` : `right:${right}`)\n        const y = anchorY === \"bottom\" ? `bottom:${bottom}` : `top:${top}`\n\n        sheet?.insertRule(\n            `[data-motion-pop-id=\"${id}\"]{position:absolute!important;width:${width}px!important;height:${height}px!important;${x}px!important;${y}px!important}`\n        )\n    }\n\n    /**\n     * We create and inject a style block so we can apply this explicit\n     * sizing in a non-destructive manner by just deleting the style block.\n     *\n     * We can't apply size via render as the measurement happens\n     * in getSnapshotBeforeUpdate (post-render), likewise if we apply the\n     * styles directly on the DOM node, we might be overwriting\n     * styles set via the style prop.\n     */\n    useInsertionEffect(() => {\n        const { width, height } = size.current\n        const element = getElement()\n        if (isPresent || pop === false || !element || !width || !height) return\n\n        element.dataset.motionPopId = id\n\n        /**\n         * Use the element's own document so this works when rendering\n         * into another window or iframe (e.g. via window.open()).\n         */\n        const doc = element.ownerDocument\n        const style = doc.createElement(\"style\")\n        if (nonce) style.nonce = nonce\n\n        const parent = root ?? doc.head\n        parent.appendChild(style)\n\n        insertPopRule(style.sheet)\n\n        /**\n         * Popping the element out of the layout can move its offsetParent,\n         * which we've pinned it to. For instance, a bottom-anchored or\n         * centred container shrinks around its remaining children (#2348).\n         * So once the whole commit is in the DOM, measure how far the\n         * element moved and offset it back.\n         *\n         * This runs on the microtask frameloop, so before paint even if\n         * this commit happens within an animation frame. Every popped\n         * element is measured in its first step, then corrected at the\n         * start of the read step: before the projection system, which\n         * schedules its layout measurement there from layout effects, so\n         * layout animations see the corrected position.\n         */\n        let dx = 0\n        let dy = 0\n        microtask.setup(() => {\n            const [x, y] = measurePoint(element)\n            dx = size.current.point![0] - x\n            dy = size.current.point![1] - y\n        })\n        microtask.read(() => {\n            /**\n             * The style has no sheet if it's been removed since, because\n             * the element is present again.\n             */\n            const { sheet } = style\n            if (!sheet || (!dx && !dy)) return\n            const s = size.current\n            s.left += dx\n            s.right -= dx\n            s.top += dy\n            s.bottom -= dy\n            sheet.deleteRule(0)\n            insertPopRule(sheet)\n        })\n\n        return () => {\n            element.removeAttribute(\"data-motion-pop-id\")\n            if (parent.contains(style)) {\n                parent.removeChild(style)\n            }\n        }\n    }, [isPresent])\n\n    return (\n        <PopChildMeasure isPresent={isPresent} getElement={getElement} sizeRef={size} pop={pop}>\n            {pop === false\n                ? children\n                : React.cloneElement(children as any, { ref: composedRef })}\n        </PopChildMeasure>\n    )\n}\n"],"names":[],"mappings":";;;;;;;;AAmCA;;AAEG;AACH;AACI;;AAGI;AACA;;AAER;AAEA;;;;;;;;;;;;;;AAcG;AACH;;;AAGI;AAES;AAEL;AACA;;AAEI;AACA;;AAEJ;;;AAEJ;;AAEJ;AAEA;;;AAGG;AACH;AACI;;;AAGQ;AACA;AACI;;AAEJ;AACI;;AAGJ;;;;AAIA;AACA;AACA;AACA;AACA;AACA;;AAGJ;;AAGJ;;AAEG;AACH;;AAGI;;AAEP;;AAGG;AACA;;AAEI;AACA;AACA;AACA;AACA;AACA;AACA;AACH;;AAED;;;;;;;AAOG;AACH;AAEQ;;;;AAKR;;;;AAIG;AACH;AAGA;AACI;AACA;AACA;AACI;AACA;AACJ;AAEA;AAGJ;AAEA;;;;;;;;AAQG;;;AAGC;AACA;;AAEA;AAEA;;;AAGG;AACH;;AAEA;AAAW;AAEX;AACA;AAEA;AAEA;;;;;;;;;;;;;AAaG;;;AAGH;;;;AAIA;AACA;AACI;;;AAGG;AACH;;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEJ;AAEA;AACI;AACA;AACI;;AAER;AACJ;;AAKY;AACA;AAGhB;;"}