Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ Motion adheres to [Semantic Versioning](http://semver.org/).

Undocumented APIs should be considered internal and may change without warning.

## [12.33.1] 2026-02-06

### Fixed

- `AnimatePresence`: Ensure exiting nodes are correctly removed when rapidly switching children.

## [12.33.0] 2026-02-05

### Added
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,12 @@ Motion drives the animations on the Cursor homepage, and is working with Cursor

### Platinum

<a href="https://linear.app"><img alt="Linear" src="https://github.com/user-attachments/assets/f9ce44b4-af28-4770-bb6e-9515b474bfb2" width="250px" height="150px"></a> <a href="https://figma.com"><img alt="Figma" src="https://github.com/user-attachments/assets/1077d0ab-4305-4a1f-81c8-d5be8c4c6717" width="250px" height="150px"></a> <a href="https://sanity.io"><img alt="Sanity" src="https://github.com/user-attachments/assets/80134088-f456-483f-8edd-940593c120ce" width="250px" height="150px"></a> <a href="https://animations.dev"><img alt="Sanity" src="https://github.com/user-attachments/assets/7c5ab87d-c7d9-44b4-9c7e-f9e6a9f3ba3b" width="250px" height="150px"></a>
<a href="https://linear.app"><img alt="Linear" src="https://github.com/user-attachments/assets/f9ce44b4-af28-4770-bb6e-9515b474bfb2" width="250px" height="150px"></a> <a href="https://figma.com"><img alt="Figma" src="https://github.com/user-attachments/assets/1077d0ab-4305-4a1f-81c8-d5be8c4c6717" width="250px" height="150px"></a> <a href="https://sanity.io"><img alt="Sanity" src="https://github.com/user-attachments/assets/80134088-f456-483f-8edd-940593c120ce" width="250px" height="150px"></a> <a href="https://animations.dev"><img alt="Sanity" src="https://github.com/user-attachments/assets/7c5ab87d-c7d9-44b4-9c7e-f9e6a9f3ba3b" width="250px" height="150px"></a> <a href="https://clerk.com?utm_campaign=motion"><img alt="Clerk" src="https://github.com/user-attachments/assets/16789f85-eaf4-46fb-9961-b3e66d33afbc" width="250px" height="150px"></a>


### Gold

<a href="https://liveblocks.io"><img alt="Liveblocks" src="https://github.com/user-attachments/assets/28eddbe5-1617-4e74-969d-2eb6fcd481af" width="200px" height="120px"></a> <a href="https://lu.ma"><img alt="Luma" src="https://github.com/user-attachments/assets/ac282433-6adb-4ad2-9fd2-5c6ee513c14b" width="200px" height="120px"></a> <a href="https://notion.com"><img alt="Notion" src="https://github.com/user-attachments/assets/a27a6033-3cb0-4232-a6bb-625e1824517b" width="200px" height="120px"></a> <a href="https://lottiefiles.com"><img alt="LottieFiles" src="https://github.com/user-attachments/assets/4e99d8c7-4cba-43ee-93c5-93861ae708ec" width="200px" height="120px"></a>
<a href="https://liveblocks.io"><img alt="Liveblocks" src="https://github.com/user-attachments/assets/28eddbe5-1617-4e74-969d-2eb6fcd481af" width="200px" height="120px"></a> <a href="https://lu.ma"><img alt="Luma" src="https://github.com/user-attachments/assets/ac282433-6adb-4ad2-9fd2-5c6ee513c14b" width="200px" height="120px"></a> <a href="https://lottiefiles.com"><img alt="LottieFiles" src="https://github.com/user-attachments/assets/4e99d8c7-4cba-43ee-93c5-93861ae708ec" width="200px" height="120px"></a>

### Silver

Expand Down
8 changes: 4 additions & 4 deletions dev/html/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "html-env",
"private": true,
"version": "12.33.0",
"version": "12.33.1",
"type": "module",
"scripts": {
"dev": "vite",
Expand All @@ -10,9 +10,9 @@
"preview": "vite preview"
},
"dependencies": {
"framer-motion": "^12.33.0",
"motion": "^12.33.0",
"motion-dom": "^12.33.0",
"framer-motion": "^12.33.1",
"motion": "^12.33.1",
"motion-dom": "^12.33.1",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
Expand Down
4 changes: 2 additions & 2 deletions dev/next/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "next-env",
"private": true,
"version": "12.33.0",
"version": "12.33.1",
"type": "module",
"scripts": {
"dev": "next dev",
Expand All @@ -10,7 +10,7 @@
"build": "next build"
},
"dependencies": {
"motion": "^12.33.0",
"motion": "^12.33.1",
"next": "15.4.10",
"react": "19.0.0",
"react-dom": "19.0.0"
Expand Down
4 changes: 2 additions & 2 deletions dev/react-19/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "react-19-env",
"private": true,
"version": "12.33.0",
"version": "12.33.1",
"type": "module",
"scripts": {
"dev": "vite",
Expand All @@ -11,7 +11,7 @@
"preview": "vite preview"
},
"dependencies": {
"motion": "^12.33.0",
"motion": "^12.33.1",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
Expand Down
4 changes: 2 additions & 2 deletions dev/react/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "react-env",
"private": true,
"version": "12.33.0",
"version": "12.33.1",
"type": "module",
"scripts": {
"dev": "yarn vite",
Expand All @@ -11,7 +11,7 @@
"preview": "yarn vite preview"
},
"dependencies": {
"framer-motion": "^12.33.0",
"framer-motion": "^12.33.1",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
Expand Down
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "12.33.0",
"version": "12.33.1",
"packages": [
"packages/*",
"dev/*"
Expand Down
5 changes: 3 additions & 2 deletions packages/framer-motion/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,12 @@ Motion drives the animations on the Cursor homepage, and is working with Cursor

### Platinum

<a href="https://linear.app"><img alt="Linear" src="https://github.com/user-attachments/assets/f9ce44b4-af28-4770-bb6e-9515b474bfb2" width="250px" height="150px"></a> <a href="https://figma.com"><img alt="Figma" src="https://github.com/user-attachments/assets/1077d0ab-4305-4a1f-81c8-d5be8c4c6717" width="250px" height="150px"></a> <a href="https://sanity.io"><img alt="Sanity" src="https://github.com/user-attachments/assets/80134088-f456-483f-8edd-940593c120ce" width="250px" height="150px"></a> <a href="https://animations.dev"><img alt="Sanity" src="https://github.com/user-attachments/assets/7c5ab87d-c7d9-44b4-9c7e-f9e6a9f3ba3b" width="250px" height="150px"></a>
<a href="https://linear.app"><img alt="Linear" src="https://github.com/user-attachments/assets/f9ce44b4-af28-4770-bb6e-9515b474bfb2" width="250px" height="150px"></a> <a href="https://figma.com"><img alt="Figma" src="https://github.com/user-attachments/assets/1077d0ab-4305-4a1f-81c8-d5be8c4c6717" width="250px" height="150px"></a> <a href="https://sanity.io"><img alt="Sanity" src="https://github.com/user-attachments/assets/80134088-f456-483f-8edd-940593c120ce" width="250px" height="150px"></a> <a href="https://animations.dev"><img alt="Sanity" src="https://github.com/user-attachments/assets/7c5ab87d-c7d9-44b4-9c7e-f9e6a9f3ba3b" width="250px" height="150px"></a> <a href="https://clerk.com?utm_campaign=motion"><img alt="Clerk" src="https://github.com/user-attachments/assets/16789f85-eaf4-46fb-9961-b3e66d33afbc" width="250px" height="150px"></a>


### Gold

<a href="https://liveblocks.io"><img alt="Liveblocks" src="https://github.com/user-attachments/assets/28eddbe5-1617-4e74-969d-2eb6fcd481af" width="200px" height="120px"></a> <a href="https://lu.ma"><img alt="Luma" src="https://github.com/user-attachments/assets/ac282433-6adb-4ad2-9fd2-5c6ee513c14b" width="200px" height="120px"></a> <a href="https://notion.com"><img alt="Notion" src="https://github.com/user-attachments/assets/a27a6033-3cb0-4232-a6bb-625e1824517b" width="200px" height="120px"></a> <a href="https://lottiefiles.com"><img alt="LottieFiles" src="https://github.com/user-attachments/assets/4e99d8c7-4cba-43ee-93c5-93861ae708ec" width="200px" height="120px"></a>
<a href="https://liveblocks.io"><img alt="Liveblocks" src="https://github.com/user-attachments/assets/28eddbe5-1617-4e74-969d-2eb6fcd481af" width="200px" height="120px"></a> <a href="https://lu.ma"><img alt="Luma" src="https://github.com/user-attachments/assets/ac282433-6adb-4ad2-9fd2-5c6ee513c14b" width="200px" height="120px"></a> <a href="https://lottiefiles.com"><img alt="LottieFiles" src="https://github.com/user-attachments/assets/4e99d8c7-4cba-43ee-93c5-93861ae708ec" width="200px" height="120px"></a>

### Silver

Expand Down
4 changes: 2 additions & 2 deletions packages/framer-motion/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "framer-motion",
"version": "12.33.0",
"version": "12.33.1",
"description": "A simple and powerful JavaScript animation library",
"main": "dist/cjs/index.js",
"module": "dist/es/index.mjs",
Expand Down Expand Up @@ -88,7 +88,7 @@
"measure": "rollup -c ./rollup.size.config.mjs"
},
"dependencies": {
"motion-dom": "^12.33.0",
"motion-dom": "^12.33.1",
"motion-utils": "^12.29.2",
"tslib": "^2.4.0"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1245,4 +1245,149 @@ describe("AnimatePresence with custom components", () => {
})
})
})

test("Removes exiting children during rapid key switches with dynamic custom variants", async () => {
const variants: Variants = {
enter: (custom: string) => ({
...(custom === "fade"
? { opacity: 0 }
: { x: -100 }),
transition: { duration: 0.1 },
}),
center: {
opacity: 1,
x: 0,
transition: { duration: 0.1 },
},
exit: (custom: string) => ({
...(custom === "fade"
? { opacity: 0 }
: { x: 100 }),
transition: { duration: 0.1 },
}),
}

const items = [
{ id: "a", transition: "fade" },
{ id: "b", transition: "slide" },
{ id: "c", transition: "fade" },
{ id: "d", transition: "slide" },
]

const Component = ({ active }: { active: number }) => {
const item = items[active]
return (
<AnimatePresence custom={item.transition}>
<motion.div
key={item.id}
data-testid={item.id}
variants={variants}
initial="enter"
animate="center"
exit="exit"
custom={item.transition}
/>
</AnimatePresence>
)
}

const { container, rerender } = render(<Component active={0} />)
rerender(<Component active={0} />)

// Rapidly switch through all items
await act(async () => {
rerender(<Component active={1} />)
})
await act(async () => {
rerender(<Component active={2} />)
})
await act(async () => {
rerender(<Component active={3} />)
})

// Wait for all exit animations to complete
await new Promise((resolve) => setTimeout(resolve, 500))
await act(async () => {
await nextFrame()
await nextFrame()
})

// Only the last item should remain
expect(container.childElementCount).toBe(1)
})

test("Fires onExitComplete during rapid key switches with dynamic custom variants", async () => {
const variants: Variants = {
enter: (custom: string) => ({
...(custom === "fade"
? { opacity: 0 }
: { x: -100 }),
transition: { duration: 0.1 },
}),
center: {
opacity: 1,
x: 0,
transition: { duration: 0.1 },
},
exit: (custom: string) => ({
...(custom === "fade"
? { opacity: 0 }
: { x: 100 }),
transition: { duration: 0.1 },
}),
}

const items = [
{ id: "a", transition: "fade" },
{ id: "b", transition: "slide" },
{ id: "c", transition: "fade" },
{ id: "d", transition: "slide" },
]

let exitCompleteCount = 0

const Component = ({ active }: { active: number }) => {
const item = items[active]
return (
<AnimatePresence
custom={item.transition}
onExitComplete={() => {
exitCompleteCount++
}}
>
<motion.div
key={item.id}
variants={variants}
initial="enter"
animate="center"
exit="exit"
custom={item.transition}
/>
</AnimatePresence>
)
}

const { rerender } = render(<Component active={0} />)
rerender(<Component active={0} />)

// Rapidly switch through all items
await act(async () => {
rerender(<Component active={1} />)
})
await act(async () => {
rerender(<Component active={2} />)
})
await act(async () => {
rerender(<Component active={3} />)
})

// Wait for all exit animations to complete
await new Promise((resolve) => setTimeout(resolve, 500))
await act(async () => {
await nextFrame()
await nextFrame()
})

expect(exitCompleteCount).toBeGreaterThan(0)
})
})
2 changes: 1 addition & 1 deletion packages/motion-dom/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "motion-dom",
"version": "12.33.0",
"version": "12.33.1",
"author": "Matt Perry",
"license": "MIT",
"repository": "https://github.com/motiondivision/motion",
Expand Down
17 changes: 17 additions & 0 deletions packages/motion-dom/src/render/utils/animation-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,23 @@ export function createAnimationState(visualElement: any): AnimationState {
continue
}

