import * as React from "react" ; import { useState , useEffect , memo , cloneElement } from "react" ; import { Size , useAnimation } from "framer" ; import * as Framer from "framer" ; import { getCache } from "./store/keyStore" ; import { pick } from "./utils/pick" ; import { randomID } from "./utils/randomID" ; import { getBackgroundColorPair , getBorderPair , getBorderRadius , getBoxShadowPair , getOpacity , getRotate , isBackgroundTransitionAnimatable , filterOutAbsolutePositioningProps , } from "./utils/styleParsing" ; import { canAnimateNodeChildren , getNodeName , getNodeId , getNodeType , getNodeRect , getNodeChildren , isNodeVisible , nodeWithIdAndKey , isNodeAnimatable , isSameComponent , } from "./utils/nodeHelpers" ; import { addAnimatableWrapperToNodeIfNeeded } from "./utils/addAnimatableWrapperToNodeIfNeeded" ; const motionSupportsLayoutProp = "AnimateLayoutFeature" in Framer ; const motionNeedsInitialVariant = ! ( "createCrossfader" in Framer ) ; const propsForPositionReset = { top : null , right : null , bottom : null , left : null , center : null , } ; const _AutoAnimatedState = ( { source , target , transitionPropsForElement , sourceParentSize , targetParentSize , direction , morphCodeComponentPropsOnly = true , parentContext = null , sourceKey = null , keyCache = null , transitionKey = null , } ) => { const [ id , _ ] = useState ( randomID ( ) ) ; const keySourceCache = keyCache || getCache ( id ) ; const getSourceKey = keySourceCache . getSourceKey ; source = nodeWithIdAndKey ( source ) ; target = nodeWithIdAndKey ( target ) ; const tkey = transitionKey || `${ source . key }-${ target . key }` ; const sourceNodeType = getNodeType ( source ) ; const targetNodeType = getNodeType ( target ) ; const useAbsolutePositioning = ! ( [ "StackLegacyContainer" , "Stack" ] . indexOf ( sourceNodeType ) > - 1 || [ "StackLegacyContainer" , "Stack" ] . indexOf ( targetNodeType ) > - 1 || parentContext === "Stack" ) ; const controls = useAnimation ( ) ; const initialVariantName = `__switch_initial_${ tkey }` ; const nextVariantName = `__switch_next_${ tkey }` ; const isRoot = transitionKey === null ; const sourcePositionAndSizeProps = getNodeRect ( source , sourceParentSize ) ; const targetPositionAndSizeProps = getNodeRect ( target , targetParentSize ) ; const shouldAnimateChildren = canAnimateNodeChildren ( source ) && canAnimateNodeChildren ( target ) ; const sourceStateChildren = shouldAnimateChildren ? getNodeChildren ( source ) : [ ] ; const targetStateChildren = shouldAnimateChildren ? getNodeChildren ( target ) : [ ] ; const morphingChildrenPairs = [ ] ; const morphingChildrenIds = [ ] ; const enteringChildrenIds = [ ] ; const exitingChildrenIds = [ ] ; const sourceStateChildrenIds = sourceStateChildren . map ( getNodeId ) ; targetStateChildren . forEach ( ( child ) => { const name = getNodeName ( child ) ; const id = getNodeId ( child ) ; if ( ! isNodeVisible ( child ) ) { return ; } const match = sourceStateChildren . find ( ( otherChild ) => { const otherName = getNodeName ( otherChild ) ; const otherId = getNodeId ( otherChild ) ; return ( name === otherName && morphingChildrenIds . indexOf ( otherId ) === - 1 ) ; } ) ; if ( match ) { const otherId = getNodeId ( match ) ; morphingChildrenIds . push ( otherId ) ; morphingChildrenPairs . push ( { source : otherId , target : id } ) ; return ; } enteringChildrenIds . push ( id ) ; } ) ; sourceStateChildren . forEach ( ( child ) => { const id = getNodeId ( child ) ; if ( morphingChildrenIds . indexOf ( id ) === - 1 ) { exitingChildrenIds . push ( id ) ; } } ) ; const targetHierarchy = targetStateChildren . map ( ( child ) => { const id = getNodeId ( child ) ; const morphingPair = morphingChildrenPairs . find ( ( c ) => c . target === id ) ; if ( morphingPair ) { return sourceStateChildren . find ( ( c ) => getNodeId ( c ) === morphingPair . source ) ; } return child ; } ) ; exitingChildrenIds . forEach ( ( id ) => { const index = sourceStateChildrenIds . indexOf ( id ) ; let closestMorphingChildId ; for ( let i = index ; i >= 0 ; i -- ) { if ( morphingChildrenIds . indexOf ( sourceStateChildrenIds [ i ] ) !== - 1 ) { closestMorphingChildId = sourceStateChildrenIds [ i ] ; break ; } } const indexOfClosestMorphingChild = targetHierarchy . findIndex ( ( c ) => getNodeId ( c ) === closestMorphingChildId ) ; const child = sourceStateChildren . find ( ( c ) => getNodeId ( c ) === id ) ; if ( typeof indexOfClosestMorphingChild !== "undefined" ) { targetHierarchy . splice ( indexOfClosestMorphingChild + 1 , 0 , child ) ; } else { targetHierarchy . unshift ( child ) ; } } ) ; const animatedHierarchy = targetHierarchy . map ( ( child ) => { const id = getNodeId ( child ) ; const morphingPair = morphingChildrenPairs . find ( ( c ) => c . source === id ) ; if ( morphingPair ) { const targetChild = targetStateChildren . find ( ( c ) => getNodeId ( c ) === morphingPair . target ) ; const key = getSourceKey ( targetChild . key , child . key ) ; return React . createElement ( AutoAnimatedState , { key , sourceKey : key , transitionKey : tkey , source : child , target : targetChild , transitionPropsForElement , sourceParentSize : Size ( sourcePositionAndSizeProps . width , sourcePositionAndSizeProps . height ) , targetParentSize : Size ( targetPositionAndSizeProps . width , targetPositionAndSizeProps . height ) , direction , parentContext : [ "StackLegacyContainer" , "Stack" ] . indexOf ( sourceNodeType ) > - 1 || [ "StackLegacyContainer" , "Stack" ] . indexOf ( targetNodeType ) > - 1 ? "Stack" : null , keyCache : keySourceCache , } ) ; } const positionAndSize = getNodeRect ( child , sourcePositionAndSizeProps ) ; const wrappedChild = addAnimatableWrapperToNodeIfNeeded ( child , { ... propsForPositionReset , width : "100%" , height : "100%" , } ) ; if ( enteringChildrenIds . indexOf ( id ) !== - 1 ) { const positionAndSizeInTarget = getNodeRect ( child , targetPositionAndSizeProps ) ; let props = { ... propsForPositionReset , ... positionAndSizeInTarget , ... transitionPropsForElement ( { source : child , sourceRect : positionAndSize , transition : "enter" , transitionKey : tkey , useAbsolutePositioning , } ) , } ; props = useAbsolutePositioning ? props : filterOutAbsolutePositioningProps ( props ) ; return cloneElement ( wrappedChild , props ) ; } if ( exitingChildrenIds . indexOf ( id ) !== - 1 ) { let props = { ... propsForPositionReset , ... positionAndSize , ... transitionPropsForElement ( { source : child , sourceRect : positionAndSize , transition : "exit" , transitionKey : tkey , useAbsolutePositioning , } ) , } ; props = useAbsolutePositioning ? props : filterOutAbsolutePositioningProps ( props ) ; return cloneElement ( wrappedChild , props ) ; } } ) ; const hasNonAnimatableTransitions = sourceNodeType === targetNodeType && ! isBackgroundTransitionAnimatable ( source , target ) ; const shouldCrossDissolve = sourceNodeType !== targetNodeType || hasNonAnimatableTransitions || ! isNodeAnimatable ( source ) || ! isNodeAnimatable ( target ) ; const shouldMorphComponentProps = morphCodeComponentPropsOnly && sourceNodeType === "ComponentContainer" && targetNodeType === "ComponentContainer" && isSameComponent ( source , target ) ; useEffect ( ( ) => { if ( ! isRoot ) { return ; } if ( source . type === target . type && getNodeId ( source ) === getNodeId ( target ) ) { controls . set ( nextVariantName ) ; } else { requestAnimationFrame ( ( ) => setTimeout ( ( ) => { controls . start ( nextVariantName ) ; } , 0 ) ) ; } } , [ source , target , controls , initialVariantName , nextVariantName ] ) ; if ( shouldCrossDissolve && ! shouldMorphComponentProps ) { const enteringChildVariants = { [ initialVariantName ] : { opacity : 0 , display : "block" , } , [ nextVariantName ] : { ... targetPositionAndSizeProps , opacity : getOpacity ( target . props . style || { } ) , display : "block" , scaleX : 1 , scaleY : 1 , translateX : [ 0 , 0 ] , } , } ; const exitingChildVariants = { [ initialVariantName ] : { opacity : getOpacity ( source . props . style || { } ) , display : "block" , } , [ nextVariantName ] : { ... pick ( targetPositionAndSizeProps , [ "top" , "left" ] ) , scaleX : targetPositionAndSizeProps . width / sourcePositionAndSizeProps . width , scaleY : targetPositionAndSizeProps . height / sourcePositionAndSizeProps . height , opacity : 0 , transitionEnd : { translateX : - 9999 , } , } , } ; const enterTransitionProps = transitionPropsForElement ( { source , target , transition : "cross-dissolve-enter" , } ) ; const exitTransitionProps = transitionPropsForElement ( { source , target , transition : "cross-dissolve-exit" , } ) ; const key = getSourceKey ( sourceKey , source . key ) ; const keyForEnteringChild = direction === 1 ? `__enter_${ key }` : `__exit_${ key }` ; const sameSourceAndTarget = source . key === target . key ; let enteringChildProps = { key : keyForEnteringChild , ... propsForPositionReset , ... pick ( sourcePositionAndSizeProps , [ "top" , "left" ] ) , ... pick ( targetPositionAndSizeProps , [ "width" , "height" ] ) , originX : 0 , originY : 0 , scaleX : sourcePositionAndSizeProps . width / targetPositionAndSizeProps . width , scaleY : sourcePositionAndSizeProps . height / targetPositionAndSizeProps . height , variants : { ... ( target . props . variants || { } ) , ... enteringChildVariants , } , animate : isRoot ? controls : undefined , ... enterTransitionProps , } ; if ( motionNeedsInitialVariant ) { enteringChildProps . initial = initialVariantName ; } enteringChildProps = useAbsolutePositioning ? enteringChildProps : filterOutAbsolutePositioningProps ( enteringChildProps ) ; const keyForExitingChild = direction === 1 ? `__exit_${ key }` : `__enter_${ key }` ; let exitingChildProps = { key : keyForExitingChild , _canMagicMotion : ! sameSourceAndTarget , ... propsForPositionReset , ... sourcePositionAndSizeProps , originX : 0 , originY : 0 , scaleX : 1 , scaleY : 1 , variants : { ... ( source . props . variants || { } ) , ... exitingChildVariants , } , animate : isRoot ? controls : undefined , ... exitTransitionProps , } ; if ( motionNeedsInitialVariant ) { exitingChildProps . initial = initialVariantName ; } exitingChildProps = useAbsolutePositioning ? exitingChildProps : filterOutAbsolutePositioningProps ( exitingChildProps ) ; const wrappedSource = addAnimatableWrapperToNodeIfNeeded ( source , { ... propsForPositionReset , width : "100%" , height : "100%" , } , shouldAnimateChildren ? animatedHierarchy : [ ] ) ; const wrappedTarget = addAnimatableWrapperToNodeIfNeeded ( target , { ... propsForPositionReset , ... pick ( targetPositionAndSizeProps , [ "width" , "height" ] ) , } , shouldAnimateChildren ? animatedHierarchy : [ ] ) ; const enteringElement = cloneElement ( wrappedTarget , enteringChildProps ) ; const exitingElement = cloneElement ( wrappedSource , exitingChildProps ) ; return ( < > { exitingElement } { enteringElement } < />); } const { sourceProps : initialVariant , targetProps : nextVariant , } = getPropTransitionsBetweenNodes ( source , target , sourceParentSize , targetParentSize , parentContext ) ; let transitionProps = { ... propsForPositionReset , ... sourcePositionAndSizeProps , _border : null , style : { ... source . props . style , } , variants : { ... ( target . props . variants || { } ) , [ nextVariantName ] : nextVariant , } , animate : isRoot ? controls : undefined , ... transitionPropsForElement ( { source , target , transition : "morph" , } ) , } ; if ( motionNeedsInitialVariant ) { transitionProps . initial = initialVariantName ; transitionProps . variants [ initialVariantName ] = initialVariant ; } transitionProps = ! useAbsolutePositioning && sourceNodeType !== "Stack" ? filterOutAbsolutePositioningProps ( transitionProps ) : transitionProps ; const key = getSourceKey ( sourceKey , source . key ) ; if ( sourceNodeType === "StackLegacyContainer" ) { const containerProps = { key , id : null , ... propsForPositionReset , ... sourcePositionAndSizeProps , top : 0 , left : 0 , _border : null , } ; const sourceStack = React . Children . toArray ( source . props . children ) [ 0 ] ; const targetStack = React . Children . toArray ( target . props . children ) [ 0 ] ; const { sourceProps : stackInitialVariant , targetProps : stackNextVariant , } = getPropTransitionsBetweenNodes ( sourceStack , targetStack , sourcePositionAndSizeProps , targetPositionAndSizeProps , parentContext ) ; let stackProps = { key , id : null , ... transitionProps , top : sourcePositionAndSizeProps . top , left : sourcePositionAndSizeProps . left , variants : { [ initialVariantName ] : stackInitialVariant , [ nextVariantName ] : { ... stackNextVariant , top : targetPositionAndSizeProps . top , left : targetPositionAndSizeProps . left , } , } , } ; stackProps = parentContext === "Stack" ? filterOutAbsolutePositioningProps ( stackProps ) : stackProps ; return cloneElement ( source , containerProps , cloneElement ( sourceStack , stackProps , ... animatedHierarchy ) ) ; } if ( shouldMorphComponentProps ) { const sourceComponent = React . Children . toArray ( source . props . children ) [ 0 ] ; const targetComponent = React . Children . toArray ( target . props . children ) [ 0 ] ; const layoutTransitionProps = motionSupportsLayoutProp ? { layout : ! useAbsolutePositioning } : { positionTransition : useAbsolutePositioning ? false : transitionPropsForElement ( { source , target , transition : "morph" , } ) . transition , } ; const containerProps = { ... transitionProps , ... layoutTransitionProps , key , } ; const wrappedSource = addAnimatableWrapperToNodeIfNeeded ( source , { ... propsForPositionReset , width : "100%" , height : "100%" , id : target . props . id , key , } , [ cloneElement ( sourceComponent , { ... targetComponent . props , key , } ) , ] ) ; return < > { cloneElement ( wrappedSource , containerProps ) } < />; } const wrappedTarget = addAnimatableWrapperToNodeIfNeeded ( target , { ... propsForPositionReset , width : "100%" , height : "100%" , key , } ) ; const layoutTransitionProps = motionSupportsLayoutProp ? { layout : ! useAbsolutePositioning } : { positionTransition : useAbsolutePositioning ? false : transitionPropsForElement ( { source , target , transition : "morph" , } ) . transition , } ; return cloneElement ( wrappedTarget , { id : null , ... transitionProps , ... layoutTransitionProps , key , } , [ < React . Fragment key = { key } > { animatedHierarchy } < /React.Fragment>]); } ; _AutoAnimatedState . displayName = "AutoAnimatedState" ; export const AutoAnimatedState = memo ( _AutoAnimatedState ) as any ; const getPropTransitionsBetweenNodes = ( source , target , sourceParentSize , targetParentSize , parentContext ) => { const targetPositionAndSizeProps = getNodeRect ( target , targetParentSize ) ; const [ sourceBackgroundProps , targetBackgroundProps , ] = getBackgroundColorPair ( source . props , target . props ) ; const [ sourceBoxShadow , targetBoxShadow ] = getBoxShadowPair ( source . props , target . props ) ; const [ sourceBorder , targetBorder ] = getBorderPair ( source . props , target . props ) ; const sourceProps = { opacity : getOpacity ( source . props . style ) , rotate : getRotate ( source . props . style ) , ... getBorderRadius ( source . props . style ) , ... sourceBackgroundProps , boxShadow : sourceBoxShadow , ... sourceBorder , } ; let targetProps = { ... targetPositionAndSizeProps , opacity : getOpacity ( target . props . style ) , rotate : getRotate ( target . props . style ) , ... getBorderRadius ( target . props . style ) , ... targetBackgroundProps , boxShadow : targetBoxShadow , ... targetBorder , } ; targetProps = parentContext === "Stack" ? filterOutAbsolutePositioningProps ( targetProps ) : targetProps ; return { sourceProps , targetProps , } ; } ;