import { onBeforeMount, onBeforeUnmount, watch, type Ref, } from 'vue'; import { throttle, type ThrottleFunction } from '@/application/Common/Timing/Throttle'; import { useResizeObserverPolyfill } from './UseResizeObserverPolyfill'; import { useAnimationFrameLimiter } from './UseAnimationFrameLimiter'; import type { LifecycleHook } from '../Common/LifecycleHook'; export function useResizeObserver( config: ResizeObserverConfig, usePolyfill = useResizeObserverPolyfill, useFrameLimiter = useAnimationFrameLimiter, throttler: ThrottleFunction = throttle, onSetup: LifecycleHook = onBeforeMount, onTeardown: LifecycleHook = onBeforeUnmount, ) { const { resetNextFrame, cancelNextFrame } = useFrameLimiter(); // This prevents the 'ResizeObserver loop completed with undelivered notifications' error when // the browser can't process all observations within one animation frame. // Reference: https://github.com/WICG/resize-observer/issues/38 const { resizeObserverReady } = usePolyfill(); // This ensures compatibility with ancient browsers. All modern browsers support ResizeObserver. // Compatibility info: https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver#browser_compatibility const throttledCallback = throttler(config.observeCallback, config.throttleInMs); // Throttling enhances performance during rapid changes such as window resizing. let observer: ResizeObserver | null; const disposeObserver = () => { cancelNextFrame(); observer?.disconnect(); observer = null; }; onSetup(() => { watch(() => config.observedElementRef.value, (element) => { if (!element) { disposeObserver(); return; } resizeObserverReady.then((createObserver) => { disposeObserver(); observer = createObserver((...args) => { resetNextFrame(() => throttledCallback(...args)); }); observer.observe(element, config?.observeOptions); }); }, { immediate: true }); }); onTeardown(() => { disposeObserver(); }); } export interface ResizeObserverConfig { readonly observedElementRef: ObservedElementReference; readonly throttleInMs: number; readonly observeCallback: ResizeObserverCallback; readonly observeOptions?: ResizeObserverOptions; } export type ObservedElementReference = Readonly>;