From 331513ed3bf2f6abf63e136d2fe43e93128e5e67 Mon Sep 17 00:00:00 2001 From: InventingWithMonster Date: Fri, 19 Feb 2021 12:04:50 +0100 Subject: [PATCH 1/3] Latest changes --- dev/examples/AnimatePresence-variants.tsx | 30 ++-- .../__tests__/AnimatePresence.test.tsx | 48 ++++++ src/gestures/__tests__/hover.test.tsx | 2 +- .../utils/__tests__/animation-state.test.ts | 158 +++++++++++------- src/render/utils/animation-state.ts | 44 +++-- src/render/utils/animation.ts | 36 +++- 6 files changed, 225 insertions(+), 93 deletions(-) diff --git a/dev/examples/AnimatePresence-variants.tsx b/dev/examples/AnimatePresence-variants.tsx index ac5c3e33ca..e7ea1b2243 100644 --- a/dev/examples/AnimatePresence-variants.tsx +++ b/dev/examples/AnimatePresence-variants.tsx @@ -26,9 +26,11 @@ const itemVariants = { const listVariants = { open: { - transition: { staggerChildren: 0.07, when: "beforeChildren" }, + opacity: 1, + transition: { staggerChildren: 1, when: "beforeChildren" }, }, closed: { + opacity: 0, transition: { when: "afterChildren", staggerChildren: 0.3, @@ -49,27 +51,25 @@ export const App = () => { return ( console.log("rest")}> {isVisible && ( - - - - Test - - - Test - - - Test - - - + + Test + + + Test + + + Test + + )} ) diff --git a/src/components/AnimatePresence/__tests__/AnimatePresence.test.tsx b/src/components/AnimatePresence/__tests__/AnimatePresence.test.tsx index fb74798a84..7202147ec6 100644 --- a/src/components/AnimatePresence/__tests__/AnimatePresence.test.tsx +++ b/src/components/AnimatePresence/__tests__/AnimatePresence.test.tsx @@ -2,6 +2,7 @@ import { render } from "../../../../jest.setup" import * as React from "react" import { AnimatePresence, motion, MotionConfig, useAnimation } from "../../.." import { motionValue } from "../../../value" +import { ResolvedValues } from "../../../render/types" describe("AnimatePresence", () => { test("Allows initial animation if no `initial` prop defined", async () => { @@ -95,6 +96,53 @@ describe("AnimatePresence", () => { expect(child).toBeFalsy() }) + test("when: afterChildren fires correctly", async () => { + const promise = new Promise((resolve) => { + const parentOpacityOutput: ResolvedValues[] = [] + + const variants = { + visible: { opacity: 1 }, + hidden: { opacity: 0 }, + } + + const Component = ({ isVisible }: { isVisible: boolean }) => { + return ( + + {isVisible && ( + parentOpacityOutput.push(v)} + onAnimationComplete={() => + resolve(parentOpacityOutput.length) + } + > + + + )} + + ) + } + + const { rerender } = render() + rerender() + rerender() + rerender() + }) + + const child = await promise + expect(child).toBeGreaterThan(1) + }) + test("Animates a component back in if it's re-added before animating out", async () => { const promise = new Promise((resolve) => { const Component = ({ isVisible }: { isVisible: boolean }) => { diff --git a/src/gestures/__tests__/hover.test.tsx b/src/gestures/__tests__/hover.test.tsx index 3948f093e3..050a3e55ae 100644 --- a/src/gestures/__tests__/hover.test.tsx +++ b/src/gestures/__tests__/hover.test.tsx @@ -157,7 +157,7 @@ describe("hover", () => { style={{ opacity, scale }} /> ) - + console.log("hover testt") const { container, rerender } = render() rerender() diff --git a/src/render/utils/__tests__/animation-state.test.ts b/src/render/utils/__tests__/animation-state.test.ts index 38516f8f45..1f78182d3c 100644 --- a/src/render/utils/__tests__/animation-state.test.ts +++ b/src/render/utils/__tests__/animation-state.test.ts @@ -79,7 +79,9 @@ describe("Animation state - Initiating props", () => { animate: { opacity: 1 }, }) - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({}) + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual( + {} + ) expect(animate).toBeCalledWith([{ opacity: 1 }]) }) @@ -94,7 +96,9 @@ describe("Animation state - Initiating props", () => { }, }) - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({}) + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual( + {} + ) expect(animate).toBeCalledWith(["test"]) }) @@ -109,7 +113,9 @@ describe("Animation state - Initiating props", () => { }, }) - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({}) + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual( + {} + ) expect(animate).toBeCalledWith(["test", "heyoo"]) }) @@ -126,7 +132,9 @@ describe("Animation state - Initiating props", () => { }, }) - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({}) + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual( + {} + ) expect(animate).not.toBeCalled() }) @@ -139,7 +147,9 @@ describe("Animation state - Initiating props", () => { animate: { opacity: 1 }, }) - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({}) + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual( + {} + ) expect(animate).not.toBeCalled() }) @@ -155,7 +165,9 @@ describe("Animation state - Initiating props", () => { }, }) - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({}) + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual( + {} + ) expect(animate).not.toBeCalled() }) @@ -171,7 +183,9 @@ describe("Animation state - Initiating props", () => { }, }) - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({}) + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual( + {} + ) expect(animate).not.toBeCalled() }) }) @@ -191,7 +205,7 @@ describe("Animation state - Setting props", () => { }) expect(animate).not.toBeCalled() - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({ + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual({ opacity: true, }) }) @@ -209,7 +223,7 @@ describe("Animation state - Setting props", () => { }) expect(animate).not.toBeCalled() - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({ + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual({ opacity: true, }) }) @@ -234,7 +248,7 @@ describe("Animation state - Setting props", () => { }) expect(animate).not.toBeCalled() - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({ + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual({ opacity: true, }) }) @@ -259,7 +273,7 @@ describe("Animation state - Setting props", () => { }) expect(animate).not.toBeCalled() - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({ + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual({ opacity: true, }) }) @@ -278,7 +292,9 @@ describe("Animation state - Setting props", () => { }) expect(animate).toBeCalledWith([{ opacity: 0 }]) - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({}) + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual( + {} + ) }) test("Change single value, keyframes", () => { @@ -295,7 +311,9 @@ describe("Animation state - Setting props", () => { }) expect(animate).toBeCalledWith([{ opacity: [0.5, 1] }]) - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({}) + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual( + {} + ) }) test("Change single value, variant", () => { @@ -320,7 +338,9 @@ describe("Animation state - Setting props", () => { }) expect(animate).toBeCalledWith(["b"]) - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({}) + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual( + {} + ) }) test("Change single value, variant list", () => { @@ -345,7 +365,9 @@ describe("Animation state - Setting props", () => { }) expect(animate).toBeCalledWith(["b"]) - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({}) + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual( + {} + ) }) test("Swap between value in target and transitionEnd, target", () => { @@ -364,7 +386,9 @@ describe("Animation state - Setting props", () => { }) expect(animate).toBeCalledWith([{ transitionEnd: { opacity: 0.3 } }]) - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({}) + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual( + {} + ) animate = mockAnimate(state) @@ -373,7 +397,9 @@ describe("Animation state - Setting props", () => { animate: { opacity: 0.2 }, }) expect(animate).toBeCalledWith([{ opacity: 0.2 }]) - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({}) + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual( + {} + ) }) test("Change single value, target, with unchanging values", () => { @@ -390,7 +416,7 @@ describe("Animation state - Setting props", () => { }) expect(animate).toBeCalledWith([{ opacity: 0, x: 0 }]) - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({ + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual({ x: true, }) @@ -401,7 +427,7 @@ describe("Animation state - Setting props", () => { }) expect(animate).toBeCalledWith([{ opacity: 0, x: 100 }]) - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({ + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual({ opacity: true, }) }) @@ -421,7 +447,9 @@ describe("Animation state - Setting props", () => { }) expect(animate).toBeCalledWith([{ opacity: 0 }]) - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({}) + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual( + {} + ) }) test("Removing values, target undefined", () => { @@ -439,7 +467,9 @@ describe("Animation state - Setting props", () => { }) expect(animate).toBeCalledWith([{ opacity: 0 }]) - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({}) + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual( + {} + ) }) /** @@ -468,7 +498,9 @@ describe("Animation state - Setting props", () => { }) expect(animate).toBeCalledWith(["b", { opacity: 1 }]) - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({}) + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual( + {} + ) }) test.skip("Removing values, inherited variant changed", () => { @@ -493,7 +525,9 @@ describe("Animation state - Setting props", () => { }) expect(animate).toBeCalledWith([{ opacity: 1 }]) - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({}) + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual( + {} + ) }) }) @@ -512,71 +546,77 @@ describe("Animation state - Set active", () => { let animate = mockAnimate(state) state.setActive(AnimationType.Hover, true) expect(animate).toBeCalledWith([{ opacity: 0.5 }]) - expect(state.getProtectedKeys(AnimationType.Animate)).toHaveProperty( - "opacity" - ) - expect(state.getProtectedKeys(AnimationType.Hover)).toEqual({}) + expect( + state.getState()[AnimationType.Animate].protectedKeys + ).toHaveProperty("opacity") + expect(state.getState()[AnimationType.Hover].protectedKeys).toEqual({}) // Set hover to false animate = mockAnimate(state) state.setActive(AnimationType.Hover, false) expect(animate).toBeCalledWith([{ opacity: 1 }]) - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({}) - expect(state.getProtectedKeys(AnimationType.Hover)).toEqual({}) + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual( + {} + ) + expect(state.getState()[AnimationType.Hover].protectedKeys).toEqual({}) // Set hover to true animate = mockAnimate(state) state.setActive(AnimationType.Hover, true) expect(animate).toBeCalledWith([{ opacity: 0.5 }]) - expect(state.getProtectedKeys(AnimationType.Animate)).toHaveProperty( - "opacity" - ) - expect(state.getProtectedKeys(AnimationType.Hover)).toEqual({}) + expect( + state.getState()[AnimationType.Animate].protectedKeys + ).toHaveProperty("opacity") + expect(state.getState()[AnimationType.Hover].protectedKeys).toEqual({}) // Set hover to false animate = mockAnimate(state) state.setActive(AnimationType.Hover, false) expect(animate).toBeCalledWith([{ opacity: 1 }]) - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({}) - expect(state.getProtectedKeys(AnimationType.Hover)).toEqual({}) + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual( + {} + ) + expect(state.getState()[AnimationType.Hover].protectedKeys).toEqual({}) // Set hover to true animate = mockAnimate(state) state.setActive(AnimationType.Hover, true) expect(animate).toBeCalledWith([{ opacity: 0.5 }]) - expect(state.getProtectedKeys(AnimationType.Animate)).toHaveProperty( - "opacity" - ) - expect(state.getProtectedKeys(AnimationType.Hover)).toEqual({}) + expect( + state.getState()[AnimationType.Animate].protectedKeys + ).toHaveProperty("opacity") + expect(state.getState()[AnimationType.Hover].protectedKeys).toEqual({}) // Set press to true animate = mockAnimate(state) state.setActive(AnimationType.Tap, true) expect(animate).toBeCalledWith([{ opacity: 0.8 }]) - expect(state.getProtectedKeys(AnimationType.Animate)).toHaveProperty( - "opacity" - ) - expect(state.getProtectedKeys(AnimationType.Hover)).toHaveProperty( - "opacity" - ) - expect(state.getProtectedKeys(AnimationType.Tap)).toEqual({}) + expect( + state.getState()[AnimationType.Animate].protectedKeys + ).toHaveProperty("opacity") + expect( + state.getState()[AnimationType.Hover].protectedKeys + ).toHaveProperty("opacity") + expect(state.getState()[AnimationType.Tap].protectedKeys).toEqual({}) // Set hover to false animate = mockAnimate(state) state.setActive(AnimationType.Hover, false) - expect(state.getProtectedKeys(AnimationType.Animate)).toHaveProperty( - "opacity" - ) - expect(state.getProtectedKeys(AnimationType.Tap)).toHaveProperty( - "opacity" - ) + expect( + state.getState()[AnimationType.Animate].protectedKeys + ).toHaveProperty("opacity") + expect( + state.getState()[AnimationType.Tap].protectedKeys + ).toHaveProperty("opacity") expect(animate).not.toBeCalled() // Set press to false animate = mockAnimate(state) state.setActive(AnimationType.Tap, false) expect(animate).toBeCalledWith([{ opacity: 1 }]) - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({}) + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual( + {} + ) }) test("Change active variant where no variants are defined", () => { @@ -594,14 +634,18 @@ describe("Animation state - Set active", () => { let animate = mockAnimate(state) state.setActive(AnimationType.Hover, true) expect(animate).toBeCalledWith(["b"]) - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({}) - expect(state.getProtectedKeys(AnimationType.Hover)).toEqual({}) + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual( + {} + ) + expect(state.getState()[AnimationType.Hover].protectedKeys).toEqual({}) // Set hover to false animate = mockAnimate(state) state.setActive(AnimationType.Hover, false) expect(animate).toBeCalledWith(["a"]) - expect(state.getProtectedKeys(AnimationType.Animate)).toEqual({}) - expect(state.getProtectedKeys(AnimationType.Hover)).toEqual({}) + expect(state.getState()[AnimationType.Animate].protectedKeys).toEqual( + {} + ) + expect(state.getState()[AnimationType.Hover].protectedKeys).toEqual({}) }) }) diff --git a/src/render/utils/animation-state.ts b/src/render/utils/animation-state.ts index e03b05ba72..2dd426734b 100644 --- a/src/render/utils/animation-state.ts +++ b/src/render/utils/animation-state.ts @@ -22,9 +22,8 @@ export interface AnimationState { options?: AnimationOptions ) => Promise setAnimateFunction: (fn: any) => void - getProtectedKeys: (type: AnimationType) => { [key: string]: any } isAnimated(key: string): boolean - getState: () => { [key: string]: TypeState } + getState: () => { [key: string]: AnimationTypeState } } interface DefinitionAndOptions { @@ -90,10 +89,6 @@ export function createAnimationState( return acc } - function getProtectedKeys(type: AnimationType) { - return state[type].protectedKeys - } - function isAnimated(key: string) { return allAnimatedKeys[key] !== undefined } @@ -255,6 +250,12 @@ export function createAnimationState( ...resolvedValues, } + const markToAnimate = (key: string) => { + shouldAnimateType = true + removedKeys.delete(key) + typeState.needsAnimating[key] = true + } + for (const key in allKeys) { const next = resolvedValues[key] const prev = prevResolvedValues[key] @@ -262,18 +263,27 @@ export function createAnimationState( // If we've already handled this we can just skip ahead if (encounteredKeys.hasOwnProperty(key)) continue + /** + * If the value has changed, we probably want to animate it. + */ if (next !== prev) { + /** + * If both values are keyframes, we need to shallow compare them to + * detect whether any value has changed. If it has, we animate it. + */ if (isKeyframesTarget(next) && isKeyframesTarget(prev)) { if (!shallowCompare(next, prev)) { - shouldAnimateType = true - removedKeys.delete(key) + markToAnimate(key) } else { + /** + * If it hasn't changed, we want to ensure it doesn't animate by + * adding it to the list of protected keys. + */ typeState.protectedKeys[key] = true } } else if (next !== undefined) { // If next is defined and doesn't equal prev, it needs animating - shouldAnimateType = true - removedKeys.delete(key) + markToAnimate(key) } else { // If it's undefined, it's been removed. removedKeys.add(key) @@ -283,9 +293,12 @@ export function createAnimationState( * If next hasn't changed and it isn't undefined, we want to check if it's * been removed by a higher priority */ - shouldAnimateType = true - removedKeys.delete(key) + markToAnimate(key) } else { + /** + * If it hasn't changed, we add it to the list of protected values + * to ensure it doesn't get animated. + */ typeState.protectedKeys[key] = true } } @@ -377,7 +390,6 @@ export function createAnimationState( } return { - getProtectedKeys, isAnimated, animateChanges, setActive, @@ -396,17 +408,19 @@ export function variantsHaveChanged(prev: any, next: any) { return false } -interface TypeState { +export interface AnimationTypeState { isActive: boolean protectedKeys: { [key: string]: true } + needsAnimating: { [key: string]: boolean } prevResolvedValues: { [key: string]: any } prevProp?: VariantLabels | TargetAndTransition } -function createTypeState(isActive = false): TypeState { +function createTypeState(isActive = false): AnimationTypeState { return { isActive, protectedKeys: {}, + needsAnimating: {}, prevResolvedValues: {}, } } diff --git a/src/render/utils/animation.ts b/src/render/utils/animation.ts index a3587f5694..cc2f1b4dd1 100644 --- a/src/render/utils/animation.ts +++ b/src/render/utils/animation.ts @@ -8,7 +8,7 @@ import { Transition, } from "../../types" import { VisualElement } from "../types" -import { AnimationType } from "./animation-state" +import { AnimationType, AnimationTypeState } from "./animation-state" import { setTarget } from "./setters" import { resolveVariant } from "./variants" @@ -77,6 +77,7 @@ function animateVariant( * If we have a variant, create a callback that runs it as an animation. * Otherwise, we resolve a Promise immediately for a composable no-op. */ + const getAnimation = resolved ? () => animateTarget(visualElement, resolved, options) : () => Promise.resolve() @@ -139,9 +140,9 @@ function animateTarget( const animations: Promise[] = [] - const protectedValues = - type && visualElement.animationState?.getProtectedKeys(type) - + const animationTypeState = + type && visualElement.animationState?.getState()[type] + console.log("animation type:", type) for (const key in target) { const value = visualElement.getValue(key) const valueTarget = target[key] @@ -149,7 +150,8 @@ function animateTarget( if ( !value || valueTarget === undefined || - protectedValues?.[key] !== undefined + (animationTypeState && + shouldBlockAnimation(animationTypeState, key)) ) { continue } @@ -206,3 +208,27 @@ export function stopAnimation(visualElement: VisualElement) { export function sortByTreeOrder(a: VisualElement, b: VisualElement) { return a.sortNodePosition(b) } + +/** + * Decide whether we should block this animation. Previously, we achieved this + * just by checking whether the key was listed in protectedKeys, but this + * posed problems if an animation was triggered by afterChildren and protectedKeys + * had been set to true in the meantime. + */ +function shouldBlockAnimation( + { protectedKeys, needsAnimating }: AnimationTypeState, + key: string +) { + const shouldBlock = + protectedKeys[key] === true && needsAnimating[key] !== true + + console.log( + key, + "should block:", + shouldBlock, + "is in protected keys", + protectedKeys[key] === true + ) + needsAnimating[key] = false + return shouldBlock +} From 9af2cdbaeddadbaeee96a04a003c38d4e60f24b5 Mon Sep 17 00:00:00 2001 From: InventingWithMonster Date: Fri, 19 Feb 2021 12:21:05 +0100 Subject: [PATCH 2/3] Fixing afterChildren --- CHANGELOG.md | 6 ++++++ src/gestures/__tests__/hover.test.tsx | 2 +- src/render/utils/animation.ts | 13 +++---------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f777236d11..62818945ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ Framer Motion adheres to [Semantic Versioning](http://semver.org/). +## [3.5.3] 2021-02-19 + +### Fixed + +- Fixing bug with `afterChildren` and `exit` animations. + ## [3.5.2] 2021-02-18 ### Added diff --git a/src/gestures/__tests__/hover.test.tsx b/src/gestures/__tests__/hover.test.tsx index 050a3e55ae..3948f093e3 100644 --- a/src/gestures/__tests__/hover.test.tsx +++ b/src/gestures/__tests__/hover.test.tsx @@ -157,7 +157,7 @@ describe("hover", () => { style={{ opacity, scale }} /> ) - console.log("hover testt") + const { container, rerender } = render() rerender() diff --git a/src/render/utils/animation.ts b/src/render/utils/animation.ts index cc2f1b4dd1..52d3edcc7b 100644 --- a/src/render/utils/animation.ts +++ b/src/render/utils/animation.ts @@ -142,7 +142,7 @@ function animateTarget( const animationTypeState = type && visualElement.animationState?.getState()[type] - console.log("animation type:", type) + for (const key in target) { const value = visualElement.getValue(key) const valueTarget = target[key] @@ -220,15 +220,8 @@ function shouldBlockAnimation( key: string ) { const shouldBlock = - protectedKeys[key] === true && needsAnimating[key] !== true - - console.log( - key, - "should block:", - shouldBlock, - "is in protected keys", - protectedKeys[key] === true - ) + protectedKeys.hasOwnProperty(key) && needsAnimating[key] !== true + needsAnimating[key] = false return shouldBlock } From 93570e94da6349b9fee55faa91e4119c7533ffce Mon Sep 17 00:00:00 2001 From: InventingWithMonster Date: Fri, 19 Feb 2021 12:22:35 +0100 Subject: [PATCH 3/3] Exporting VisualElementLifecycles --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index 4002558752..f75296149b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -127,6 +127,7 @@ export { Variants, } from "./types" export { EventInfo } from "./events/types" +export { VisualElementLifecycles } from "./render/utils/lifecycles" export { MotionFeature, FeatureProps } from "./motion/features/types" export { DraggableProps, DragHandlers } from "./gestures/drag/types" export { LayoutProps } from "./motion/features/layout/types"