From bee8d87da85ad8a4b3aac4c722aee13e85fdc3c8 Mon Sep 17 00:00:00 2001 From: FlowerBlackG Date: Sun, 28 Sep 2025 21:50:24 +0800 Subject: [PATCH 1/2] fix(popup): fix popup positioning issue with React Flow pan and zoom fix #3851 --- packages/components/popup/Popup.tsx | 83 +++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/packages/components/popup/Popup.tsx b/packages/components/popup/Popup.tsx index 993106979f..4889fa8b6f 100644 --- a/packages/components/popup/Popup.tsx +++ b/packages/components/popup/Popup.tsx @@ -152,6 +152,89 @@ const Popup = forwardRef((originalProps, ref) => { } }, [visible, updateScrollTop, getTriggerDom]); + + // 监听 trigger 位置变化调整位置 + // 缺陷:无法监听鼠标滚动。 + const positionState = useRef({ x: 0, y: 0, width: 0, height: 0 }); + const frameIdRef = useRef(null); + + useEffect(() => { + const triggerEl = getRefDom(triggerRef); + if (!visible || !triggerEl) { + return; + } + + const checkPosition = (once: boolean = false) => { + const { x, y, width, height } = triggerEl.getBoundingClientRect(); + if ( + x !== positionState.current.x || + y !== positionState.current.y || + width !== positionState.current.width || + height !== positionState.current.height + ) { + popperRef.current?.update?.(); + positionState.current = { x, y, width, height }; + } + + if (!once) { + // 持续请求下一帧 + frameIdRef.current = requestAnimationFrame(() => checkPosition(once)); + } + }; + + const startLoop = () => { + if (frameIdRef.current === null) { + checkPosition(); + } + }; + + const stopLoop = () => { + if (frameIdRef.current !== null) { + cancelAnimationFrame(frameIdRef.current); + frameIdRef.current = null; + } + }; + + + window.addEventListener('pointerdown', startLoop); + window.addEventListener('pointerup', stopLoop); + + return () => { + stopLoop(); + window.removeEventListener('pointerdown', startLoop); + window.removeEventListener('pointerup', stopLoop); + }; + }, [visible]); + + + // 对 React Flow 的特殊适配,可同时支持滚动、缩放等场景 + useEffect(() => { + const triggerEl = getRefDom(triggerRef); + if (!visible || !triggerEl) { + return; + } + + const viewportEl = triggerEl.closest('.react-flow__viewport'); + + if (!viewportEl) { + return; // 未检测到 react-flow,不用管了。 + } + + const observer = new MutationObserver(() => { + popperRef.current?.update?.(); + }); + + observer.observe(viewportEl, { + attributes: true, + attributeFilter: ['style'], // 只关心 'style' 这一个属性,性能更好。 + }); + + return () => { + observer.disconnect(); + }; + }, [visible]); + + function handleExited() { !destroyOnClose && popupElement && (popupElement.style.display = 'none'); } From 4e4ebd0326b46d491342f65368dffd5800399fa5 Mon Sep 17 00:00:00 2001 From: FlowerBlackG Date: Sun, 28 Sep 2025 21:58:57 +0800 Subject: [PATCH 2/2] style(popup.tsx): lint --- packages/components/popup/Popup.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/popup/Popup.tsx b/packages/components/popup/Popup.tsx index 4889fa8b6f..75be5ab252 100644 --- a/packages/components/popup/Popup.tsx +++ b/packages/components/popup/Popup.tsx @@ -164,7 +164,7 @@ const Popup = forwardRef((originalProps, ref) => { return; } - const checkPosition = (once: boolean = false) => { + const checkPosition = (once = false) => { const { x, y, width, height } = triggerEl.getBoundingClientRect(); if ( x !== positionState.current.x ||