diff --git a/semcore/core/src/utils/use/useScrollBarWidth.ts b/semcore/core/src/utils/use/useScrollBarWidth.ts index f7a2008763..af51bb5135 100644 --- a/semcore/core/src/utils/use/useScrollBarWidth.ts +++ b/semcore/core/src/utils/use/useScrollBarWidth.ts @@ -1,39 +1,74 @@ -import { useEffect, useState, useRef } from 'react'; +import { useEffect, useRef, useState } from 'react'; -export function useScrollBarWidth(vertical = true): number { - const [scrollBarWidth, setScrollBarWidth] = useState(0); - const af = useRef(null); +type Dimension = 'vertical' | 'horizontal'; - useEffect(() => { - const calculateScrollBar = () => { - if (!window.visualViewport) return; +const state: Record = { vertical: 0, horizontal: 0 }; +const listeners = new Set<() => void>(); + +let rafId: number | null = null; +let inited = false; + +function measure() { + if (!window.visualViewport) return; + + const nextVertical = window.innerWidth - window.visualViewport.width; + const nextHorizontal = window.innerHeight - window.visualViewport.height; + + if (nextVertical === state.vertical && nextHorizontal === state.horizontal) return; - if (!vertical) { - setScrollBarWidth(window.innerHeight - window.visualViewport.height); - return; - } + state.vertical = nextVertical; + state.horizontal = nextHorizontal; - setScrollBarWidth(window.innerWidth - window.visualViewport.width); - }; + listeners.forEach((l) => l()); +} + +function handleResize() { + if (rafId !== null) return; + + rafId = requestAnimationFrame(() => { + measure(); + rafId = null; + }); +} + +function init() { + if (inited) return; - const handleResize = () => { - // to handle resize 1 time per frame - if (af.current !== null) return; + inited = true; - af.current = requestAnimationFrame(() => { - calculateScrollBar(); - af.current = null; - }); - }; + measure(); - calculateScrollBar(); - window.addEventListener('resize', handleResize); + window.addEventListener('resize', handleResize); +} - return () => { +function subscribe(onStoreChange: () => void) { + listeners.add(onStoreChange); + + init(); + + return () => { + listeners.delete(onStoreChange); + + if (listeners.size === 0) { window.removeEventListener('resize', handleResize); - if (af.current !== null) cancelAnimationFrame(af.current); - }; + } + }; +} + +export function useScrollBarWidth(vertical = true): number { + const dim: Dimension = vertical ? 'vertical' : 'horizontal'; + const [value, setValue] = useState(() => state[dim]); + const dimRef = useRef(dim); + + dimRef.current = dim; + + useEffect(() => { + const onChange = () => setValue(state[dimRef.current]); + + onChange(); + + return subscribe(onChange); }, []); - return scrollBarWidth; + return value; }