From 00b999ef85147239d1fac3355b2df939c28a6490 Mon Sep 17 00:00:00 2001 From: Cody Bennett Date: Thu, 30 Jan 2025 06:49:15 -0600 Subject: [PATCH 1/3] fix: upstream use-measure (#3441) --- example/src/demos/SVGRenderer.tsx | 2 +- packages/fiber/package.json | 3 +- packages/fiber/src/web/Canvas.tsx | 2 +- packages/fiber/src/web/use-measure.ts | 222 -------------------------- yarn.lock | 15 +- 5 files changed, 8 insertions(+), 236 deletions(-) delete mode 100644 packages/fiber/src/web/use-measure.ts diff --git a/example/src/demos/SVGRenderer.tsx b/example/src/demos/SVGRenderer.tsx index dc632ab11e..dac92110d1 100644 --- a/example/src/demos/SVGRenderer.tsx +++ b/example/src/demos/SVGRenderer.tsx @@ -9,7 +9,7 @@ import { events, ReconcilerRoot, } from '@react-three/fiber' -import { useMeasure, Options as ResizeOptions } from '../../../packages/fiber/src/web/use-measure' +import useMeasure, { Options as ResizeOptions } from 'react-use-measure' import { SVGRenderer } from 'three-stdlib' function TorusKnot() { diff --git a/packages/fiber/package.json b/packages/fiber/package.json index 703eaff8c7..2fc74b4e81 100644 --- a/packages/fiber/package.json +++ b/packages/fiber/package.json @@ -43,14 +43,13 @@ }, "dependencies": { "@babel/runtime": "^7.17.8", - "@types/debounce": "^1.2.1", "@types/react-reconciler": "^0.26.7", "@types/webxr": "*", "base64-js": "^1.5.1", "buffer": "^6.0.3", - "debounce": "^1.2.1", "its-fine": "^1.0.6", "react-reconciler": "^0.27.0", + "react-use-measure": "^2.1.6", "scheduler": "^0.21.0", "suspend-react": "^0.1.3", "zustand": "^3.7.1" diff --git a/packages/fiber/src/web/Canvas.tsx b/packages/fiber/src/web/Canvas.tsx index 6384921275..82ecd8e6f2 100644 --- a/packages/fiber/src/web/Canvas.tsx +++ b/packages/fiber/src/web/Canvas.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import * as THREE from 'three' -import { useMeasure, Options as ResizeOptions } from './use-measure' +import useMeasure, { Options as ResizeOptions } from 'react-use-measure' import { useContextBridge, FiberProvider } from 'its-fine' import { isRef, SetBlock, Block, ErrorBoundary, useMutableCallback, useIsomorphicLayoutEffect } from '../core/utils' import { ReconcilerRoot, extend, createRoot, unmountComponentAtNode, RenderProps } from '../core' diff --git a/packages/fiber/src/web/use-measure.ts b/packages/fiber/src/web/use-measure.ts deleted file mode 100644 index cc5ffef13c..0000000000 --- a/packages/fiber/src/web/use-measure.ts +++ /dev/null @@ -1,222 +0,0 @@ -/* eslint-disable react-hooks/rules-of-hooks */ -import { useEffect, useState, useRef, useMemo } from 'react' -import createDebounce from 'debounce' - -declare type ResizeObserverCallback = (entries: any[], observer: ResizeObserver) => void -declare class ResizeObserver { - constructor(callback: ResizeObserverCallback) - observe(target: Element, options?: any): void - unobserve(target: Element): void - disconnect(): void - static toString(): string -} - -export interface RectReadOnly { - readonly x: number - readonly y: number - readonly width: number - readonly height: number - readonly top: number - readonly right: number - readonly bottom: number - readonly left: number - [key: string]: number -} - -type HTMLOrSVGElement = HTMLElement | SVGElement - -type Result = [(element: HTMLOrSVGElement | null) => void, RectReadOnly, () => void] - -type State = { - element: HTMLOrSVGElement | null - scrollContainers: HTMLOrSVGElement[] | null - resizeObserver: ResizeObserver | null - lastBounds: RectReadOnly - orientationHandler: null | (() => void) -} - -export type Options = { - debounce?: number | { scroll: number; resize: number } - scroll?: boolean - polyfill?: { new (cb: ResizeObserverCallback): ResizeObserver } - offsetSize?: boolean -} - -export function useMeasure( - { debounce, scroll, polyfill, offsetSize }: Options = { debounce: 0, scroll: false, offsetSize: false }, -): Result { - const ResizeObserver = polyfill || (typeof window !== 'undefined' && (window as any).ResizeObserver) - - const [bounds, set] = useState({ - left: 0, - top: 0, - width: 0, - height: 0, - bottom: 0, - right: 0, - x: 0, - y: 0, - }) - - // In test mode - if (!ResizeObserver) { - // @ts-ignore - bounds.width = 1280 - // @ts-ignore - bounds.height = 800 - return [() => {}, bounds, () => {}] - } - - // keep all state in a ref - const state = useRef({ - element: null, - scrollContainers: null, - resizeObserver: null, - lastBounds: bounds, - orientationHandler: null, - }) - - // set actual debounce values early, so effects know if they should react accordingly - const scrollDebounce = debounce ? (typeof debounce === 'number' ? debounce : debounce.scroll) : null - const resizeDebounce = debounce ? (typeof debounce === 'number' ? debounce : debounce.resize) : null - - // make sure to update state only as long as the component is truly mounted - const mounted = useRef(false) - useEffect(() => { - mounted.current = true - return () => void (mounted.current = false) - }) - - // memoize handlers, so event-listeners know when they should update - const [forceRefresh, resizeChange, scrollChange] = useMemo(() => { - const callback = () => { - if (!state.current.element) return - const { left, top, width, height, bottom, right, x, y } = - state.current.element.getBoundingClientRect() as unknown as RectReadOnly - - const size = { - left, - top, - width, - height, - bottom, - right, - x, - y, - } - - if (state.current.element instanceof HTMLElement && offsetSize) { - size.height = state.current.element.offsetHeight - size.width = state.current.element.offsetWidth - } - - Object.freeze(size) - if (mounted.current && !areBoundsEqual(state.current.lastBounds, size)) set((state.current.lastBounds = size)) - } - return [ - callback, - resizeDebounce ? createDebounce(callback, resizeDebounce) : callback, - scrollDebounce ? createDebounce(callback, scrollDebounce) : callback, - ] - }, [set, offsetSize, scrollDebounce, resizeDebounce]) - - // cleanup current scroll-listeners / observers - function removeListeners() { - if (state.current.scrollContainers) { - state.current.scrollContainers.forEach((element) => element.removeEventListener('scroll', scrollChange, true)) - state.current.scrollContainers = null - } - - if (state.current.resizeObserver) { - state.current.resizeObserver.disconnect() - state.current.resizeObserver = null - } - - if (state.current.orientationHandler) { - if ('orientation' in screen && 'removeEventListener' in screen.orientation) { - screen.orientation.removeEventListener('change', state.current.orientationHandler) - } else if ('onorientationchange' in window) { - window.removeEventListener('orientationchange', state.current.orientationHandler) - } - } - } - - // add scroll-listeners / observers - function addListeners() { - if (!state.current.element) return - state.current.resizeObserver = new ResizeObserver(resizeChange) - state.current.resizeObserver?.observe(state.current.element) - if (scroll && state.current.scrollContainers) { - state.current.scrollContainers.forEach((scrollContainer) => - scrollContainer.addEventListener('scroll', scrollChange, { capture: true, passive: true }), - ) - } - - // Handle orientation changes - state.current.orientationHandler = () => { - scrollChange() - } - - // Use screen.orientation if available - if ('orientation' in screen && 'addEventListener' in screen.orientation) { - screen.orientation.addEventListener('change', state.current.orientationHandler) - } else if ('onorientationchange' in window) { - // Fallback to orientationchange event - window.addEventListener('orientationchange', state.current.orientationHandler) - } - } - - // the ref we expose to the user - const ref = (node: HTMLOrSVGElement | null) => { - if (!node || node === state.current.element) return - removeListeners() - state.current.element = node - state.current.scrollContainers = findScrollContainers(node) - addListeners() - } - - // add general event listeners - useOnWindowScroll(scrollChange, Boolean(scroll)) - useOnWindowResize(resizeChange) - - // respond to changes that are relevant for the listeners - useEffect(() => { - removeListeners() - addListeners() - }, [scroll, scrollChange, resizeChange]) - - // remove all listeners when the components unmounts - useEffect(() => removeListeners, []) - return [ref, bounds, forceRefresh] -} - -// Adds native resize listener to window -function useOnWindowResize(onWindowResize: (event: Event) => void) { - useEffect(() => { - const cb = onWindowResize - window.addEventListener('resize', cb) - return () => void window.removeEventListener('resize', cb) - }, [onWindowResize]) -} -function useOnWindowScroll(onScroll: () => void, enabled: boolean) { - useEffect(() => { - if (enabled) { - const cb = onScroll - window.addEventListener('scroll', cb, { capture: true, passive: true }) - return () => void window.removeEventListener('scroll', cb, true) - } - }, [onScroll, enabled]) -} - -// Returns a list of scroll offsets -function findScrollContainers(element: HTMLOrSVGElement | null): HTMLOrSVGElement[] { - const result: HTMLOrSVGElement[] = [] - if (!element || element === document.body) return result - const { overflow, overflowX, overflowY } = window.getComputedStyle(element) - if ([overflow, overflowX, overflowY].some((prop) => prop === 'auto' || prop === 'scroll')) result.push(element) - return [...result, ...findScrollContainers(element.parentElement)] -} - -// Checks if element boundaries are equal -const keys: (keyof RectReadOnly)[] = ['x', 'y', 'top', 'bottom', 'left', 'right', 'width', 'height'] -const areBoundsEqual = (a: RectReadOnly, b: RectReadOnly): boolean => keys.every((key) => a[key] === b[key]) diff --git a/yarn.lock b/yarn.lock index 56c9a1abf3..5b9b11011d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2815,11 +2815,6 @@ dependencies: "@babel/types" "^7.3.0" -"@types/debounce@^1.2.1": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@types/debounce/-/debounce-1.2.4.tgz#cb7e85d9ad5ababfac2f27183e8ac8b576b2abb3" - integrity sha512-jBqiORIzKDOToaF63Fm//haOCHuwQuLa2202RK4MozpA6lh93eCBc+/8+wZn5OzjJt3ySdc+74SXWXB55Ewtyw== - "@types/draco3d@^1.4.0": version "1.4.2" resolved "https://registry.yarnpkg.com/@types/draco3d/-/draco3d-1.4.2.tgz#7faccb809db2a5e19b9efb97c5f2eb9d64d527ea" @@ -4447,11 +4442,6 @@ dayjs@^1.8.15: resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.1.tgz#90b33a3dda3417258d48ad2771b415def6545eb0" integrity sha512-ER7EjqVAMkRRsxNCC5YqJ9d9VQYuWdGt7aiH2qA5R5wt8ZmWaP2dLUSIK6y/kVzLMlmh1Tvu5xUf4M/wdGJ5KA== -debounce@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" - integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== - debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -8916,6 +8906,11 @@ react-test-renderer@^18.0.0: react-shallow-renderer "^16.13.1" scheduler "^0.21.0" +react-use-measure@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/react-use-measure/-/react-use-measure-2.1.6.tgz#d0dc826b2020361da8323adcd39f72a8191f7a19" + integrity sha512-IobUVRLFrtrpUX2RMDrdAfRTaEftVRXFlTEuTdJFgtrnbxAMhueimO7nFix4/X40KI8LnJFV7LHxPnMi9Tc1Cw== + react-use-refs@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/react-use-refs/-/react-use-refs-1.0.1.tgz#44cab5f4764b3fa4a112189c0058fc8752d1eb2c" From 0a0f2acdc08064429ab88948278f35068fb6633f Mon Sep 17 00:00:00 2001 From: Cody Bennett Date: Thu, 30 Jan 2025 06:49:55 -0600 Subject: [PATCH 2/3] docs(changeset): fix: upstream use-measure --- .changeset/dull-mails-wink.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/dull-mails-wink.md diff --git a/.changeset/dull-mails-wink.md b/.changeset/dull-mails-wink.md new file mode 100644 index 0000000000..55750801e5 --- /dev/null +++ b/.changeset/dull-mails-wink.md @@ -0,0 +1,5 @@ +--- +'@react-three/fiber': patch +--- + +fix: upstream use-measure From ba21c5bf3b3a80049cc9b015fcb62ddec391ed7c Mon Sep 17 00:00:00 2001 From: Cody Bennett Date: Thu, 30 Jan 2025 06:49:57 -0600 Subject: [PATCH 3/3] RELEASING: Releasing 1 package(s) Releases: @react-three/fiber@8.17.13 [skip ci] --- .changeset/dull-mails-wink.md | 5 ----- packages/fiber/CHANGELOG.md | 6 ++++++ packages/fiber/package.json | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) delete mode 100644 .changeset/dull-mails-wink.md diff --git a/.changeset/dull-mails-wink.md b/.changeset/dull-mails-wink.md deleted file mode 100644 index 55750801e5..0000000000 --- a/.changeset/dull-mails-wink.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@react-three/fiber': patch ---- - -fix: upstream use-measure diff --git a/packages/fiber/CHANGELOG.md b/packages/fiber/CHANGELOG.md index 7d2b928972..6633484a98 100644 --- a/packages/fiber/CHANGELOG.md +++ b/packages/fiber/CHANGELOG.md @@ -1,5 +1,11 @@ # @react-three/fiber +## 8.17.13 + +### Patch Changes + +- 0a0f2acd: fix: upstream use-measure + ## 8.17.12 ### Patch Changes diff --git a/packages/fiber/package.json b/packages/fiber/package.json index 2fc74b4e81..c6699d2155 100644 --- a/packages/fiber/package.json +++ b/packages/fiber/package.json @@ -1,6 +1,6 @@ { "name": "@react-three/fiber", - "version": "8.17.12", + "version": "8.17.13", "description": "A React renderer for Threejs", "keywords": [ "react",