Repository navigation
fix(webkit): [ENG-47063] flip, then shrink and scroll floating panels instead of clipping them - #973
Merged
Conversation
… instead of clipping them Every anchored panel (Select, MultiSelect, Calendar, Popover, Dropdown, Tooltip, the Table filter and column selector) positioned itself without checking whether it fit: usePlacement flipped but pinned an oversized panel to the top edge over its own trigger, and select-content / multi-select-content hand-rolled a "below, always" position with a fixed max-h-[20rem]. usePlacement (now index.ts) resolves the side flip-first and shrink-second, exposing maxHeight / maxWidth in panelStyle so the panel scrolls internally; the collision boundary is the viewport intersected with the trigger's scrolling ancestors (a Drawer body, a ScrollArea), not the window; the getFixedFrame correction for a transformed Teleport target moves into the composable; and a matchTriggerWidth option keeps list boxes as wide as their field. The pure geometry is exported as computePlacement / getClippingBoundary so the NavigationMenu positioner can wrap it in a follow-up. Select and MultiSelect drop their hand-rolled positioning; the other consumers gain overflow-y-auto so the cap scrolls instead of clipping. Five bundle budgets rise by 0.5 KB (calendar-root, chip, doc-prompt, doc-tooltip, doc-page-header): the composable grew ~0.9 KB gzip for the size, boundary and frame logic, which every overlay consumer inherits.
…e shared placement geometry use-navigation-menu-positioner.js drops its own flip/shift maths and calls the exported computePlacement / getClippingBoundary, so one positioning implementation remains in the package. It keeps what is specific to the menu: the settled popup size as the measured box, the arrow, rAF-coalesced updates and the first-placement transition guard. --available-width / --available-height were hardcoded to 100vw / 100vh; they now carry the real cap when the popup fits on neither side. The caps move from the positioner to the popup so the positioner still shrink-wraps for the natural-size measurement, measureNatural lifts any inline max-* while it measures so the cap cannot feed back into the size it is computed from, and the viewport scrolls (overflow-y-auto) only while the positioner is data-constrained, so an unconstrained morph never flashes a scrollbar. computePlacement gains alignOffset for the positioner's align-axis offset.
…ink-and-scroll placement The GroupsWithTopAndBottomSlots panel (355px) no longer fits under the trigger at 1280x720, so the placement caps it to the free space (328px) and lets it scroll instead of shifting it up over the trigger. Baselines regenerated by the Storybook Regenerate Baseline workflow for dark-desktop and light-desktop.
gdsantana
approved these changes
Sep 17, 2026
guilherme-santana-azion
approved these changes
Sep 17, 2026
lucas-langeloh-azion
approved these changes
Sep 18, 2026
Merged
gabriel-lisboa-azion
added a commit
that referenced
this pull request
Sep 21, 2026
demo/vue-sample had drifted 540 ahead / 31 behind main while most of its webkit work was reviewed and landed upstream (#941 command-menu, #942 GlobalHeader placements, #943 NavigationMenu, #952 Menu, #960 sidebar collapse, #973 floating-panel placement, #975 paginator, #977 templates exports, #921 amount ladder), after which main swept comments (#951) and reworked the CLI onto a derived bundle (#968/#970). Main is the source for everything the two share. Of the 28 conflicted source files, 17 differed from main only in comments — their code was byte-identical — and 6 more were genuinely newer on main, so all 23 take main verbatim, including the use-placement TypeScript rewrite that replaces the old index.js. Three carve-outs keep demo work main does not have yet: - sidebar: main's CSS-driven collapse is the base; the `side` axis is re-applied on top of it as data-[side=end] variants rather than the JS edge map it used to be, and the drag sign moves into the rail composable. The one test that asserted the removed inline transform now asserts the panel's published edge, since the direction is CSS the unstyled test env cannot see. - theme: texts.data.js keeps demo's 13 extra text-wrap and 10 extra tracking declarations (PR #982, in flight). Both sides carry the same 34 entries, so an entry count would not have caught the loss. - doc-prose keeps its chrome-padding fix (PR #981, in flight). catalog.json, authoring-baseline.json, the theme dist and the lockfile are regenerated rather than hand-resolved; the 13 conflicted visual baselines take main's and are regenerated from CI next. Verified: every file main touched is byte-identical to main except the 22 accounted for above; 2364 browser tests, 179 toolkit tests, lint, vue-tsc, 99.98% type coverage, both ratchets, the Storybook build and the webkit-sample build all pass.
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.
Summary
usePlacementis now size-aware: flip first, and when no side fits, cap the panel to the free space (maxHeight/maxWidthinpanelStyle) so it scrolls internally instead of overlapping its trigger or clipping at the viewport edge. The boundary is the nearest scrolling ancestor of the trigger intersected with the viewport, not the window.select-contentandmulti-select-contentmigrate ontousePlacement(thegetFixedFrameStorybook-zoom correction moved into the composable, plus amatchTriggerWidthoption), and the NavigationMenu positioner becomes a thin wrapper over the exported purecomputePlacement/getClippingBoundary, keeping only its arrow, rAF coalescing and settled-size handling. Its--available-height/--available-widthnow carry the real cap instead of a hardcoded100vh/100vw, and the viewport scrolls whiledata-constrained.overflow-y-auto/min-h-0so the computed cap produces scroll; new browser-mode tests cover flip, shrink and the scroll-container boundary for the composable, Select, MultiSelect, Popover and NavigationMenu.How to test
pnpm webkit:test— 109 files pass, including the newsrc/composables/use-placement/use-placement.test.tsand the flip/shrink cases inselect.test.ts,multi-select.test.ts,popover.test.tsandnavigation-menu.test.ts.pnpm storybook:dev, openComponents/Inputs/Select→LongList, shrink the browser window to ~420px tall and open the select: the list is capped to the space below the trigger, ends 8px above the viewport edge and scrolls inside the panel.style="position:fixed;bottom:8px"to#storybook-rootin DevTools) and open it: the list opens above the trigger (--popup-origin: bottom left), never overlapping it.Components/Navigation/NavigationMenu→Default, shrink the window to ~260px tall and hover "Soluções": the popup is capped 8px above the viewport edge, the positioner carriesdata-constrained, the panel scrolls, and the arrow stays centred on the trigger. Hover "Produtos": the box morphs to the new width and keeps the cap.pnpm --filter webkit size— every budgeted entry is within its (raised, see Notes) budget.Notes
panelStyle/resolvedPlacementare nowReadonly<Ref>; all 8 call sites keep working unchanged. The NavigationMenu positioner's public props are unchanged; it gains adata-constrainedattribute.calendar-root,chip,doc-prompt,doc-tooltip,doc-page-header: the composable grew ~0.9 KB gzip (size cap, scroll-ancestor boundary, frame correction,ResizeObserver), inherited by every overlay consumer. The old code lacked all three behaviours the ticket requires, so the growth is the feature, not slack.--popup-originchanges fromtoptotop left, so the open-state visual baselines for Select/MultiSelect may need regenerating.main(same count, verified against 444e357).multi-select-contentlacks the trigger-contains guard inonDocumentPointerDownthat Select got in ENG-46739;.specs/select.mdis stillstatus: draftand both select specs describe a Popover-API implementation that never shipped;NavigationMenuPositioner'sstickyprop is declared but never read.