diff --git a/CHANGELOG.md b/CHANGELOG.md
index cc95a1521b..331157ca55 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,12 @@ Framer Motion adheres to [Semantic Versioning](http://semver.org/).
Undocumented APIs should be considered internal and may change without warning.
+## [11.11.10] 2024-10-23
+
+### Changed
+
+- SVG elements (like `motion.text`) now update when given a `MotionValue` as children, matching HTML element behavior.
+
## [11.11.9] 2024-10-15
### Changed
diff --git a/dev/react/src/examples/SVG-Text-MotionValue-Child.tsx b/dev/react/src/examples/SVG-Text-MotionValue-Child.tsx
new file mode 100644
index 0000000000..a6ec223d5d
--- /dev/null
+++ b/dev/react/src/examples/SVG-Text-MotionValue-Child.tsx
@@ -0,0 +1,40 @@
+import { animate, motion, useMotionValue, useTransform } from "framer-motion"
+import { useEffect } from "react"
+
+/**
+ * An example of providing a MotionValue to a component directly. Testing both
+ * a SVG text and HTML h1 element.
+ */
+export const App = () => {
+ const count = useMotionValue(0);
+ const rounded = useTransform(count, Math.round);
+ useEffect(() => {
+ const animation = animate(count, 100, { duration: 10 });
+ return animation.stop;
+ }, [])
+
+ return (<>
+
SVG
+
+ HTML
+ {rounded}
+ {rounded}
+ >)
+}
diff --git a/packages/framer-motion/src/motion/__tests__/child-motion-value.test.tsx b/packages/framer-motion/src/motion/__tests__/child-motion-value.test.tsx
index 0e0d63909a..bb76b4c21a 100644
--- a/packages/framer-motion/src/motion/__tests__/child-motion-value.test.tsx
+++ b/packages/framer-motion/src/motion/__tests__/child-motion-value.test.tsx
@@ -16,6 +16,18 @@ describe("child as motion value", () => {
return expect(promise).resolves.toHaveTextContent("1")
})
+ test("accepts motion values as children for motion.text inside an svg", async () => {
+ const promise = new Promise((resolve) => {
+ const child = motionValue(3)
+ const Component = () =>
+ const { container, rerender } = render()
+ rerender()
+ resolve(container.firstChild?.firstChild as SVGTextElement)
+ })
+
+ return expect(promise).resolves.toHaveTextContent("3")
+ })
+
test("updates textContent when motion value changes", async () => {
const promise = new Promise((resolve) => {
const child = motionValue(1)
@@ -34,4 +46,23 @@ describe("child as motion value", () => {
return expect(promise).resolves.toHaveTextContent("2")
})
+
+ test("updates svg text when motion value changes", async () => {
+ const promise = new Promise((resolve) => {
+ const child = motionValue(3)
+ const Component = () =>
+ const { container, rerender } = render()
+ rerender()
+
+ frame.postRender(() => {
+ child.set(4)
+
+ frame.postRender(() => {
+ resolve(container.firstChild?.firstChild as SVGTextElement)
+ })
+ })
+ })
+
+ return expect(promise).resolves.toHaveTextContent("4")
+ })
})
diff --git a/packages/framer-motion/src/render/dom/DOMVisualElement.ts b/packages/framer-motion/src/render/dom/DOMVisualElement.ts
index 67a168f696..28b4b211af 100644
--- a/packages/framer-motion/src/render/dom/DOMVisualElement.ts
+++ b/packages/framer-motion/src/render/dom/DOMVisualElement.ts
@@ -4,6 +4,7 @@ import { MotionProps, MotionStyle } from "../../motion/types"
import { MotionValue } from "../../value"
import { HTMLRenderState } from "../html/types"
import { DOMKeyframesResolver } from "./DOMKeyframesResolver"
+import { isMotionValue } from "../../value/utils/is-motion-value"
export abstract class DOMVisualElement<
Instance extends HTMLElement | SVGElement = HTMLElement,
@@ -37,4 +38,21 @@ export abstract class DOMVisualElement<
}
KeyframeResolver = DOMKeyframesResolver
+
+ childSubscription?: VoidFunction
+ handleChildMotionValue() {
+ if (this.childSubscription) {
+ this.childSubscription()
+ delete this.childSubscription
+ }
+
+ const { children } = this.props
+ if (isMotionValue(children)) {
+ this.childSubscription = children.on("change", (latest) => {
+ if (this.current) {
+ this.current.textContent = `${latest}`;
+ }
+ })
+ }
+ }
}
diff --git a/packages/framer-motion/src/render/html/HTMLVisualElement.ts b/packages/framer-motion/src/render/html/HTMLVisualElement.ts
index c538de2787..8c43cd10f5 100644
--- a/packages/framer-motion/src/render/html/HTMLVisualElement.ts
+++ b/packages/framer-motion/src/render/html/HTMLVisualElement.ts
@@ -11,7 +11,6 @@ import { MotionProps } from "../../motion/types"
import type { Box } from "../../projection/geometry/types"
import { DOMVisualElement } from "../dom/DOMVisualElement"
import { MotionConfigContext } from "../../context/MotionConfigContext"
-import { isMotionValue } from "../../value/utils/is-motion-value"
import type { ResolvedValues } from "../types"
import { VisualElement } from "../VisualElement"
@@ -69,20 +68,5 @@ export class HTMLVisualElement extends DOMVisualElement<
return scrapeMotionValuesFromProps(props, prevProps, visualElement)
}
- childSubscription?: VoidFunction
- handleChildMotionValue() {
- if (this.childSubscription) {
- this.childSubscription()
- delete this.childSubscription
- }
-
- const { children } = this.props
- if (isMotionValue(children)) {
- this.childSubscription = children.on("change", (latest) => {
- if (this.current) this.current.textContent = `${latest}`
- })
- }
- }
-
renderInstance = renderHTML
}