From 91c64edcf9c9e3d12ef78cf80317e012625ada8b Mon Sep 17 00:00:00 2001 From: Minwook Shin <163576506+minwookshin@users.noreply.github.com> Date: Sat, 3 Oct 2026 05:50:55 -0400 Subject: [PATCH] fix(common): support both resize observer export shapes --- .../common/lib/utils/useResizeObserver.ts | 27 ++++++++++++- .../common/spec/useResizeObserver.spec.ts | 40 +++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 modules/react/common/spec/useResizeObserver.spec.ts diff --git a/modules/react/common/lib/utils/useResizeObserver.ts b/modules/react/common/lib/utils/useResizeObserver.ts index 2b77ca0e55..3d75d3c3e9 100644 --- a/modules/react/common/lib/utils/useResizeObserver.ts +++ b/modules/react/common/lib/utils/useResizeObserver.ts @@ -1,3 +1,26 @@ -import useResizeObserver from 'use-resize-observer'; +import {RefCallback} from 'react'; +import * as resizeObserver from 'use-resize-observer'; -export {useResizeObserver}; +type ObservedSize = { + width: number | undefined; + height: number | undefined; +}; + +// Keep our public declaration independent of the installed dependency's export shape. +type ResizeObserverHook = (opts?: { + // RefObject's generic nullability differs between React 17/18 and React 19. + ref?: {readonly current: T | null} | T | null; + onResize?: (size: ObservedSize) => void; + box?: 'border-box' | 'content-box' | 'device-pixel-content-box'; + round?: (n: number) => number; +}) => ObservedSize & {ref: RefCallback}; + +// Reading through a parameter avoids bundler warnings about exports missing in v9 or v10. +function getUseResizeObserver(module: { + useResizeObserver?: ResizeObserverHook; + default?: ResizeObserverHook; +}): ResizeObserverHook { + return (module.useResizeObserver ?? module.default)!; +} + +export const useResizeObserver = getUseResizeObserver(resizeObserver); diff --git a/modules/react/common/spec/useResizeObserver.spec.ts b/modules/react/common/spec/useResizeObserver.spec.ts new file mode 100644 index 0000000000..5df1dbf522 --- /dev/null +++ b/modules/react/common/spec/useResizeObserver.spec.ts @@ -0,0 +1,40 @@ +beforeEach(() => { + vi.resetModules(); +}); + +afterEach(() => { + vi.doUnmock('use-resize-observer'); +}); + +describe('useResizeObserver', () => { + it.each(['default', 'useResizeObserver'])('should support the %s export', async exportName => { + const result = {ref: vi.fn(), width: 100, height: 50}; + const hook = vi.fn(() => result); + vi.doMock('use-resize-observer', () => ({ + default: undefined, + useResizeObserver: undefined, + [exportName]: hook, + })); + + const {useResizeObserver} = await import('../lib/utils/useResizeObserver'); + const options = {box: 'border-box' as const, round: Math.floor, onResize: vi.fn()}; + + expect(useResizeObserver(options)).toBe(result); + expect(hook).toHaveBeenCalledWith(options); + }); + + it('should prefer the named export when both are present', async () => { + const namedHook = vi.fn(() => ({ref: vi.fn(), width: 0, height: 0})); + const defaultHook = vi.fn(); + vi.doMock('use-resize-observer', () => ({ + useResizeObserver: namedHook, + default: defaultHook, + })); + + const {useResizeObserver} = await import('../lib/utils/useResizeObserver'); + + expect(useResizeObserver().width).toBe(0); + expect(namedHook).toHaveBeenCalledOnce(); + expect(defaultHook).not.toHaveBeenCalled(); + }); +});