feat(component): helper function observeResize to observe size change via global ResizeObserver (#7241)

```ts
import { observeResize } from "@affine/component";

useEffect(() => {
  const dispose = observeResize(element entry => {
    console.log(entry.contentRect);
  });

  return () => dispose();
}, []);
```
This commit is contained in:
CatsJuice
2024-06-19 09:04:56 +00:00
parent be36e033f2
commit 98e35384a6
5 changed files with 83 additions and 15 deletions

View File

@@ -24,3 +24,4 @@ export * from './ui/switch';
export * from './ui/table';
export * from './ui/toast';
export * from './ui/tooltip';
export * from './utils';

View File

@@ -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

View File

@@ -0,0 +1 @@
export * from './observe-resize';

View File

@@ -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<Element, Array<ObserveResize>>();
// 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;
};