/**
* If exit is already active and wasn't just activated, skip
* re-processing to prevent interrupting running exit animations.
* Re-resolving exit with a changed custom value can start new
* value animations that stop the originals, leaving the exit
* animation promise unresolved and the component stuck in the DOM.
*/
if (type === "exit" && typeState.isActive && activeDelta !== true) {
if (typeState.prevResolvedValues) {
encounteredKeys = {
...encounteredKeys,
...typeState.prevResolvedValues,
}
}
continue
}

/**
* As we go look through the values defined on this type, if we detect
* a changed value or a value that was removed in a higher priority, we set
Expand Down
5 changes: 3 additions & 2 deletions packages/motion/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,12 @@ Motion drives the animations on the Cursor homepage, and is working with Cursor

### Platinum

<a href="https://linear.app"><img alt="Linear" src="https://github.com/user-attachments/assets/f9ce44b4-af28-4770-bb6e-9515b474bfb2" width="250px" height="150px"></a> <a href="https://figma.com"><img alt="Figma" src="https://github.com/user-attachments/assets/1077d0ab-4305-4a1f-81c8-d5be8c4c6717" width="250px" height="150px"></a> <a href="https://sanity.io"><img alt="Sanity" src="https://github.com/user-attachments/assets/80134088-f456-483f-8edd-940593c120ce" width="250px" height="150px"></a> <a href="https://animations.dev"><img alt="Sanity" src="https://github.com/user-attachments/assets/7c5ab87d-c7d9-44b4-9c7e-f9e6a9f3ba3b" width="250px" height="150px"></a>
<a href="https://linear.app"><img alt="Linear" src="https://github.com/user-attachments/assets/f9ce44b4-af28-4770-bb6e-9515b474bfb2" width="250px" height="150px"></a> <a href="https://figma.com"><img alt="Figma" src="https://github.com/user-attachments/assets/1077d0ab-4305-4a1f-81c8-d5be8c4c6717" width="250px" height="150px"></a> <a href="https://sanity.io"><img alt="Sanity" src="https://github.com/user-attachments/assets/80134088-f456-483f-8edd-940593c120ce" width="250px" height="150px"></a> <a href="https://animations.dev"><img alt="Sanity" src="https://github.com/user-attachments/assets/7c5ab87d-c7d9-44b4-9c7e-f9e6a9f3ba3b" width="250px" height="150px"></a> <a href="https://clerk.com?utm_campaign=motion"><img alt="Clerk" src="https://github.com/user-attachments/assets/16789f85-eaf4-46fb-9961-b3e66d33afbc" width="250px" height="150px"></a>


### Gold

<a href="https://liveblocks.io"><img alt="Liveblocks" src="https://github.com/user-attachments/assets/28eddbe5-1617-4e74-969d-2eb6fcd481af" width="200px" height="120px"></a> <a href="https://lu.ma"><img alt="Luma" src="https://github.com/user-attachments/assets/ac282433-6adb-4ad2-9fd2-5c6ee513c14b" width="200px" height="120px"></a> <a href="https://notion.com"><img alt="Notion" src="https://github.com/user-attachments/assets/a27a6033-3cb0-4232-a6bb-625e1824517b" width="200px" height="120px"></a> <a href="https://lottiefiles.com"><img alt="LottieFiles" src="https://github.com/user-attachments/assets/4e99d8c7-4cba-43ee-93c5-93861ae708ec" width="200px" height="120px"></a>
<a href="https://liveblocks.io"><img alt="Liveblocks" src="https://github.com/user-attachments/assets/28eddbe5-1617-4e74-969d-2eb6fcd481af" width="200px" height="120px"></a> <a href="https://lu.ma"><img alt="Luma" src="https://github.com/user-attachments/assets/ac282433-6adb-4ad2-9fd2-5c6ee513c14b" width="200px" height="120px"></a> <a href="https://lottiefiles.com"><img alt="LottieFiles" src="https://github.com/user-attachments/assets/4e99d8c7-4cba-43ee-93c5-93861ae708ec" width="200px" height="120px"></a>

### Silver

Expand Down
4 changes: 2 additions & 2 deletions packages/motion/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "motion",
"version": "12.33.0",
"version": "12.33.1",
"description": "An animation library for JavaScript and React.",
"main": "dist/cjs/index.js",
"module": "dist/es/index.mjs",
Expand Down Expand Up @@ -76,7 +76,7 @@
"postpublish": "git push --tags"
},
"dependencies": {
"framer-motion": "^12.33.0",
"framer-motion": "^12.33.1",
"tslib": "^2.4.0"
},
"peerDependencies": {
Expand Down
Loading
Loading