Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
489a4de
Fix layoutDependency not working with layoutId (issue #1436)
claude Jan 19, 2026
5214c63
Add React 19 tests to CI and fix failing tests
mattgperry Jan 20, 2026
50536b0
Add transformViewBoxPoint utility for SVG drag with scaled viewBox
claude Jan 19, 2026
35361d2
Make pause animation test more permissive for CI timing
mattgperry Jan 21, 2026
7fdd4c2
Fix CreateVisualElement type compatibility in createMotionProxy
claude Jan 21, 2026
4106d6b
Bump lodash from 4.17.21 to 4.17.23
dependabot[bot] Jan 22, 2026
a5e96c3
Fix React 19 Reorder drag gesture ending prematurely
mattgperry Jan 22, 2026
47543c8
Fix flaky pause animation test for React 19 StrictMode
mattgperry Jan 22, 2026
b16834f
Updating changelog
mattgperry Jan 22, 2026
00555c5
Fix flaky drag-svg constraint tests by adding wait after get
mattgperry Jan 22, 2026
8fe80ae
Fix scrollYProgress recalculation on dynamic content changes
mattgperry Jan 21, 2026
62b40de
Fixing dependency
mattgperry Jan 22, 2026
42b52f8
Fix flaky AnimatePresence WAAPI tests for React 19
mattgperry Jan 22, 2026
93f7fc8
Adding tests
mattgperry Jan 22, 2026
911cd39
Merge pull request #3493 from motiondivision/react-19-ci
mattgperry Jan 22, 2026
479f35e
Merge pull request #3507 from motiondivision/dependabot/npm_and_yarn/…
mattgperry Jan 22, 2026
d3bd6de
Merge pull request #3506 from motiondivision/claude/fix-create-proxy-…
mattgperry Jan 22, 2026
e63d56a
Merge pull request #3495 from motiondivision/claude/fix-issue-1436-8qFF9
mattgperry Jan 22, 2026
84d28bf
Merge pull request #3494 from motiondivision/claude/fix-transformed-l…
mattgperry Jan 22, 2026
fcfe5cd
Merge pull request #3497 from motiondivision/scroll-progress-after-la…
mattgperry Jan 22, 2026
17218e3
Updating changelog
mattgperry Jan 22, 2026
b8a46fe
Updating changelog
mattgperry Jan 22, 2026
80802dc
Updating changelog
mattgperry Jan 22, 2026
9482fc1
v12.29.0
mattgperry Jan 22, 2026
b32b466
Add reduceMotion option to animate() and make useAnimate respect Moti…
mattgperry Jan 21, 2026
ea4b2e0
Merge pull request #3500 from motiondivision/reduced-motion-animate
mattgperry Jan 22, 2026
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
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -194,4 +194,5 @@ workflows:
jobs:
- test
- test-react
- test-react-19
- test-html
15 changes: 14 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,20 @@ Motion adheres to [Semantic Versioning](http://semver.org/).

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

## [12.28.2] 2026-01-21
## [12.29.0] 2026-01-22

### Added

- `transformViewBoxPoint`: Scale drag gestures within `<svg>` elements where `viewBox` and rendered `width`/`height` are mismatched.
- `trackContentSize`: New `scroll` and `useScroll` option for tracking changes to content size.

### Fixed

- Add React 19 test suite to CI.
- Fix types with `motion.create()`.
- Shared element animations now respect `layoutDependency`.

## [12.28.2] 2026-01-22

### Added

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.28.2",
"version": "12.29.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand All @@ -10,9 +10,9 @@
"preview": "vite preview"
},
"dependencies": {
"framer-motion": "^12.28.2",
"motion": "^12.28.2",
"motion-dom": "^12.28.2",
"framer-motion": "^12.29.0",
"motion": "^12.29.0",
"motion-dom": "^12.29.0",
"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.28.2",
"version": "12.29.0",
"type": "module",
"scripts": {
"dev": "next dev",
Expand All @@ -10,7 +10,7 @@
"build": "next build"
},
"dependencies": {
"motion": "^12.28.2",
"motion": "^12.29.0",
"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.28.2",
"version": "12.29.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand All @@ -11,7 +11,7 @@
"preview": "vite preview"
},
"dependencies": {
"motion": "^12.28.2",
"motion": "^12.29.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
Expand Down
12 changes: 12 additions & 0 deletions dev/react-19/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import path from "path"
import react from "@vitejs/plugin-react-swc"
import { defineConfig } from "vite"

Expand All @@ -8,4 +9,15 @@ export default defineConfig({
hmr: false,
},
plugins: [react()],
resolve: {
dedupe: ["react", "react-dom"],
alias: {
react: path.resolve(__dirname, "node_modules/react"),
"react-dom": path.resolve(__dirname, "node_modules/react-dom"),
},
},
optimizeDeps: {
include: ["react", "react-dom", "@radix-ui/react-dialog"],
force: true,
},
})
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.28.2",
"version": "12.29.0",
"type": "module",
"scripts": {
"dev": "yarn vite",
Expand All @@ -11,7 +11,7 @@
"preview": "yarn vite preview"
},
"dependencies": {
"framer-motion": "^12.28.2",
"framer-motion": "^12.29.0",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
Expand Down
46 changes: 46 additions & 0 deletions dev/react/src/examples/Drag-svg-viewbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useRef } from "react"
import { motion, MotionConfig, transformViewBoxPoint } from "framer-motion"

/**
* Example demonstrating SVG drag with mismatched viewBox and dimensions.
*
* This example shows how to use `transformViewBoxPoint` to correctly
* handle drag within an SVG where the viewBox coordinates differ from
* the rendered pixel dimensions.
*
* Without transformViewBoxPoint, dragging would move the element 5x
* faster than expected because the viewBox is 100x100 but rendered at 500x500.
*/
export const App = () => {
const svgRef = useRef<SVGSVGElement>(null)

return (
<MotionConfig transformPagePoint={transformViewBoxPoint(svgRef)}>
<svg
ref={svgRef}
viewBox="0 0 100 100"
style={{
width: 500,
height: 500,
border: "2px solid white",
borderRadius: 20,
}}
>
<motion.circle
cx={50}
cy={50}
r={10}
fill="white"
drag
dragConstraints={{
left: -40,
right: 40,
top: -40,
bottom: 40,
}}
dragElastic={0.1}
/>
</svg>
</MotionConfig>
)
}
9 changes: 6 additions & 3 deletions dev/react/src/tests/animate-style-pause.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@ export const App = () => {
const animation = animateMini(
ref.current,
{ width: 200 },
{ duration: 0.2 }
{ duration: 1 } // Longer duration for CI timing reliability
)

setTimeout(() => {
const timeoutId = setTimeout(() => {
animation.pause()
}, 100)

return () => animation.cancel()
return () => {
clearTimeout(timeoutId)
animation.cancel()
}
}, [])

return <div id="box" ref={ref} style={style} />
Expand Down
49 changes: 49 additions & 0 deletions dev/react/src/tests/drag-svg-viewbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useRef } from "react"
import { motion, MotionConfig, transformViewBoxPoint } from "framer-motion"

/**
* Test for SVG drag with mismatched viewBox and dimensions.
*
* When an SVG has viewBox="0 0 100 100" but width/height="500",
* dragging should correctly transform pointer coordinates.
*
* A mouse movement of 100 pixels in screen space should translate to
* 20 units in SVG coordinate space (100 * 100/500 = 20).
*/
export const App = () => {
const params = new URLSearchParams(window.location.search)
const svgRef = useRef<SVGSVGElement>(null)

// Default: viewBox is 100x100 but rendered size is 500x500
// This creates a 5x scale factor
const viewBoxX = parseFloat(params.get("viewBoxX") || "0")
const viewBoxY = parseFloat(params.get("viewBoxY") || "0")
const viewBoxWidth = parseFloat(params.get("viewBoxWidth") || "100")
const viewBoxHeight = parseFloat(params.get("viewBoxHeight") || "100")
const svgWidth = parseFloat(params.get("svgWidth") || "500")
const svgHeight = parseFloat(params.get("svgHeight") || "500")

return (
<MotionConfig transformPagePoint={transformViewBoxPoint(svgRef)}>
<svg
ref={svgRef}
viewBox={`${viewBoxX} ${viewBoxY} ${viewBoxWidth} ${viewBoxHeight}`}
width={svgWidth}
height={svgHeight}
style={{ border: "1px solid black" }}
>
<motion.rect
data-testid="draggable"
x={10}
y={10}
width={20}
height={20}
fill="red"
drag
dragElastic={0}
dragMomentum={false}
/>
</svg>
</MotionConfig>
)
}
110 changes: 110 additions & 0 deletions dev/react/src/tests/layout-shared-dependency.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { motion, useMotionValue } from "framer-motion"
import { useState } from "react"

/**
* Test for issue #1436: layoutDependency not working with layoutId
*
* This test verifies that when a component with layoutId unmounts and remounts
* in a different location (e.g., switching sections), it should NOT animate
* if layoutDependency hasn't changed.
*
* Expected behavior:
* - When clicking "Switch Section": NO animation (same layoutDependency)
* - When clicking "Jump here": animation should occur (layoutDependency changes)
*/

function Items() {
const [selected, setSelected] = useState(0)
const backgroundColor = useMotionValue("#f00")
console.log('render')
return (
<>
<article style={{ marginBottom: 20 }}>
<button id="jump-0" onClick={() => setSelected(0)}>
Jump here
</button>
{selected === 0 && (
<motion.div
id="box"
layoutId="box"
layoutDependency={selected}
style={{
width: 100,
height: 100,
backgroundColor,
borderRadius: 10,
}}
transition={{ duration: 0.5, ease: () => 0.5 }}
onLayoutAnimationStart={() => backgroundColor.set("#0f0")}
onLayoutAnimationComplete={() => backgroundColor.set("#00f")}
/>
)}
</article>
<article>
<button id="jump-1" onClick={() => setSelected(1)}>
Jump here
</button>
{selected === 1 && (
<motion.div
id="box"
layoutId="box"
layoutDependency={selected}
style={{
width: 100,
height: 100,
backgroundColor,
borderRadius: 10,
}}
transition={{ duration: 0.5, ease: () => 0.5 }}
onLayoutAnimationStart={() => backgroundColor.set("#0f0")}
onLayoutAnimationComplete={() => backgroundColor.set("#00f")}
/>
)}
</article>
</>
)
}

function SectionA() {
return (
<div id="section-a">
<p>Section A Header</p>
<Items />
</div>
)
}

function SectionB() {
return (
<div id="section-b">
<Items />
</div>
)
}

export const App = () => {
const [section, setSection] = useState<"a" | "b">("a")

return (
<div style={{ position: "relative" }}>
<div style={{ marginBottom: 20 }}>
<button
id="section-a-btn"
onClick={() => setSection("a")}
>
Section A
</button>
<button
id="section-b-btn"
onClick={() => setSection("b")}
style={{ marginLeft: 10 }}
>
Section B
</button>
</div>

{section === "a" && <SectionA />}
{section === "b" && <SectionB />}
</div>
)
}
Loading
Loading