Skip to content

fix(Menu): only extend the submenu safe area while the pointer moves closer - #2192

Merged
huntabyte merged 3 commits into
huntabyte:mainfrom
samitkapoor:fix/submenu-intent
Nov 1, 2026
Merged

huntabyte merged 3 commits into
huntabyte:mainfrom
samitkapoor:fix/submenu-intent

Conversation

@samitkapoor

@samitkapoor samitkapoor commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #2191

Problem

While the pointer is inside a submenu's safe area, every pointermove restarts a 500ms fallback timer, whether or not the pointer is getting closer to the submenu. Moving diagonally onto a sibling SubTrigger and stopping there leaves the sibling unresponsive for ~500ms, and small movements while the pointer settles keep extending the wait.

Changes

All in MenuSubmenuIntent:

  • A move only restarts the timer when it brings the pointer at least 1px closer to the target than it has been so far. Moves that keep the same distance or drift away leave the timer counting, so jitter and sideways drift no longer extend the safe area.
  • The window is now SUBMENU_INTENT_TIMEOUT_MS = 100 (was a hardcoded 500).
  • Distance is measured to the target's rect, so it works for every submenu side and for the return path to the trigger.
Pointer move inside the safe area Before After
Gets closer to the submenu than ever before (by ≥1px) Restart the 500ms timer Restart the 100ms timer
Same distance (e.g. a pure vertical move) Restart the 500ms timer Leave the timer counting
Drifts away Restart the 500ms timer Leave the timer counting
No events (pointer stopped) Hand off 500ms after the last move Hand off 100ms after the last move closer
Leaves the safe area Close immediately Close immediately (unchanged)

Unchanged: the triangle and corridor geometry, closing immediately when the pointer leaves the safe area, the 100ms return-to-trigger delay, and the descendant sub-content path (#1945).

Why 100ms: tried on the "Nested Menus" docs demo with six sibling sub triggers. 50ms felt snappy but leaves little room for a long frame or a corrective pause near the end of a slow diagonal move; 100ms kept real moves into the submenu working while making sibling hand-off feel immediate. For reference, the transit timeout was a fixed 300ms before 2.17.0.

Result

The "Nested Menus" docs demo with this change: diagonal moves between "Status" and "Issue properties" hand off as soon as the pointer settles, and moves into either submenu still keep it open.

CleanShot.2026-10-08.at.7.08.59.PM.mp4

Tests

  • New test in the DropdownMenu and ContextMenu browser suites: after the pointer leaves towards the submenu, repeated moves that don't get closer must not keep it open. It fails on main and passes with this change in Chromium and WebKit.
  • The test components get a subContentProps passthrough (same pattern as contentProps / subTriggerProps) so the test can set sideOffset: 16. In the test layout, the submenu sits flush against the trigger (0.27px gap in ContextMenu, 0.3px overlap in DropdownMenu), which leaves no reliable point between the two. Existing tests don't pass it, so their layout is unchanged.
  • DropdownMenu, ContextMenu and Menubar suites: 202 passed. Two WebKit-only DropdownMenu tests time out ("should maintain correct focus when opening dropdown via keyboard", "should not scroll to previous dropdown trigger when closing a different dropdown"); they fail the same way on main.

Side note (not changed here)

When a submenu opens to the left, a pointerleave exit point that isn't inside the corridor doesn't engage the safe area: the exit point is the intent triangle's apex, and isPointInPolygon counts that vertex as inside for right-side submenus but outside for left-side ones. As a result, the existing ContextMenu test "should keep submenu open while pointer is moving toward it" passes without the safe area ever engaging. Happy to look at that separately if it's worth fixing.

@changeset-bot

changeset-bot Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a9b5ce9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
bits-ui Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor
built with Refined Cloudflare Pages Action

⚡ Cloudflare Pages Deployment

Name Status Preview Last Commit
bits-ui ✅ Ready (View Log) Visit Preview a9b5ce9

@huntabyte
huntabyte merged commit d793533 into huntabyte:main Nov 1, 2026
6 checks passed
@github-actions github-actions Bot mentioned this pull request Nov 1, 2026

This branch was successfully deployed

1 active deployment
Preview — a9b5ce90 Deployed Nov 1, 2026 by github-actions[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Menu: submenu safe area is slow and imprecise at detecting pointer intent

2 participants