From c46dc478261597f720201bd6bd5dc3512a1316dc Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Mon, 9 Dec 2024 12:28:51 +0100 Subject: [PATCH] React 19-compatible types (#2930) * Incorporating makefile changes * Fixing ref types and replacing ReactHTML reference in Reorder * Updating types * Changing HTML types * Fixing tests --- CHANGELOG.md | 6 + Makefile | 4 +- dev/html/public/optimized-appear/portal.html | 2 +- ...constraints-ref-small-container-layout.tsx | 2 +- .../Drag-constraints-ref-small-container.tsx | 2 +- .../src/examples/Drag-constraints-ref.tsx | 2 +- dev/react/src/main.tsx | 16 +- .../src/tests/scroll-callback-element-x.tsx | 2 +- .../src/tests/scroll-callback-element.tsx | 3 +- .../src/components/MotionCanvas.tsx | 2 +- .../src/components/use-layout-camera.ts | 2 +- .../src/components/Reorder/Group.tsx | 11 +- .../src/components/Reorder/Item.tsx | 5 +- .../framer-motion/src/events/use-dom-event.ts | 2 +- .../framer-motion/src/gestures/drag/types.ts | 2 +- .../src/motion/features/viewport/types.ts | 2 +- .../src/motion/utils/use-visual-element.ts | 4 +- .../src/render/html/supported-elements.ts | 240 +++++++++--------- .../framer-motion/src/render/html/types.ts | 43 +--- .../framer-motion/src/utils/use-in-view.ts | 4 +- .../src/utils/use-instant-transition.ts | 4 +- .../src/value/scroll/use-element-scroll.ts | 2 +- .../framer-motion/src/value/use-scroll.ts | 6 +- 23 files changed, 172 insertions(+), 196 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f99868ce6..1c33d6bd1c 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. +## [11.13.3] 2024-12-09 + +### Fixed + +- Attempting to update types to be compatible with both React 18 and 19. + ## [11.13.2] 2024-12-04 ### Fixed diff --git a/Makefile b/Makefile index ff8f6bb18c..2ec84f5355 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ WORKTREE_NODE_MODULES := $(BASE_DIR)/node_modules/.yarn-state.yml WORKSPACE_NODE_MODULES := node_modules # Update node modules if package.json is newer than node_modules or yarn lockfile -$(WORKTREE_NODE_MODULES) $(WORKSPACE_NODE_MODULES): $(BASE_DIR)/yarn.lock package.json packages/framer-motion/package.json packages/framer-motion-3d/package.json +$(WORKTREE_NODE_MODULES) $(WORKSPACE_NODE_MODULES): $(BASE_DIR)/yarn.lock package.json packages/framer-motion/package.json yarn install touch $@ @@ -38,8 +38,6 @@ $(WORKTREE_NODE_MODULES) $(WORKSPACE_NODE_MODULES): $(BASE_DIR)/yarn.lock packag # Makefile bootstrap:: $(WORKTREE_NODE_MODULES) -SOURCE_FILES := $(shell find packages/framer-motion/src packages/framer-motion-3d/src -type f) - ###### # The location to gather test reports diff --git a/dev/html/public/optimized-appear/portal.html b/dev/html/public/optimized-appear/portal.html index d4a2bee96c..91257717b1 100644 --- a/dev/html/public/optimized-appear/portal.html +++ b/dev/html/public/optimized-appear/portal.html @@ -92,7 +92,7 @@ (animation) => { // Hydrate root mid-way through animation setTimeout(() => { - ReactDOM.createRoot( + ReactDOMClient.createRoot( document.getElementById("portal") ).render( React.createElement(motion.div, { diff --git a/dev/react/src/examples/Drag-constraints-ref-small-container-layout.tsx b/dev/react/src/examples/Drag-constraints-ref-small-container-layout.tsx index 7d5cd503f5..16c8c02ea0 100644 --- a/dev/react/src/examples/Drag-constraints-ref-small-container-layout.tsx +++ b/dev/react/src/examples/Drag-constraints-ref-small-container-layout.tsx @@ -16,7 +16,7 @@ const child = { } export const App = () => { - const ref = useRef() + const ref = useRef(null) return (
{ - const ref = useRef() + const ref = useRef(null) return (
{ } export const App = () => { - const ref = useRef() + const ref = useRef(null) const [count, setCount] = useState(0) return ( <> diff --git a/dev/react/src/main.tsx b/dev/react/src/main.tsx index 3d7150da80..df573810d3 100644 --- a/dev/react/src/main.tsx +++ b/dev/react/src/main.tsx @@ -1,10 +1,10 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App.tsx' -import './index.css' +import React from "react" +import ReactDOMClient from "react-dom/client" +import App from "./App.tsx" +import "./index.css" -ReactDOM.createRoot(document.getElementById('root')!).render( - - - , +ReactDOMClient.createRoot(document.getElementById("root")!).render( + + + ) diff --git a/dev/react/src/tests/scroll-callback-element-x.tsx b/dev/react/src/tests/scroll-callback-element-x.tsx index c5bf15f680..0a33802712 100644 --- a/dev/react/src/tests/scroll-callback-element-x.tsx +++ b/dev/react/src/tests/scroll-callback-element-x.tsx @@ -6,7 +6,7 @@ const width = 400 export const App = () => { const [progress, setProgress] = useState(0) - const ref = useRef(null) + const ref = useRef(null) useEffect(() => { if (!ref.current) return diff --git a/dev/react/src/tests/scroll-callback-element.tsx b/dev/react/src/tests/scroll-callback-element.tsx index b53513d38e..ea51c7180f 100644 --- a/dev/react/src/tests/scroll-callback-element.tsx +++ b/dev/react/src/tests/scroll-callback-element.tsx @@ -6,9 +6,10 @@ const height = 400 export const App = () => { const [progress, setProgress] = useState(0) - const ref = useRef(null) + const ref = useRef(null) useEffect(() => { + if (!ref.current) return return scroll(setProgress, { source: ref.current }) }, []) diff --git a/packages/framer-motion-3d/src/components/MotionCanvas.tsx b/packages/framer-motion-3d/src/components/MotionCanvas.tsx index c688675dc0..6b6cd72238 100644 --- a/packages/framer-motion-3d/src/components/MotionCanvas.tsx +++ b/packages/framer-motion-3d/src/components/MotionCanvas.tsx @@ -127,7 +127,7 @@ function CanvasComponent( // Throw exception outwards if anything within canvas throws if (error) throw error - const root = useRef>() + const root = useRef | null>(null) if (size.width > 0 && size.height > 0) { if (!root.current) { root.current = createRoot(canvasRef.current) diff --git a/packages/framer-motion-3d/src/components/use-layout-camera.ts b/packages/framer-motion-3d/src/components/use-layout-camera.ts index aeda6e31cc..224a0c904f 100644 --- a/packages/framer-motion-3d/src/components/use-layout-camera.ts +++ b/packages/framer-motion-3d/src/components/use-layout-camera.ts @@ -31,7 +31,7 @@ export function useLayoutCamera( const size = useThree((three) => three.size) const gl = useThree((three) => three.gl) const { visualElement: parentVisualElement } = useContext(MotionContext) - const measuredLayoutSize = useRef() + const measuredLayoutSize = useRef(undefined) useLayoutEffect(() => { measuredLayoutSize.current = size diff --git a/packages/framer-motion/src/components/Reorder/Group.tsx b/packages/framer-motion/src/components/Reorder/Group.tsx index a6eee5b1ca..18b93c36b9 100644 --- a/packages/framer-motion/src/components/Reorder/Group.tsx +++ b/packages/framer-motion/src/components/Reorder/Group.tsx @@ -2,13 +2,8 @@ import { invariant } from "motion-utils" import * as React from "react" -import { - forwardRef, - FunctionComponent, - ReactHTML, - useEffect, - useRef, -} from "react" +import { forwardRef, FunctionComponent, useEffect, useRef } from "react" +import type { HTMLElements } from "../../render/html/supported-elements" import { ReorderContext } from "../../context/ReorderContext" import { motion } from "../../render/components/motion/proxy" import { HTMLMotionProps } from "../../render/html/types" @@ -22,7 +17,7 @@ export interface Props { * * @public */ - as?: keyof ReactHTML + as?: keyof HTMLElements /** * The axis to reorder along. By default, items will be draggable on this axis. diff --git a/packages/framer-motion/src/components/Reorder/Item.tsx b/packages/framer-motion/src/components/Reorder/Item.tsx index 6c8df027ff..85d8b8cda0 100644 --- a/packages/framer-motion/src/components/Reorder/Item.tsx +++ b/packages/framer-motion/src/components/Reorder/Item.tsx @@ -2,7 +2,7 @@ import { invariant } from "motion-utils" import * as React from "react" -import { ReactHTML, FunctionComponent, useContext, forwardRef } from "react" +import { FunctionComponent, useContext, forwardRef } from "react" import { ReorderContext } from "../../context/ReorderContext" import { motion } from "../../render/components/motion/proxy" import { HTMLMotionProps } from "../../render/html/types" @@ -10,6 +10,7 @@ import { useConstant } from "../../utils/use-constant" import { useMotionValue } from "../../value/use-motion-value" import { useTransform } from "../../value/use-transform" import { isMotionValue } from "../../value/utils/is-motion-value" +import { HTMLElements } from "../../render/html/supported-elements" export interface Props { /** @@ -17,7 +18,7 @@ export interface Props { * * @public */ - as?: keyof ReactHTML + as?: keyof HTMLElements /** * The value in the list that this component represents. diff --git a/packages/framer-motion/src/events/use-dom-event.ts b/packages/framer-motion/src/events/use-dom-event.ts index 27ccfa675f..cbfc8bd1bf 100644 --- a/packages/framer-motion/src/events/use-dom-event.ts +++ b/packages/framer-motion/src/events/use-dom-event.ts @@ -23,7 +23,7 @@ import { addDomEvent } from "./add-dom-event" * @public */ export function useDomEvent( - ref: RefObject, + ref: RefObject, eventName: string, handler?: EventListener | undefined, options?: AddEventListenerOptions diff --git a/packages/framer-motion/src/gestures/drag/types.ts b/packages/framer-motion/src/gestures/drag/types.ts index 60131bfc95..268f44c22e 100644 --- a/packages/framer-motion/src/gestures/drag/types.ts +++ b/packages/framer-motion/src/gestures/drag/types.ts @@ -188,7 +188,7 @@ export interface DraggableProps extends DragHandlers { * } * ``` */ - dragConstraints?: false | Partial | RefObject + dragConstraints?: false | Partial | RefObject /** * The degree of movement allowed outside constraints. 0 = no movement, 1 = diff --git a/packages/framer-motion/src/motion/features/viewport/types.ts b/packages/framer-motion/src/motion/features/viewport/types.ts index dffa174c59..4f943d0073 100644 --- a/packages/framer-motion/src/motion/features/viewport/types.ts +++ b/packages/framer-motion/src/motion/features/viewport/types.ts @@ -7,7 +7,7 @@ export type ViewportEventHandler = ( ) => void export interface ViewportOptions { - root?: RefObject + root?: RefObject once?: boolean margin?: string amount?: "some" | "all" | number 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 664e1f6584..417e4165e2 100644 --- a/packages/framer-motion/src/motion/utils/use-visual-element.ts +++ b/packages/framer-motion/src/motion/utils/use-visual-element.ts @@ -30,7 +30,7 @@ export function useVisualElement( const presenceContext = useContext(PresenceContext) const reducedMotionConfig = useContext(MotionConfigContext).reducedMotion - const visualElementRef = useRef>() + const visualElementRef = useRef | null>(null) /** * If we haven't preloaded a renderer, check to see if we have one lazy-loaded @@ -137,7 +137,7 @@ export function useVisualElement( } }) - return visualElement + return visualElement! } function createProjectionNode( diff --git a/packages/framer-motion/src/render/html/supported-elements.ts b/packages/framer-motion/src/render/html/supported-elements.ts index 9361ffa9cc..3553be996d 100644 --- a/packages/framer-motion/src/render/html/supported-elements.ts +++ b/packages/framer-motion/src/render/html/supported-elements.ts @@ -1,119 +1,121 @@ -type UnionStringArray> = T[number] - -export const htmlElements = [ - "a", - "abbr", - "address", - "area", - "article", - "aside", - "audio", - "b", - "base", - "bdi", - "bdo", - "big", - "blockquote", - "body", - "br", - "button", - "canvas", - "caption", - "cite", - "code", - "col", - "colgroup", - "data", - "datalist", - "dd", - "del", - "details", - "dfn", - "dialog", - "div", - "dl", - "dt", - "em", - "embed", - "fieldset", - "figcaption", - "figure", - "footer", - "form", - "h1", - "h2", - "h3", - "h4", - "h5", - "h6", - "head", - "header", - "hgroup", - "hr", - "html", - "i", - "iframe", - "img", - "input", - "ins", - "kbd", - "keygen", - "label", - "legend", - "li", - "link", - "main", - "map", - "mark", - "menu", - "menuitem", - "meta", - "meter", - "nav", - "noscript", - "object", - "ol", - "optgroup", - "option", - "output", - "p", - "param", - "picture", - "pre", - "progress", - "q", - "rp", - "rt", - "ruby", - "s", - "samp", - "script", - "section", - "select", - "small", - "source", - "span", - "strong", - "style", - "sub", - "summary", - "sup", - "table", - "tbody", - "td", - "textarea", - "tfoot", - "th", - "thead", - "time", - "title", - "tr", - "track", - "u", - "ul", - "var", - "video", - "wbr", - "webview", -] as const -export type HTMLElements = UnionStringArray +export interface HTMLElements { + a: HTMLAnchorElement + abbr: HTMLElement + address: HTMLElement + area: HTMLAreaElement + article: HTMLElement + aside: HTMLElement + audio: HTMLAudioElement + b: HTMLElement + base: HTMLBaseElement + bdi: HTMLElement + bdo: HTMLElement + big: HTMLElement + blockquote: HTMLQuoteElement + body: HTMLBodyElement + br: HTMLBRElement + button: HTMLButtonElement + canvas: HTMLCanvasElement + caption: HTMLElement + center: HTMLElement + cite: HTMLElement + code: HTMLElement + col: HTMLTableColElement + colgroup: HTMLTableColElement + data: HTMLDataElement + datalist: HTMLDataListElement + dd: HTMLElement + del: HTMLModElement + details: HTMLDetailsElement + dfn: HTMLElement + dialog: HTMLDialogElement + div: HTMLDivElement + dl: HTMLDListElement + dt: HTMLElement + em: HTMLElement + embed: HTMLEmbedElement + fieldset: HTMLFieldSetElement + figcaption: HTMLElement + figure: HTMLElement + footer: HTMLElement + form: HTMLFormElement + h1: HTMLHeadingElement + h2: HTMLHeadingElement + h3: HTMLHeadingElement + h4: HTMLHeadingElement + h5: HTMLHeadingElement + h6: HTMLHeadingElement + head: HTMLHeadElement + header: HTMLElement + hgroup: HTMLElement + hr: HTMLHRElement + html: HTMLHtmlElement + i: HTMLElement + iframe: HTMLIFrameElement + img: HTMLImageElement + input: HTMLInputElement + ins: HTMLModElement + kbd: HTMLElement + keygen: HTMLElement + label: HTMLLabelElement + legend: HTMLLegendElement + li: HTMLLIElement + link: HTMLLinkElement + main: HTMLElement + map: HTMLMapElement + mark: HTMLElement + menu: HTMLElement + menuitem: HTMLElement + meta: HTMLMetaElement + meter: HTMLMeterElement + nav: HTMLElement + noindex: HTMLElement + noscript: HTMLElement + object: HTMLObjectElement + ol: HTMLOListElement + optgroup: HTMLOptGroupElement + option: HTMLOptionElement + output: HTMLOutputElement + p: HTMLParagraphElement + param: HTMLParamElement + picture: HTMLElement + pre: HTMLPreElement + progress: HTMLProgressElement + q: HTMLQuoteElement + rp: HTMLElement + rt: HTMLElement + ruby: HTMLElement + s: HTMLElement + samp: HTMLElement + search: HTMLElement + slot: HTMLSlotElement + script: HTMLScriptElement + section: HTMLElement + select: HTMLSelectElement + small: HTMLElement + source: HTMLSourceElement + span: HTMLSpanElement + strong: HTMLElement + style: HTMLStyleElement + sub: HTMLElement + summary: HTMLElement + sup: HTMLElement + table: HTMLTableElement + template: HTMLTemplateElement + tbody: HTMLTableSectionElement + td: HTMLTableDataCellElement + textarea: HTMLTextAreaElement + tfoot: HTMLTableSectionElement + th: HTMLTableHeaderCellElement + thead: HTMLTableSectionElement + time: HTMLTimeElement + title: HTMLTitleElement + tr: HTMLTableRowElement + track: HTMLTrackElement + u: HTMLElement + ul: HTMLUListElement + var: HTMLElement + video: HTMLVideoElement + wbr: HTMLElement + webview: HTMLWebViewElement +} diff --git a/packages/framer-motion/src/render/html/types.ts b/packages/framer-motion/src/render/html/types.ts index 2f2d28c0b7..31cd2adf31 100644 --- a/packages/framer-motion/src/render/html/types.ts +++ b/packages/framer-motion/src/render/html/types.ts @@ -1,11 +1,5 @@ import { ResolvedValues } from "../types" -import { - DetailedHTMLFactory, - HTMLAttributes, - PropsWithoutRef, - ReactHTML, - RefAttributes, -} from "react" +import { PropsWithoutRef, RefAttributes } from "react" import { MotionProps } from "../../motion/types" import { HTMLElements } from "./supported-elements" @@ -48,36 +42,15 @@ export type ForwardRefComponent = { readonly $$typeof: symbol } & (( props: PropsWithoutRef

& RefAttributes ) => JSX.Element) -/** - * Support for React component props - */ -export type UnwrapFactoryAttributes = F extends DetailedHTMLFactory< - infer P, - any -> - ? P - : never -export type UnwrapFactoryElement = F extends DetailedHTMLFactory< - any, - infer P -> - ? P - : never - -type HTMLAttributesWithoutMotionProps< - Attributes extends HTMLAttributes, - Element extends HTMLElement -> = { [K in Exclude]?: Attributes[K] } +type AttributesWithoutMotionProps = { + [K in Exclude]?: Attributes[K] +} /** * @public */ -export type HTMLMotionProps = - HTMLAttributesWithoutMotionProps< - UnwrapFactoryAttributes, - UnwrapFactoryElement - > & - MotionProps +export type HTMLMotionProps = + AttributesWithoutMotionProps & MotionProps /** * Motion-optimised versions of React's HTML components. @@ -85,8 +58,8 @@ export type HTMLMotionProps = * @public */ export type HTMLMotionComponents = { - [K in HTMLElements]: ForwardRefComponent< - UnwrapFactoryElement, + [K in keyof HTMLElements]: ForwardRefComponent< + HTMLElements[K], HTMLMotionProps > } diff --git a/packages/framer-motion/src/utils/use-in-view.ts b/packages/framer-motion/src/utils/use-in-view.ts index 3e5ad4de8d..eff867e5a0 100644 --- a/packages/framer-motion/src/utils/use-in-view.ts +++ b/packages/framer-motion/src/utils/use-in-view.ts @@ -3,13 +3,13 @@ import { inView, InViewOptions } from "../render/dom/viewport" export interface UseInViewOptions extends Omit { - root?: RefObject + root?: RefObject once?: boolean amount?: "some" | "all" | number } export function useInView( - ref: RefObject, + ref: RefObject, { root, margin, amount, once = false }: UseInViewOptions = {} ) { const [isInView, setInView] = useState(false) diff --git a/packages/framer-motion/src/utils/use-instant-transition.ts b/packages/framer-motion/src/utils/use-instant-transition.ts index 2d71090cde..0189b74fe4 100644 --- a/packages/framer-motion/src/utils/use-instant-transition.ts +++ b/packages/framer-motion/src/utils/use-instant-transition.ts @@ -7,7 +7,7 @@ import { instantAnimationState } from "./use-instant-transition-state" export function useInstantTransition() { const [forceUpdate, forcedRenderCount] = useForceUpdate() const startInstantLayoutTransition = useInstantLayoutTransition() - const unlockOnFrameRef = useRef() + const unlockOnFrameRef = useRef(-1) useEffect(() => { /** @@ -40,4 +40,4 @@ export function useInstantTransition() { export function disableInstantTransitions() { instantAnimationState.current = false -} \ No newline at end of file +} diff --git a/packages/framer-motion/src/value/scroll/use-element-scroll.ts b/packages/framer-motion/src/value/scroll/use-element-scroll.ts index 22c3a0ec96..75b639eaee 100644 --- a/packages/framer-motion/src/value/scroll/use-element-scroll.ts +++ b/packages/framer-motion/src/value/scroll/use-element-scroll.ts @@ -5,7 +5,7 @@ import { useScroll } from "../use-scroll" /** * @deprecated useElementScroll is deprecated. Convert to useScroll({ container: ref }) */ -export function useElementScroll(ref: RefObject) { +export function useElementScroll(ref: RefObject) { if (process.env.NODE_ENV === "development") { warnOnce( false, diff --git a/packages/framer-motion/src/value/use-scroll.ts b/packages/framer-motion/src/value/use-scroll.ts index 46706fee3d..97d724becb 100644 --- a/packages/framer-motion/src/value/use-scroll.ts +++ b/packages/framer-motion/src/value/use-scroll.ts @@ -9,12 +9,12 @@ import { ScrollInfoOptions } from "../render/dom/scroll/types" export interface UseScrollOptions extends Omit { - container?: RefObject - target?: RefObject + container?: RefObject + target?: RefObject layoutEffect?: boolean } -function refWarning(name: string, ref?: RefObject) { +function refWarning(name: string, ref?: RefObject) { warning( Boolean(!ref || ref.current), `You have defined a ${name} options but the provided ref is not yet hydrated, probably because it's defined higher up the tree. Try calling useScroll() in the same component as the ref, or setting its \`layoutEffect: false\` option.`