fix(desktop): keep submenus pinned to their trigger during scroll

A portaled DropdownMenuSubContent keeps its Floating UI positioning on
the default "optimized" strategy: passive scroll listeners that only
reposition between events. While the parent Content scrolls, the submenu
visibly lags behind (or fully detaches from) its SubTrigger because the
scroll -> getBoundingClientRect -> reposition pipeline cannot keep pace
with fast wheel events.

Set updatePositionStrategy="always" so autoUpdate runs a continuous rAF
loop and the submenu tracks its trigger frame-by-frame. The prop is
placed after the {...props} spread so callers cannot accidentally revert
it.

Fixes #55856
Salvages #55858 (same one-prop fix, authored by David Metcalfe)

Co-authored-by: David Metcalfe <80915+DavidMetcalfe@users.noreply.github.com>
This commit is contained in:
Hermes Agent
2026-09-25 12:17:35 -05:00
committed by brooklyn!
parent 2dcb8d7562
commit faf9231bc2

View File

@@ -487,6 +487,12 @@ function DropdownMenuSubContent({
// `overflow` clip. Radix Popper still anchors it to the SubTrigger and
// handles collision/flip. React events still bubble through the portal, so
// the parent menu doesn't treat a press here as an outside click.
//
// `updatePositionStrategy="always"` makes Floating UI's autoUpdate use a
// continuous rAF loop instead of the default "optimized" passive scroll
// listeners. Without it, a portaled submenu visibly lags behind its trigger
// while the parent Content scrolls: the scroll → getBoundingClientRect →
// reposition pipeline cannot keep pace with fast wheel events.
<DropdownMenuPrimitive.Portal container={container}>
<DropdownMenuPrimitive.SubContent
// Fixed `max-h-80` rather than the Radix available-height variable:
@@ -516,6 +522,10 @@ function DropdownMenuSubContent({
// first (child effects run first), so Radix marks it `none`.
style={{ ...(container ? { pointerEvents: 'auto' } : null), ...style }}
{...props}
// Placed after the spread so callers cannot override it: the rAF loop
// is load-bearing for scroll-tracked submenus and must never revert to
// the default passive-listener strategy.
updatePositionStrategy="always"
/>
</DropdownMenuPrimitive.Portal>
)