Skip to content

Port fix from vitejs/devtools#518: FloatingPopover escapes a transformed ancestor's containing block; native Select mode #205

Description

@antfubot

Context

vitejs/devtools#518 ("fix(core): FloatingPopover escapes a transformed ancestor's containing block; native Select mode") by @dvcolomban targeted FloatingPopover, floating-position.ts, and the json-render Select component, at packages/core/src/client/webcomponents/** in vitejs/devtools. That tree moved here as part of the v0.9 migration — the equivalent code today lives under packages/hub-ui/src/client/components/floating/ and packages/json-render-ui/src/components/Select.ts.

Since the original repo has since deleted that source, the PR is unmergeable there. This issue tracks porting its fix here instead.

Original PR description

What changed

  • FloatingPopover resolves the outermost ancestor that forms a containing block for position: fixed descendants and <Teleport>s its panel into that ancestor's parent. With no such ancestor, it renders in place as before.
  • New surface prop on FloatingPopover, tooltip (default) or menu. menu renders p0 bg-glass:25 border-#8883 in place of px2 p1 bg-glass:80 border-base.
  • Select gains native, rendering a real <select> instead of the custom listbox. It has no icon, description or searchable support.
  • Select's dropdown now uses surface="menu" and gap: 4, colours the selected option with primary and the active row with surfaceBadge, and no longer passes !p0 in panelClass.
  • webcomponents API snapshot updated for SelectProps.native.

Why it changed

A position: fixed panel is positioned relative to its nearest ancestor carrying a non-none transform or filter, not the viewport, and is clipped by that ancestor's overflow. Rendered inside such a panel, the Select dropdown was offset from its trigger and cut off — unusable. Escaping the outermost rather than the nearest such ancestor matters because escaping one can land inside another. The teleport target stays inside the shadow root, since that is where the panel's stylesheet is adopted and document.body would strip its styling.

The menu surface exists because a floating panel stacks over an already-tinted surface, where tooltip-strength glass composites to near-black, and because tooltip padding is wrong for a listbox — it inset the options and made the panel wider than its trigger.

native is an escape hatch worth having regardless of the fix above: the browser draws a native option list outside the page's layout, so no ancestor can clip or misposition it at all, which is the dependable choice for a Select embedded in a host layout this component doesn't control. Its option list needs no styling here, as ColorSchemeRoot already sets color-scheme inline and that inherits.

Diff: https://github.com/vitejs/devtools/pull/518/files

For agents picking this up

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions