fix(hub-ui,json-render-ui): FloatingPopover escapes a transformed ancestor's containing block; native Select mode - #228
Open
antfubot wants to merge 1 commit into
Conversation
…estor's containing block; native Select mode `FloatingPopover` (hub-ui) is positioned `fixed` against its anchor's viewport rect, but a `transform`/`filter`/`contain` ancestor makes itself the containing block for that `position: fixed`, so the panel ends up positioned relative to — and clipped by — that ancestor instead of the viewport. `resolveFixedEscapeTarget` walks up from the anchor to the outermost such ancestor (escaping only the nearest one can land inside another) and `<Teleport>`s the panel to its parent; walking `parentElement` stops at a shadow root's boundary, so a dock's popover never escapes the shadow root its stylesheet is scoped to. With no such ancestor, the panel renders in place as before. `Select` (json-render-ui) gains `native`, rendering a real `<select>` instead of `FormSelect`/`FormCombobox`: the browser draws its option list outside the page's layout, so no ancestor can clip or reposition it at all — a dependable escape hatch for a `Select` embedded in a host layout this component doesn't control, at the cost of `icon`, `description` and `searchable`, which have no native equivalent. Ports vitejs/devtools#518, adapted to this fork: that PR's `surface` prop and `Select`'s switch to it don't have an equivalent here — this fork's `Select` renders through @antfu/design's `FormSelect`/`FormCombobox` (reka-ui popovers), not through `FloatingPopover`, so there's no consumer to point at a menu surface. (Those reka-ui popovers default to portalling into `document.body`, which suffers the same class of bug inside a shadow root or a transformed ancestor — worth a follow-up, but out of scope for a first-party fix here.) Closes #205 Co-authored-by: dvcolomban <dinh-van.colomban@contentsquare.com>
✅ Deploy Preview for devfra ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
FloatingPopover(hub-ui) resolves the outermost ancestor that forms a containing block forposition: fixeddescendants and<Teleport>s its panel into that ancestor's parent. With no such ancestor, it renders in place as before.Select(json-render-ui) gainsnative, rendering a real<select>instead ofFormSelect/FormCombobox. It has noicon,descriptionorsearchablesupport.SelectPropsSchema(json-render) gains the matchingnativefield, so a spec can opt an element into it and the tsnapi API snapshot is updated.EscapesTransformedAncestor) demonstrating the escape fix against atransformed,overflow-clipping ancestor.Why it changed
A
position: fixedpanel is positioned relative to its nearest ancestor carrying a non-nonetransform/filter/contain, not the viewport, and is clipped by that ancestor'soverflow. Any ofFloatingPopover's current consumers — the dock overflow panel, group popover, context menu, edge-position dropdown — would be mispositioned and clipped if rendered inside such an ancestor. Escaping the outermost rather than the nearest such ancestor matters because escaping one can land inside another. WalkingparentElement(notparentNode) naturally stops at a shadow root's boundary, so the teleport target stays inside the dock's shadow root — where its stylesheet is scoped — rather than escaping todocument.bodyand losing its styling.nativeonSelectis an escape hatch worth having regardless: the browser draws a native option list outside the page's layout, so no ancestor can clip or misposition it at all — the dependable choice for aSelectembedded in a host layout this component doesn't control.Porting notes
This ports vitejs/devtools#518 ("fix(core): FloatingPopover escapes a transformed ancestor's containing block; native Select mode" by @dvcolomban), per #205. That PR targeted
packages/core/src/client/webcomponents/**in vitejs/devtools, which moved here aspackages/hub-ui/src/client/components/floating/**andpackages/json-render-ui/src/components/Select.tsin the v0.9 migration.Adapted rather than ported verbatim:
surfaceprop onFloatingPopover(tooltip|menu) andSelect's dropdown switching tosurface="menu"don't have an equivalent here — this fork'sSelectrenders through@antfu/design'sFormSelect/FormCombobox(reka-ui popovers), not throughFloatingPopover, so there's no consumer to point a menu surface at.FloatingPopover's own five menu-style consumers (dock overflow, group popover, context menu, edge-position dropdown) are left as-is — restyling their padding is a separate, visually-verifiable change I didn't want to bundle into a positioning bug fix.native's styling is written against this fork's design-system semantic classes (border-base,bg-base,color-base, …) per this repo's conventions, rather than the upstream fix's inline styles.@antfu/design'sFormSelect/FormCombobox(reka-uiSelectPortal/ComboboxPortal) default to portalling intodocument.body, which suffers the same class of positioning/clipping bug inside a shadow root or transformed ancestor. Fixing that is out of scope here (it's an upstream dependency, not first-party code) but may be worth its own follow-up issue.Credit to @dvcolomban for the original fix and rationale.
Closes #205
Prepared with the help of an agent.