diff --git a/CHANGELOG.md b/CHANGELOG.md index eab08b889f..d71e45bcd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ Motion adheres to [Semantic Versioning](http://semver.org/). Undocumented APIs should be considered internal and may change without warning. +## [12.26.2] 2026-01-13 + +### Fixed + +- Internal refactor of projection system into `motion-dom`. + ## [12.26.1] 2026-01-12 ### Fixed diff --git a/dev/html/package.json b/dev/html/package.json index 7a5c24ee08..424d9cfe6e 100644 --- a/dev/html/package.json +++ b/dev/html/package.json @@ -1,7 +1,7 @@ { "name": "html-env", "private": true, - "version": "12.26.1", + "version": "12.26.2", "type": "module", "scripts": { "dev": "vite", @@ -10,9 +10,9 @@ "preview": "vite preview" }, "dependencies": { - "framer-motion": "^12.26.1", - "motion": "^12.26.1", - "motion-dom": "^12.24.11", + "framer-motion": "^12.26.2", + "motion": "^12.26.2", + "motion-dom": "^12.26.2", "react": "^18.3.1", "react-dom": "^18.3.1" }, diff --git a/dev/next/package.json b/dev/next/package.json index fe11184992..3d69491ffb 100644 --- a/dev/next/package.json +++ b/dev/next/package.json @@ -1,7 +1,7 @@ { "name": "next-env", "private": true, - "version": "12.26.1", + "version": "12.26.2", "type": "module", "scripts": { "dev": "next dev", @@ -10,7 +10,7 @@ "build": "next build" }, "dependencies": { - "motion": "^12.26.1", + "motion": "^12.26.2", "next": "15.4.10", "react": "19.0.0", "react-dom": "19.0.0" diff --git a/dev/react-19/package.json b/dev/react-19/package.json index 409580702e..ae92c80cb7 100644 --- a/dev/react-19/package.json +++ b/dev/react-19/package.json @@ -1,7 +1,7 @@ { "name": "react-19-env", "private": true, - "version": "12.26.1", + "version": "12.26.2", "type": "module", "scripts": { "dev": "vite", @@ -11,7 +11,7 @@ "preview": "vite preview" }, "dependencies": { - "motion": "^12.26.1", + "motion": "^12.26.2", "react": "^19.0.0", "react-dom": "^19.0.0" }, diff --git a/dev/react/package.json b/dev/react/package.json index 20fd792163..a8d26058f6 100644 --- a/dev/react/package.json +++ b/dev/react/package.json @@ -1,7 +1,7 @@ { "name": "react-env", "private": true, - "version": "12.26.1", + "version": "12.26.2", "type": "module", "scripts": { "dev": "yarn vite", @@ -11,7 +11,7 @@ "preview": "yarn vite preview" }, "dependencies": { - "framer-motion": "^12.26.1", + "framer-motion": "^12.26.2", "react": "^18.3.1", "react-dom": "^18.3.1" }, diff --git a/lerna.json b/lerna.json index d5f40fe3d6..eb0aa44051 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "12.26.1", + "version": "12.26.2", "packages": [ "packages/*", "dev/*" diff --git a/packages/framer-motion/package.json b/packages/framer-motion/package.json index 6334abd71c..4737e7f9d1 100644 --- a/packages/framer-motion/package.json +++ b/packages/framer-motion/package.json @@ -1,6 +1,6 @@ { "name": "framer-motion", - "version": "12.26.1", + "version": "12.26.2", "description": "A simple and powerful JavaScript animation library", "main": "dist/cjs/index.js", "module": "dist/es/index.mjs", @@ -88,7 +88,7 @@ "measure": "rollup -c ./rollup.size.config.mjs" }, "dependencies": { - "motion-dom": "^12.24.11", + "motion-dom": "^12.26.2", "motion-utils": "^12.24.10", "tslib": "^2.4.0" }, diff --git a/packages/framer-motion/src/animation/animate/subject.ts b/packages/framer-motion/src/animation/animate/subject.ts index 43fd266bb1..fc09b0b7d2 100644 --- a/packages/framer-motion/src/animation/animate/subject.ts +++ b/packages/framer-motion/src/animation/animate/subject.ts @@ -1,19 +1,19 @@ import { + animateTarget, AnimationPlaybackControlsWithThen, AnimationScope, AnyResolvedKeyframe, DOMKeyframesDefinition, AnimationOptions as DynamicAnimationOptions, ElementOrSelector, + isMotionValue, MotionValue, TargetAndTransition, UnresolvedValueKeyframe, ValueAnimationTransition, - isMotionValue, + visualElementStore, } from "motion-dom" import { invariant } from "motion-utils" -import { visualElementStore } from "../../render/store" -import { animateTarget } from "../interfaces/visual-element-target" import { ObjectTarget } from "../sequence/types" import { createDOMVisualElement, @@ -21,7 +21,7 @@ import { } from "../utils/create-visual-element" import { isDOMKeyframes } from "../utils/is-dom-keyframes" import { resolveSubjects } from "./resolve-subjects" -import { animateSingleValue } from "./single-value" +import { animateSingleValue } from "motion-dom" export type AnimationSubject = Element | MotionValue | any diff --git a/packages/framer-motion/src/animation/animators/waapi/utils/__tests__/get-final-keyframes.test.ts b/packages/framer-motion/src/animation/animators/waapi/utils/__tests__/get-final-keyframes.test.ts deleted file mode 100644 index ec18ff108e..0000000000 --- a/packages/framer-motion/src/animation/animators/waapi/utils/__tests__/get-final-keyframes.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { getFinalKeyframe } from "../get-final-keyframe" - -describe("getFinalKeyframe", () => { - test("returns final keyframe", () => { - expect(getFinalKeyframe([0, 1], {})).toEqual(1) - expect(getFinalKeyframe([0, 1], { repeat: 1 })).toEqual(1) - expect(getFinalKeyframe([0, 1], { repeat: 2 })).toEqual(1) - expect( - getFinalKeyframe([0, 1], { repeat: 1, repeatType: "loop" }) - ).toEqual(1) - expect( - getFinalKeyframe([0, 1], { repeat: 2, repeatType: "loop" }) - ).toEqual(1) - expect( - getFinalKeyframe([0, 1], { repeat: 1, repeatType: "reverse" }) - ).toEqual(0) - expect( - getFinalKeyframe([0, 1], { repeat: 2, repeatType: "reverse" }) - ).toEqual(1) - expect( - getFinalKeyframe([0, 1], { repeat: 1, repeatType: "mirror" }) - ).toEqual(0) - expect( - getFinalKeyframe([0, 1], { repeat: 2, repeatType: "mirror" }) - ).toEqual(1) - }) -}) diff --git a/packages/framer-motion/src/animation/hooks/animation-controls.ts b/packages/framer-motion/src/animation/hooks/animation-controls.ts index 952aa61237..0a40b11d65 100644 --- a/packages/framer-motion/src/animation/hooks/animation-controls.ts +++ b/packages/framer-motion/src/animation/hooks/animation-controls.ts @@ -1,8 +1,11 @@ -import type { AnimationDefinition, LegacyAnimationControls } from "motion-dom" +import { + animateVisualElement, + setTarget, + type AnimationDefinition, + type LegacyAnimationControls, + type VisualElement, +} from "motion-dom" import { invariant } from "motion-utils" -import { setTarget } from "../../render/utils/setters" -import type { VisualElement } from "../../render/VisualElement" -import { animateVisualElement } from "../interfaces/visual-element" function stopAnimation(visualElement: VisualElement) { visualElement.values.forEach((value) => value.stop()) 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 642b49113f..ee19a370c8 100644 --- a/packages/framer-motion/src/animation/hooks/use-animated-state.ts +++ b/packages/framer-motion/src/animation/hooks/use-animated-state.ts @@ -1,13 +1,15 @@ "use client" -import { TargetAndTransition } from "motion-dom" +import { + animateVisualElement, + createBox, + ResolvedValues, + TargetAndTransition, + VisualElement, +} from "motion-dom" import { useLayoutEffect, useState } from "react" import { makeUseVisualState } from "../../motion/utils/use-visual-state" -import { createBox } from "../../projection/geometry/models" -import { ResolvedValues } from "../../render/types" -import { VisualElement } from "../../render/VisualElement" import { useConstant } from "../../utils/use-constant" -import { animateVisualElement } from "../interfaces/visual-element" interface AnimatedStateOptions { initialState: ResolvedValues diff --git a/packages/framer-motion/src/animation/interfaces/types.ts b/packages/framer-motion/src/animation/interfaces/types.ts deleted file mode 100644 index 2644885b6c..0000000000 --- a/packages/framer-motion/src/animation/interfaces/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { Transition } from "motion-dom" -import type { AnimationType } from "../../render/utils/types" - -export type VisualElementAnimationOptions = { - delay?: number - transitionOverride?: Transition - custom?: any - type?: AnimationType -} diff --git a/packages/framer-motion/src/animation/optimized-appear/start.ts b/packages/framer-motion/src/animation/optimized-appear/start.ts index a35907f77e..82712dd357 100644 --- a/packages/framer-motion/src/animation/optimized-appear/start.ts +++ b/packages/framer-motion/src/animation/optimized-appear/start.ts @@ -1,18 +1,17 @@ import { AnyResolvedKeyframe, Batcher, + getOptimisedAppearId, MotionValue, + optimizedAppearDataId, startWaapiAnimation, ValueAnimationTransition, + type WithAppearProps, } from "motion-dom" import { noop } from "motion-utils" -import { optimizedAppearDataId } from "./data-id" -import { getOptimisedAppearId } from "./get-appear-id" import { handoffOptimizedAppearAnimation } from "./handoff" import { appearAnimationStore, appearComplete, AppearStoreEntry } from "./store" import { appearStoreId } from "./store-id" -import "./types" -import type { WithAppearProps } from "./types" /** * A single time to use across all animations to manually set startTime diff --git a/packages/framer-motion/src/animation/utils/create-visual-element.ts b/packages/framer-motion/src/animation/utils/create-visual-element.ts index bb364c43dc..43c5b7dbd5 100644 --- a/packages/framer-motion/src/animation/utils/create-visual-element.ts +++ b/packages/framer-motion/src/animation/utils/create-visual-element.ts @@ -1,8 +1,11 @@ -import { isSVGElement, isSVGSVGElement } from "motion-dom" -import { HTMLVisualElement } from "../../render/html/HTMLVisualElement" -import { ObjectVisualElement } from "../../render/object/ObjectVisualElement" -import { visualElementStore } from "../../render/store" -import { SVGVisualElement } from "../../render/svg/SVGVisualElement" +import { + HTMLVisualElement, + isSVGElement, + isSVGSVGElement, + ObjectVisualElement, + SVGVisualElement, + visualElementStore, +} from "motion-dom" export function createDOMVisualElement(element: HTMLElement | SVGElement) { const options = { diff --git a/packages/framer-motion/src/context/LayoutGroupContext.ts b/packages/framer-motion/src/context/LayoutGroupContext.ts index 6d4dde23d2..bcdc066efb 100644 --- a/packages/framer-motion/src/context/LayoutGroupContext.ts +++ b/packages/framer-motion/src/context/LayoutGroupContext.ts @@ -1,7 +1,7 @@ "use client" import { createContext } from "react" -import { NodeGroup } from "../projection/node/group" +import type { NodeGroup } from "motion-dom" export interface LayoutGroupContextProps { id?: string diff --git a/packages/framer-motion/src/context/MotionContext/index.ts b/packages/framer-motion/src/context/MotionContext/index.ts index 7c7710767a..14fea16efa 100644 --- a/packages/framer-motion/src/context/MotionContext/index.ts +++ b/packages/framer-motion/src/context/MotionContext/index.ts @@ -1,7 +1,7 @@ "use client" +import type { VisualElement } from "motion-dom" import { createContext } from "react" -import type { VisualElement } from "../../render/VisualElement" export interface MotionContextProps { visualElement?: VisualElement diff --git a/packages/framer-motion/src/context/MotionContext/utils.ts b/packages/framer-motion/src/context/MotionContext/utils.ts index 22d3c42406..398ec13363 100644 --- a/packages/framer-motion/src/context/MotionContext/utils.ts +++ b/packages/framer-motion/src/context/MotionContext/utils.ts @@ -1,7 +1,6 @@ +import { isControllingVariants, isVariantLabel } from "motion-dom" import type { MotionContextProps } from "." import { MotionProps } from "../../motion/types" -import { isControllingVariants } from "../../render/utils/is-controlling-variants" -import { isVariantLabel } from "../../render/utils/is-variant-label" export function getCurrentTreeVariants( props: MotionProps, diff --git a/packages/framer-motion/src/context/PresenceContext.ts b/packages/framer-motion/src/context/PresenceContext.ts index 082f6ce87f..6cbb46e472 100644 --- a/packages/framer-motion/src/context/PresenceContext.ts +++ b/packages/framer-motion/src/context/PresenceContext.ts @@ -1,19 +1,9 @@ "use client" import { createContext } from "react" -import { VariantLabels } from "../motion/types" +import type { PresenceContextProps } from "motion-dom" -/** - * @public - */ -export interface PresenceContextProps { - id: string - isPresent: boolean - register: (id: string | number) => () => void - onExitComplete?: (id: string | number) => void - initial?: false | VariantLabels - custom?: any -} +export type { PresenceContextProps } /** * @public diff --git a/packages/framer-motion/src/context/SwitchLayoutGroupContext.ts b/packages/framer-motion/src/context/SwitchLayoutGroupContext.ts index 13eba891fb..24726a4d08 100644 --- a/packages/framer-motion/src/context/SwitchLayoutGroupContext.ts +++ b/packages/framer-motion/src/context/SwitchLayoutGroupContext.ts @@ -1,8 +1,7 @@ "use client" -import type { Transition } from "motion-dom" +import type { Transition, IProjectionNode } from "motion-dom" import { createContext } from "react" -import { IProjectionNode } from "../projection/node/types" export interface SwitchLayoutGroup { register?: (member: IProjectionNode) => void diff --git a/packages/framer-motion/src/dom.ts b/packages/framer-motion/src/dom.ts index b92753f471..c2eefc2ed0 100644 --- a/packages/framer-motion/src/dom.ts +++ b/packages/framer-motion/src/dom.ts @@ -16,5 +16,5 @@ export * from "./animation/sequence/types" /** * Utils */ -export { delayInSeconds as delay, DelayedFunction } from "./utils/delay" +export { delayInSeconds as delay, type DelayedFunction } from "motion-dom" export * from "./utils/distance" diff --git a/packages/framer-motion/src/events/add-pointer-event.ts b/packages/framer-motion/src/events/add-pointer-event.ts index 65847c1db2..961940594b 100644 --- a/packages/framer-motion/src/events/add-pointer-event.ts +++ b/packages/framer-motion/src/events/add-pointer-event.ts @@ -1,4 +1,4 @@ -import { addDomEvent } from "./add-dom-event" +import { addDomEvent } from "motion-dom" import { addPointerInfo, EventListenerWithPointInfo } from "./event-info" export function addPointerEvent( diff --git a/packages/framer-motion/src/events/use-dom-event.ts b/packages/framer-motion/src/events/use-dom-event.ts index a053798c21..5a5290888b 100644 --- a/packages/framer-motion/src/events/use-dom-event.ts +++ b/packages/framer-motion/src/events/use-dom-event.ts @@ -1,7 +1,7 @@ "use client" import { RefObject, useEffect } from "react" -import { addDomEvent } from "./add-dom-event" +import { addDomEvent } from "motion-dom" /** * Attaches an event listener directly to the provided DOM element. diff --git a/packages/framer-motion/src/gestures/drag/VisualElementDragControls.ts b/packages/framer-motion/src/gestures/drag/VisualElementDragControls.ts index 3810088e67..03d3b1de6e 100644 --- a/packages/framer-motion/src/gestures/drag/VisualElementDragControls.ts +++ b/packages/framer-motion/src/gestures/drag/VisualElementDragControls.ts @@ -1,32 +1,29 @@ import { - isElementKeyboardAccessible, - PanInfo, - ResolvedConstraints, - Transition, + addValueToWillChange, + animateMotionValue, + calcLength, + convertBoundingBoxToBox, + convertBoxToBoundingBox, + createBox, + eachAxis, frame, + isElementKeyboardAccessible, + measurePageBox, mixNumber, + PanInfo, percent, + ResolvedConstraints, setDragLock, + Transition, + type VisualElement, } from "motion-dom" import { Axis, Point, invariant } from "motion-utils" -import { animateMotionValue } from "../../animation/interfaces/motion-value" -import { addDomEvent } from "../../events/add-dom-event" +import { addDomEvent, type LayoutUpdateData } from "motion-dom" import { addPointerEvent } from "../../events/add-pointer-event" import { extractEventInfo } from "../../events/event-info" import { MotionProps } from "../../motion/types" -import { - convertBoundingBoxToBox, - convertBoxToBoundingBox, -} from "../../projection/geometry/conversion" -import { calcLength } from "../../projection/geometry/delta-calc" -import { createBox } from "../../projection/geometry/models" -import type { LayoutUpdateData } from "../../projection/node/types" -import { eachAxis } from "../../projection/utils/each-axis" -import { measurePageBox } from "../../projection/utils/measure" -import type { VisualElement } from "../../render/VisualElement" import { getContextWindow } from "../../utils/get-context-window" import { isRefObject } from "../../utils/is-ref-object" -import { addValueToWillChange } from "../../value/use-will-change/add-will-change" import { PanSession } from "../pan/PanSession" import { applyConstraints, diff --git a/packages/framer-motion/src/gestures/drag/index.ts b/packages/framer-motion/src/gestures/drag/index.ts index f6cfb9cc93..be540e5d6c 100644 --- a/packages/framer-motion/src/gestures/drag/index.ts +++ b/packages/framer-motion/src/gestures/drag/index.ts @@ -1,5 +1,4 @@ -import { Feature } from "../../motion/features/Feature" -import type { VisualElement } from "../../render/VisualElement" +import { Feature, type VisualElement } from "motion-dom" import { noop } from "motion-utils" import { VisualElementDragControls } from "./VisualElementDragControls" diff --git a/packages/framer-motion/src/gestures/drag/utils/constraints.ts b/packages/framer-motion/src/gestures/drag/utils/constraints.ts index a667945f2e..0d62fdf429 100644 --- a/packages/framer-motion/src/gestures/drag/utils/constraints.ts +++ b/packages/framer-motion/src/gestures/drag/utils/constraints.ts @@ -1,5 +1,4 @@ -import type { DragElastic, ResolvedConstraints } from "motion-dom" -import { mixNumber } from "motion-dom" +import { calcLength, mixNumber, type DragElastic, type ResolvedConstraints } from "motion-dom" import { Axis, BoundingBox, @@ -8,7 +7,6 @@ import { clamp, Point, } from "motion-utils" -import { calcLength } from "../../../projection/geometry/delta-calc" /** * Apply constraints to a point. These constraints are both physical along an diff --git a/packages/framer-motion/src/gestures/focus.ts b/packages/framer-motion/src/gestures/focus.ts index 98a7a3dc2e..54ec25319f 100644 --- a/packages/framer-motion/src/gestures/focus.ts +++ b/packages/framer-motion/src/gestures/focus.ts @@ -1,6 +1,5 @@ +import { Feature, addDomEvent } from "motion-dom" import { pipe } from "motion-utils" -import { addDomEvent } from "../events/add-dom-event" -import { Feature } from "../motion/features/Feature" export class FocusGesture extends Feature { private isActive = false diff --git a/packages/framer-motion/src/gestures/hover.ts b/packages/framer-motion/src/gestures/hover.ts index a8ddd4f06e..1ac509e379 100644 --- a/packages/framer-motion/src/gestures/hover.ts +++ b/packages/framer-motion/src/gestures/hover.ts @@ -1,7 +1,5 @@ -import { frame, hover } from "motion-dom" +import { Feature, frame, hover, type VisualElement } from "motion-dom" import { extractEventInfo } from "../events/event-info" -import { Feature } from "../motion/features/Feature" -import type { VisualElement } from "../render/VisualElement" function handleHoverEvent( node: VisualElement, diff --git a/packages/framer-motion/src/gestures/pan/index.ts b/packages/framer-motion/src/gestures/pan/index.ts index c6229c5ecf..42de424e27 100644 --- a/packages/framer-motion/src/gestures/pan/index.ts +++ b/packages/framer-motion/src/gestures/pan/index.ts @@ -1,8 +1,6 @@ -import type { PanInfo } from "motion-dom" -import { frame } from "motion-dom" +import { Feature, frame, type PanInfo } from "motion-dom" import { noop } from "motion-utils" import { addPointerEvent } from "../../events/add-pointer-event" -import { Feature } from "../../motion/features/Feature" import { getContextWindow } from "../../utils/get-context-window" import { PanSession } from "./PanSession" diff --git a/packages/framer-motion/src/gestures/press.ts b/packages/framer-motion/src/gestures/press.ts index dce7d1be19..e5d2650896 100644 --- a/packages/framer-motion/src/gestures/press.ts +++ b/packages/framer-motion/src/gestures/press.ts @@ -1,7 +1,5 @@ -import { frame, press } from "motion-dom" +import { Feature, frame, press, type VisualElement } from "motion-dom" import { extractEventInfo } from "../events/event-info" -import { Feature } from "../motion/features/Feature" -import { VisualElement } from "../render/VisualElement" function handlePressEvent( node: VisualElement, diff --git a/packages/framer-motion/src/index.ts b/packages/framer-motion/src/index.ts index c0f9b5aa27..422df09ba5 100644 --- a/packages/framer-motion/src/index.ts +++ b/packages/framer-motion/src/index.ts @@ -24,8 +24,7 @@ export { makeUseVisualState, VisualState, } from "./motion/utils/use-visual-state" -export { calcLength } from "./projection/geometry/delta-calc" -export { createBox } from "./projection/geometry/models" +export { calcLength, createBox } from "motion-dom" export { filterProps } from "./render/dom/utils/filter-props" export { AnimationType } from "./render/utils/types" export { isBrowser } from "./utils/is-browser" @@ -56,7 +55,7 @@ export { useTransform } from "./value/use-transform" export { useVelocity } from "./value/use-velocity" export { useWillChange } from "./value/use-will-change" export { WillChangeMotionValue } from "./value/use-will-change/WillChangeMotionValue" -export { resolveMotionValue } from "./value/utils/resolve-motion-value" +export { resolveMotionValue } from "motion-dom" /** * Accessibility @@ -75,7 +74,7 @@ export { useAnimation, useAnimationControls, } from "./animation/hooks/use-animation" -export { animateVisualElement } from "./animation/interfaces/visual-element" +export { animateVisualElement } from "motion-dom" export { useIsPresent, usePresence, @@ -89,12 +88,10 @@ export { export { isMotionComponent } from "./motion/utils/is-motion-component" export { unwrapMotionComponent } from "./motion/utils/unwrap-motion-component" export { isValidMotionProp } from "./motion/utils/valid-prop" -export { addScaleCorrector } from "./projection/styles/scale-correction" +export { addScaleCorrector } from "motion-dom" export { useInstantLayoutTransition } from "./projection/use-instant-layout-transition" export { useResetProjection } from "./projection/use-reset-projection" -export { buildTransform } from "./render/html/utils/build-transform" -export { visualElementStore } from "./render/store" -export { VisualElement } from "./render/VisualElement" +export { buildTransform, visualElementStore, VisualElement } from "motion-dom" export { useAnimationFrame } from "./utils/use-animation-frame" export { Cycle, CycleState, useCycle } from "./utils/use-cycle" export { useInView, UseInViewOptions } from "./utils/use-in-view" @@ -107,7 +104,7 @@ export { usePageInView } from "./utils/use-page-in-view" /** * Appear animations */ -export { optimizedAppearDataAttribute } from "./animation/optimized-appear/data-id" +export { optimizedAppearDataAttribute } from "motion-dom" export { startOptimizedAppearAnimation } from "./animation/optimized-appear/start" /** @@ -125,14 +122,23 @@ export { SwitchLayoutGroupContext } from "./context/SwitchLayoutGroupContext" export type { AnimatePresenceProps } from "./components/AnimatePresence/types" export type { LazyProps } from "./components/LazyMotion/types" export type { MotionConfigProps } from "./components/MotionConfig" -export type * from "./motion/features/types" +export type { + HydratedFeatureDefinition, + HydratedFeatureDefinitions, + FeatureDefinition, + FeatureDefinitions, + FeaturePackage, + FeaturePackages, + FeatureBundle, + LazyFeatureBundle, +} from "./motion/features/types" export type { MotionProps, MotionStyle, MotionTransform, VariantLabels, } from "./motion/types" -export type { IProjectionNode } from "./projection/node/types" +export type { IProjectionNode } from "motion-dom" export type { DOMMotionComponents } from "./render/dom/types" export type { ForwardRefComponent, HTMLMotionProps } from "./render/html/types" export type { @@ -140,7 +146,7 @@ export type { SVGMotionProps, } from "./render/svg/types" export type { CreateVisualElement } from "./render/types" -export type { FlatTree } from "./render/utils/flat-tree" +export type { FlatTree } from "motion-dom" export type { ScrollMotionValues } from "./value/scroll/utils" /** @@ -152,4 +158,4 @@ export { DeprecatedLayoutGroupContext } from "./context/DeprecatedLayoutGroupCon export { useInvertedScale as useDeprecatedInvertedScale } from "./value/use-inverted-scale" // Keep explicit delay in milliseconds export for BC with Framer -export { delay, DelayedFunction } from "./utils/delay" +export { delay, type DelayedFunction } from "motion-dom" diff --git a/packages/framer-motion/src/motion/__tests__/static-prop.test.tsx b/packages/framer-motion/src/motion/__tests__/static-prop.test.tsx index b5a615f691..c081d4bdbe 100644 --- a/packages/framer-motion/src/motion/__tests__/static-prop.test.tsx +++ b/packages/framer-motion/src/motion/__tests__/static-prop.test.tsx @@ -1,9 +1,8 @@ -import { motionValue, stagger } from "motion-dom" +import { motionValue, stagger, globalProjectionState } from "motion-dom" import { useEffect } from "react" import { motion, useMotionValue } from "../.." import { MotionConfig } from "../../components/MotionConfig" import { render } from "../../jest.setup" -import { globalProjectionState } from "../../projection/node/state" describe("isStatic prop", () => { test("it prevents rendering of animated values", async () => { diff --git a/packages/framer-motion/src/motion/__tests__/transition-keyframes.test.tsx b/packages/framer-motion/src/motion/__tests__/transition-keyframes.test.tsx index ff5d6cc6f8..83b7e46910 100644 --- a/packages/framer-motion/src/motion/__tests__/transition-keyframes.test.tsx +++ b/packages/framer-motion/src/motion/__tests__/transition-keyframes.test.tsx @@ -1,6 +1,6 @@ +import { checkVariantsDidChange } from "motion-dom" import { motion, motionValue } from "../.." import { render } from "../../jest.setup" -import { checkVariantsDidChange } from "../../render/utils/animation-state" describe("keyframes transition", () => { test("keyframes as target", async () => { diff --git a/packages/framer-motion/src/motion/features/animation/exit.ts b/packages/framer-motion/src/motion/features/animation/exit.ts index a284236fc6..505d4ee37e 100644 --- a/packages/framer-motion/src/motion/features/animation/exit.ts +++ b/packages/framer-motion/src/motion/features/animation/exit.ts @@ -1,4 +1,4 @@ -import { Feature } from "../Feature" +import { Feature } from "motion-dom" let id = 0 diff --git a/packages/framer-motion/src/motion/features/animation/index.ts b/packages/framer-motion/src/motion/features/animation/index.ts index bc05d57774..3e657e0fb6 100644 --- a/packages/framer-motion/src/motion/features/animation/index.ts +++ b/packages/framer-motion/src/motion/features/animation/index.ts @@ -1,7 +1,9 @@ -import { isAnimationControls } from "../../../animation/utils/is-animation-controls" -import { createAnimationState } from "../../../render/utils/animation-state" -import { VisualElement } from "../../../render/VisualElement" -import { Feature } from "../Feature" +import { + createAnimationState, + Feature, + isAnimationControls, + type VisualElement, +} from "motion-dom" export class AnimationFeature extends Feature { unmountControls?: () => void diff --git a/packages/framer-motion/src/motion/features/definitions.ts b/packages/framer-motion/src/motion/features/definitions.ts index 50e587d3b7..61c122ce93 100644 --- a/packages/framer-motion/src/motion/features/definitions.ts +++ b/packages/framer-motion/src/motion/features/definitions.ts @@ -1,3 +1,4 @@ +import { getFeatureDefinitions, setFeatureDefinitions } from "motion-dom" import { MotionProps } from "../types" import { FeatureDefinitions } from "./types" @@ -22,13 +23,36 @@ const featureProps = { layout: ["layout", "layoutId"], } -export const featureDefinitions: Partial = {} +let isInitialized = false -for (const key in featureProps) { - featureDefinitions[key as keyof typeof featureDefinitions] = { - isEnabled: (props: MotionProps) => - featureProps[key as keyof typeof featureProps].some( - (name: string) => !!props[name as keyof typeof props] - ), +/** + * Initialize feature definitions with isEnabled checks. + * This must be called before any motion components are rendered. + */ +export function initFeatureDefinitions() { + if (isInitialized) return + + const initialFeatureDefinitions: Partial = {} + + for (const key in featureProps) { + initialFeatureDefinitions[ + key as keyof typeof initialFeatureDefinitions + ] = { + isEnabled: (props: MotionProps) => + featureProps[key as keyof typeof featureProps].some( + (name: string) => !!props[name as keyof typeof props] + ), + } } + + setFeatureDefinitions(initialFeatureDefinitions) + isInitialized = true +} + +/** + * Get the current feature definitions, initializing if needed. + */ +export function getInitializedFeatureDefinitions(): Partial { + initFeatureDefinitions() + return getFeatureDefinitions() } diff --git a/packages/framer-motion/src/motion/features/layout.ts b/packages/framer-motion/src/motion/features/layout.ts index 83e736fcb8..00e7da75e1 100644 --- a/packages/framer-motion/src/motion/features/layout.ts +++ b/packages/framer-motion/src/motion/features/layout.ts @@ -1,4 +1,4 @@ -import { HTMLProjectionNode } from "../../projection/node/HTMLProjectionNode" +import { HTMLProjectionNode } from "motion-dom" import { MeasureLayout } from "./layout/MeasureLayout" import { FeaturePackages } from "./types" diff --git a/packages/framer-motion/src/motion/features/layout/MeasureLayout.tsx b/packages/framer-motion/src/motion/features/layout/MeasureLayout.tsx index 74426ddeda..1d32bc4dc6 100644 --- a/packages/framer-motion/src/motion/features/layout/MeasureLayout.tsx +++ b/packages/framer-motion/src/motion/features/layout/MeasureLayout.tsx @@ -1,6 +1,6 @@ "use client" -import { frame, microtask } from "motion-dom" +import { frame, microtask, globalProjectionState, type VisualElement } from "motion-dom" import { Component, useContext } from "react" import { usePresence } from "../../../components/AnimatePresence/use-presence" import { @@ -8,8 +8,6 @@ import { LayoutGroupContextProps, } from "../../../context/LayoutGroupContext" import { SwitchLayoutGroupContext } from "../../../context/SwitchLayoutGroupContext" -import { globalProjectionState } from "../../../projection/node/state" -import { VisualElement } from "../../../render/VisualElement" import { MotionProps } from "../../types" interface MeasureContextProps { diff --git a/packages/framer-motion/src/motion/features/load-features.ts b/packages/framer-motion/src/motion/features/load-features.ts index 4810a844cf..fd2147ad90 100644 --- a/packages/framer-motion/src/motion/features/load-features.ts +++ b/packages/framer-motion/src/motion/features/load-features.ts @@ -1,11 +1,16 @@ -import { featureDefinitions } from "./definitions" +import { setFeatureDefinitions } from "motion-dom" +import { getInitializedFeatureDefinitions } from "./definitions" import { FeaturePackages } from "./types" export function loadFeatures(features: FeaturePackages) { + const featureDefinitions = getInitializedFeatureDefinitions() + for (const key in features) { featureDefinitions[key as keyof typeof featureDefinitions] = { ...featureDefinitions[key as keyof typeof featureDefinitions], ...features[key as keyof typeof features], } as any } + + setFeatureDefinitions(featureDefinitions) } diff --git a/packages/framer-motion/src/motion/features/types.ts b/packages/framer-motion/src/motion/features/types.ts index fe49e365af..f9ac3e8922 100644 --- a/packages/framer-motion/src/motion/features/types.ts +++ b/packages/framer-motion/src/motion/features/types.ts @@ -1,6 +1,6 @@ +import type { Feature } from "motion-dom" import { CreateVisualElement } from "../../render/types" import { MotionProps } from "../types" -import type { Feature } from "./Feature" import { MeasureLayout } from "./layout/MeasureLayout" interface FeatureClass { diff --git a/packages/framer-motion/src/motion/features/viewport/index.ts b/packages/framer-motion/src/motion/features/viewport/index.ts index ad61447577..c59b8e0c7f 100644 --- a/packages/framer-motion/src/motion/features/viewport/index.ts +++ b/packages/framer-motion/src/motion/features/viewport/index.ts @@ -1,5 +1,5 @@ +import { Feature } from "motion-dom" import { MotionProps } from "../../types" -import { Feature } from "../Feature" import { observeIntersection } from "./observers" const thresholdNames = { diff --git a/packages/framer-motion/src/motion/index.tsx b/packages/framer-motion/src/motion/index.tsx index b5737cc6b2..ebef5b2475 100644 --- a/packages/framer-motion/src/motion/index.tsx +++ b/packages/framer-motion/src/motion/index.tsx @@ -17,7 +17,7 @@ import { SVGRenderState } from "../render/svg/types" import { useSVGVisualState } from "../render/svg/use-svg-visual-state" import { CreateVisualElement } from "../render/types" import { isBrowser } from "../utils/is-browser" -import { featureDefinitions } from "./features/definitions" +import { getInitializedFeatureDefinitions } from "./features/definitions" import { loadFeatures } from "./features/load-features" import { FeatureBundle, FeaturePackages } from "./features/types" import { MotionProps } from "./types" @@ -203,6 +203,7 @@ function useStrictMode( } function getProjectionFunctionality(props: MotionProps) { + const featureDefinitions = getInitializedFeatureDefinitions() const { drag, layout } = featureDefinitions if (!drag && !layout) return {} diff --git a/packages/framer-motion/src/motion/utils/is-forced-motion-value.ts b/packages/framer-motion/src/motion/utils/is-forced-motion-value.ts deleted file mode 100644 index 1f85a2a98e..0000000000 --- a/packages/framer-motion/src/motion/utils/is-forced-motion-value.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { transformProps } from "motion-dom" -import { MotionProps } from "../.." -import { scaleCorrectors } from "../../projection/styles/scale-correction" - -export function isForcedMotionValue( - key: string, - { layout, layoutId }: MotionProps -) { - return ( - transformProps.has(key) || - key.startsWith("origin") || - ((layout || layoutId !== undefined) && - (!!scaleCorrectors[key] || key === "opacity")) - ) -} diff --git a/packages/framer-motion/src/motion/utils/use-motion-ref.ts b/packages/framer-motion/src/motion/utils/use-motion-ref.ts index 8ed2b46601..5606047551 100644 --- a/packages/framer-motion/src/motion/utils/use-motion-ref.ts +++ b/packages/framer-motion/src/motion/utils/use-motion-ref.ts @@ -1,8 +1,8 @@ "use client" +import type { VisualElement } from "motion-dom" import * as React from "react" import { useCallback, useInsertionEffect, useRef } from "react" -import type { VisualElement } from "../../render/VisualElement" import { VisualState } from "./use-visual-state" /** 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 7f009c1542..a76fe01dd5 100644 --- a/packages/framer-motion/src/motion/utils/use-visual-element.ts +++ b/packages/framer-motion/src/motion/utils/use-visual-element.ts @@ -1,8 +1,13 @@ "use client" +import { + optimizedAppearDataAttribute, + type HTMLRenderState, + type SVGRenderState, + type VisualElement, +} from "motion-dom" import * as React from "react" import { useContext, useEffect, useInsertionEffect, useRef } from "react" -import { optimizedAppearDataAttribute } from "../../animation/optimized-appear/data-id" import { LazyContext } from "../../context/LazyContext" import { MotionConfigContext } from "../../context/MotionConfigContext" import { MotionContext } from "../../context/MotionContext" @@ -12,12 +17,9 @@ import { SwitchLayoutGroupContext, } from "../../context/SwitchLayoutGroupContext" import { MotionProps } from "../../motion/types" -import { IProjectionNode } from "../../projection/node/types" +import type { IProjectionNode } from "motion-dom" import { DOMMotionComponents } from "../../render/dom/types" -import { HTMLRenderState } from "../../render/html/types" -import { SVGRenderState } from "../../render/svg/types" import { CreateVisualElement } from "../../render/types" -import type { VisualElement } from "../../render/VisualElement" import { isRefObject } from "../../utils/is-ref-object" import { useIsomorphicLayoutEffect } from "../../utils/use-isomorphic-effect" import { VisualState } from "./use-visual-state" diff --git a/packages/framer-motion/src/motion/utils/use-visual-state.ts b/packages/framer-motion/src/motion/utils/use-visual-state.ts index 81a5d5fd77..5ae93ef5dc 100644 --- a/packages/framer-motion/src/motion/utils/use-visual-state.ts +++ b/packages/framer-motion/src/motion/utils/use-visual-state.ts @@ -1,21 +1,22 @@ "use client" -import { AnyResolvedKeyframe } from "motion-dom" +import { + AnyResolvedKeyframe, + isAnimationControls, + isControllingVariants as checkIsControllingVariants, + isVariantNode as checkIsVariantNode, + ResolvedValues, + resolveVariantFromProps, +} from "motion-dom" import { useContext } from "react" -import { isAnimationControls } from "../../animation/utils/is-animation-controls" import { MotionContext, MotionContextProps } from "../../context/MotionContext" import { PresenceContext, type PresenceContextProps, } from "../../context/PresenceContext" -import { ResolvedValues, ScrapeMotionValuesFromProps } from "../../render/types" -import { - isControllingVariants as checkIsControllingVariants, - isVariantNode as checkIsVariantNode, -} from "../../render/utils/is-controlling-variants" -import { resolveVariantFromProps } from "../../render/utils/resolve-variants" +import { ScrapeMotionValuesFromProps } from "../../render/types" import { useConstant } from "../../utils/use-constant" -import { resolveMotionValue } from "../../value/utils/resolve-motion-value" +import { resolveMotionValue } from "motion-dom" import { MotionProps } from "../types" export interface VisualState { diff --git a/packages/framer-motion/src/projection.ts b/packages/framer-motion/src/projection.ts index ace274532e..1f809207dd 100644 --- a/packages/framer-motion/src/projection.ts +++ b/packages/framer-motion/src/projection.ts @@ -1,11 +1,16 @@ -export { recordStats, statsBuffer } from "motion-dom" -export { calcBoxDelta } from "./projection/geometry/delta-calc" -export { nodeGroup } from "./projection/node/group" -export { HTMLProjectionNode } from "./projection/node/HTMLProjectionNode" -export { correctBorderRadius } from "./projection/styles/scale-border-radius" -export { correctBoxShadow } from "./projection/styles/scale-box-shadow" -export { addScaleCorrector } from "./projection/styles/scale-correction" -export { HTMLVisualElement } from "./render/html/HTMLVisualElement" -export { buildTransform } from "./render/html/utils/build-transform" -export { frame, frameData, mix } -import { frame, frameData, mix } from "motion-dom" +export { + recordStats, + statsBuffer, + calcBoxDelta, + correctBorderRadius, + correctBoxShadow, + addScaleCorrector, + frame, + frameData, + mix, + HTMLVisualElement, + buildTransform, + // Re-export projection node system from motion-dom + nodeGroup, + HTMLProjectionNode, +} from "motion-dom" diff --git a/packages/framer-motion/src/projection/styles/transform-origin.ts b/packages/framer-motion/src/projection/styles/transform-origin.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/framer-motion/src/projection/styles/types.ts b/packages/framer-motion/src/projection/styles/types.ts deleted file mode 100644 index 8d4f0b8ed0..0000000000 --- a/packages/framer-motion/src/projection/styles/types.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { type AnyResolvedKeyframe } from "motion-dom" -import { IProjectionNode } from "../node/types" - -export type ScaleCorrector = ( - latest: AnyResolvedKeyframe, - node: IProjectionNode -) => AnyResolvedKeyframe - -export interface ScaleCorrectorDefinition { - correct: ScaleCorrector - applyTo?: string[] - isCSSVariable?: boolean -} - -export interface ScaleCorrectorMap { - [key: string]: ScaleCorrectorDefinition -} diff --git a/packages/framer-motion/src/projection/use-instant-layout-transition.ts b/packages/framer-motion/src/projection/use-instant-layout-transition.ts index 5958dd6626..eb17bc21de 100644 --- a/packages/framer-motion/src/projection/use-instant-layout-transition.ts +++ b/packages/framer-motion/src/projection/use-instant-layout-transition.ts @@ -1,4 +1,4 @@ -import { rootProjectionNode } from "./node/HTMLProjectionNode" +import { rootProjectionNode } from "motion-dom" export function useInstantLayoutTransition(): ( cb?: (() => void) | undefined diff --git a/packages/framer-motion/src/projection/use-reset-projection.ts b/packages/framer-motion/src/projection/use-reset-projection.ts index 0111d0795f..241917822f 100644 --- a/packages/framer-motion/src/projection/use-reset-projection.ts +++ b/packages/framer-motion/src/projection/use-reset-projection.ts @@ -1,5 +1,5 @@ import { useCallback } from "react"; -import { rootProjectionNode } from "./node/HTMLProjectionNode" +import { rootProjectionNode } from "motion-dom" export function useResetProjection() { const reset = useCallback(() => { 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 2655ee361a..cc51621b9b 100644 --- a/packages/framer-motion/src/render/dom/create-visual-element.ts +++ b/packages/framer-motion/src/render/dom/create-visual-element.ts @@ -1,6 +1,5 @@ +import { HTMLVisualElement, SVGVisualElement } from "motion-dom" import { ComponentType, Fragment } from "react" -import { HTMLVisualElement } from "../html/HTMLVisualElement" -import { SVGVisualElement } from "../svg/SVGVisualElement" import { CreateVisualElement, VisualElementOptions } from "../types" import { isSVGComponent } from "./utils/is-svg-component" diff --git a/packages/framer-motion/src/render/dom/use-motion-value-child.ts b/packages/framer-motion/src/render/dom/use-motion-value-child.ts index c0fe9258fb..8ebe07c792 100644 --- a/packages/framer-motion/src/render/dom/use-motion-value-child.ts +++ b/packages/framer-motion/src/render/dom/use-motion-value-child.ts @@ -1,9 +1,8 @@ "use client" -import { MotionValue } from "motion-dom" +import { MotionValue, type VisualElement } from "motion-dom" import { useConstant } from "../../utils/use-constant" import { useMotionValueEvent } from "../../utils/use-motion-value-event" -import type { VisualElement } from "../VisualElement" export function useMotionValueChild( children: MotionValue, diff --git a/packages/framer-motion/src/render/dom/utils/__tests__/camel-to-dash.test.ts b/packages/framer-motion/src/render/dom/utils/__tests__/camel-to-dash.test.ts index 1722467a35..6779ef60ff 100644 --- a/packages/framer-motion/src/render/dom/utils/__tests__/camel-to-dash.test.ts +++ b/packages/framer-motion/src/render/dom/utils/__tests__/camel-to-dash.test.ts @@ -1,5 +1,5 @@ import "../../../../jest.setup" -import { camelToDash } from "../camel-to-dash" +import { camelToDash } from "motion-dom" describe("camelToDash", () => { it("Converts camel case to dash case", () => { diff --git a/packages/framer-motion/src/render/dom/utils/camel-to-dash.ts b/packages/framer-motion/src/render/dom/utils/camel-to-dash.ts deleted file mode 100644 index 8219a3f8f1..0000000000 --- a/packages/framer-motion/src/render/dom/utils/camel-to-dash.ts +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Convert camelCase to dash-case properties. - */ -export const camelToDash = (str: string) => - str.replace(/([a-z])([A-Z])/gu, "$1-$2").toLowerCase() diff --git a/packages/framer-motion/src/render/html/types.ts b/packages/framer-motion/src/render/html/types.ts index 4ac2dc53a0..5bdde3fb90 100644 --- a/packages/framer-motion/src/render/html/types.ts +++ b/packages/framer-motion/src/render/html/types.ts @@ -1,39 +1,9 @@ -import { ResolvedValues } from "../types" +import { type TransformOrigin, type HTMLRenderState } from "motion-dom" import { PropsWithoutRef, RefAttributes, JSX } from "react" import { MotionProps } from "../../motion/types" import { HTMLElements } from "./supported-elements" -export interface TransformOrigin { - originX?: number | string - originY?: number | string - originZ?: number | string -} - -export interface HTMLRenderState { - /** - * A mutable record of transforms we want to apply directly to the rendered Element - * every frame. We use a mutable data structure to reduce GC during animations. - */ - transform: ResolvedValues - - /** - * A mutable record of transform origins we want to apply directly to the rendered Element - * every frame. We use a mutable data structure to reduce GC during animations. - */ - transformOrigin: TransformOrigin - - /** - * A mutable record of styles we want to apply directly to the rendered Element - * every frame. We use a mutable data structure to reduce GC during animations. - */ - style: ResolvedValues - - /** - * A mutable record of CSS variables we want to apply directly to the rendered Element - * every frame. We use a mutable data structure to reduce GC during animations. - */ - vars: ResolvedValues -} +export type { TransformOrigin, HTMLRenderState } /** * @public diff --git a/packages/framer-motion/src/render/html/use-html-visual-state.ts b/packages/framer-motion/src/render/html/use-html-visual-state.ts index 4c4c10e68f..c00d46d7dd 100644 --- a/packages/framer-motion/src/render/html/use-html-visual-state.ts +++ b/packages/framer-motion/src/render/html/use-html-visual-state.ts @@ -1,10 +1,10 @@ "use client" +import { scrapeHTMLMotionValuesFromProps } from "motion-dom" import { makeUseVisualState } from "../../motion/utils/use-visual-state" import { createHtmlRenderState } from "./utils/create-render-state" -import { scrapeMotionValuesFromProps } from "./utils/scrape-motion-values" export const useHTMLVisualState = /*@__PURE__*/ makeUseVisualState({ - scrapeMotionValuesFromProps, + scrapeMotionValuesFromProps: scrapeHTMLMotionValuesFromProps, createRenderState: createHtmlRenderState, }) diff --git a/packages/framer-motion/src/render/html/use-props.ts b/packages/framer-motion/src/render/html/use-props.ts index 4c760ae26d..9d7c61b40f 100644 --- a/packages/framer-motion/src/render/html/use-props.ts +++ b/packages/framer-motion/src/render/html/use-props.ts @@ -1,11 +1,9 @@ "use client" -import { AnyResolvedKeyframe, isMotionValue, MotionValue } from "motion-dom" +import { AnyResolvedKeyframe, buildHTMLStyles, isForcedMotionValue, isMotionValue, MotionValue } from "motion-dom" import { HTMLProps, useMemo } from "react" import { MotionProps } from "../../motion/types" -import { isForcedMotionValue } from "../../motion/utils/is-forced-motion-value" import { ResolvedValues } from "../types" -import { buildHTMLStyles } from "./utils/build-styles" import { createHtmlRenderState } from "./utils/create-render-state" export function copyRawValuesOnly( diff --git a/packages/framer-motion/src/render/html/utils/__tests__/build-styles.test.ts b/packages/framer-motion/src/render/html/utils/__tests__/build-styles.test.ts index ffa0cd9ce4..06de8441c1 100644 --- a/packages/framer-motion/src/render/html/utils/__tests__/build-styles.test.ts +++ b/packages/framer-motion/src/render/html/utils/__tests__/build-styles.test.ts @@ -1,8 +1,7 @@ +import { buildHTMLStyles, ResolvedValues } from "motion-dom" import "../../../../jest.setup" import { DOMVisualElementOptions } from "../../../dom/types" -import { ResolvedValues } from "../../../types" import { TransformOrigin } from "../../types" -import { buildHTMLStyles } from "../build-styles" describe("buildHTMLStyles", () => { test("Builds basic styles", () => { diff --git a/packages/framer-motion/src/render/html/utils/__tests__/build-transform.test.ts b/packages/framer-motion/src/render/html/utils/__tests__/build-transform.test.ts index ddcf2b1b6a..29f9d9b4a0 100644 --- a/packages/framer-motion/src/render/html/utils/__tests__/build-transform.test.ts +++ b/packages/framer-motion/src/render/html/utils/__tests__/build-transform.test.ts @@ -1,6 +1,5 @@ -import { transformProps } from "motion-dom" +import { buildTransform, transformProps } from "motion-dom" import "../../../../jest.setup" -import { buildTransform } from "../build-transform" describe("transformProps.has", () => { it("Correctly identifies only transformPerspective as a transform prop", () => { diff --git a/packages/framer-motion/src/render/html/utils/scrape-motion-values.ts b/packages/framer-motion/src/render/html/utils/scrape-motion-values.ts deleted file mode 100644 index 872d1c00fc..0000000000 --- a/packages/framer-motion/src/render/html/utils/scrape-motion-values.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { isMotionValue } from "motion-dom" -import { MotionProps, MotionStyle } from "../../../motion/types" -import { isForcedMotionValue } from "../../../motion/utils/is-forced-motion-value" -import type { VisualElement } from "../../VisualElement" - -export function scrapeMotionValuesFromProps( - props: MotionProps, - prevProps: MotionProps, - visualElement?: VisualElement -) { - const { style } = props - const newValues: { [key: string]: any } = {} - - for (const key in style) { - if ( - isMotionValue(style[key as keyof MotionStyle]) || - (prevProps.style && - isMotionValue(prevProps.style[key as keyof MotionStyle])) || - isForcedMotionValue(key, props) || - visualElement?.getValue(key)?.liveStyle !== undefined - ) { - newValues[key] = style[key as keyof MotionStyle] - } - } - - return newValues -} diff --git a/packages/framer-motion/src/render/svg/use-props.ts b/packages/framer-motion/src/render/svg/use-props.ts index 46b324d112..885ee4c0de 100644 --- a/packages/framer-motion/src/render/svg/use-props.ts +++ b/packages/framer-motion/src/render/svg/use-props.ts @@ -1,12 +1,11 @@ "use client" +import { buildSVGAttrs, isSVGTag } from "motion-dom" import { useMemo } from "react" import { MotionProps } from "../../motion/types" import { copyRawValuesOnly } from "../html/use-props" import { ResolvedValues } from "../types" -import { buildSVGAttrs } from "./utils/build-attrs" import { createSvgRenderState } from "./utils/create-render-state" -import { isSVGTag } from "./utils/is-svg-tag" export function useSVGProps( props: MotionProps, diff --git a/packages/framer-motion/src/render/svg/use-svg-visual-state.ts b/packages/framer-motion/src/render/svg/use-svg-visual-state.ts index e6806cab8c..09f29d0fce 100644 --- a/packages/framer-motion/src/render/svg/use-svg-visual-state.ts +++ b/packages/framer-motion/src/render/svg/use-svg-visual-state.ts @@ -1,10 +1,10 @@ "use client" +import { scrapeSVGMotionValuesFromProps } from "motion-dom" import { makeUseVisualState } from "../../motion/utils/use-visual-state" import { createSvgRenderState } from "./utils/create-render-state" -import { scrapeMotionValuesFromProps as scrapeSVGProps } from "./utils/scrape-motion-values" export const useSVGVisualState = /*@__PURE__*/ makeUseVisualState({ - scrapeMotionValuesFromProps: scrapeSVGProps, + scrapeMotionValuesFromProps: scrapeSVGMotionValuesFromProps, createRenderState: createSvgRenderState, }) diff --git a/packages/framer-motion/src/render/svg/utils/__tests__/path.test.ts b/packages/framer-motion/src/render/svg/utils/__tests__/path.test.ts index 84104d72cd..09ef5a8711 100644 --- a/packages/framer-motion/src/render/svg/utils/__tests__/path.test.ts +++ b/packages/framer-motion/src/render/svg/utils/__tests__/path.test.ts @@ -1,5 +1,5 @@ +import { buildSVGPath } from "motion-dom" import "../../../../jest.setup" -import { buildSVGPath } from "../path" describe("buildSVGPath", () => { it("correctly generates SVG path props", () => { diff --git a/packages/framer-motion/src/render/types.ts b/packages/framer-motion/src/render/types.ts index 5427316d3b..9f08769805 100644 --- a/packages/framer-motion/src/render/types.ts +++ b/packages/framer-motion/src/render/types.ts @@ -1,12 +1,19 @@ -import type { AnimationDefinition } from "motion-dom" -import { AnyResolvedKeyframe, MotionValue } from "motion-dom" -import type { Axis, Box } from "motion-utils" +import { + AnyResolvedKeyframe, + MotionValue, + ResolvedValues, + type VisualElement, + type VisualElementEventCallbacks, + type LayoutLifecycles, + type UseRenderState, +} from "motion-dom" import { ReducedMotionConfig } from "../context/MotionConfigContext" import type { PresenceContextProps } from "../context/PresenceContext" import { MotionProps } from "../motion/types" import { VisualState } from "../motion/utils/use-visual-state" import { DOMMotionComponents } from "./dom/types" -import type { VisualElement } from "./VisualElement" + +export type { VisualElementEventCallbacks, LayoutLifecycles, UseRenderState } export type ScrapeMotionValuesFromProps = ( props: MotionProps, @@ -16,8 +23,6 @@ export type ScrapeMotionValuesFromProps = ( [key: string]: MotionValue | AnyResolvedKeyframe } -export type UseRenderState = () => RenderState - export interface VisualElementOptions { visualState: VisualState parent?: VisualElement @@ -33,41 +38,8 @@ export interface VisualElementOptions { isSVG?: boolean } -/** - * A generic set of string/number values - */ -export interface ResolvedValues { - [key: string]: AnyResolvedKeyframe -} - -export interface VisualElementEventCallbacks { - BeforeLayoutMeasure: () => void - LayoutMeasure: (layout: Box, prevLayout?: Box) => void - LayoutUpdate: (layout: Axis, prevLayout: Axis) => void - Update: (latest: ResolvedValues) => void - AnimationStart: (definition: AnimationDefinition) => void - AnimationComplete: (definition: AnimationDefinition) => void - LayoutAnimationStart: () => void - LayoutAnimationComplete: () => void - SetAxisTarget: () => void - Unmount: () => void -} - -export interface LayoutLifecycles { - onBeforeLayoutMeasure?(box: Box): void - - onLayoutMeasure?(box: Box, prevBox: Box): void - - /** - * @internal - */ - onLayoutAnimationStart?(): void - - /** - * @internal - */ - onLayoutAnimationComplete?(): void -} +// Re-export ResolvedValues from motion-dom for backward compatibility +export type { ResolvedValues } export type CreateVisualElement< Props = {}, diff --git a/packages/framer-motion/src/render/utils/__tests__/StateVisualElement.ts b/packages/framer-motion/src/render/utils/__tests__/StateVisualElement.ts index 8857a7c5d8..5dfe931bee 100644 --- a/packages/framer-motion/src/render/utils/__tests__/StateVisualElement.ts +++ b/packages/framer-motion/src/render/utils/__tests__/StateVisualElement.ts @@ -1,7 +1,5 @@ -import { ResolvedValues } from "../../types" +import { createBox, ResolvedValues, VisualElement } from "motion-dom" import { MotionProps, MotionStyle } from "../../../motion/types" -import { createBox } from "../../../projection/geometry/models" -import { VisualElement } from "../../VisualElement" export class StateVisualElement extends VisualElement< ResolvedValues, 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 9848ac69c3..3c17527627 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 @@ -1,7 +1,6 @@ -import { AnimationState, createAnimationState } from "../animation-state" +import { createAnimationState, type AnimationState, type VisualElement } from "motion-dom" import { MotionProps } from "../../../motion/types" import { createHtmlRenderState } from "../../html/utils/create-render-state" -import { VisualElement } from "../../VisualElement" import { StateVisualElement } from "./StateVisualElement" function createTest( diff --git a/packages/framer-motion/src/render/utils/__tests__/flat-tree.test.ts b/packages/framer-motion/src/render/utils/__tests__/flat-tree.test.ts index 314c534920..1dac1cac38 100644 --- a/packages/framer-motion/src/render/utils/__tests__/flat-tree.test.ts +++ b/packages/framer-motion/src/render/utils/__tests__/flat-tree.test.ts @@ -1,5 +1,4 @@ -import { WithDepth } from "../compare-by-depth" -import { FlatTree } from "../flat-tree" +import { type WithDepth, FlatTree } from "motion-dom" describe("FlatTree", () => { test("Correctly sorts by depth on iteration", () => { diff --git a/packages/framer-motion/src/render/utils/__tests__/variants.test.ts b/packages/framer-motion/src/render/utils/__tests__/variants.test.ts index 81e6985951..4f4d01d94d 100644 --- a/packages/framer-motion/src/render/utils/__tests__/variants.test.ts +++ b/packages/framer-motion/src/render/utils/__tests__/variants.test.ts @@ -1,4 +1,4 @@ -import { resolveVariantFromProps } from "../resolve-variants" +import { resolveVariantFromProps } from "motion-dom" describe("resolveVariantFromProps", () => { test("Resolves string", () => { diff --git a/packages/framer-motion/src/render/utils/is-draggable.ts b/packages/framer-motion/src/render/utils/is-draggable.ts index 508dfaaaad..1ea56eb8b6 100644 --- a/packages/framer-motion/src/render/utils/is-draggable.ts +++ b/packages/framer-motion/src/render/utils/is-draggable.ts @@ -1,4 +1,4 @@ -import type { VisualElement } from "../VisualElement" +import type { VisualElement } from "motion-dom" export function isDraggable(visualElement: VisualElement) { const { drag, _dragX } = visualElement.getProps() diff --git a/packages/framer-motion/src/utils/__tests__/delay.test.ts b/packages/framer-motion/src/utils/__tests__/delay.test.ts index a566a58d2e..ecdc937321 100644 --- a/packages/framer-motion/src/utils/__tests__/delay.test.ts +++ b/packages/framer-motion/src/utils/__tests__/delay.test.ts @@ -1,4 +1,4 @@ -import { delay } from "../delay" +import { delay } from "motion-dom" describe("delay", () => { test("resolves after provided duration", async () => { diff --git a/packages/framer-motion/src/utils/get-context-window.ts b/packages/framer-motion/src/utils/get-context-window.ts index ac4fbb6cf0..5f0926c74a 100644 --- a/packages/framer-motion/src/utils/get-context-window.ts +++ b/packages/framer-motion/src/utils/get-context-window.ts @@ -1,4 +1,4 @@ -import { VisualElement } from "../render/VisualElement" +import type { VisualElement } from "motion-dom" // Fixes https://github.com/motiondivision/motion/issues/2270 export const getContextWindow = ({ current }: VisualElement) => { diff --git a/packages/framer-motion/src/utils/reduced-motion/__tests__/index.test.tsx b/packages/framer-motion/src/utils/reduced-motion/__tests__/index.test.tsx index 887f558c45..181ee059cb 100644 --- a/packages/framer-motion/src/utils/reduced-motion/__tests__/index.test.tsx +++ b/packages/framer-motion/src/utils/reduced-motion/__tests__/index.test.tsx @@ -1,7 +1,7 @@ +import { hasReducedMotionListener } from "motion-dom" import { render } from "../../../jest.setup" import { motion } from "../../../render/components/motion" import { MotionConfig } from "../../../components/MotionConfig" -import { hasReducedMotionListener } from "../state" describe("reduced motion listener initialization", () => { beforeEach(() => { diff --git a/packages/framer-motion/src/utils/reduced-motion/use-reduced-motion.ts b/packages/framer-motion/src/utils/reduced-motion/use-reduced-motion.ts index ec3f0d3501..765ddd0db1 100644 --- a/packages/framer-motion/src/utils/reduced-motion/use-reduced-motion.ts +++ b/packages/framer-motion/src/utils/reduced-motion/use-reduced-motion.ts @@ -1,9 +1,12 @@ "use client" +import { + hasReducedMotionListener, + initPrefersReducedMotion, + prefersReducedMotion, +} from "motion-dom" import { warnOnce } from "motion-utils" import { useState } from "react" -import { initPrefersReducedMotion } from "." -import { hasReducedMotionListener, prefersReducedMotion } from "./state" /** * A hook that returns `true` if we should be using reduced motion based on the current device's Reduced Motion setting. diff --git a/packages/framer-motion/src/value/__tests__/unwrap-value.test.ts b/packages/framer-motion/src/value/__tests__/unwrap-value.test.ts index 579fec59b5..ebf0b72cb2 100644 --- a/packages/framer-motion/src/value/__tests__/unwrap-value.test.ts +++ b/packages/framer-motion/src/value/__tests__/unwrap-value.test.ts @@ -1,5 +1,4 @@ -import { MotionValue } from "motion-dom" -import { resolveMotionValue } from "../utils/resolve-motion-value" +import { MotionValue, resolveMotionValue } from "motion-dom" describe("resolveMotionValue", () => { it("should leave non-motion values alone", () => { diff --git a/packages/framer-motion/src/value/use-will-change/WillChangeMotionValue.ts b/packages/framer-motion/src/value/use-will-change/WillChangeMotionValue.ts index 470a15a3af..1dffa39120 100644 --- a/packages/framer-motion/src/value/use-will-change/WillChangeMotionValue.ts +++ b/packages/framer-motion/src/value/use-will-change/WillChangeMotionValue.ts @@ -1,5 +1,9 @@ -import { acceleratedValues, MotionValue, transformProps } from "motion-dom" -import { WillChange } from "./types" +import { + acceleratedValues, + MotionValue, + transformProps, + type WillChange, +} from "motion-dom" export class WillChangeMotionValue extends MotionValue diff --git a/packages/framer-motion/src/value/use-will-change/__tests__/is.test.ts b/packages/framer-motion/src/value/use-will-change/__tests__/is.test.ts index c568a9562b..e10d3a606c 100644 --- a/packages/framer-motion/src/value/use-will-change/__tests__/is.test.ts +++ b/packages/framer-motion/src/value/use-will-change/__tests__/is.test.ts @@ -1,5 +1,4 @@ -import { MotionValue } from "motion-dom" -import { isWillChangeMotionValue } from "../is" +import { isWillChangeMotionValue, MotionValue } from "motion-dom" import { WillChangeMotionValue } from "../WillChangeMotionValue" describe("isWillChangeMotionValue", () => { diff --git a/packages/framer-motion/src/value/use-will-change/index.ts b/packages/framer-motion/src/value/use-will-change/index.ts index f212e0a84f..eec008d7f9 100644 --- a/packages/framer-motion/src/value/use-will-change/index.ts +++ b/packages/framer-motion/src/value/use-will-change/index.ts @@ -1,8 +1,8 @@ "use client" +import type { WillChange } from "motion-dom" import { useConstant } from "../../utils/use-constant" import { WillChangeMotionValue } from "./WillChangeMotionValue" -import { WillChange } from "./types" export function useWillChange(): WillChange { return useConstant(() => new WillChangeMotionValue("auto")) diff --git a/packages/motion-dom/package.json b/packages/motion-dom/package.json index 5be9744359..a48a333865 100644 --- a/packages/motion-dom/package.json +++ b/packages/motion-dom/package.json @@ -1,6 +1,6 @@ { "name": "motion-dom", - "version": "12.24.11", + "version": "12.26.2", "author": "Matt Perry", "license": "MIT", "repository": "https://github.com/motiondivision/motion", diff --git a/packages/framer-motion/src/animation/animate/single-value.ts b/packages/motion-dom/src/animation/animate/single-value.ts similarity index 84% rename from packages/framer-motion/src/animation/animate/single-value.ts rename to packages/motion-dom/src/animation/animate/single-value.ts index db34093deb..5711cc3aa7 100644 --- a/packages/framer-motion/src/animation/animate/single-value.ts +++ b/packages/motion-dom/src/animation/animate/single-value.ts @@ -1,13 +1,15 @@ -import { +import { animateMotionValue } from "../interfaces/motion-value" +import type { AnimationPlaybackControlsWithThen, AnyResolvedKeyframe, - motionValue as createMotionValue, - isMotionValue, - MotionValue, UnresolvedValueKeyframe, ValueAnimationTransition, -} from "motion-dom" -import { animateMotionValue } from "../interfaces/motion-value" +} from "../types" +import { + motionValue as createMotionValue, + MotionValue, +} from "../../value" +import { isMotionValue } from "../../value/utils/is-motion-value" export function animateSingleValue( value: MotionValue | V, diff --git a/packages/framer-motion/src/animation/interfaces/motion-value.ts b/packages/motion-dom/src/animation/interfaces/motion-value.ts similarity index 89% rename from packages/framer-motion/src/animation/interfaces/motion-value.ts rename to packages/motion-dom/src/animation/interfaces/motion-value.ts index 28c4e6f6cb..319c2713cb 100644 --- a/packages/framer-motion/src/animation/interfaces/motion-value.ts +++ b/packages/motion-dom/src/animation/interfaces/motion-value.ts @@ -1,23 +1,20 @@ +import { MotionGlobalConfig, secondsToMilliseconds } from "motion-utils" +import { AsyncMotionValueAnimation } from "../AsyncMotionValueAnimation" +import { JSAnimation } from "../JSAnimation" import type { AnyResolvedKeyframe, - MotionValue, - StartAnimation, - UnresolvedKeyframes, - ValueTransition, -} from "motion-dom" -import { - AsyncMotionValueAnimation, - frame, - getValueTransition, - JSAnimation, - makeAnimationInstant, ValueAnimationOptions, -} from "motion-dom" -import { MotionGlobalConfig, secondsToMilliseconds } from "motion-utils" -import type { VisualElement } from "../../render/VisualElement" -import { getFinalKeyframe } from "../animators/waapi/utils/get-final-keyframe" + ValueTransition, +} from "../types" +import type { UnresolvedKeyframes } from "../keyframes/KeyframesResolver" +import { getValueTransition } from "../utils/get-value-transition" +import { makeAnimationInstant } from "../utils/make-animation-instant" import { getDefaultTransition } from "../utils/default-transitions" +import { getFinalKeyframe } from "../utils/get-final-keyframe" import { isTransitionDefined } from "../utils/is-transition-defined" +import { frame } from "../../frameloop" +import type { MotionValue, StartAnimation } from "../../value" +import type { VisualElement } from "../../render/VisualElement" export const animateMotionValue = ( diff --git a/packages/motion-dom/src/animation/interfaces/types.ts b/packages/motion-dom/src/animation/interfaces/types.ts new file mode 100644 index 0000000000..76960b4583 --- /dev/null +++ b/packages/motion-dom/src/animation/interfaces/types.ts @@ -0,0 +1,9 @@ +import type { AnimationType } from "../../render/types" +import type { Transition } from "../types" + +export interface VisualElementAnimationOptions { + delay?: number + transitionOverride?: Transition + custom?: any + type?: AnimationType +} diff --git a/packages/framer-motion/src/animation/interfaces/visual-element-target.ts b/packages/motion-dom/src/animation/interfaces/visual-element-target.ts similarity index 91% rename from packages/framer-motion/src/animation/interfaces/visual-element-target.ts rename to packages/motion-dom/src/animation/interfaces/visual-element-target.ts index 8110c502c8..62db8a3cdb 100644 --- a/packages/framer-motion/src/animation/interfaces/visual-element-target.ts +++ b/packages/motion-dom/src/animation/interfaces/visual-element-target.ts @@ -1,17 +1,15 @@ -import type { TargetAndTransition } from "motion-dom" -import { - AnimationPlaybackControlsWithThen, - frame, - getValueTransition, - positionalKeys, -} from "motion-dom" -import type { AnimationTypeState } from "../../render/utils/animation-state" +import { frame } from "../../frameloop" +import { getValueTransition } from "../utils/get-value-transition" +import { positionalKeys } from "../../render/utils/keys-position" import { setTarget } from "../../render/utils/setters" -import type { VisualElement } from "../../render/VisualElement" -import { addValueToWillChange } from "../../value/use-will-change/add-will-change" +import { addValueToWillChange } from "../../value/will-change/add-will-change" import { getOptimisedAppearId } from "../optimized-appear/get-appear-id" import { animateMotionValue } from "./motion-value" import type { VisualElementAnimationOptions } from "./types" +import type { AnimationPlaybackControlsWithThen } from "../types" +import type { TargetAndTransition } from "../../node/types" +import type { AnimationTypeState } from "../../render/utils/animation-state" +import type { VisualElement } from "../../render/VisualElement" /** * Decide whether we should block this animation. Previously, we achieved this diff --git a/packages/framer-motion/src/animation/interfaces/visual-element-variant.ts b/packages/motion-dom/src/animation/interfaces/visual-element-variant.ts similarity index 95% rename from packages/framer-motion/src/animation/interfaces/visual-element-variant.ts rename to packages/motion-dom/src/animation/interfaces/visual-element-variant.ts index e1e3121dee..439562d897 100644 --- a/packages/framer-motion/src/animation/interfaces/visual-element-variant.ts +++ b/packages/motion-dom/src/animation/interfaces/visual-element-variant.ts @@ -1,9 +1,9 @@ -import { DynamicOption } from "motion-dom" import { resolveVariant } from "../../render/utils/resolve-dynamic-variants" -import { VisualElement } from "../../render/VisualElement" import { calcChildStagger } from "../utils/calc-child-stagger" -import { VisualElementAnimationOptions } from "./types" +import type { VisualElementAnimationOptions } from "./types" import { animateTarget } from "./visual-element-target" +import type { DynamicOption } from "../types" +import type { VisualElement } from "../../render/VisualElement" export function animateVariant( visualElement: VisualElement, diff --git a/packages/framer-motion/src/animation/interfaces/visual-element.ts b/packages/motion-dom/src/animation/interfaces/visual-element.ts similarity index 86% rename from packages/framer-motion/src/animation/interfaces/visual-element.ts rename to packages/motion-dom/src/animation/interfaces/visual-element.ts index f97d9d2598..5c47c10456 100644 --- a/packages/framer-motion/src/animation/interfaces/visual-element.ts +++ b/packages/motion-dom/src/animation/interfaces/visual-element.ts @@ -1,7 +1,7 @@ -import type { AnimationDefinition } from "motion-dom" import { resolveVariant } from "../../render/utils/resolve-dynamic-variants" -import { VisualElement } from "../../render/VisualElement" -import { VisualElementAnimationOptions } from "./types" +import type { AnimationDefinition } from "../../node/types" +import type { VisualElement } from "../../render/VisualElement" +import type { VisualElementAnimationOptions } from "./types" import { animateTarget } from "./visual-element-target" import { animateVariant } from "./visual-element-variant" diff --git a/packages/framer-motion/src/animation/optimized-appear/data-id.ts b/packages/motion-dom/src/animation/optimized-appear/data-id.ts similarity index 100% rename from packages/framer-motion/src/animation/optimized-appear/data-id.ts rename to packages/motion-dom/src/animation/optimized-appear/data-id.ts diff --git a/packages/framer-motion/src/animation/optimized-appear/get-appear-id.ts b/packages/motion-dom/src/animation/optimized-appear/get-appear-id.ts similarity index 82% rename from packages/framer-motion/src/animation/optimized-appear/get-appear-id.ts rename to packages/motion-dom/src/animation/optimized-appear/get-appear-id.ts index e2878f6deb..be40859350 100644 --- a/packages/framer-motion/src/animation/optimized-appear/get-appear-id.ts +++ b/packages/motion-dom/src/animation/optimized-appear/get-appear-id.ts @@ -1,5 +1,5 @@ import { optimizedAppearDataAttribute } from "./data-id" -import { WithAppearProps } from "./types" +import type { WithAppearProps } from "./types" export function getOptimisedAppearId( visualElement: WithAppearProps diff --git a/packages/framer-motion/src/animation/optimized-appear/types.ts b/packages/motion-dom/src/animation/optimized-appear/types.ts similarity index 93% rename from packages/framer-motion/src/animation/optimized-appear/types.ts rename to packages/motion-dom/src/animation/optimized-appear/types.ts index 654ffb6245..ff7f276558 100644 --- a/packages/framer-motion/src/animation/optimized-appear/types.ts +++ b/packages/motion-dom/src/animation/optimized-appear/types.ts @@ -1,5 +1,5 @@ -import type { Batcher } from "motion-dom" -import { MotionValue } from "motion-dom" +import type { Batcher } from "../../frameloop/types" +import type { MotionValue } from "../../value" import { optimizedAppearDataAttribute } from "./data-id" /** diff --git a/packages/framer-motion/src/animation/utils/__tests__/transitions.test.ts b/packages/motion-dom/src/animation/utils/__tests__/is-transition-defined.test.ts similarity index 100% rename from packages/framer-motion/src/animation/utils/__tests__/transitions.test.ts rename to packages/motion-dom/src/animation/utils/__tests__/is-transition-defined.test.ts diff --git a/packages/framer-motion/src/animation/utils/calc-child-stagger.ts b/packages/motion-dom/src/animation/utils/calc-child-stagger.ts similarity index 86% rename from packages/framer-motion/src/animation/utils/calc-child-stagger.ts rename to packages/motion-dom/src/animation/utils/calc-child-stagger.ts index 8ae31c363f..546b629e39 100644 --- a/packages/framer-motion/src/animation/utils/calc-child-stagger.ts +++ b/packages/motion-dom/src/animation/utils/calc-child-stagger.ts @@ -1,5 +1,5 @@ -import { DynamicOption } from "motion-dom" -import { VisualElement } from "../../render/VisualElement" +import type { DynamicOption } from "../types" +import type { VisualElement } from "../../render/VisualElement" export function calcChildStagger( children: Set, diff --git a/packages/framer-motion/src/animation/utils/default-transitions.ts b/packages/motion-dom/src/animation/utils/default-transitions.ts similarity index 90% rename from packages/framer-motion/src/animation/utils/default-transitions.ts rename to packages/motion-dom/src/animation/utils/default-transitions.ts index f73e4698bb..ad7d48eaed 100644 --- a/packages/framer-motion/src/animation/utils/default-transitions.ts +++ b/packages/motion-dom/src/animation/utils/default-transitions.ts @@ -1,4 +1,5 @@ -import { transformProps, ValueAnimationOptions } from "motion-dom" +import { transformProps } from "../../render/utils/keys-transform" +import type { ValueAnimationOptions } from "../types" const underDampedSpring: Partial = { type: "spring", diff --git a/packages/framer-motion/src/animation/animators/waapi/utils/get-final-keyframe.ts b/packages/motion-dom/src/animation/utils/get-final-keyframe.ts similarity index 89% rename from packages/framer-motion/src/animation/animators/waapi/utils/get-final-keyframe.ts rename to packages/motion-dom/src/animation/utils/get-final-keyframe.ts index bac2f647ad..8ca690f293 100644 --- a/packages/framer-motion/src/animation/animators/waapi/utils/get-final-keyframe.ts +++ b/packages/motion-dom/src/animation/utils/get-final-keyframe.ts @@ -1,4 +1,4 @@ -import { AnimationPlaybackOptions } from "motion-dom" +import type { AnimationPlaybackOptions } from "../types" const isNotNull = (value: unknown) => value !== null diff --git a/packages/framer-motion/src/animation/utils/is-transition-defined.ts b/packages/motion-dom/src/animation/utils/is-transition-defined.ts similarity index 83% rename from packages/framer-motion/src/animation/utils/is-transition-defined.ts rename to packages/motion-dom/src/animation/utils/is-transition-defined.ts index c59664c8bc..8e6e25259d 100644 --- a/packages/framer-motion/src/animation/utils/is-transition-defined.ts +++ b/packages/motion-dom/src/animation/utils/is-transition-defined.ts @@ -1,4 +1,5 @@ -import { type AnyResolvedKeyframe, type Transition } from "motion-dom" +import type { AnyResolvedKeyframe } from "../types" +import type { Transition } from "../types" /** * Decide whether a transition is defined on a given Transition. diff --git a/packages/framer-motion/src/events/add-dom-event.ts b/packages/motion-dom/src/events/add-dom-event.ts similarity index 100% rename from packages/framer-motion/src/events/add-dom-event.ts rename to packages/motion-dom/src/events/add-dom-event.ts diff --git a/packages/motion-dom/src/index.ts b/packages/motion-dom/src/index.ts index 571e450a9b..3e42bdc695 100644 --- a/packages/motion-dom/src/index.ts +++ b/packages/motion-dom/src/index.ts @@ -11,6 +11,22 @@ export * from "./animation/utils/css-variables-conversion" export * from "./animation/utils/get-value-transition" export * from "./animation/utils/is-css-variable" export * from "./animation/utils/make-animation-instant" +export { getDefaultTransition } from "./animation/utils/default-transitions" +export { isTransitionDefined } from "./animation/utils/is-transition-defined" +export { getFinalKeyframe } from "./animation/utils/get-final-keyframe" +export { calcChildStagger } from "./animation/utils/calc-child-stagger" + +// Animation interfaces +export { animateMotionValue } from "./animation/interfaces/motion-value" +export { animateTarget } from "./animation/interfaces/visual-element-target" +export { animateVariant } from "./animation/interfaces/visual-element-variant" +export { animateVisualElement } from "./animation/interfaces/visual-element" +export type { VisualElementAnimationOptions } from "./animation/interfaces/types" + +// Optimized appear +export { optimizedAppearDataId, optimizedAppearDataAttribute } from "./animation/optimized-appear/data-id" +export { getOptimisedAppearId } from "./animation/optimized-appear/get-appear-id" +export type { WithAppearProps, HandoffFunction } from "./animation/optimized-appear/types" export * from "./animation/generators/inertia" export * from "./animation/generators/keyframes" @@ -69,6 +85,7 @@ export * from "./render/dom/style-set" export * from "./render/svg/types" export * from "./render/utils/keys-position" export * from "./render/utils/keys-transform" +export { isKeyframesTarget } from "./render/utils/is-keyframes-target" export * from "./resize" @@ -120,12 +137,137 @@ export * from "./value/types/utils/animatable-none" export * from "./value/types/utils/find" export * from "./value/types/utils/get-as-type" export * from "./value/utils/is-motion-value" +export type { WillChange } from "./value/will-change/types" +export { isWillChangeMotionValue } from "./value/will-change/is" +export { addValueToWillChange } from "./value/will-change/add-will-change" export * from "./view" export * from "./view/types" export * from "./view/utils/get-layer-info" export * from "./view/utils/get-view-animations" +// Visual Element +export { VisualElement, setFeatureDefinitions, getFeatureDefinitions } from "./render/VisualElement" +export type { MotionStyle } from "./render/VisualElement" +export { Feature } from "./render/Feature" +export { DOMVisualElement } from "./render/dom/DOMVisualElement" +export { HTMLVisualElement } from "./render/html/HTMLVisualElement" +export { SVGVisualElement } from "./render/svg/SVGVisualElement" +export { ObjectVisualElement } from "./render/object/ObjectVisualElement" +export { visualElementStore } from "./render/store" +export type { + ResolvedValues, + PresenceContextProps, + ReducedMotionConfig, + MotionConfigContextProps, + VisualState, + VisualElementOptions, + VisualElementEventCallbacks, + LayoutLifecycles, + ScrapeMotionValuesFromProps, + UseRenderState, + AnimationType, + FeatureClass, +} from "./render/types" +export * from "./render/dom/types" +export * from "./render/html/types" + +// Animation State +export { createAnimationState, checkVariantsDidChange } from "./render/utils/animation-state" +export type { AnimationState, AnimationTypeState, AnimationList } from "./render/utils/animation-state" + +// Variant utilities +export { isVariantLabel } from "./render/utils/is-variant-label" +export { isControllingVariants, isVariantNode } from "./render/utils/is-controlling-variants" +export { getVariantContext } from "./render/utils/get-variant-context" +export { resolveVariantFromProps } from "./render/utils/resolve-variants" +export { resolveVariant } from "./render/utils/resolve-dynamic-variants" +export { updateMotionValuesFromProps } from "./render/utils/motion-values" +export { variantProps, variantPriorityOrder } from "./render/utils/variant-props" +export { isAnimationControls } from "./render/utils/is-animation-controls" +export { isForcedMotionValue, scaleCorrectors, addScaleCorrector } from "./render/utils/is-forced-motion-value" +export { setTarget } from "./render/utils/setters" + +// Reduced motion +export { + initPrefersReducedMotion, + hasReducedMotionListener, + prefersReducedMotion, +} from "./render/utils/reduced-motion" + +// Projection geometry +export * from "./projection/geometry/models" +export * from "./projection/geometry/delta-calc" +export * from "./projection/geometry/delta-apply" +export * from "./projection/geometry/delta-remove" +export * from "./projection/geometry/copy" +export * from "./projection/geometry/conversion" +export * from "./projection/geometry/utils" +export { hasTransform, hasScale, has2DTranslate } from "./projection/utils/has-transform" +export { measureViewportBox, measurePageBox } from "./projection/utils/measure" +export { eachAxis } from "./projection/utils/each-axis" + +// Projection styles +export * from "./projection/styles/types" +export { pixelsToPercent, correctBorderRadius } from "./projection/styles/scale-border-radius" +export { correctBoxShadow } from "./projection/styles/scale-box-shadow" +export { buildProjectionTransform } from "./projection/styles/transform" + +// Projection animation +export { mixValues } from "./projection/animation/mix-values" + +// Utilities (used by projection system) +export { delay, delayInSeconds } from "./utils/delay" +export type { DelayedFunction } from "./utils/delay" +export { addDomEvent } from "./events/add-dom-event" +export { resolveMotionValue } from "./value/utils/resolve-motion-value" +export { animateSingleValue } from "./animation/animate/single-value" +export { FlatTree } from "./projection/utils/flat-tree" +export { compareByDepth } from "./projection/utils/compare-by-depth" +export type { WithDepth } from "./projection/utils/compare-by-depth" + +// Projection node system +export { + createProjectionNode, + propagateDirtyNodes, + cleanDirtyNodes, +} from "./projection/node/create-projection-node" +export { + HTMLProjectionNode, + rootProjectionNode, +} from "./projection/node/HTMLProjectionNode" +export { DocumentProjectionNode } from "./projection/node/DocumentProjectionNode" +export { globalProjectionState } from "./projection/node/state" +export { nodeGroup } from "./projection/node/group" +export type { NodeGroup } from "./projection/node/group" +export { NodeStack } from "./projection/shared/stack" +export type { + IProjectionNode, + Measurements, + Phase, + ScrollMeasurements, + LayoutEvents, + LayoutUpdateData, + LayoutUpdateHandler, + ProjectionNodeConfig, + ProjectionNodeOptions, + ProjectionEventName, + InitialPromotionConfig, +} from "./projection/node/types" + +// HTML/SVG utilities +export { buildHTMLStyles } from "./render/html/utils/build-styles" +export { buildTransform } from "./render/html/utils/build-transform" +export { renderHTML } from "./render/html/utils/render" +export { scrapeMotionValuesFromProps as scrapeHTMLMotionValuesFromProps } from "./render/html/utils/scrape-motion-values" +export { buildSVGAttrs } from "./render/svg/utils/build-attrs" +export { renderSVG } from "./render/svg/utils/render" +export { buildSVGPath } from "./render/svg/utils/path" +export { camelCaseAttributes } from "./render/svg/utils/camel-case-attrs" +export { isSVGTag } from "./render/svg/utils/is-svg-tag" +export { scrapeMotionValuesFromProps as scrapeSVGMotionValuesFromProps } from "./render/svg/utils/scrape-motion-values" +export { camelToDash } from "./render/dom/utils/camel-to-dash" + /** * Deprecated */ diff --git a/packages/framer-motion/src/projection/animation/__tests__/mix-values.test.ts b/packages/motion-dom/src/projection/animation/__tests__/mix-values.test.ts similarity index 100% rename from packages/framer-motion/src/projection/animation/__tests__/mix-values.test.ts rename to packages/motion-dom/src/projection/animation/__tests__/mix-values.test.ts diff --git a/packages/framer-motion/src/projection/animation/mix-values.ts b/packages/motion-dom/src/projection/animation/mix-values.ts similarity index 70% rename from packages/framer-motion/src/projection/animation/mix-values.ts rename to packages/motion-dom/src/projection/animation/mix-values.ts index 92a4108dad..e6a701939d 100644 --- a/packages/framer-motion/src/projection/animation/mix-values.ts +++ b/packages/motion-dom/src/projection/animation/mix-values.ts @@ -1,11 +1,13 @@ -import { type AnyResolvedKeyframe, mixNumber, percent, px } from "motion-dom" +import { mixNumber } from "../../utils/mix/number" +import { percent, px } from "../../value/types/numbers/units" +import type { AnyResolvedKeyframe } from "../../animation/types" import { progress as calcProgress, circOut, EasingFunction, noop, } from "motion-utils" -import { ResolvedValues } from "../../render/types" +import type { ResolvedValues } from "../../node/types" const borders = ["TopLeft", "TopRight", "BottomLeft", "BottomRight"] const numBorders = borders.length @@ -97,30 +99,6 @@ function getRadius(values: ResolvedValues, radiusName: string) { : values.borderRadius } -// /** -// * We only want to mix the background color if there's a follow element -// * that we're not crossfading opacity between. For instance with switch -// * AnimateSharedLayout animations, this helps the illusion of a continuous -// * element being animated but also cuts down on the number of paints triggered -// * for elements where opacity is doing that work for us. -// */ -// if ( -// !hasFollowElement && -// latestLeadValues.backgroundColor && -// latestFollowValues.backgroundColor -// ) { -// /** -// * This isn't ideal performance-wise as mixColor is creating a new function every frame. -// * We could probably create a mixer that runs at the start of the animation but -// * the idea behind the crossfader is that it runs dynamically between two potentially -// * changing targets (ie opacity or borderRadius may be animating independently via variants) -// */ -// leadState.backgroundColor = followState.backgroundColor = mixColor( -// latestFollowValues.backgroundColor as string, -// latestLeadValues.backgroundColor as string -// )(p) -// } - const easeCrossfadeIn = /*@__PURE__*/ compress(0, 0.5, circOut) const easeCrossfadeOut = /*@__PURE__*/ compress(0.5, 0.95, noop) diff --git a/packages/framer-motion/src/projection/geometry/__tests__/conversion.test.ts b/packages/motion-dom/src/projection/geometry/__tests__/conversion.test.ts similarity index 100% rename from packages/framer-motion/src/projection/geometry/__tests__/conversion.test.ts rename to packages/motion-dom/src/projection/geometry/__tests__/conversion.test.ts diff --git a/packages/framer-motion/src/projection/geometry/__tests__/copy.test.ts b/packages/motion-dom/src/projection/geometry/__tests__/copy.test.ts similarity index 100% rename from packages/framer-motion/src/projection/geometry/__tests__/copy.test.ts rename to packages/motion-dom/src/projection/geometry/__tests__/copy.test.ts diff --git a/packages/framer-motion/src/projection/geometry/__tests__/delta-apply.test.ts b/packages/motion-dom/src/projection/geometry/__tests__/delta-apply.test.ts similarity index 100% rename from packages/framer-motion/src/projection/geometry/__tests__/delta-apply.test.ts rename to packages/motion-dom/src/projection/geometry/__tests__/delta-apply.test.ts diff --git a/packages/framer-motion/src/projection/geometry/__tests__/delta-calc.test.ts b/packages/motion-dom/src/projection/geometry/__tests__/delta-calc.test.ts similarity index 96% rename from packages/framer-motion/src/projection/geometry/__tests__/delta-calc.test.ts rename to packages/motion-dom/src/projection/geometry/__tests__/delta-calc.test.ts index 881ef50263..3aaf66c102 100644 --- a/packages/framer-motion/src/projection/geometry/__tests__/delta-calc.test.ts +++ b/packages/motion-dom/src/projection/geometry/__tests__/delta-calc.test.ts @@ -1,9 +1,4 @@ -import { - isNear, - calcAxisDelta, - calcRelativeBox, - calcRelativePosition, -} from "../delta-calc" +import { isNear, calcAxisDelta, calcRelativeBox, calcRelativePosition } from "../delta-calc" import { applyAxisDelta } from "../delta-apply" import { createBox, createDelta } from "../models" diff --git a/packages/framer-motion/src/projection/geometry/__tests__/operations.test.ts b/packages/motion-dom/src/projection/geometry/__tests__/operations.test.ts similarity index 100% rename from packages/framer-motion/src/projection/geometry/__tests__/operations.test.ts rename to packages/motion-dom/src/projection/geometry/__tests__/operations.test.ts diff --git a/packages/framer-motion/src/projection/geometry/conversion.ts b/packages/motion-dom/src/projection/geometry/conversion.ts similarity index 100% rename from packages/framer-motion/src/projection/geometry/conversion.ts rename to packages/motion-dom/src/projection/geometry/conversion.ts diff --git a/packages/framer-motion/src/projection/geometry/copy.ts b/packages/motion-dom/src/projection/geometry/copy.ts similarity index 100% rename from packages/framer-motion/src/projection/geometry/copy.ts rename to packages/motion-dom/src/projection/geometry/copy.ts diff --git a/packages/framer-motion/src/projection/geometry/delta-apply.ts b/packages/motion-dom/src/projection/geometry/delta-apply.ts similarity index 97% rename from packages/framer-motion/src/projection/geometry/delta-apply.ts rename to packages/motion-dom/src/projection/geometry/delta-apply.ts index c0404cdebb..7e72e695d2 100644 --- a/packages/framer-motion/src/projection/geometry/delta-apply.ts +++ b/packages/motion-dom/src/projection/geometry/delta-apply.ts @@ -1,7 +1,6 @@ -import { mixNumber } from "motion-dom" import { Axis, Box, Delta, Point } from "motion-utils" +import { mixNumber } from "../../utils/mix/number" import { ResolvedValues } from "../../render/types" -import { IProjectionNode } from "../node/types" import { hasTransform } from "../utils/has-transform" /** @@ -77,7 +76,7 @@ const TREE_SCALE_SNAP_MAX = 1.0000000000001 export function applyTreeDeltas( box: Box, treeScale: Point, - treePath: IProjectionNode[], + treePath: any[], isSharedTransition: boolean = false ) { const treeLength = treePath.length @@ -86,7 +85,7 @@ export function applyTreeDeltas( // Reset the treeScale treeScale.x = treeScale.y = 1 - let node: IProjectionNode + let node: any let delta: Delta | undefined for (let i = 0; i < treeLength; i++) { diff --git a/packages/framer-motion/src/projection/geometry/delta-calc.ts b/packages/motion-dom/src/projection/geometry/delta-calc.ts similarity index 97% rename from packages/framer-motion/src/projection/geometry/delta-calc.ts rename to packages/motion-dom/src/projection/geometry/delta-calc.ts index 088579d542..6f405bc625 100644 --- a/packages/framer-motion/src/projection/geometry/delta-calc.ts +++ b/packages/motion-dom/src/projection/geometry/delta-calc.ts @@ -1,5 +1,5 @@ -import { mixNumber } from "motion-dom" import { Axis, AxisDelta, Box, Delta } from "motion-utils" +import { mixNumber } from "../../utils/mix/number" import { ResolvedValues } from "../../render/types" const SCALE_PRECISION = 0.0001 diff --git a/packages/framer-motion/src/projection/geometry/delta-remove.ts b/packages/motion-dom/src/projection/geometry/delta-remove.ts similarity index 96% rename from packages/framer-motion/src/projection/geometry/delta-remove.ts rename to packages/motion-dom/src/projection/geometry/delta-remove.ts index 8426297880..0d196c4daf 100644 --- a/packages/framer-motion/src/projection/geometry/delta-remove.ts +++ b/packages/motion-dom/src/projection/geometry/delta-remove.ts @@ -1,5 +1,6 @@ -import { mixNumber, percent } from "motion-dom" import { Axis, Box } from "motion-utils" +import { mixNumber } from "../../utils/mix/number" +import { percent } from "../../value/types/numbers/units" import { ResolvedValues } from "../../render/types" import { scalePoint } from "./delta-apply" diff --git a/packages/framer-motion/src/projection/geometry/models.ts b/packages/motion-dom/src/projection/geometry/models.ts similarity index 100% rename from packages/framer-motion/src/projection/geometry/models.ts rename to packages/motion-dom/src/projection/geometry/models.ts diff --git a/packages/framer-motion/src/projection/geometry/utils.ts b/packages/motion-dom/src/projection/geometry/utils.ts similarity index 100% rename from packages/framer-motion/src/projection/geometry/utils.ts rename to packages/motion-dom/src/projection/geometry/utils.ts diff --git a/packages/framer-motion/src/projection/node/DocumentProjectionNode.ts b/packages/motion-dom/src/projection/node/DocumentProjectionNode.ts similarity index 100% rename from packages/framer-motion/src/projection/node/DocumentProjectionNode.ts rename to packages/motion-dom/src/projection/node/DocumentProjectionNode.ts diff --git a/packages/framer-motion/src/projection/node/HTMLProjectionNode.ts b/packages/motion-dom/src/projection/node/HTMLProjectionNode.ts similarity index 100% rename from packages/framer-motion/src/projection/node/HTMLProjectionNode.ts rename to packages/motion-dom/src/projection/node/HTMLProjectionNode.ts diff --git a/packages/framer-motion/src/projection/node/__tests__/TestProjectionNode.ts b/packages/motion-dom/src/projection/node/__tests__/TestProjectionNode.ts similarity index 93% rename from packages/framer-motion/src/projection/node/__tests__/TestProjectionNode.ts rename to packages/motion-dom/src/projection/node/__tests__/TestProjectionNode.ts index 124d1266c0..fafbfe7273 100644 --- a/packages/framer-motion/src/projection/node/__tests__/TestProjectionNode.ts +++ b/packages/motion-dom/src/projection/node/__tests__/TestProjectionNode.ts @@ -1,6 +1,6 @@ import { Box } from "motion-utils" import { createProjectionNode } from "../create-projection-node" -import { IProjectionNode, ProjectionNodeOptions } from "../types" +import type { IProjectionNode, ProjectionNodeOptions } from "../types" let rootNode: IProjectionNode diff --git a/packages/framer-motion/src/projection/node/__tests__/group.test.ts b/packages/motion-dom/src/projection/node/__tests__/group.test.ts similarity index 100% rename from packages/framer-motion/src/projection/node/__tests__/group.test.ts rename to packages/motion-dom/src/projection/node/__tests__/group.test.ts diff --git a/packages/framer-motion/src/projection/node/__tests__/node.test.ts b/packages/motion-dom/src/projection/node/__tests__/node.test.ts similarity index 98% rename from packages/framer-motion/src/projection/node/__tests__/node.test.ts rename to packages/motion-dom/src/projection/node/__tests__/node.test.ts index 662325b085..450dc6cf0e 100644 --- a/packages/framer-motion/src/projection/node/__tests__/node.test.ts +++ b/packages/motion-dom/src/projection/node/__tests__/node.test.ts @@ -1,7 +1,7 @@ import { createTestNode } from "./TestProjectionNode" import { propagateDirtyNodes, cleanDirtyNodes } from "../create-projection-node" -import { IProjectionNode } from "../types" -import { nextFrame, nextMicrotask } from "../../../gestures/__tests__/utils" +import type { IProjectionNode } from "../types" +import { nextFrame, nextMicrotask } from "./utils" describe("node", () => { test("If a child updates layout, and parent has scale, parent resetsTransform during measurement", async () => { diff --git a/packages/motion-dom/src/projection/node/__tests__/utils.ts b/packages/motion-dom/src/projection/node/__tests__/utils.ts new file mode 100644 index 0000000000..064283f3bc --- /dev/null +++ b/packages/motion-dom/src/projection/node/__tests__/utils.ts @@ -0,0 +1,14 @@ +import { frame } from "../../../frameloop" +import { microtask } from "../../../frameloop/microtask" + +export async function nextFrame() { + return new Promise((resolve) => { + frame.postRender(() => resolve()) + }) +} + +export async function nextMicrotask() { + return new Promise((resolve) => { + microtask.postRender(() => resolve()) + }) +} diff --git a/packages/framer-motion/src/projection/node/create-projection-node.ts b/packages/motion-dom/src/projection/node/create-projection-node.ts similarity index 98% rename from packages/framer-motion/src/projection/node/create-projection-node.ts rename to packages/motion-dom/src/projection/node/create-projection-node.ts index 7ffac3b483..63feb136ea 100644 --- a/packages/framer-motion/src/projection/node/create-projection-node.ts +++ b/packages/motion-dom/src/projection/node/create-projection-node.ts @@ -1,44 +1,11 @@ -import { - activeAnimations, - cancelFrame, - frame, - frameData, - frameSteps, - getValueTransition, - isSVGElement, - isSVGSVGElement, - JSAnimation, - microtask, - mixNumber, - MotionValue, - motionValue, - statsBuffer, - time, - Transition, - ValueAnimationOptions, - type Process, -} from "motion-dom" -import { - Axis, - AxisDelta, - Box, - clamp, - Delta, - noop, - Point, - SubscriptionManager, -} from "motion-utils" -import { animateSingleValue } from "../../animation/animate/single-value" -import { getOptimisedAppearId } from "../../animation/optimized-appear/get-appear-id" -import { MotionStyle } from "../../motion/types" -import { HTMLVisualElement } from "../../projection" -import { ResolvedValues } from "../../render/types" -import { FlatTree } from "../../render/utils/flat-tree" -import { VisualElement } from "../../render/VisualElement" -import { delay } from "../../utils/delay" -import { resolveMotionValue } from "../../value/utils/resolve-motion-value" -import { mixValues } from "../animation/mix-values" -import { copyAxisDeltaInto, copyBoxInto } from "../geometry/copy" +import { activeAnimations } from "../../stats/animation-count" +import { JSAnimation } from "../../animation/JSAnimation" +import { Transition, ValueAnimationOptions } from "../../animation/types" +import { getValueTransition } from "../../animation/utils/get-value-transition" +import { cancelFrame, frame, frameData, frameSteps } from "../../frameloop" +import { microtask } from "../../frameloop/microtask" +import { time } from "../../frameloop/sync-time" +import type { Process } from "../../frameloop/types" import { applyBoxDelta, applyTreeDeltas, @@ -53,6 +20,7 @@ import { isNear, } from "../geometry/delta-calc" import { removeBoxTransforms } from "../geometry/delta-remove" +import { copyAxisDeltaInto, copyBoxInto } from "../geometry/copy" import { createBox, createDelta } from "../geometry/models" import { aspectRatio, @@ -61,11 +29,35 @@ import { boxEqualsRounded, isDeltaZero, } from "../geometry/utils" -import { NodeStack } from "../shared/stack" -import { scaleCorrectors } from "../styles/scale-correction" import { buildProjectionTransform } from "../styles/transform" import { eachAxis } from "../utils/each-axis" import { has2DTranslate, hasScale, hasTransform } from "../utils/has-transform" +import { mixValues } from "../animation/mix-values" +import { isSVGElement } from "../../utils/is-svg-element" +import { isSVGSVGElement } from "../../utils/is-svg-svg-element" +import { mixNumber } from "../../utils/mix/number" +import { MotionValue, motionValue } from "../../value" +import { scaleCorrectors } from "../../render/utils/is-forced-motion-value" +import { statsBuffer } from "../../stats/buffer" +import { + Axis, + AxisDelta, + Box, + clamp, + Delta, + noop, + Point, + SubscriptionManager, +} from "motion-utils" +import { animateSingleValue } from "../../animation/animate/single-value" +import { getOptimisedAppearId } from "../../animation/optimized-appear/get-appear-id" +import type { ResolvedValues } from "../../render/types" +import type { MotionStyle, VisualElement } from "../../render/VisualElement" +import { HTMLVisualElement } from "../../render/html/HTMLVisualElement" +import { FlatTree } from "../utils/flat-tree" +import { delay } from "../../utils/delay" +import { resolveMotionValue } from "../../value/utils/resolve-motion-value" +import { NodeStack } from "../shared/stack" import { globalProjectionState } from "./state" import { IProjectionNode, diff --git a/packages/framer-motion/src/projection/node/group.ts b/packages/motion-dom/src/projection/node/group.ts similarity index 100% rename from packages/framer-motion/src/projection/node/group.ts rename to packages/motion-dom/src/projection/node/group.ts diff --git a/packages/framer-motion/src/projection/node/state.ts b/packages/motion-dom/src/projection/node/state.ts similarity index 100% rename from packages/framer-motion/src/projection/node/state.ts rename to packages/motion-dom/src/projection/node/state.ts diff --git a/packages/framer-motion/src/projection/node/types.ts b/packages/motion-dom/src/projection/node/types.ts similarity index 84% rename from packages/framer-motion/src/projection/node/types.ts rename to packages/motion-dom/src/projection/node/types.ts index b01bb2abf0..59fa8a289c 100644 --- a/packages/framer-motion/src/projection/node/types.ts +++ b/packages/motion-dom/src/projection/node/types.ts @@ -1,10 +1,9 @@ -import type { JSAnimation, Transition, ValueTransition } from "motion-dom" +import type { JSAnimation } from "../../animation/JSAnimation" +import type { Transition, ValueTransition } from "../../animation/types" +import type { ResolvedValues } from "../../render/types" +import type { VisualElement, MotionStyle } from "../../render/VisualElement" import { Box, Delta, Point } from "motion-utils" -import { InitialPromotionConfig } from "../../context/SwitchLayoutGroupContext" -import { MotionStyle } from "../../motion/types" -import { ResolvedValues } from "../../render/types" -import { FlatTree } from "../../render/utils/flat-tree" -import type { VisualElement } from "../../render/VisualElement" +import { FlatTree } from "../utils/flat-tree" import { NodeStack } from "../shared/stack" export interface Measurements { @@ -164,6 +163,23 @@ export interface ProjectionNodeConfig { resetTransform?: (instance: I, value?: string) => void } +/** + * Configuration for initial promotion of shared layout elements. + * This was originally in React's SwitchLayoutGroupContext but is now + * framework-agnostic to support vanilla JS usage. + */ +export interface InitialPromotionConfig { + /** + * The initial transition to use when the elements in this group mount (and automatically promoted). + * Subsequent updates should provide a transition in the promote method. + */ + transition?: Transition + /** + * If the follow tree should preserve its opacity when the lead is promoted on mount + */ + shouldPreserveFollowOpacity?: (member: IProjectionNode) => boolean +} + export interface ProjectionNodeOptions { animate?: boolean layoutScroll?: boolean diff --git a/packages/framer-motion/src/projection/shared/stack.ts b/packages/motion-dom/src/projection/shared/stack.ts similarity index 81% rename from packages/framer-motion/src/projection/shared/stack.ts rename to packages/motion-dom/src/projection/shared/stack.ts index 455c3b4938..e66cd06c5e 100644 --- a/packages/framer-motion/src/projection/shared/stack.ts +++ b/packages/motion-dom/src/projection/shared/stack.ts @@ -80,20 +80,7 @@ export class NodeStack { const { crossfade } = node.options if (crossfade === false) { prevLead.hide() - } else { } - /** - * TODO: - * - Test border radius when previous node was deleted - * - boxShadow mixing - * - Shared between element A in scrolled container and element B (scroll stays the same or changes) - * - Shared between element A in transformed container and element B (transform stays the same or changes) - * - Shared between element A in scrolled page and element B (scroll stays the same or changes) - * --- - * - Crossfade opacity of root nodes - * - layoutId changes after animation - * - layoutId changes mid animation - */ } } diff --git a/packages/framer-motion/src/projection/styles/__tests__/scale-correction.test.ts b/packages/motion-dom/src/projection/styles/__tests__/scale-correction.test.ts similarity index 87% rename from packages/framer-motion/src/projection/styles/__tests__/scale-correction.test.ts rename to packages/motion-dom/src/projection/styles/__tests__/scale-correction.test.ts index 834ef40a0b..c363b1b383 100644 --- a/packages/framer-motion/src/projection/styles/__tests__/scale-correction.test.ts +++ b/packages/motion-dom/src/projection/styles/__tests__/scale-correction.test.ts @@ -1,8 +1,19 @@ -import { createTestNode } from "../../node/__tests__/TestProjectionNode" -import { IProjectionNode } from "../../node/types" import { correctBorderRadius, pixelsToPercent } from "../scale-border-radius" import { correctBoxShadow } from "../scale-box-shadow" +interface TestNode { + target?: { x: { min: number; max: number }; y: { min: number; max: number } } + projectionDelta?: { + x: { scale: number; translate: number; origin: number; originPoint: number } + y: { scale: number; translate: number; origin: number; originPoint: number } + } + treeScale?: { x: number; y: number } +} + +function createTestNode(): TestNode { + return {} +} + describe("pixelsToPercent", () => { test("Correctly converts pixels to percent", () => { expect(pixelsToPercent(10, { min: 300, max: 500 })).toEqual(5) @@ -14,7 +25,7 @@ describe("pixelsToPercent", () => { }) describe("correctBorderRadius", () => { - let node: IProjectionNode + let node: TestNode beforeEach(() => { node = createTestNode() }) @@ -45,7 +56,7 @@ describe("correctBorderRadius", () => { }) describe("correctBoxShadow", () => { - let node: IProjectionNode + let node: TestNode beforeEach(() => { node = createTestNode() node.projectionDelta = { diff --git a/packages/framer-motion/src/projection/styles/__tests__/transform.test.ts b/packages/motion-dom/src/projection/styles/__tests__/transform.test.ts similarity index 100% rename from packages/framer-motion/src/projection/styles/__tests__/transform.test.ts rename to packages/motion-dom/src/projection/styles/__tests__/transform.test.ts diff --git a/packages/framer-motion/src/projection/styles/scale-border-radius.ts b/packages/motion-dom/src/projection/styles/scale-border-radius.ts similarity index 90% rename from packages/framer-motion/src/projection/styles/scale-border-radius.ts rename to packages/motion-dom/src/projection/styles/scale-border-radius.ts index 8d7e1cbfd9..21fd541a1c 100644 --- a/packages/framer-motion/src/projection/styles/scale-border-radius.ts +++ b/packages/motion-dom/src/projection/styles/scale-border-radius.ts @@ -1,6 +1,6 @@ -import { px } from "motion-dom" -import { Axis } from "motion-utils" -import { ScaleCorrectorDefinition } from "./types" +import { px } from "../../value/types/numbers/units" +import type { Axis } from "motion-utils" +import type { ScaleCorrectorDefinition } from "./types" export function pixelsToPercent(pixels: number, axis: Axis): number { if (axis.max === axis.min) return 0 diff --git a/packages/framer-motion/src/projection/styles/scale-box-shadow.ts b/packages/motion-dom/src/projection/styles/scale-box-shadow.ts similarity index 89% rename from packages/framer-motion/src/projection/styles/scale-box-shadow.ts rename to packages/motion-dom/src/projection/styles/scale-box-shadow.ts index 20b627c5c9..c5f594e1d6 100644 --- a/packages/framer-motion/src/projection/styles/scale-box-shadow.ts +++ b/packages/motion-dom/src/projection/styles/scale-box-shadow.ts @@ -1,5 +1,6 @@ -import { complex, mixNumber } from "motion-dom" -import { ScaleCorrectorDefinition } from "./types" +import { complex } from "../../value/types/complex" +import { mixNumber } from "../../utils/mix/number" +import type { ScaleCorrectorDefinition } from "./types" export const correctBoxShadow: ScaleCorrectorDefinition = { correct: (latest: string, { treeScale, projectionDelta }) => { diff --git a/packages/framer-motion/src/projection/styles/scale-correction.ts b/packages/motion-dom/src/projection/styles/scale-correction.ts similarity index 87% rename from packages/framer-motion/src/projection/styles/scale-correction.ts rename to packages/motion-dom/src/projection/styles/scale-correction.ts index e03473694a..030445024b 100644 --- a/packages/framer-motion/src/projection/styles/scale-correction.ts +++ b/packages/motion-dom/src/projection/styles/scale-correction.ts @@ -1,7 +1,7 @@ -import { isCSSVariableName } from "motion-dom" +import { isCSSVariableName } from "../../animation/utils/is-css-variable" import { correctBorderRadius } from "./scale-border-radius" import { correctBoxShadow } from "./scale-box-shadow" -import { ScaleCorrectorMap } from "./types" +import type { ScaleCorrectorMap } from "./types" export const scaleCorrectors: ScaleCorrectorMap = { borderRadius: { diff --git a/packages/framer-motion/src/projection/styles/transform.ts b/packages/motion-dom/src/projection/styles/transform.ts similarity index 97% rename from packages/framer-motion/src/projection/styles/transform.ts rename to packages/motion-dom/src/projection/styles/transform.ts index b26bb700b4..dde9c04d3e 100644 --- a/packages/framer-motion/src/projection/styles/transform.ts +++ b/packages/motion-dom/src/projection/styles/transform.ts @@ -1,5 +1,5 @@ import type { Delta, Point } from "motion-utils" -import { ResolvedValues } from "../../render/types" +import type { ResolvedValues } from "../../node/types" export function buildProjectionTransform( delta: Delta, diff --git a/packages/motion-dom/src/projection/styles/types.ts b/packages/motion-dom/src/projection/styles/types.ts new file mode 100644 index 0000000000..d1d89ec0ac --- /dev/null +++ b/packages/motion-dom/src/projection/styles/types.ts @@ -0,0 +1,27 @@ +import type { AnyResolvedKeyframe } from "../../animation/types" +import type { Box, Delta, Point } from "motion-utils" + +/** + * Minimal interface for projection node properties used by scale correctors. + * This avoids circular dependencies with the full IProjectionNode interface. + */ +export interface ScaleCorrectionNode { + target?: Box + treeScale?: Point + projectionDelta?: Delta +} + +export type ScaleCorrector = ( + latest: AnyResolvedKeyframe, + node: ScaleCorrectionNode +) => AnyResolvedKeyframe + +export interface ScaleCorrectorDefinition { + correct: ScaleCorrector + applyTo?: string[] + isCSSVariable?: boolean +} + +export interface ScaleCorrectorMap { + [key: string]: ScaleCorrectorDefinition +} diff --git a/packages/framer-motion/src/projection/utils/__tests__/each-axis.test.ts b/packages/motion-dom/src/projection/utils/__tests__/each-axis.test.ts similarity index 100% rename from packages/framer-motion/src/projection/utils/__tests__/each-axis.test.ts rename to packages/motion-dom/src/projection/utils/__tests__/each-axis.test.ts diff --git a/packages/framer-motion/src/render/utils/compare-by-depth.ts b/packages/motion-dom/src/projection/utils/compare-by-depth.ts similarity index 69% rename from packages/framer-motion/src/render/utils/compare-by-depth.ts rename to packages/motion-dom/src/projection/utils/compare-by-depth.ts index d8eee636f7..4127726cbf 100644 --- a/packages/framer-motion/src/render/utils/compare-by-depth.ts +++ b/packages/motion-dom/src/projection/utils/compare-by-depth.ts @@ -1,4 +1,4 @@ -import type { VisualElement } from "../VisualElement" +import type { VisualElement } from "../../render/VisualElement" export interface WithDepth { depth: number diff --git a/packages/framer-motion/src/projection/utils/each-axis.ts b/packages/motion-dom/src/projection/utils/each-axis.ts similarity index 100% rename from packages/framer-motion/src/projection/utils/each-axis.ts rename to packages/motion-dom/src/projection/utils/each-axis.ts diff --git a/packages/framer-motion/src/render/utils/flat-tree.ts b/packages/motion-dom/src/projection/utils/flat-tree.ts similarity index 100% rename from packages/framer-motion/src/render/utils/flat-tree.ts rename to packages/motion-dom/src/projection/utils/flat-tree.ts diff --git a/packages/framer-motion/src/projection/utils/has-transform.ts b/packages/motion-dom/src/projection/utils/has-transform.ts similarity index 93% rename from packages/framer-motion/src/projection/utils/has-transform.ts rename to packages/motion-dom/src/projection/utils/has-transform.ts index 279e3c5b7b..e76432bd3e 100644 --- a/packages/framer-motion/src/projection/utils/has-transform.ts +++ b/packages/motion-dom/src/projection/utils/has-transform.ts @@ -1,4 +1,4 @@ -import { type AnyResolvedKeyframe } from "motion-dom" +import { type AnyResolvedKeyframe } from "../../animation/types" import { ResolvedValues } from "../../render/types" function isIdentityScale(scale: AnyResolvedKeyframe | undefined) { diff --git a/packages/framer-motion/src/projection/utils/measure.ts b/packages/motion-dom/src/projection/utils/measure.ts similarity index 89% rename from packages/framer-motion/src/projection/utils/measure.ts rename to packages/motion-dom/src/projection/utils/measure.ts index 03fc64a653..4503738595 100644 --- a/packages/framer-motion/src/projection/utils/measure.ts +++ b/packages/motion-dom/src/projection/utils/measure.ts @@ -4,7 +4,6 @@ import { transformBoxPoints, } from "../geometry/conversion" import { translateAxis } from "../geometry/delta-apply" -import { IProjectionNode } from "../node/types" export function measureViewportBox( instance: HTMLElement, @@ -17,7 +16,7 @@ export function measureViewportBox( export function measurePageBox( element: HTMLElement, - rootProjectionNode: IProjectionNode, + rootProjectionNode: any, transformPagePoint?: TransformPoint ) { const viewportBox = measureViewportBox(element, transformPagePoint) diff --git a/packages/framer-motion/src/motion/features/Feature.ts b/packages/motion-dom/src/render/Feature.ts similarity index 51% rename from packages/framer-motion/src/motion/features/Feature.ts rename to packages/motion-dom/src/render/Feature.ts index bf5b64222a..f5713f68d5 100644 --- a/packages/framer-motion/src/motion/features/Feature.ts +++ b/packages/motion-dom/src/render/Feature.ts @@ -1,5 +1,10 @@ -import type { VisualElement } from "../../render/VisualElement" +import type { VisualElement } from "./VisualElement" +/** + * Feature base class for extending VisualElement functionality. + * Features are plugins that can be mounted/unmounted to add behavior + * like gestures, animations, or layout tracking. + */ export abstract class Feature { isMounted = false diff --git a/packages/framer-motion/src/render/VisualElement.ts b/packages/motion-dom/src/render/VisualElement.ts similarity index 91% rename from packages/framer-motion/src/render/VisualElement.ts rename to packages/motion-dom/src/render/VisualElement.ts index 537a6a65d5..2106ec7741 100644 --- a/packages/framer-motion/src/render/VisualElement.ts +++ b/packages/motion-dom/src/render/VisualElement.ts @@ -1,46 +1,37 @@ -import { - cancelFrame, - complex, - findValueType, - frame, - getAnimatableNone, - isMotionValue, - KeyframeResolver, - microtask, - motionValue, - time, - transformProps, - type AnyResolvedKeyframe, - type MotionValue, -} from "motion-dom" -import type { Box } from "motion-utils" +import { Box } from "motion-utils" import { isNumericalString, isZeroValueString, SubscriptionManager, warnOnce, } from "motion-utils" -import { - MotionConfigContext, - ReducedMotionConfig, -} from "../context/MotionConfigContext" -import type { PresenceContextProps } from "../context/PresenceContext" -import { featureDefinitions } from "../motion/features/definitions" -import { Feature } from "../motion/features/Feature" -import { FeatureDefinitions } from "../motion/features/types" -import { MotionProps, MotionStyle } from "../motion/types" +import { cancelFrame, frame } from "../frameloop" +import { microtask } from "../frameloop/microtask" +import { time } from "../frameloop/sync-time" +import { motionValue, MotionValue } from "../value" +import { isMotionValue } from "../value/utils/is-motion-value" +import { KeyframeResolver } from "../animation/keyframes/KeyframesResolver" +import type { AnyResolvedKeyframe } from "../animation/types" +import { transformProps } from "./utils/keys-transform" +import { complex } from "../value/types/complex" +import { findValueType } from "../value/types/utils/find" +import { getAnimatableNone } from "../value/types/utils/animatable-none" +import type { MotionNodeOptions } from "../node/types" import { createBox } from "../projection/geometry/models" -import { IProjectionNode } from "../projection/node/types" -import { initPrefersReducedMotion } from "../utils/reduced-motion" import { + initPrefersReducedMotion, hasReducedMotionListener, prefersReducedMotion, -} from "../utils/reduced-motion/state" +} from "./utils/reduced-motion" import { visualElementStore } from "./store" import { ResolvedValues, VisualElementEventCallbacks, VisualElementOptions, + PresenceContextProps, + ReducedMotionConfig, + FeatureDefinitions, + MotionConfigContextProps, } from "./types" import { AnimationState } from "./utils/animation-state" import { @@ -49,6 +40,7 @@ import { } from "./utils/is-controlling-variants" import { updateMotionValuesFromProps } from "./utils/motion-values" import { resolveVariantFromProps } from "./utils/resolve-variants" +import { Feature } from "./Feature" const propEventHandlers = [ "AnimationStart", @@ -60,6 +52,33 @@ const propEventHandlers = [ "LayoutAnimationComplete", ] as const +/** + * Static feature definitions - can be injected by framework layer + */ +let featureDefinitions: Partial = {} + +/** + * Set feature definitions for all VisualElements. + * This should be called by the framework layer (e.g., framer-motion) during initialization. + */ +export function setFeatureDefinitions(definitions: Partial) { + featureDefinitions = definitions +} + +/** + * Get the current feature definitions + */ +export function getFeatureDefinitions(): Partial { + return featureDefinitions +} + +/** + * Motion style type - a subset of CSS properties that can contain motion values + */ +export type MotionStyle = { + [K: string]: AnyResolvedKeyframe | MotionValue | undefined +} + /** * A VisualElement is an imperative abstraction around UI elements such as * HTMLElement, SVGElement, Three.Object3D etc. @@ -87,7 +106,7 @@ export abstract class VisualElement< */ abstract measureInstanceViewportBox( instance: Instance, - props: MotionProps & Partial + props: MotionNodeOptions & Partial ): Box /** @@ -96,7 +115,7 @@ export abstract class VisualElement< * we can look in the style prop. */ abstract getBaseTargetFromProps( - props: MotionProps, + props: MotionNodeOptions, key: string ): AnyResolvedKeyframe | undefined | MotionValue @@ -128,7 +147,7 @@ export abstract class VisualElement< abstract build( renderState: RenderState, latestValues: ResolvedValues, - props: MotionProps + props: MotionNodeOptions ): void /** @@ -140,7 +159,7 @@ export abstract class VisualElement< instance: Instance, renderState: RenderState, styleProp?: MotionStyle, - projection?: IProjectionNode + projection?: any ): void /** @@ -163,8 +182,8 @@ export abstract class VisualElement< * intended to be one. */ scrapeMotionValuesFromProps( - _props: MotionProps, - _prevProps: MotionProps, + _props: MotionNodeOptions, + _prevProps: MotionNodeOptions, _visualElement: VisualElement ): { [key: string]: MotionValue | AnyResolvedKeyframe @@ -252,7 +271,7 @@ export abstract class VisualElement< /** * A reference to this VisualElement's projection node, used in layout animations. */ - projection?: IProjectionNode + projection?: any /** * A map of all motion values attached to this visual element. Motion @@ -277,8 +296,8 @@ export abstract class VisualElement< /** * A reference to the latest props provided to the VisualElement's host React component. */ - props: MotionProps - prevProps?: MotionProps + props: MotionNodeOptions + prevProps?: MotionNodeOptions presenceContext: PresenceContextProps | null prevPresenceContext?: PresenceContextProps | null @@ -500,8 +519,8 @@ export abstract class VisualElement< ) let removeSyncCheck: VoidFunction | void - if (window.MotionCheckAppearSync) { - removeSyncCheck = window.MotionCheckAppearSync(this, key, value) + if (typeof window !== "undefined" && (window as any).MotionCheckAppearSync) { + removeSyncCheck = (window as any).MotionCheckAppearSync(this, key, value) } this.valueSubscriptions.set(key, () => { @@ -577,7 +596,7 @@ export abstract class VisualElement< this.renderInstance( this.current, this.renderState, - this.props.style, + (this.props as any).style, this.projection ) } @@ -614,7 +633,7 @@ export abstract class VisualElement< * Update the provided props. Ensure any newly-added motion values are * added to our map, old ones removed, and listeners updated. */ - update(props: MotionProps, presenceContext: PresenceContextProps | null) { + update(props: MotionNodeOptions, presenceContext: PresenceContextProps | null) { if (props.transformTemplate || this.props.transformTemplate) { this.scheduleRender() } @@ -644,7 +663,7 @@ export abstract class VisualElement< this.prevMotionValues = updateMotionValuesFromProps( this, - this.scrapeMotionValuesFromProps(props, this.prevProps, this), + this.scrapeMotionValuesFromProps(props, this.prevProps || {}, this), this.prevMotionValues ) diff --git a/packages/framer-motion/src/render/dom/DOMVisualElement.ts b/packages/motion-dom/src/render/dom/DOMVisualElement.ts similarity index 64% rename from packages/framer-motion/src/render/dom/DOMVisualElement.ts rename to packages/motion-dom/src/render/dom/DOMVisualElement.ts index 627dd07fcb..6dbbfd675d 100644 --- a/packages/framer-motion/src/render/dom/DOMVisualElement.ts +++ b/packages/motion-dom/src/render/dom/DOMVisualElement.ts @@ -1,13 +1,11 @@ -import { - AnyResolvedKeyframe, - DOMKeyframesResolver, - isMotionValue, - MotionValue, -} from "motion-dom" -import { MotionProps, MotionStyle } from "../../motion/types" -import { DOMVisualElementOptions } from "../dom/types" -import { HTMLRenderState } from "../html/types" -import { VisualElement } from "../VisualElement" +import { isMotionValue } from "../../value/utils/is-motion-value" +import type { MotionValue } from "../../value" +import type { AnyResolvedKeyframe } from "../../animation/types" +import { DOMKeyframesResolver } from "../../animation/keyframes/DOMKeyframesResolver" +import type { MotionNodeOptions } from "../../node/types" +import type { DOMVisualElementOptions } from "./types" +import type { HTMLRenderState } from "../html/types" +import { VisualElement, MotionStyle } from "../VisualElement" export abstract class DOMVisualElement< Instance extends HTMLElement | SVGElement = HTMLElement, @@ -24,12 +22,11 @@ export abstract class DOMVisualElement< } getBaseTargetFromProps( - props: MotionProps, + props: MotionNodeOptions, key: string ): AnyResolvedKeyframe | MotionValue | undefined { - return props.style - ? (props.style[key as keyof MotionStyle] as string) - : undefined + const style = (props as MotionNodeOptions & { style?: MotionStyle }).style + return style ? (style[key] as string) : undefined } removeValueFromRenderState( @@ -49,7 +46,7 @@ export abstract class DOMVisualElement< delete this.childSubscription } - const { children } = this.props + const { children } = this.props as MotionNodeOptions & { children?: MotionValue | any } if (isMotionValue(children)) { this.childSubscription = children.on("change", (latest) => { if (this.current) { diff --git a/packages/motion-dom/src/render/dom/types.ts b/packages/motion-dom/src/render/dom/types.ts new file mode 100644 index 0000000000..f24fdeac82 --- /dev/null +++ b/packages/motion-dom/src/render/dom/types.ts @@ -0,0 +1,18 @@ +export interface DOMVisualElementOptions { + /** + * If `true`, this element will be included in the projection tree. + * + * Default: `true` + * + * @public + */ + allowProjection?: boolean + + /** + * Allow this element to be GPU-accelerated. We currently enable this by + * adding a `translateZ(0)`. + * + * @public + */ + enableHardwareAcceleration?: boolean +} diff --git a/packages/framer-motion/src/render/html/HTMLVisualElement.ts b/packages/motion-dom/src/render/html/HTMLVisualElement.ts similarity index 74% rename from packages/framer-motion/src/render/html/HTMLVisualElement.ts rename to packages/motion-dom/src/render/html/HTMLVisualElement.ts index 9a955783cc..f6e38ca293 100644 --- a/packages/framer-motion/src/render/html/HTMLVisualElement.ts +++ b/packages/motion-dom/src/render/html/HTMLVisualElement.ts @@ -1,18 +1,17 @@ +import type { Box } from "motion-utils" +import type { AnyResolvedKeyframe } from "../../animation/types" +import { isCSSVariableName } from "../../animation/utils/is-css-variable" +import type { MotionNodeOptions } from "../../node/types" +import { transformProps } from "../utils/keys-transform" import { - AnyResolvedKeyframe, defaultTransformValue, - isCSSVariableName, readTransformValue, - transformProps, -} from "motion-dom" -import type { Box } from "motion-utils" -import { MotionConfigContext } from "../../context/MotionConfigContext" -import { MotionProps } from "../../motion/types" +} from "../dom/parse-transform" import { measureViewportBox } from "../../projection/utils/measure" import { DOMVisualElement } from "../dom/DOMVisualElement" -import { DOMVisualElementOptions } from "../dom/types" -import type { ResolvedValues } from "../types" -import { VisualElement } from "../VisualElement" +import type { DOMVisualElementOptions } from "../dom/types" +import type { ResolvedValues, MotionConfigContextProps } from "../types" +import type { VisualElement } from "../VisualElement" import { HTMLRenderState } from "./types" import { buildHTMLStyles } from "./utils/build-styles" import { renderHTML } from "./utils/render" @@ -50,7 +49,7 @@ export class HTMLVisualElement extends DOMVisualElement< measureInstanceViewportBox( instance: HTMLElement, - { transformPagePoint }: MotionProps & Partial + { transformPagePoint }: MotionNodeOptions & Partial ): Box { return measureViewportBox(instance, transformPagePoint) } @@ -58,14 +57,14 @@ export class HTMLVisualElement extends DOMVisualElement< build( renderState: HTMLRenderState, latestValues: ResolvedValues, - props: MotionProps + props: MotionNodeOptions ) { buildHTMLStyles(renderState, latestValues, props.transformTemplate) } scrapeMotionValuesFromProps( - props: MotionProps, - prevProps: MotionProps, + props: MotionNodeOptions, + prevProps: MotionNodeOptions, visualElement: VisualElement ) { return scrapeMotionValuesFromProps(props, prevProps, visualElement) diff --git a/packages/motion-dom/src/render/html/types.ts b/packages/motion-dom/src/render/html/types.ts new file mode 100644 index 0000000000..1021fb8637 --- /dev/null +++ b/packages/motion-dom/src/render/html/types.ts @@ -0,0 +1,33 @@ +import { ResolvedValues } from "../types" + +export interface TransformOrigin { + originX?: number | string + originY?: number | string + originZ?: number | string +} + +export interface HTMLRenderState { + /** + * A mutable record of transforms we want to apply directly to the rendered Element + * every frame. We use a mutable data structure to reduce GC during animations. + */ + transform: ResolvedValues + + /** + * A mutable record of transform origins we want to apply directly to the rendered Element + * every frame. We use a mutable data structure to reduce GC during animations. + */ + transformOrigin: TransformOrigin + + /** + * A mutable record of styles we want to apply directly to the rendered Element + * every frame. We use a mutable data structure to reduce GC during animations. + */ + style: ResolvedValues + + /** + * A mutable record of CSS variables we want to apply directly to the rendered Element + * every frame. We use a mutable data structure to reduce GC during animations. + */ + vars: ResolvedValues +} diff --git a/packages/framer-motion/src/render/html/utils/build-styles.ts b/packages/motion-dom/src/render/html/utils/build-styles.ts similarity index 85% rename from packages/framer-motion/src/render/html/utils/build-styles.ts rename to packages/motion-dom/src/render/html/utils/build-styles.ts index 8cd2954717..5914df7876 100644 --- a/packages/framer-motion/src/render/html/utils/build-styles.ts +++ b/packages/motion-dom/src/render/html/utils/build-styles.ts @@ -1,18 +1,16 @@ -import { - getValueAsType, - isCSSVariableName, - numberValueTypes, - transformProps, -} from "motion-dom" -import { MotionProps } from "../../../motion/types" +import { getValueAsType } from "../../../value/types/utils/get-as-type" +import { numberValueTypes } from "../../../value/types/maps/number" +import { transformProps } from "../../utils/keys-transform" +import { isCSSVariableName } from "../../../animation/utils/is-css-variable" import { ResolvedValues } from "../../types" import { HTMLRenderState } from "../types" import { buildTransform } from "./build-transform" +import type { MotionNodeOptions } from "../../../node/types" export function buildHTMLStyles( state: HTMLRenderState, latestValues: ResolvedValues, - transformTemplate?: MotionProps["transformTemplate"] + transformTemplate?: MotionNodeOptions["transformTemplate"] ) { const { style, vars, transformOrigin } = state diff --git a/packages/framer-motion/src/render/html/utils/build-transform.ts b/packages/motion-dom/src/render/html/utils/build-transform.ts similarity index 87% rename from packages/framer-motion/src/render/html/utils/build-transform.ts rename to packages/motion-dom/src/render/html/utils/build-transform.ts index 0b19ad52fd..17e52bdc2c 100644 --- a/packages/framer-motion/src/render/html/utils/build-transform.ts +++ b/packages/motion-dom/src/render/html/utils/build-transform.ts @@ -1,11 +1,9 @@ -import { - getValueAsType, - numberValueTypes, - transformPropOrder, -} from "motion-dom" -import { MotionProps } from "../../../motion/types" +import { getValueAsType } from "../../../value/types/utils/get-as-type" +import { numberValueTypes } from "../../../value/types/maps/number" +import { transformPropOrder } from "../../utils/keys-transform" import { ResolvedValues } from "../../types" import { HTMLRenderState } from "../types" +import type { MotionNodeOptions } from "../../../node/types" const translateAlias = { x: "translateX", @@ -25,7 +23,7 @@ const numTransforms = transformPropOrder.length export function buildTransform( latestValues: ResolvedValues, transform: HTMLRenderState["transform"], - transformTemplate?: MotionProps["transformTemplate"] + transformTemplate?: MotionNodeOptions["transformTemplate"] ) { // The transform string we're going to build into. let transformString = "" diff --git a/packages/framer-motion/src/render/html/utils/render.ts b/packages/motion-dom/src/render/html/utils/render.ts similarity index 84% rename from packages/framer-motion/src/render/html/utils/render.ts rename to packages/motion-dom/src/render/html/utils/render.ts index 6587e2d920..dd20bda831 100644 --- a/packages/framer-motion/src/render/html/utils/render.ts +++ b/packages/motion-dom/src/render/html/utils/render.ts @@ -1,12 +1,11 @@ -import { MotionStyle } from "../../.." -import { IProjectionNode } from "../../../projection/node/types" +import type { MotionStyle } from "../../VisualElement" import { HTMLRenderState } from "../types" export function renderHTML( element: HTMLElement, { style, vars }: HTMLRenderState, styleProp?: MotionStyle, - projection?: IProjectionNode + projection?: any ) { const elementStyle = element.style diff --git a/packages/motion-dom/src/render/html/utils/scrape-motion-values.ts b/packages/motion-dom/src/render/html/utils/scrape-motion-values.ts new file mode 100644 index 0000000000..b125347706 --- /dev/null +++ b/packages/motion-dom/src/render/html/utils/scrape-motion-values.ts @@ -0,0 +1,29 @@ +import { isMotionValue } from "../../../value/utils/is-motion-value" +import type { MotionNodeOptions } from "../../../node/types" +import { isForcedMotionValue } from "../../utils/is-forced-motion-value" +import type { VisualElement } from "../../VisualElement" + +export function scrapeMotionValuesFromProps( + props: MotionNodeOptions, + prevProps: MotionNodeOptions, + visualElement?: VisualElement +) { + const style = (props as any).style + const prevStyle = (prevProps as any)?.style + const newValues: { [key: string]: any } = {} + + if (!style) return newValues + + for (const key in style) { + if ( + isMotionValue(style[key]) || + (prevStyle && isMotionValue(prevStyle[key])) || + isForcedMotionValue(key, props) || + visualElement?.getValue(key)?.liveStyle !== undefined + ) { + newValues[key] = style[key] + } + } + + return newValues +} diff --git a/packages/framer-motion/src/render/object/ObjectVisualElement.ts b/packages/motion-dom/src/render/object/ObjectVisualElement.ts similarity index 100% rename from packages/framer-motion/src/render/object/ObjectVisualElement.ts rename to packages/motion-dom/src/render/object/ObjectVisualElement.ts diff --git a/packages/framer-motion/src/render/store.ts b/packages/motion-dom/src/render/store.ts similarity index 100% rename from packages/framer-motion/src/render/store.ts rename to packages/motion-dom/src/render/store.ts diff --git a/packages/framer-motion/src/render/svg/SVGVisualElement.ts b/packages/motion-dom/src/render/svg/SVGVisualElement.ts similarity index 72% rename from packages/framer-motion/src/render/svg/SVGVisualElement.ts rename to packages/motion-dom/src/render/svg/SVGVisualElement.ts index c4af3c13d9..0c17249a3d 100644 --- a/packages/framer-motion/src/render/svg/SVGVisualElement.ts +++ b/packages/motion-dom/src/render/svg/SVGVisualElement.ts @@ -1,17 +1,14 @@ -import { - AnyResolvedKeyframe, - getDefaultValueType, - MotionValue, - transformProps, -} from "motion-dom" -import { MotionProps, MotionStyle } from "../../motion/types" +import type { AnyResolvedKeyframe } from "../../animation/types" +import type { MotionValue } from "../../value" +import type { MotionNodeOptions } from "../../node/types" +import { transformProps } from "../utils/keys-transform" +import { getDefaultValueType } from "../../value/types/maps/defaults" import { createBox } from "../../projection/geometry/models" -import { IProjectionNode } from "../../projection/node/types" import { DOMVisualElement } from "../dom/DOMVisualElement" -import { DOMVisualElementOptions } from "../dom/types" +import type { DOMVisualElementOptions } from "../dom/types" import { camelToDash } from "../dom/utils/camel-to-dash" -import { ResolvedValues } from "../types" -import { VisualElement } from "../VisualElement" +import type { ResolvedValues } from "../types" +import type { VisualElement, MotionStyle } from "../VisualElement" import { SVGRenderState } from "./types" import { buildSVGAttrs } from "./utils/build-attrs" import { camelCaseAttributes } from "./utils/camel-case-attrs" @@ -29,10 +26,10 @@ export class SVGVisualElement extends DOMVisualElement< isSVGTag = false getBaseTargetFromProps( - props: MotionProps, + props: MotionNodeOptions, key: string ): AnyResolvedKeyframe | MotionValue | undefined { - return props[key as keyof MotionProps] + return props[key as keyof MotionNodeOptions] } readValueFromInstance(instance: SVGElement, key: string) { @@ -47,8 +44,8 @@ export class SVGVisualElement extends DOMVisualElement< measureInstanceViewportBox = createBox scrapeMotionValuesFromProps( - props: MotionProps, - prevProps: MotionProps, + props: MotionNodeOptions, + prevProps: MotionNodeOptions, visualElement: VisualElement ) { return scrapeMotionValuesFromProps(props, prevProps, visualElement) @@ -57,14 +54,14 @@ export class SVGVisualElement extends DOMVisualElement< build( renderState: SVGRenderState, latestValues: ResolvedValues, - props: MotionProps + props: MotionNodeOptions ) { buildSVGAttrs( renderState, latestValues, this.isSVGTag, props.transformTemplate, - props.style + (props as any).style ) } @@ -72,7 +69,7 @@ export class SVGVisualElement extends DOMVisualElement< instance: SVGElement, renderState: SVGRenderState, styleProp?: MotionStyle | undefined, - projection?: IProjectionNode | undefined + projection?: any ): void { renderSVG(instance, renderState, styleProp, projection) } diff --git a/packages/motion-dom/src/render/svg/types.ts b/packages/motion-dom/src/render/svg/types.ts index a3065d0864..5cdb90a757 100644 --- a/packages/motion-dom/src/render/svg/types.ts +++ b/packages/motion-dom/src/render/svg/types.ts @@ -1,4 +1,14 @@ import { AnyResolvedKeyframe } from "../../animation/types" +import { ResolvedValues } from "../types" +import { HTMLRenderState } from "../html/types" + +export interface SVGRenderState extends HTMLRenderState { + /** + * A mutable record of attributes we want to apply directly to the rendered Element + * every frame. We use a mutable data structure to reduce GC during animations. + */ + attrs: ResolvedValues +} export interface SVGAttributes { accentHeight?: AnyResolvedKeyframe | undefined diff --git a/packages/framer-motion/src/render/svg/utils/__tests__/scrape-motion-values.test.ts b/packages/motion-dom/src/render/svg/utils/__tests__/scrape-motion-values.test.ts similarity index 94% rename from packages/framer-motion/src/render/svg/utils/__tests__/scrape-motion-values.test.ts rename to packages/motion-dom/src/render/svg/utils/__tests__/scrape-motion-values.test.ts index 37af044158..3f18bcd12b 100644 --- a/packages/framer-motion/src/render/svg/utils/__tests__/scrape-motion-values.test.ts +++ b/packages/motion-dom/src/render/svg/utils/__tests__/scrape-motion-values.test.ts @@ -1,4 +1,4 @@ -import { motionValue } from "motion-dom" +import { motionValue } from "../../../../value" import { scrapeMotionValuesFromProps } from "../scrape-motion-values" describe("SVG scrapeMotionValuesFromProps", () => { diff --git a/packages/framer-motion/src/render/svg/utils/build-attrs.ts b/packages/motion-dom/src/render/svg/utils/build-attrs.ts similarity index 94% rename from packages/framer-motion/src/render/svg/utils/build-attrs.ts rename to packages/motion-dom/src/render/svg/utils/build-attrs.ts index 0696171640..95e4a48aab 100644 --- a/packages/framer-motion/src/render/svg/utils/build-attrs.ts +++ b/packages/motion-dom/src/render/svg/utils/build-attrs.ts @@ -1,4 +1,4 @@ -import { MotionProps } from "../../../motion/types" +import type { MotionNodeOptions } from "../../../node/types" import { buildHTMLStyles } from "../../html/utils/build-styles" import { ResolvedValues } from "../../types" import { SVGRenderState } from "../types" @@ -30,8 +30,8 @@ export function buildSVGAttrs( ...latest }: ResolvedValues, isSVGTag: boolean, - transformTemplate?: MotionProps["transformTemplate"], - styleProp?: MotionProps["style"] + transformTemplate?: MotionNodeOptions["transformTemplate"], + styleProp?: Record ) { buildHTMLStyles(state, latest, transformTemplate) diff --git a/packages/framer-motion/src/render/svg/utils/camel-case-attrs.ts b/packages/motion-dom/src/render/svg/utils/camel-case-attrs.ts similarity index 100% rename from packages/framer-motion/src/render/svg/utils/camel-case-attrs.ts rename to packages/motion-dom/src/render/svg/utils/camel-case-attrs.ts diff --git a/packages/framer-motion/src/render/svg/utils/is-svg-tag.ts b/packages/motion-dom/src/render/svg/utils/is-svg-tag.ts similarity index 100% rename from packages/framer-motion/src/render/svg/utils/is-svg-tag.ts rename to packages/motion-dom/src/render/svg/utils/is-svg-tag.ts diff --git a/packages/framer-motion/src/render/svg/utils/path.ts b/packages/motion-dom/src/render/svg/utils/path.ts similarity index 95% rename from packages/framer-motion/src/render/svg/utils/path.ts rename to packages/motion-dom/src/render/svg/utils/path.ts index b8db94976a..aa76dcfcb6 100644 --- a/packages/framer-motion/src/render/svg/utils/path.ts +++ b/packages/motion-dom/src/render/svg/utils/path.ts @@ -1,4 +1,4 @@ -import { px } from "motion-dom" +import { px } from "../../../value/types/numbers/units" import { ResolvedValues } from "../../types" const dashKeys = { diff --git a/packages/framer-motion/src/render/svg/utils/render.ts b/packages/motion-dom/src/render/svg/utils/render.ts similarity index 80% rename from packages/framer-motion/src/render/svg/utils/render.ts rename to packages/motion-dom/src/render/svg/utils/render.ts index b643c874e3..5741dc03aa 100644 --- a/packages/framer-motion/src/render/svg/utils/render.ts +++ b/packages/motion-dom/src/render/svg/utils/render.ts @@ -1,5 +1,4 @@ -import { MotionStyle } from "../../../motion/types" -import { IProjectionNode } from "../../../projection/node/types" +import type { MotionStyle } from "../../VisualElement" import { camelToDash } from "../../dom/utils/camel-to-dash" import { renderHTML } from "../../html/utils/render" import { SVGRenderState } from "../types" @@ -9,7 +8,7 @@ export function renderSVG( element: SVGElement, renderState: SVGRenderState, _styleProp?: MotionStyle, - projection?: IProjectionNode + projection?: any ) { renderHTML(element as any, renderState, undefined, projection) diff --git a/packages/framer-motion/src/render/svg/utils/scrape-motion-values.ts b/packages/motion-dom/src/render/svg/utils/scrape-motion-values.ts similarity index 76% rename from packages/framer-motion/src/render/svg/utils/scrape-motion-values.ts rename to packages/motion-dom/src/render/svg/utils/scrape-motion-values.ts index c0760d0094..c902de0873 100644 --- a/packages/framer-motion/src/render/svg/utils/scrape-motion-values.ts +++ b/packages/motion-dom/src/render/svg/utils/scrape-motion-values.ts @@ -1,11 +1,12 @@ -import { isMotionValue, transformPropOrder } from "motion-dom" -import { MotionProps } from "../../../motion/types" +import { isMotionValue } from "../../../value/utils/is-motion-value" +import type { MotionNodeOptions } from "../../../node/types" +import { transformPropOrder } from "../../utils/keys-transform" import { scrapeMotionValuesFromProps as scrapeHTMLMotionValuesFromProps } from "../../html/utils/scrape-motion-values" import type { VisualElement } from "../../VisualElement" export function scrapeMotionValuesFromProps( - props: MotionProps, - prevProps: MotionProps, + props: MotionNodeOptions, + prevProps: MotionNodeOptions, visualElement?: VisualElement ) { const newValues = scrapeHTMLMotionValuesFromProps( diff --git a/packages/motion-dom/src/render/types.ts b/packages/motion-dom/src/render/types.ts new file mode 100644 index 0000000000..23e091391f --- /dev/null +++ b/packages/motion-dom/src/render/types.ts @@ -0,0 +1,160 @@ +import type { + AnimationDefinition, + MotionNodeOptions, + ResolvedValues, + VariantLabels, +} from "../node/types" +import type { AnyResolvedKeyframe, Transition } from "../animation/types" +import type { MotionValue } from "../value" +import type { Axis, Box, TransformPoint } from "motion-utils" + +// Re-export types for convenience +export type { ResolvedValues } + +/** + * @public + */ +export interface PresenceContextProps { + id: string + isPresent: boolean + register: (id: string | number) => () => void + onExitComplete?: (id: string | number) => void + initial?: false | VariantLabels + custom?: any +} + +/** + * @public + */ +export type ReducedMotionConfig = "always" | "never" | "user" + +/** + * @public + */ +export interface MotionConfigContextProps { + /** + * Internal, exported only for usage in Framer + */ + transformPagePoint: TransformPoint + + /** + * Internal. Determines whether this is a static context ie the Framer canvas. If so, + * it'll disable all dynamic functionality. + */ + isStatic: boolean + + /** + * Defines a new default transition for the entire tree. + * + * @public + */ + transition?: Transition + + /** + * If true, will respect the device prefersReducedMotion setting by switching + * transform animations off. + * + * @public + */ + reducedMotion?: ReducedMotionConfig + + /** + * A custom `nonce` attribute used when wanting to enforce a Content Security Policy (CSP). + * For more details see: + * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src#unsafe_inline_styles + * + * @public + */ + nonce?: string +} + +export interface VisualState<_Instance, RenderState> { + latestValues: ResolvedValues + renderState: RenderState +} + +export interface VisualElementOptions { + visualState: VisualState + parent?: any // VisualElement - circular reference handled at runtime + variantParent?: any + presenceContext: PresenceContextProps | null + props: MotionNodeOptions + blockInitialAnimation?: boolean + reducedMotionConfig?: ReducedMotionConfig + /** + * Explicit override for SVG detection. When true, uses SVG rendering; + * when false, uses HTML rendering. If undefined, auto-detects. + */ + isSVG?: boolean +} + +export interface VisualElementEventCallbacks { + BeforeLayoutMeasure: () => void + LayoutMeasure: (layout: Box, prevLayout?: Box) => void + LayoutUpdate: (layout: Axis, prevLayout: Axis) => void + Update: (latest: ResolvedValues) => void + AnimationStart: (definition: AnimationDefinition) => void + AnimationComplete: (definition: AnimationDefinition) => void + LayoutAnimationStart: () => void + LayoutAnimationComplete: () => void + SetAxisTarget: () => void + Unmount: () => void +} + +export interface LayoutLifecycles { + onBeforeLayoutMeasure?(box: Box): void + onLayoutMeasure?(box: Box, prevBox: Box): void + /** + * @internal + */ + onLayoutAnimationStart?(): void + /** + * @internal + */ + onLayoutAnimationComplete?(): void +} + +export type ScrapeMotionValuesFromProps = ( + props: MotionNodeOptions, + prevProps: MotionNodeOptions, + visualElement?: any +) => { + [key: string]: MotionValue | AnyResolvedKeyframe +} + +export type UseRenderState = () => RenderState + +/** + * Animation type for variant state management + */ +export type AnimationType = + | "animate" + | "whileHover" + | "whileTap" + | "whileDrag" + | "whileFocus" + | "whileInView" + | "exit" + +export interface FeatureClass { + new (props: Props): any +} + +export interface FeatureDefinition { + isEnabled: (props: MotionNodeOptions) => boolean + Feature?: FeatureClass + ProjectionNode?: any + MeasureLayout?: any +} + +export type FeatureDefinitions = { + animation?: FeatureDefinition + exit?: FeatureDefinition + drag?: FeatureDefinition + tap?: FeatureDefinition + focus?: FeatureDefinition + hover?: FeatureDefinition + pan?: FeatureDefinition + inView?: FeatureDefinition + layout?: FeatureDefinition +} diff --git a/packages/framer-motion/src/render/utils/animation-state.ts b/packages/motion-dom/src/render/utils/animation-state.ts similarity index 93% rename from packages/framer-motion/src/render/utils/animation-state.ts rename to packages/motion-dom/src/render/utils/animation-state.ts index a6e1fce249..b1cc01bc74 100644 --- a/packages/framer-motion/src/render/utils/animation-state.ts +++ b/packages/motion-dom/src/render/utils/animation-state.ts @@ -1,18 +1,22 @@ -import type { AnimationDefinition, TargetAndTransition } from "motion-dom" -import { VisualElementAnimationOptions } from "../../animation/interfaces/types" +import type { + AnimationDefinition, + TargetAndTransition, + VariantLabels, +} from "../../node/types" +import type { AnimationType } from "../types" +import type { VisualElementAnimationOptions } from "../../animation/interfaces/types" import { animateVisualElement } from "../../animation/interfaces/visual-element" import { calcChildStagger } from "../../animation/utils/calc-child-stagger" -import { isAnimationControls } from "../../animation/utils/is-animation-controls" -import { isKeyframesTarget } from "../../animation/utils/is-keyframes-target" -import { VariantLabels } from "../../motion/types" -import { shallowCompare } from "../../utils/shallow-compare" -import type { VisualElement } from "../VisualElement" import { getVariantContext } from "./get-variant-context" +import { isAnimationControls } from "./is-animation-controls" +import { isKeyframesTarget } from "./is-keyframes-target" import { isVariantLabel } from "./is-variant-label" import { resolveVariant } from "./resolve-dynamic-variants" -import { AnimationType } from "./types" +import { shallowCompare } from "./shallow-compare" import { variantPriorityOrder } from "./variant-props" +export type { VisualElementAnimationOptions } + export interface AnimationState { animateChanges: (type?: AnimationType) => Promise setActive: ( @@ -35,19 +39,24 @@ export type AnimationList = string[] | TargetAndTransition[] const reversePriorityOrder = [...variantPriorityOrder].reverse() const numAnimationTypes = variantPriorityOrder.length -function animateList(visualElement: VisualElement) { - return (animations: DefinitionAndOptions[]) => - Promise.all( +/** + * Type for the animate function that can be injected. + * This allows the animation implementation to be provided by the framework layer. + */ +export type AnimateFunction = (animations: DefinitionAndOptions[]) => Promise + +function createAnimateFunction(visualElement: any): AnimateFunction { + return (animations: DefinitionAndOptions[]) => { + return Promise.all( animations.map(({ animation, options }) => animateVisualElement(visualElement, animation, options) ) ) + } } -export function createAnimationState( - visualElement: VisualElement -): AnimationState { - let animate = animateList(visualElement) +export function createAnimationState(visualElement: any): AnimationState { + let animate = createAnimateFunction(visualElement) let state = createState() let isInitialRender = true @@ -81,7 +90,9 @@ export function createAnimationState( * This just allows us to inject mocked animation functions * @internal */ - function setAnimateFunction(makeAnimator: typeof animateList) { + function setAnimateFunction( + makeAnimator: (visualElement: any) => AnimateFunction + ) { animate = makeAnimator(visualElement) } @@ -116,7 +127,7 @@ export function createAnimationState( * copy it without iteration. Each time we hit an animation type we set its protected * keys - the keys its not allowed to animate - to the latest version of this object. */ - let encounteredKeys = {} + let encounteredKeys: { [key: string]: any } = {} /** * If a variant has been removed at a given index, and this component is controlling @@ -416,7 +427,7 @@ export function createAnimationState( if (state[type].isActive === isActive) return Promise.resolve() // Propagate active change to children - visualElement.variantChildren?.forEach((child) => + visualElement.variantChildren?.forEach((child: any) => child.animationState?.setActive(type, isActive) ) diff --git a/packages/framer-motion/src/render/utils/get-variant-context.ts b/packages/motion-dom/src/render/utils/get-variant-context.ts similarity index 81% rename from packages/framer-motion/src/render/utils/get-variant-context.ts rename to packages/motion-dom/src/render/utils/get-variant-context.ts index f2c9089f50..e8027001b6 100644 --- a/packages/framer-motion/src/render/utils/get-variant-context.ts +++ b/packages/motion-dom/src/render/utils/get-variant-context.ts @@ -1,4 +1,3 @@ -import { VisualElement } from "../VisualElement" import { isVariantLabel } from "./is-variant-label" import { variantProps } from "./variant-props" @@ -14,8 +13,12 @@ type VariantStateContext = { whileTap?: string | string[] } +/** + * Get variant context from a visual element's parent chain. + * Uses `any` type for visualElement to avoid circular dependencies. + */ export function getVariantContext( - visualElement?: VisualElement + visualElement?: any ): undefined | VariantStateContext { if (!visualElement) return undefined @@ -29,13 +32,13 @@ export function getVariantContext( return context } - const context = {} + const context: VariantStateContext = {} for (let i = 0; i < numVariantProps; i++) { const name = variantProps[i] as keyof typeof context const prop = visualElement.props[name] if (isVariantLabel(prop) || prop === false) { - context[name] = prop + ;(context as any)[name] = prop } } diff --git a/packages/framer-motion/src/animation/utils/is-animation-controls.ts b/packages/motion-dom/src/render/utils/is-animation-controls.ts similarity index 77% rename from packages/framer-motion/src/animation/utils/is-animation-controls.ts rename to packages/motion-dom/src/render/utils/is-animation-controls.ts index 5029b5a3bd..11dca87215 100644 --- a/packages/framer-motion/src/animation/utils/is-animation-controls.ts +++ b/packages/motion-dom/src/render/utils/is-animation-controls.ts @@ -1,4 +1,4 @@ -import { LegacyAnimationControls } from "motion-dom" +import type { LegacyAnimationControls } from "../../node/types" export function isAnimationControls(v?: unknown): v is LegacyAnimationControls { return ( diff --git a/packages/framer-motion/src/render/utils/is-controlling-variants.ts b/packages/motion-dom/src/render/utils/is-controlling-variants.ts similarity index 58% rename from packages/framer-motion/src/render/utils/is-controlling-variants.ts rename to packages/motion-dom/src/render/utils/is-controlling-variants.ts index 2fef33d16e..eac8d8e4bd 100644 --- a/packages/framer-motion/src/render/utils/is-controlling-variants.ts +++ b/packages/motion-dom/src/render/utils/is-controlling-variants.ts @@ -1,9 +1,9 @@ -import { isAnimationControls } from "../../animation/utils/is-animation-controls" -import { MotionProps } from "../../motion/types" +import type { MotionNodeOptions } from "../../node/types" +import { isAnimationControls } from "./is-animation-controls" import { isVariantLabel } from "./is-variant-label" import { variantProps } from "./variant-props" -export function isControllingVariants(props: MotionProps) { +export function isControllingVariants(props: MotionNodeOptions) { return ( isAnimationControls(props.animate) || variantProps.some((name) => @@ -12,6 +12,6 @@ export function isControllingVariants(props: MotionProps) { ) } -export function isVariantNode(props: MotionProps) { +export function isVariantNode(props: MotionNodeOptions) { return Boolean(isControllingVariants(props) || props.variants) } diff --git a/packages/motion-dom/src/render/utils/is-forced-motion-value.ts b/packages/motion-dom/src/render/utils/is-forced-motion-value.ts new file mode 100644 index 0000000000..ca9373fa7d --- /dev/null +++ b/packages/motion-dom/src/render/utils/is-forced-motion-value.ts @@ -0,0 +1,20 @@ +import { transformProps } from "./keys-transform" +import type { MotionNodeOptions } from "../../node/types" +import { + scaleCorrectors, + addScaleCorrector, +} from "../../projection/styles/scale-correction" + +export { scaleCorrectors, addScaleCorrector } + +export function isForcedMotionValue( + key: string, + { layout, layoutId }: MotionNodeOptions +) { + return ( + transformProps.has(key) || + key.startsWith("origin") || + ((layout || layoutId !== undefined) && + (!!scaleCorrectors[key] || key === "opacity")) + ) +} diff --git a/packages/motion-dom/src/render/utils/is-keyframes-target.ts b/packages/motion-dom/src/render/utils/is-keyframes-target.ts new file mode 100644 index 0000000000..074ad7d09b --- /dev/null +++ b/packages/motion-dom/src/render/utils/is-keyframes-target.ts @@ -0,0 +1,7 @@ +import type { UnresolvedValueKeyframe, ValueKeyframesDefinition } from "../../animation/types" + +export const isKeyframesTarget = ( + v: ValueKeyframesDefinition +): v is UnresolvedValueKeyframe[] => { + return Array.isArray(v) +} diff --git a/packages/framer-motion/src/render/utils/is-variant-label.ts b/packages/motion-dom/src/render/utils/is-variant-label.ts similarity index 100% rename from packages/framer-motion/src/render/utils/is-variant-label.ts rename to packages/motion-dom/src/render/utils/is-variant-label.ts diff --git a/packages/framer-motion/src/render/utils/motion-values.ts b/packages/motion-dom/src/render/utils/motion-values.ts similarity index 77% rename from packages/framer-motion/src/render/utils/motion-values.ts rename to packages/motion-dom/src/render/utils/motion-values.ts index 0f7803647e..6f6247e678 100644 --- a/packages/framer-motion/src/render/utils/motion-values.ts +++ b/packages/motion-dom/src/render/utils/motion-values.ts @@ -1,15 +1,20 @@ -import { isMotionValue, motionValue } from "motion-dom" -import { MotionStyle } from "../../motion/types" -import type { VisualElement } from "../VisualElement" +import { motionValue } from "../../value" +import { isMotionValue } from "../../value/utils/is-motion-value" +type MotionStyleLike = Record + +/** + * Updates motion values from props changes. + * Uses `any` type for element to avoid circular dependencies with VisualElement. + */ export function updateMotionValuesFromProps( - element: VisualElement, - next: MotionStyle, - prev: MotionStyle + element: any, + next: MotionStyleLike, + prev: MotionStyleLike ) { for (const key in next) { - const nextValue = next[key as keyof MotionStyle] - const prevValue = prev[key as keyof MotionStyle] + const nextValue = next[key] + const prevValue = prev[key] if (isMotionValue(nextValue)) { /** @@ -52,8 +57,7 @@ export function updateMotionValuesFromProps( // Handle removed values for (const key in prev) { - if (next[key as keyof MotionStyle] === undefined) - element.removeValue(key) + if (next[key] === undefined) element.removeValue(key) } return next diff --git a/packages/framer-motion/src/utils/reduced-motion/index.ts b/packages/motion-dom/src/render/utils/reduced-motion/index.ts similarity index 84% rename from packages/framer-motion/src/utils/reduced-motion/index.ts rename to packages/motion-dom/src/render/utils/reduced-motion/index.ts index b6cbe47bff..6ee2128cd4 100644 --- a/packages/framer-motion/src/utils/reduced-motion/index.ts +++ b/packages/motion-dom/src/render/utils/reduced-motion/index.ts @@ -1,6 +1,7 @@ -import { isBrowser } from "../is-browser" import { hasReducedMotionListener, prefersReducedMotion } from "./state" +const isBrowser = typeof window !== "undefined" + export function initPrefersReducedMotion() { hasReducedMotionListener.current = true if (!isBrowser) return @@ -18,3 +19,5 @@ export function initPrefersReducedMotion() { prefersReducedMotion.current = false } } + +export { prefersReducedMotion, hasReducedMotionListener } diff --git a/packages/framer-motion/src/utils/reduced-motion/state.ts b/packages/motion-dom/src/render/utils/reduced-motion/state.ts similarity index 100% rename from packages/framer-motion/src/utils/reduced-motion/state.ts rename to packages/motion-dom/src/render/utils/reduced-motion/state.ts diff --git a/packages/framer-motion/src/render/utils/resolve-dynamic-variants.ts b/packages/motion-dom/src/render/utils/resolve-dynamic-variants.ts similarity index 75% rename from packages/framer-motion/src/render/utils/resolve-dynamic-variants.ts rename to packages/motion-dom/src/render/utils/resolve-dynamic-variants.ts index 7e15df1df2..e7a111418e 100644 --- a/packages/framer-motion/src/render/utils/resolve-dynamic-variants.ts +++ b/packages/motion-dom/src/render/utils/resolve-dynamic-variants.ts @@ -2,25 +2,25 @@ import type { AnimationDefinition, TargetAndTransition, TargetResolver, -} from "motion-dom" -import type { VisualElement } from "../VisualElement" +} from "../../node/types" import { resolveVariantFromProps } from "./resolve-variants" /** - * Resovles a variant if it's a variant resolver + * Resolves a variant if it's a variant resolver. + * Uses `any` type for visualElement to avoid circular dependencies. */ export function resolveVariant( - visualElement: VisualElement, + visualElement: any, definition?: TargetAndTransition | TargetResolver, custom?: any ): TargetAndTransition export function resolveVariant( - visualElement: VisualElement, + visualElement: any, definition?: AnimationDefinition, custom?: any ): TargetAndTransition | undefined export function resolveVariant( - visualElement: VisualElement, + visualElement: any, definition?: AnimationDefinition, custom?: any ) { diff --git a/packages/framer-motion/src/render/utils/resolve-variants.ts b/packages/motion-dom/src/render/utils/resolve-variants.ts similarity index 79% rename from packages/framer-motion/src/render/utils/resolve-variants.ts rename to packages/motion-dom/src/render/utils/resolve-variants.ts index 71e45cc2b6..08241498d7 100644 --- a/packages/framer-motion/src/render/utils/resolve-variants.ts +++ b/packages/motion-dom/src/render/utils/resolve-variants.ts @@ -1,18 +1,15 @@ import type { AnimationDefinition, + MotionNodeOptions, TargetAndTransition, TargetResolver, -} from "motion-dom" -import type { MotionProps } from "../../motion/types" -import { VisualElement } from "../VisualElement" +} from "../../node/types" import type { ResolvedValues } from "../types" -function getValueState( - visualElement?: VisualElement -): [ResolvedValues, ResolvedValues] { +function getValueState(visualElement?: any): [ResolvedValues, ResolvedValues] { const state: [ResolvedValues, ResolvedValues] = [{}, {}] - visualElement?.values.forEach((value, key) => { + visualElement?.values.forEach((value: any, key: string) => { state[0][key] = value.get() state[1][key] = value.getVelocity() }) @@ -21,22 +18,22 @@ function getValueState( } export function resolveVariantFromProps( - props: MotionProps, + props: MotionNodeOptions, definition: TargetAndTransition | TargetResolver, custom?: any, - visualElement?: VisualElement + visualElement?: any ): TargetAndTransition export function resolveVariantFromProps( - props: MotionProps, + props: MotionNodeOptions, definition?: AnimationDefinition, custom?: any, - visualElement?: VisualElement + visualElement?: any ): undefined | TargetAndTransition export function resolveVariantFromProps( - props: MotionProps, + props: MotionNodeOptions, definition?: AnimationDefinition, custom?: any, - visualElement?: VisualElement + visualElement?: any ) { /** * If the variant definition is a function, resolve. diff --git a/packages/framer-motion/src/render/utils/setters.ts b/packages/motion-dom/src/render/utils/setters.ts similarity index 87% rename from packages/framer-motion/src/render/utils/setters.ts rename to packages/motion-dom/src/render/utils/setters.ts index 196964355d..cf6d06c0f7 100644 --- a/packages/framer-motion/src/render/utils/setters.ts +++ b/packages/motion-dom/src/render/utils/setters.ts @@ -1,13 +1,13 @@ +import { motionValue } from "../../value" +import { resolveVariant } from "./resolve-dynamic-variants" +import { isKeyframesTarget } from "./is-keyframes-target" +import type { AnimationDefinition } from "../../node/types" import type { - AnimationDefinition, AnyResolvedKeyframe, UnresolvedValueKeyframe, ValueKeyframesDefinition, -} from "motion-dom" -import { motionValue } from "motion-dom" -import { isKeyframesTarget } from "../../animation/utils/is-keyframes-target" +} from "../../animation/types" import type { VisualElement } from "../VisualElement" -import { resolveVariant } from "./resolve-dynamic-variants" /** * Set VisualElement's MotionValue, creating a new MotionValue for it if diff --git a/packages/motion-dom/src/render/utils/shallow-compare.ts b/packages/motion-dom/src/render/utils/shallow-compare.ts new file mode 100644 index 0000000000..96a87ecade --- /dev/null +++ b/packages/motion-dom/src/render/utils/shallow-compare.ts @@ -0,0 +1,13 @@ +export function shallowCompare(next: any[], prev: any[] | null) { + if (!Array.isArray(prev)) return false + + const prevLength = prev.length + + if (prevLength !== next.length) return false + + for (let i = 0; i < prevLength; i++) { + if (prev[i] !== next[i]) return false + } + + return true +} diff --git a/packages/framer-motion/src/render/utils/variant-props.ts b/packages/motion-dom/src/render/utils/variant-props.ts similarity index 83% rename from packages/framer-motion/src/render/utils/variant-props.ts rename to packages/motion-dom/src/render/utils/variant-props.ts index eedb5dba6d..5dd6de0c7d 100644 --- a/packages/framer-motion/src/render/utils/variant-props.ts +++ b/packages/motion-dom/src/render/utils/variant-props.ts @@ -1,4 +1,4 @@ -import { AnimationType } from "./types" +import type { AnimationType } from "../types" export const variantPriorityOrder: AnimationType[] = [ "animate", diff --git a/packages/framer-motion/src/utils/delay.ts b/packages/motion-dom/src/utils/delay.ts similarity index 82% rename from packages/framer-motion/src/utils/delay.ts rename to packages/motion-dom/src/utils/delay.ts index f3753f3b20..51ab5dedf3 100644 --- a/packages/framer-motion/src/utils/delay.ts +++ b/packages/motion-dom/src/utils/delay.ts @@ -1,4 +1,6 @@ -import { cancelFrame, frame, FrameData, time } from "motion-dom" +import { cancelFrame, frame } from "../frameloop" +import { time } from "../frameloop/sync-time" +import type { FrameData } from "../frameloop/types" import { secondsToMilliseconds } from "motion-utils" export type DelayedFunction = (overshoot: number) => void diff --git a/packages/framer-motion/src/value/utils/resolve-motion-value.ts b/packages/motion-dom/src/value/utils/resolve-motion-value.ts similarity index 63% rename from packages/framer-motion/src/value/utils/resolve-motion-value.ts rename to packages/motion-dom/src/value/utils/resolve-motion-value.ts index b3bd99d3f0..81d00689b6 100644 --- a/packages/framer-motion/src/value/utils/resolve-motion-value.ts +++ b/packages/motion-dom/src/value/utils/resolve-motion-value.ts @@ -1,9 +1,9 @@ -import { AnyResolvedKeyframe, isMotionValue, MotionValue } from "motion-dom" +import type { AnyResolvedKeyframe } from "../../animation/types" +import { isMotionValue } from "./is-motion-value" +import type { MotionValue } from "../index" /** * If the provided value is a MotionValue, this returns the actual value, otherwise just the value itself - * - * TODO: Remove and move to library */ export function resolveMotionValue( value?: AnyResolvedKeyframe | MotionValue diff --git a/packages/framer-motion/src/value/use-will-change/add-will-change.ts b/packages/motion-dom/src/value/will-change/add-will-change.ts similarity index 100% rename from packages/framer-motion/src/value/use-will-change/add-will-change.ts rename to packages/motion-dom/src/value/will-change/add-will-change.ts diff --git a/packages/framer-motion/src/value/use-will-change/is.ts b/packages/motion-dom/src/value/will-change/is.ts similarity index 59% rename from packages/framer-motion/src/value/use-will-change/is.ts rename to packages/motion-dom/src/value/will-change/is.ts index 98eac48e40..e48f2d2bd2 100644 --- a/packages/framer-motion/src/value/use-will-change/is.ts +++ b/packages/motion-dom/src/value/will-change/is.ts @@ -1,5 +1,5 @@ -import { isMotionValue } from "motion-dom" -import { WillChange } from "./types" +import { isMotionValue } from "../utils/is-motion-value" +import type { WillChange } from "./types" export function isWillChangeMotionValue(value: any): value is WillChange { return Boolean(isMotionValue(value) && (value as WillChange).add) diff --git a/packages/framer-motion/src/value/use-will-change/types.ts b/packages/motion-dom/src/value/will-change/types.ts similarity index 65% rename from packages/framer-motion/src/value/use-will-change/types.ts rename to packages/motion-dom/src/value/will-change/types.ts index b58b9b7987..cafa43ec61 100644 --- a/packages/framer-motion/src/value/use-will-change/types.ts +++ b/packages/motion-dom/src/value/will-change/types.ts @@ -1,4 +1,4 @@ -import type { MotionValue } from "motion-dom" +import type { MotionValue } from "../index" export interface WillChange extends MotionValue { add(name: string): void diff --git a/packages/motion/package.json b/packages/motion/package.json index 45118f678b..90e529a5dc 100644 --- a/packages/motion/package.json +++ b/packages/motion/package.json @@ -1,6 +1,6 @@ { "name": "motion", - "version": "12.26.1", + "version": "12.26.2", "description": "An animation library for JavaScript and React.", "main": "dist/cjs/index.js", "module": "dist/es/index.mjs", @@ -76,7 +76,7 @@ "postpublish": "git push --tags" }, "dependencies": { - "framer-motion": "^12.26.1", + "framer-motion": "^12.26.2", "tslib": "^2.4.0" }, "peerDependencies": { diff --git a/yarn.lock b/yarn.lock index 84f9dccd2f..090daab0cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7420,14 +7420,14 @@ __metadata: languageName: node linkType: hard -"framer-motion@^12.26.1, framer-motion@workspace:packages/framer-motion": +"framer-motion@^12.26.2, framer-motion@workspace:packages/framer-motion": version: 0.0.0-use.local resolution: "framer-motion@workspace:packages/framer-motion" dependencies: "@radix-ui/react-dialog": ^1.1.15 "@thednp/dommatrix": ^2.0.11 "@types/three": 0.137.0 - motion-dom: ^12.24.11 + motion-dom: ^12.26.2 motion-utils: ^12.24.10 three: 0.137.0 tslib: ^2.4.0 @@ -8192,9 +8192,9 @@ __metadata: version: 0.0.0-use.local resolution: "html-env@workspace:dev/html" dependencies: - framer-motion: ^12.26.1 - motion: ^12.26.1 - motion-dom: ^12.24.11 + framer-motion: ^12.26.2 + motion: ^12.26.2 + motion-dom: ^12.26.2 react: ^18.3.1 react-dom: ^18.3.1 vite: ^5.2.0 @@ -10936,7 +10936,7 @@ __metadata: languageName: node linkType: hard -"motion-dom@^12.24.11, motion-dom@workspace:packages/motion-dom": +"motion-dom@^12.26.2, motion-dom@workspace:packages/motion-dom": version: 0.0.0-use.local resolution: "motion-dom@workspace:packages/motion-dom" dependencies: @@ -11013,11 +11013,11 @@ __metadata: languageName: unknown linkType: soft -"motion@^12.26.1, motion@workspace:packages/motion": +"motion@^12.26.2, motion@workspace:packages/motion": version: 0.0.0-use.local resolution: "motion@workspace:packages/motion" dependencies: - framer-motion: ^12.26.1 + framer-motion: ^12.26.2 tslib: ^2.4.0 peerDependencies: "@emotion/is-prop-valid": "*" @@ -11134,7 +11134,7 @@ __metadata: version: 0.0.0-use.local resolution: "next-env@workspace:dev/next" dependencies: - motion: ^12.26.1 + motion: ^12.26.2 next: 15.4.10 react: 19.0.0 react-dom: 19.0.0 @@ -12599,7 +12599,7 @@ __metadata: "@typescript-eslint/parser": ^7.2.0 "@vitejs/plugin-react-swc": ^3.5.0 eslint-plugin-react-refresh: ^0.4.6 - motion: ^12.26.1 + motion: ^12.26.2 react: ^19.0.0 react-dom: ^19.0.0 vite: ^5.2.0 @@ -12683,7 +12683,7 @@ __metadata: "@typescript-eslint/parser": ^7.2.0 "@vitejs/plugin-react-swc": ^3.5.0 eslint-plugin-react-refresh: ^0.4.6 - framer-motion: ^12.26.1 + framer-motion: ^12.26.2 react: ^18.3.1 react-dom: ^18.3.1 vite: ^5.2.0