{"version":3,"file":"use-visual-element.mjs","sources":["../../../../src/motion/utils/use-visual-element.ts"],"sourcesContent":["\"use client\"\n\nimport {\n    optimizedAppearDataAttribute,\n    type HTMLRenderState,\n    type SVGRenderState,\n    type VisualElement,\n} from \"motion-dom\"\nimport * as React from \"react\"\nimport { useContext, useEffect, useInsertionEffect, useRef } from \"react\"\nimport { LazyContext } from \"../../context/LazyContext\"\nimport { MotionConfigContext } from \"../../context/MotionConfigContext\"\nimport { MotionContext } from \"../../context/MotionContext\"\nimport { PresenceContext } from \"../../context/PresenceContext\"\nimport {\n    InitialPromotionConfig,\n    SwitchLayoutGroupContext,\n} from \"../../context/SwitchLayoutGroupContext\"\nimport { MotionProps } from \"../../motion/types\"\nimport type { IProjectionNode } from \"motion-dom\"\nimport { DOMMotionComponents } from \"../../render/dom/types\"\nimport { CreateVisualElement } from \"../../render/types\"\nimport { isRefObject } from \"../../utils/is-ref-object\"\nimport { useIsomorphicLayoutEffect } from \"../../utils/use-isomorphic-effect\"\nimport { VisualState } from \"./use-visual-state\"\n\nexport function useVisualElement<\n    Props,\n    TagName extends keyof DOMMotionComponents | string\n>(\n    Component: TagName | string | React.ComponentType<Props>,\n    visualState:\n        | VisualState<SVGElement, SVGRenderState>\n        | VisualState<HTMLElement, HTMLRenderState>,\n    props: MotionProps & Partial<MotionConfigContext>,\n    createVisualElement?: CreateVisualElement<Props, TagName>,\n    ProjectionNodeConstructor?: any,\n    isSVG?: boolean\n): VisualElement<HTMLElement | SVGElement> | undefined {\n    const { visualElement: parent } = useContext(MotionContext)\n    const lazyContext = useContext(LazyContext)\n    const presenceContext = useContext(PresenceContext)\n    const motionConfig = useContext(MotionConfigContext)\n    const reducedMotionConfig = motionConfig.reducedMotion\n    const skipAnimations = motionConfig.skipAnimations\n\n    const visualElementRef = useRef<VisualElement<\n        HTMLElement | SVGElement\n    > | null>(null)\n\n    /**\n     * Track whether the component has been through React's commit phase.\n     * Used to detect when LazyMotion features load after the component has mounted.\n     */\n    const hasMountedOnce = useRef(false)\n\n    /**\n     * If we haven't preloaded a renderer, check to see if we have one lazy-loaded\n     */\n    createVisualElement =\n        createVisualElement ||\n        (lazyContext.renderer as CreateVisualElement<Props, TagName>)\n\n    if (!visualElementRef.current && createVisualElement) {\n        visualElementRef.current = createVisualElement(Component, {\n            visualState,\n            parent,\n            props,\n            presenceContext,\n            blockInitialAnimation: presenceContext\n                ? presenceContext.initial === false\n                : false,\n            reducedMotionConfig,\n            skipAnimations,\n            isSVG,\n        })\n\n        /**\n         * If the component has already mounted before features loaded (e.g. via\n         * LazyMotion with async feature loading), we need to force the initial\n         * animation to run. Otherwise state changes that occurred before features\n         * loaded will be lost and the element will snap to its final state.\n         */\n        if (hasMountedOnce.current && visualElementRef.current) {\n            visualElementRef.current.manuallyAnimateOnMount = true\n        }\n    }\n\n    const visualElement = visualElementRef.current\n\n    /**\n     * Load Motion gesture and animation features. These are rendered as renderless\n     * components so each feature can optionally make use of React lifecycle methods.\n     */\n    const initialLayoutGroupConfig = useContext(SwitchLayoutGroupContext)\n\n    if (\n        visualElement &&\n        !visualElement.projection &&\n        ProjectionNodeConstructor &&\n        (visualElement.type === \"html\" || visualElement.type === \"svg\")\n    ) {\n        createProjectionNode(\n            visualElementRef.current!,\n            props,\n            ProjectionNodeConstructor,\n            initialLayoutGroupConfig\n        )\n    }\n\n    const isMounted = useRef(false)\n    useInsertionEffect(() => {\n        /**\n         * Check the component has already mounted before calling\n         * `update` unnecessarily. This ensures we skip the initial update.\n         */\n        if (visualElement && isMounted.current) {\n            visualElement.update(props, presenceContext)\n        }\n    })\n\n    /**\n     * Cache this value as we want to know whether HandoffAppearAnimations\n     * was present on initial render - it will be deleted after this.\n     */\n    const optimisedAppearId =\n        props[optimizedAppearDataAttribute as keyof typeof props]\n    const wantsHandoff = useRef(\n        Boolean(optimisedAppearId) &&\n            typeof window !== \"undefined\" &&\n            !window.MotionHandoffIsComplete?.(optimisedAppearId) &&\n            window.MotionHasOptimisedAnimation?.(optimisedAppearId)\n    )\n\n    useIsomorphicLayoutEffect(() => {\n        /**\n         * Track that this component has mounted. This is used to detect when\n         * LazyMotion features load after the component has already committed.\n         */\n        hasMountedOnce.current = true\n\n        if (!visualElement) return\n\n        isMounted.current = true\n        window.MotionIsMounted = true\n\n        visualElement.updateFeatures()\n        visualElement.scheduleRenderMicrotask()\n\n        /**\n         * Ideally this function would always run in a useEffect.\n         *\n         * However, if we have optimised appear animations to handoff from,\n         * it needs to happen synchronously to ensure there's no flash of\n         * incorrect styles in the event of a hydration error.\n         *\n         * So if we detect a situtation where optimised appear animations\n         * are running, we use useLayoutEffect to trigger animations.\n         */\n        if (wantsHandoff.current && visualElement.animationState) {\n            visualElement.animationState.animateChanges()\n        }\n    })\n\n    useEffect(() => {\n        if (!visualElement) return\n\n        if (!wantsHandoff.current && visualElement.animationState) {\n            visualElement.animationState.animateChanges()\n        }\n\n        if (wantsHandoff.current) {\n            // This ensures all future calls to animateChanges() in this component will run in useEffect\n            queueMicrotask(() => {\n                window.MotionHandoffMarkAsComplete?.(optimisedAppearId)\n            })\n\n            wantsHandoff.current = false\n        }\n\n        /**\n         * Now we've finished triggering animations for this element we\n         * can wipe the enteringChildren set for the next render.\n         */\n        visualElement.enteringChildren = undefined\n    })\n\n    return visualElement!\n}\n\nfunction createProjectionNode(\n    visualElement: VisualElement<any>,\n    props: MotionProps,\n    ProjectionNodeConstructor: any,\n    initialPromotionConfig?: InitialPromotionConfig\n) {\n    const {\n        layoutId,\n        layout,\n        drag,\n        dragConstraints,\n        layoutScroll,\n        layoutRoot,\n        layoutAnchor,\n        layoutCrossfade,\n    } = props\n\n    visualElement.projection = new ProjectionNodeConstructor(\n        visualElement.latestValues,\n        props[\"data-framer-portal-id\"]\n            ? undefined\n            : getClosestProjectingNode(visualElement.parent)\n    ) as IProjectionNode\n\n    visualElement.projection.setOptions({\n        layoutId,\n        layout,\n        alwaysMeasureLayout:\n            Boolean(drag) || (dragConstraints && isRefObject(dragConstraints)),\n        visualElement,\n        /**\n         * TODO: Update options in an effect. This could be tricky as it'll be too late\n         * to update by the time layout animations run.\n         * We also need to fix this safeToRemove by linking it up to the one returned by usePresence,\n         * ensuring it gets called if there's no potential layout animations.\n         *\n         */\n        animationType: typeof layout === \"string\" ? layout : \"both\",\n        initialPromotionConfig,\n        crossfade: layoutCrossfade,\n        layoutScroll,\n        layoutRoot,\n        layoutAnchor,\n    })\n}\n\nfunction getClosestProjectingNode(\n    visualElement?: VisualElement<\n        unknown,\n        unknown,\n        { allowProjection?: boolean }\n    >\n): IProjectionNode | undefined {\n    if (!visualElement) return undefined\n\n    return visualElement.options.allowProjection !== false\n        ? visualElement.projection\n        : getClosestProjectingNode(visualElement.parent)\n}\n"],"names":[],"mappings":";;;;;;;;;;;AA0BM;;AAcF;AACA;AACA;AACA;AACA;AAEA;AAIA;;;AAGG;AACH;AAEA;;AAEG;;;;AAKH;AACI;;;;;AAKI;AACI;AACA;;;;AAIP;AAED;;;;;AAKG;;AAEC;;;AAIR;AAEA;;;AAGG;AACH;AAEA;;;AAII;;;AAUJ;;AAEI;;;AAGG;AACH;AACI;;AAER;AAEA;;;AAGG;AACH;AAEA;;AAGQ;AACA;;AAIJ;;;AAGG;AACH;AAEA;;AAEA;AACA;;;AAKA;;;;;;;;;AASG;;AAEC;;AAER;;AAGI;;;AAGI;;AAGJ;;;AAGQ;AACJ;AAEA;;AAGJ;;;AAGG;AACH;AACJ;AAEA;AACJ;AAEA;AAMI;AAWA;AAGQ;;AAIR;;;AAGI;;AAGA;;;;;;AAMG;AACH;;AAEA;;;;AAIH;AACL;AAEA;AAOI;AAAoB;AAEpB;;AAEI;AACR;;"}