diff --git a/change/@fluentui-priority-overflow-10c22153-de2e-4c16-8fc0-b49aadbd1d08.json b/change/@fluentui-priority-overflow-10c22153-de2e-4c16-8fc0-b49aadbd1d08.json new file mode 100644 index 00000000000000..a0b0f98f1b0413 --- /dev/null +++ b/change/@fluentui-priority-overflow-10c22153-de2e-4c16-8fc0-b49aadbd1d08.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "fix: Use container window's resize observer", + "packageName": "@fluentui/priority-overflow", + "email": "lingfangao@hotmail.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-table-01e02e96-5aa2-493f-8614-61799987dce2.json b/change/@fluentui-react-table-01e02e96-5aa2-493f-8614-61799987dce2.json new file mode 100644 index 00000000000000..2bf488c509175c --- /dev/null +++ b/change/@fluentui-react-table-01e02e96-5aa2-493f-8614-61799987dce2.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "fix: use targetDocument resize observer", + "packageName": "@fluentui/react-table", + "email": "lingfangao@hotmail.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-virtualizer-6da34b46-57b4-4d39-92c6-b5abe95ef0c0.json b/change/@fluentui-react-virtualizer-6da34b46-57b4-4d39-92c6-b5abe95ef0c0.json new file mode 100644 index 00000000000000..4b20ebd4f309e0 --- /dev/null +++ b/change/@fluentui-react-virtualizer-6da34b46-57b4-4d39-92c6-b5abe95ef0c0.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "fix: use targetDocument resize observer", + "packageName": "@fluentui/react-virtualizer", + "email": "lingfangao@hotmail.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/priority-overflow/src/createResizeObserver.ts b/packages/react-components/priority-overflow/src/createResizeObserver.ts new file mode 100644 index 00000000000000..7e8a7597c946d1 --- /dev/null +++ b/packages/react-components/priority-overflow/src/createResizeObserver.ts @@ -0,0 +1,25 @@ +/** + * Helper function that creates a resize observer in the element's own window global + * @param elementToObserve - Uses the element's window global to create the resize observer + * @param callback + * @returns function to cleanup the resize observer + */ +export function observeResize(elementToObserve: HTMLElement, callback: ResizeObserverCallback) { + const GlobalResizeObsever = elementToObserve.ownerDocument.defaultView?.ResizeObserver; + + if (!GlobalResizeObsever) { + if (process.env.NODE_ENV !== 'production') { + // eslint-disable-next-line no-console + console.error('@fluentui/priority-overflow', 'ResizeObserver does not exist on container window'); + } + return () => null; + } + + let resizeObserver: ResizeObserver | undefined = new GlobalResizeObsever(callback); + resizeObserver.observe(elementToObserve); + + return () => { + resizeObserver?.disconnect(); + resizeObserver = undefined; + }; +} diff --git a/packages/react-components/priority-overflow/src/overflowManager.ts b/packages/react-components/priority-overflow/src/overflowManager.ts index 2b223303166919..0dedfd414095ba 100644 --- a/packages/react-components/priority-overflow/src/overflowManager.ts +++ b/packages/react-components/priority-overflow/src/overflowManager.ts @@ -1,4 +1,5 @@ import { DATA_OVERFLOWING, DATA_OVERFLOW_GROUP } from './consts'; +import { observeResize } from './createResizeObserver'; import { debounce } from './debounce'; import { createPriorityQueue, PriorityQueue } from './priorityQueue'; import type { @@ -35,13 +36,7 @@ export function createOverflowManager(): OverflowManager { const overflowItems: Record = {}; const overflowDividers: Record = {}; - const resizeObserver = new ResizeObserver(entries => { - if (!entries[0] || !container) { - return; - } - - update(); - }); + let disposeResizeObserver: () => void = () => null; const getNextItem = (queueToDequeue: PriorityQueue, queueToEnqueue: PriorityQueue) => { const nextItem = queueToDequeue.dequeue(); @@ -247,13 +242,19 @@ export function createOverflowManager(): OverflowManager { Object.values(overflowItems).forEach(item => visibleItemQueue.enqueue(item.id)); container = observedContainer; - resizeObserver.observe(container); + disposeResizeObserver = observeResize(container, entries => { + if (!entries[0] || !container) { + return; + } + + update(); + }); }; const disconnect: OverflowManager['disconnect'] = () => { observing = false; sizeCache.clear(); - resizeObserver.disconnect(); + disposeResizeObserver(); }; const addItem: OverflowManager['addItem'] = item => { diff --git a/packages/react-components/react-table/src/hooks/useMeasureElement.ts b/packages/react-components/react-table/src/hooks/useMeasureElement.ts index babe01bd2eedeb..89899f74115cea 100644 --- a/packages/react-components/react-table/src/hooks/useMeasureElement.ts +++ b/packages/react-components/react-table/src/hooks/useMeasureElement.ts @@ -1,4 +1,3 @@ -import { canUseDOM } from '@fluentui/react-utilities'; import * as React from 'react'; import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; @@ -21,7 +20,7 @@ export function useMeasureElement() }, []); // Keep the reference of ResizeObserver in the state, as it should live through renders - const [resizeObserver] = React.useState(canUseDOM() ? new ResizeObserver(handleResize) : undefined); + const [resizeObserver] = React.useState(() => createResizeObserverFromDocument(targetDocument, handleResize)); const measureElementRef = React.useCallback( (el: TElement | null) => { if (!targetDocument || !resizeObserver) { @@ -49,3 +48,21 @@ export function useMeasureElement() return { width, measureElementRef }; } + +/** + * FIXME - TS 3.8/3.9 don't have ResizeObserver types by default, move this to a shared utility once we bump the minbar + * A utility method that creates a ResizeObserver from a target document + * @param targetDocument - document to use to create the ResizeObserver + * @param callback - https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver/ResizeObserver#callback + * @returns a ResizeObserver instance or null if the global does not exist on the document + */ +export function createResizeObserverFromDocument( + targetDocument: Document | null | undefined, + callback: ResizeObserverCallback, +) { + if (!targetDocument?.defaultView?.ResizeObserver) { + return null; + } + + return new targetDocument.defaultView.ResizeObserver(callback); +} diff --git a/packages/react-components/react-virtualizer/package.json b/packages/react-components/react-virtualizer/package.json index 3948c6af6b7727..1b9173f22bfdc7 100644 --- a/packages/react-components/react-virtualizer/package.json +++ b/packages/react-components/react-virtualizer/package.json @@ -34,6 +34,7 @@ "dependencies": { "@fluentui/react-jsx-runtime": "^9.0.18", "@fluentui/react-utilities": "^9.15.1", + "@fluentui/react-shared-contexts": "^9.10.0", "@griffel/react": "^1.5.14", "@swc/helpers": "^0.5.1" }, diff --git a/packages/react-components/react-virtualizer/src/hooks/useResizeObserverRef.ts b/packages/react-components/react-virtualizer/src/hooks/useResizeObserverRef.ts index 6734eb52d1f4f4..3de686b456aed6 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useResizeObserverRef.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useResizeObserverRef.ts @@ -1,12 +1,14 @@ import * as React from 'react'; +import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; import { debounce } from '../utilities/debounce'; -import { canUseDOM } from '@fluentui/react-utilities'; +import { createResizeObserverFromDocument } from '../utilities/createResizeObserverFromDocument'; import { ResizeCallbackWithRef } from './hooks.types'; /** * useResizeObserverRef_unstable simplifies resize observer connection and ensures debounce/cleanup */ export const useResizeObserverRef_unstable = (resizeCallback: ResizeCallbackWithRef) => { + const { targetDocument } = useFluent(); const container = React.useRef(null); // the handler for resize observer const handleResize = debounce((entries: ResizeObserverEntry[], observer: ResizeObserver) => { @@ -15,16 +17,16 @@ export const useResizeObserverRef_unstable = (resizeCallback: ResizeCallbackWith // Keep the reference of ResizeObserver in the state, as it should live through renders const [resizeObserver, setResizeObserver] = React.useState(() => - canUseDOM() ? new ResizeObserver(handleResize) : undefined, + createResizeObserverFromDocument(targetDocument, handleResize), ); React.useEffect(() => { // Update our state when resizeCallback changes container.current = null; resizeObserver?.disconnect(); - setResizeObserver(canUseDOM() ? new ResizeObserver(handleResize) : undefined); + setResizeObserver(() => createResizeObserverFromDocument(targetDocument, handleResize)); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [resizeCallback]); + }, [resizeCallback, targetDocument]); React.useEffect(() => { return () => { diff --git a/packages/react-components/react-virtualizer/src/utilities/createResizeObserverFromDocument.ts b/packages/react-components/react-virtualizer/src/utilities/createResizeObserverFromDocument.ts new file mode 100644 index 00000000000000..ca4e9930d84931 --- /dev/null +++ b/packages/react-components/react-virtualizer/src/utilities/createResizeObserverFromDocument.ts @@ -0,0 +1,17 @@ +/** + * FIXME - TS 3.8/3.9 don't have ResizeObserver types by default, move this to a shared utility once we bump the minbar + * A utility method that creates a ResizeObserver from a target document + * @param targetDocument - document to use to create the ResizeObserver + * @param callback - https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver/ResizeObserver#callback + * @returns a ResizeObserver instance or null if the global does not exist on the document + */ +export function createResizeObserverFromDocument( + targetDocument: Document | null | undefined, + callback: ResizeObserverCallback, +) { + if (!targetDocument?.defaultView?.ResizeObserver) { + return null; + } + + return new targetDocument.defaultView.ResizeObserver(callback); +}