From 38f75ab06dc0ba8d67ce4bddf19509127b1c8018 Mon Sep 17 00:00:00 2001 From: Shantur Rathore Date: Mon, 2 Mar 2026 23:17:22 +0000 Subject: [PATCH] fix(ui): prevent virtual items mounting offscreen --- packages/ui/src/components/virtual-item.tsx | 26 ++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/virtual-item.tsx b/packages/ui/src/components/virtual-item.tsx index e81e9062..5683e05e 100644 --- a/packages/ui/src/components/virtual-item.tsx +++ b/packages/ui/src/components/virtual-item.tsx @@ -334,7 +334,31 @@ export default function VirtualItem(props: VirtualItemProps) { // Ignore measurement failures; IntersectionObserver will correct us. } - intersectionCleanup = subscribeToSharedObserver(wrapperRef, targetRoot, margin, (entry) => { + const wrapperEl = wrapperRef + intersectionCleanup = subscribeToSharedObserver(wrapperEl, targetRoot, margin, (entry) => { + // IntersectionObserver can produce transient false-positives during pane + // activation/layout transitions (e.g. `isIntersecting: true` for items far + // outside the scroll root). For element roots, prefer explicit rect math. + if (targetRoot && !(targetRoot instanceof Document)) { + // When rootBounds is null we cannot trust the entry; treat as hidden. + if (entry.rootBounds === null) { + queueVisibility(false) + return + } + try { + const rootRect = (targetRoot as Element).getBoundingClientRect() + const visible = shouldRenderByRects({ + wrapperRect: wrapperEl.getBoundingClientRect(), + rootRect: { top: rootRect.top, bottom: rootRect.bottom }, + margin, + }) + queueVisibility(visible) + return + } catch { + // Fall through to the entry-based heuristic. + } + } + const nextVisible = shouldRenderEntry(entry) queueVisibility(nextVisible) })