From 2be4725df78beb1163cb9e0f9015488c22121a52 Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Tue, 3 Jan 2023 16:55:39 +0100 Subject: [PATCH 1/3] Custom SVG component --- packages/framer-motion/src/render/dom/motion-proxy.ts | 1 + .../framer-motion/src/render/dom/utils/create-config.ts | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/framer-motion/src/render/dom/motion-proxy.ts b/packages/framer-motion/src/render/dom/motion-proxy.ts index 0143f66901..2a6385bfcb 100644 --- a/packages/framer-motion/src/render/dom/motion-proxy.ts +++ b/packages/framer-motion/src/render/dom/motion-proxy.ts @@ -17,6 +17,7 @@ export type CustomDomComponent = React.ForwardRefExoticComponent< export interface CustomMotionComponentConfig { forwardMotionProps?: boolean + svg?: boolean } export type CreateConfig = ( diff --git a/packages/framer-motion/src/render/dom/utils/create-config.ts b/packages/framer-motion/src/render/dom/utils/create-config.ts index 4bb445b538..67446cfae1 100644 --- a/packages/framer-motion/src/render/dom/utils/create-config.ts +++ b/packages/framer-motion/src/render/dom/utils/create-config.ts @@ -12,14 +12,13 @@ import { CustomMotionComponentConfig } from "../motion-proxy" export function createDomMotionConfig( Component: string | React.ComponentType>, - { forwardMotionProps = false }: CustomMotionComponentConfig, + { forwardMotionProps = false, svg }: CustomMotionComponentConfig, preloadedFeatures?: FeatureComponents, createVisualElement?: CreateVisualElement, projectionNodeConstructor?: any ) { - const baseConfig = isSVGComponent(Component) - ? svgMotionConfig - : htmlMotionConfig + const baseConfig = + svg || isSVGComponent(Component) ? svgMotionConfig : htmlMotionConfig return { ...baseConfig, From 97a27531ddad150806284d33cf5c4c8f9fedb526 Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Tue, 3 Jan 2023 17:26:50 +0100 Subject: [PATCH 2/3] Fixing test --- .../src/components/use-layout-camera.ts | 3 +-- .../src/render/create-visual-element.ts | 4 +-- .../src/animation/hooks/use-animated-state.ts | 2 +- .../src/motion/__tests__/custom.test.tsx | 25 ++++++++++++++++++- packages/framer-motion/src/motion/index.tsx | 4 ++- .../src/motion/utils/use-visual-element.ts | 6 ++--- .../src/render/dom/create-visual-element.ts | 9 ++----- .../src/render/dom/utils/create-config.ts | 5 ++-- packages/framer-motion/src/render/types.ts | 2 +- .../utils/__tests__/animation-state.test.ts | 1 + 10 files changed, 41 insertions(+), 20 deletions(-) diff --git a/packages/framer-motion-3d/src/components/use-layout-camera.ts b/packages/framer-motion-3d/src/components/use-layout-camera.ts index 3324b410b6..1685e2d534 100644 --- a/packages/framer-motion-3d/src/components/use-layout-camera.ts +++ b/packages/framer-motion-3d/src/components/use-layout-camera.ts @@ -1,11 +1,10 @@ import type { Box } from "framer-motion" +import { calcLength, clamp, useVisualElementContext } from "framer-motion" import { RefObject, useContext, useLayoutEffect, useRef } from "react" import { Size, useThree } from "@react-three/fiber" import { LayoutCameraProps } from "./types" -import { useVisualElementContext } from "framer-motion" import { MotionCanvasContext } from "./MotionCanvasContext" import { invariant } from "hey-listen" -import { calcLength, clamp } from "framer-motion" const calcBoxSize = ({ x, y }: Box) => ({ width: calcLength(x), diff --git a/packages/framer-motion-3d/src/render/create-visual-element.ts b/packages/framer-motion-3d/src/render/create-visual-element.ts index f318c01622..b27586a89c 100644 --- a/packages/framer-motion-3d/src/render/create-visual-element.ts +++ b/packages/framer-motion-3d/src/render/create-visual-element.ts @@ -75,5 +75,5 @@ export class ThreeVisualElement extends VisualElement< } } -export const createVisualElement: CreateVisualElement = (_, options) => - new ThreeVisualElement(options, {}) +export const createVisualElement: CreateVisualElement = (options) => + new ThreeVisualElement({ ...options, type: "three" }, {}) diff --git a/packages/framer-motion/src/animation/hooks/use-animated-state.ts b/packages/framer-motion/src/animation/hooks/use-animated-state.ts index 3ea7eb0f74..02763b4ccc 100644 --- a/packages/framer-motion/src/animation/hooks/use-animated-state.ts +++ b/packages/framer-motion/src/animation/hooks/use-animated-state.ts @@ -73,7 +73,7 @@ export function useAnimatedState(initialState: any) { const element = useConstant(() => { return new StateVisualElement( - { props: {}, visualState }, + { props: {}, visualState, type: "state" }, { initialState } ) }) diff --git a/packages/framer-motion/src/motion/__tests__/custom.test.tsx b/packages/framer-motion/src/motion/__tests__/custom.test.tsx index 95d158da8b..f0126dcd85 100644 --- a/packages/framer-motion/src/motion/__tests__/custom.test.tsx +++ b/packages/framer-motion/src/motion/__tests__/custom.test.tsx @@ -1,5 +1,5 @@ import { render } from "../../../jest.setup" -import { motion } from "../.." +import { motion, motionValue } from "../.." import * as React from "react" import { RefObject } from "react" import { MotionProps } from "../types" @@ -66,4 +66,27 @@ describe("motion()", () => { expect(animate).toEqual({ x: 100 }) expect(foo).toBe(true) }) + + test("creates SVG component if svg: true", async () => { + const BaseComponent = React.forwardRef( + (_props: Props, ref: RefObject) => { + return + } + ) + + const MotionComponent = motion(BaseComponent, { svg: true }) + + const Component = () => ( + + ) + + const { container } = render() + const element = container.firstChild as Element + expect(element).toHaveAttribute("cx", "5") + }) }) diff --git a/packages/framer-motion/src/motion/index.tsx b/packages/framer-motion/src/motion/index.tsx index 7fd1c51efc..9ff8db2d29 100644 --- a/packages/framer-motion/src/motion/index.tsx +++ b/packages/framer-motion/src/motion/index.tsx @@ -20,6 +20,7 @@ import { motionComponentSymbol } from "./utils/symbol" import { CreateVisualElement } from "../render/types" export interface MotionComponentConfig { + type: string preloadedFeatures?: FeatureBundle createVisualElement?: CreateVisualElement projectionNodeConstructor?: any @@ -44,6 +45,7 @@ export function createMotionComponent({ useRender, useVisualState, Component, + type, }: MotionComponentConfig) { preloadedFeatures && loadFeatures(preloadedFeatures) @@ -88,9 +90,9 @@ export function createMotionComponent({ * for more performant animations and interactions */ context.visualElement = useVisualElement( - Component, visualState, configAndProps, + type, createVisualElement ) diff --git a/packages/framer-motion/src/motion/utils/use-visual-element.ts b/packages/framer-motion/src/motion/utils/use-visual-element.ts index f662a56fcd..022f2dd1d8 100644 --- a/packages/framer-motion/src/motion/utils/use-visual-element.ts +++ b/packages/framer-motion/src/motion/utils/use-visual-element.ts @@ -1,4 +1,3 @@ -import * as React from "react" import { useContext, useRef } from "react" import { PresenceContext } from "../../context/PresenceContext" import { MotionProps } from "../../motion/types" @@ -12,9 +11,9 @@ import { MotionConfigContext } from "../../context/MotionConfigContext" import type { VisualElement } from "../../render/VisualElement" export function useVisualElement( - Component: string | React.ComponentType>, visualState: VisualState, props: MotionProps & MotionConfigProps, + type: string, createVisualElement?: CreateVisualElement ): VisualElement | undefined { const parent = useVisualElementContext() @@ -30,10 +29,11 @@ export function useVisualElement( createVisualElement = createVisualElement || lazyContext.renderer if (!visualElementRef.current && createVisualElement) { - visualElementRef.current = createVisualElement(Component, { + visualElementRef.current = createVisualElement({ visualState, parent, props, + type, presenceId: presenceContext ? presenceContext.id : undefined, blockInitialAnimation: presenceContext ? presenceContext.initial === false diff --git a/packages/framer-motion/src/render/dom/create-visual-element.ts b/packages/framer-motion/src/render/dom/create-visual-element.ts index 8be3d20b78..da18b73995 100644 --- a/packages/framer-motion/src/render/dom/create-visual-element.ts +++ b/packages/framer-motion/src/render/dom/create-visual-element.ts @@ -1,16 +1,11 @@ -import { ComponentType } from "react" import { HTMLVisualElement } from "../html/HTMLVisualElement" import { SVGVisualElement } from "../svg/SVGVisualElement" import { CreateVisualElement, VisualElementOptions } from "../types" -import { isSVGComponent } from "./utils/is-svg-component" export const createDomVisualElement: CreateVisualElement< HTMLElement | SVGElement -> = ( - Component: string | ComponentType>, - options: VisualElementOptions -) => { - return isSVGComponent(Component) +> = (options: VisualElementOptions) => { + return options.type === "svg" ? new SVGVisualElement(options, { enableHardwareAcceleration: false }) : new HTMLVisualElement(options, { enableHardwareAcceleration: true }) } diff --git a/packages/framer-motion/src/render/dom/utils/create-config.ts b/packages/framer-motion/src/render/dom/utils/create-config.ts index 67446cfae1..076e1a6f5d 100644 --- a/packages/framer-motion/src/render/dom/utils/create-config.ts +++ b/packages/framer-motion/src/render/dom/utils/create-config.ts @@ -17,11 +17,12 @@ export function createDomMotionConfig( createVisualElement?: CreateVisualElement, projectionNodeConstructor?: any ) { - const baseConfig = - svg || isSVGComponent(Component) ? svgMotionConfig : htmlMotionConfig + const type = svg || isSVGComponent(Component) ? "svg" : "html" + const baseConfig = type === "svg" ? svgMotionConfig : htmlMotionConfig return { ...baseConfig, + type, preloadedFeatures, useRender: createUseRender(forwardMotionProps), createVisualElement, diff --git a/packages/framer-motion/src/render/types.ts b/packages/framer-motion/src/render/types.ts index 6fae106ee9..cd16a05c5d 100644 --- a/packages/framer-motion/src/render/types.ts +++ b/packages/framer-motion/src/render/types.ts @@ -28,6 +28,7 @@ export type ScrapeMotionValuesFromProps = (props: MotionProps) => { export type UseRenderState = () => RenderState export type VisualElementOptions = { + type: string visualState: VisualState parent?: VisualElement variantParent?: VisualElement @@ -135,6 +136,5 @@ export interface AnimationLifecycles { export type EventProps = LayoutLifecycles & AnimationLifecycles export type CreateVisualElement = ( - Component: string | React.ComponentType>, options: VisualElementOptions ) => VisualElement diff --git a/packages/framer-motion/src/render/utils/__tests__/animation-state.test.ts b/packages/framer-motion/src/render/utils/__tests__/animation-state.test.ts index 6ce47ea675..a430dd5c1e 100644 --- a/packages/framer-motion/src/render/utils/__tests__/animation-state.test.ts +++ b/packages/framer-motion/src/render/utils/__tests__/animation-state.test.ts @@ -17,6 +17,7 @@ function createTest( latestValues: {}, renderState: createHtmlRenderState(), }, + type: "state", }, { initialState: {}, From 306c3a21f6c2bc3d0de06a84b16824c63bca1539 Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Tue, 3 Jan 2023 17:29:55 +0100 Subject: [PATCH 3/3] Removing unused import --- packages/framer-motion/src/motion/__tests__/custom.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/framer-motion/src/motion/__tests__/custom.test.tsx b/packages/framer-motion/src/motion/__tests__/custom.test.tsx index f0126dcd85..815ca819fd 100644 --- a/packages/framer-motion/src/motion/__tests__/custom.test.tsx +++ b/packages/framer-motion/src/motion/__tests__/custom.test.tsx @@ -1,5 +1,5 @@ import { render } from "../../../jest.setup" -import { motion, motionValue } from "../.." +import { motion } from "../.." import * as React from "react" import { RefObject } from "react" import { MotionProps } from "../types"