diff --git a/packages/frontend/component/src/index.ts b/packages/frontend/component/src/index.ts index 074c2f54c4..1142b3ad83 100644 --- a/packages/frontend/component/src/index.ts +++ b/packages/frontend/component/src/index.ts @@ -24,3 +24,4 @@ export * from './ui/switch'; export * from './ui/table'; export * from './ui/toast'; export * from './ui/tooltip'; +export * from './utils'; diff --git a/packages/frontend/component/src/ui/date-picker/week-date-picker.tsx b/packages/frontend/component/src/ui/date-picker/week-date-picker.tsx index 7a188fd0ea..f4d57fb7a0 100644 --- a/packages/frontend/component/src/ui/date-picker/week-date-picker.tsx +++ b/packages/frontend/component/src/ui/date-picker/week-date-picker.tsx @@ -13,6 +13,7 @@ import { useState, } from 'react'; +import { observeResize } from '../../utils'; import { IconButton } from '../button'; import * as styles from './week-date-picker.css'; @@ -116,8 +117,7 @@ export const WeekDatePicker = memo(function WeekDatePicker({ const el = weekRef.current; if (!el) return; - const resizeObserver = new ResizeObserver(entries => { - const rect = entries[0].contentRect; + return observeResize(el, ({ contentRect: rect }) => { const width = rect.width; if (!width) return; @@ -127,11 +127,6 @@ export const WeekDatePicker = memo(function WeekDatePicker({ setViewPortSize(Math.max(1, Math.min(viewPortCount, 7))); setDense(width < 300); }); - resizeObserver.observe(el); - - return () => { - resizeObserver.disconnect(); - }; }, []); // when value changes, reset cursor diff --git a/packages/frontend/component/src/utils/index.ts b/packages/frontend/component/src/utils/index.ts new file mode 100644 index 0000000000..358271ede7 --- /dev/null +++ b/packages/frontend/component/src/utils/index.ts @@ -0,0 +1 @@ +export * from './observe-resize'; diff --git a/packages/frontend/component/src/utils/observe-resize.ts b/packages/frontend/component/src/utils/observe-resize.ts new file mode 100644 index 0000000000..30da21d904 --- /dev/null +++ b/packages/frontend/component/src/utils/observe-resize.ts @@ -0,0 +1,77 @@ +import type { ResizeObserverEntry } from '@juggle/resize-observer'; + +type ObserveResize = { + callback: (entity: ResizeObserverEntry) => void; + dispose: () => void; +}; + +let _resizeObserver: ResizeObserver | null = null; +const elementsMap = new WeakMap>(); + +// for debugging +(window as any)._resizeObserverElementsMap = elementsMap; + +/** + * @internal get or initialize the ResizeObserver instance + */ +const getResizeObserver = () => + (_resizeObserver ??= new ResizeObserver(entries => { + entries.forEach(entry => { + const listeners = elementsMap.get(entry.target) ?? []; + listeners.forEach(({ callback }) => callback(entry)); + }); + })); + +/** + * @internal remove element's specific listener + */ +const removeListener = (element: Element, listener: ObserveResize) => { + if (!element) return; + const listeners = elementsMap.get(element) ?? []; + const observer = getResizeObserver(); + // remove the listener from the element + if (listeners.includes(listener)) { + elementsMap.set( + element, + listeners.filter(l => l !== listener) + ); + } + // if no more listeners, unobserve the element + if (elementsMap.get(element)?.length === 0) { + observer.unobserve(element); + elementsMap.delete(element); + } +}; + +/** + * A function to observe the resize of an element use global ResizeObserver. + * + * ```ts + * useEffect(() => { + * const dispose1 = observeResize(elRef1.current, (entry) => {}); + * const dispose2 = observeResize(elRef2.current, (entry) => {}); + * + * return () => { + * dispose1(); + * dispose2(); + * }; + * }, []) + * ``` + * @return A function to dispose the observer. + */ +export const observeResize = ( + element: Element, + callback: ObserveResize['callback'] +) => { + const observer = getResizeObserver(); + if (!elementsMap.has(element)) { + observer.observe(element); + } + const prevListeners = elementsMap.get(element) ?? []; + const listener = { callback, dispose: () => {} }; + listener.dispose = () => removeListener(element, listener); + + elementsMap.set(element, [...prevListeners, listener]); + + return listener.dispose; +}; diff --git a/packages/frontend/core/src/modules/find-in-page/view/find-in-page-modal.tsx b/packages/frontend/core/src/modules/find-in-page/view/find-in-page-modal.tsx index 9ad9020051..96aa2663b9 100644 --- a/packages/frontend/core/src/modules/find-in-page/view/find-in-page-modal.tsx +++ b/packages/frontend/core/src/modules/find-in-page/view/find-in-page-modal.tsx @@ -1,4 +1,4 @@ -import { Button, IconButton } from '@affine/component'; +import { Button, IconButton, observeResize } from '@affine/component'; import { ArrowDownSmallIcon, ArrowUpSmallIcon, @@ -63,13 +63,7 @@ const CanvasText = ({ return; } drawText(canvas, text); - const resizeObserver = new ResizeObserver(() => { - drawText(canvas, text); - }); - resizeObserver.observe(canvas); - return () => { - resizeObserver.disconnect(); - }; + return observeResize(canvas, () => drawText(canvas, text)); }, [text]); return ;