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

+ + + {rounded} + + +

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 = () => {child} + 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 = () => {child} + 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 }