[Bug Fix] HoverCard: let the card escape a clipping ancestor

Merged
LE
1mo ago
+109-9

Related issue

No existing issue — happy to open one if you'd prefer to track it there.

Description

HoverCardContent is position: absolute, so a positioned ancestor that hides its overflow — a scroll area, a table cell, a truncating container — clips the card instead of letting it overflow.

DropdownMenu already solves this: the root switches between is-absolute and is-fixed from options[:strategy], the content picks up its position through a group variant, and the controller passes the strategy on to computePosition. This gives HoverCard the same escape hatch, following that implementation exactly:

  • HoverCard root gains group/hover-card plus is-absolute / is-fixed;
  • HoverCardContent trades its hardcoded absolute for group-[.is-absolute]/hover-card:absolute group-[.is-fixed]/hover-card:fixed;
  • the controller passes strategy: this.optionsValue.strategy || "absolute" to computePosition.

The default stays absolute, so existing usage renders and positions exactly as before.

Docs get an "Escaping a clipping ancestor" example, and mcp/data/registry.json is rebuilt (cd mcp && bundle exec rake mcp:build) so mcp-registry-check stays green.

BeforeAfter
imageimage

Testing instructions

  1. cd gem && bundle exec rake — 292 runs, 0 failures; standardrb clean across 402 files.
  2. cd docs && bin/dev, open /docs/hover_card and scroll to Escaping a clipping ancestor. Hover @joeldrapper: the card renders in full, overflowing the relative … overflow-hidden container around it.
  3. Revert that example to the default strategy (drop option: {strategy: "fixed"}) and hover again: the card is clipped by the container — the behaviour on main today.
  4. Every other HoverCard on the docs site still opens and positions as before, since the default is unchanged.

Summary by cubic

Fixes HoverCardContent being clipped by ancestors with overflow: hidden. HoverCard now supports option: {strategy: "fixed"} to position the card against the viewport instead; the default remains absolute, so existing usage is unchanged.

  • Mirrors DropdownMenu: the root switches between is-absolute and is-fixed, the content picks up its position through a group variant, and the controller passes the strategy to computePosition.
  • Adds an "Escaping a clipping ancestor" docs example and rebuilds mcp/data/registry.json.
  • Also fixes DropdownMenu's strategy ternary so unsupported values fall back to absolute like Floating UI, preventing a fixed card from drifting on scroll.