Skip to content

fix(webkit): stop re-anchoring the popover panel when its own content scrolls - #891

Merged
isaque-bock-azion merged 2 commits into
mainfrom
fix/popover-inner-scroll-anchor
Aug 17, 2026
Merged

isaque-bock-azion merged 2 commits into
mainfrom
fix/popover-inner-scroll-anchor

Conversation

@gabriel-lisboa-azion

Copy link
Copy Markdown
Contributor

Summary

  • usePlacement's scroll listener is in the capture phase (scroll does not bubble), so it also fired for the panel's own scroll container — repositioning read two getBoundingClientRects and rewrote the panel's inline style on every scrolled frame, for a trigger that had not moved. That is what made a long scrollable panel (the "+N overflow" list) stutter.
  • Bail out early when the scroll target is inside the panel, before either the dismiss or the reposition path. The existing dismiss-path guard is now redundant and removed.

How to test

  1. cd packages/webkit && npx vitest run src/components/overlay/popover/popover.test.ts → 35 passed, including the new does not re-measure the trigger when the panel scrolls, but does when the page does.
  2. pnpm storybook:dev → Components/Overlay/Popover. Open a panel whose content overflows and scroll inside it: the panel stays put and the list scrolls smoothly. Then scroll the page: the panel still re-anchors (or dismisses) as before.

Notes

  • The new test asserts on the measurement (getBoundingClientRect spy), not on style.top: the recomputed coordinates are identical when the trigger has not moved, so a style assertion would pass in the broken state too.

… scrolls

The scroll listener is registered in the capture phase (scroll does not bubble),
so it also fired for the panel's own scroll container. Repositioning there read
two getBoundingClientRects and rewrote the panel's inline style on every scrolled
frame, for a trigger that had not moved — which is what made a long scrollable
panel stutter. Bail out early when the scroll target is inside the panel, before
either the dismiss or the reposition path.
@gabriel-lisboa-azion
gabriel-lisboa-azion requested a review from a team as a code owner August 14, 2026 15:18
gabriel-lisboa-azion added a commit that referenced this pull request Aug 14, 2026
The design-system half of this branch's working tree, committed here so the
main-based branches it was split onto (#891-#897) can be merged back in:
the popover inner-scroll fix, the toast-item and progress-bar corrections, the
two arrival animations, and the create-surface / errors / navigation skills.
@gabriel-lisboa-azion
gabriel-lisboa-azion force-pushed the fix/popover-inner-scroll-anchor branch from 6e7776e to 2bc01ac Compare August 17, 2026 12:41
@isaque-bock-azion
isaque-bock-azion merged commit bea9917 into main Aug 17, 2026
23 checks passed
@isaque-bock-azion
isaque-bock-azion deleted the fix/popover-inner-scroll-anchor branch August 17, 2026 13:27
gabriel-lisboa-azion added a commit that referenced this pull request Aug 26, 2026
Brings in the 17 commits main has since 2026-08-17 — the webkit
documentation category (#919), the shared leading column for Select /
MultiSelect / Dropdown (#935), resizable-panel (#902), the copy-button
tooltip and card-box header inset (#936), --container-site (#923), the
page-enter / content-enter arrivals (#896), the colored-icon currentColor
fix (#930) and the skills that landed with them.

Conflict resolution, all fifteen:

Demo wins where it already carries work that is still an OPEN PR on main
and main's side is the older shape that PR rewrote — toast-item (#900's
`h-[1lh]` line box over #892's `self-center`), use-placement and
popover.test (#932's translate anchoring over #891's prose), and the
theme animation tokens, where the entry-count canary confirms every
main entry survived alongside demo's.

Main wins where its side is a superset or the corrected form — DESIGN.md
(gains `text-body-prose-md` and `.layout-column-docs`), package.json
(keeping demo's footer block would have duplicated the four keys main
already merged in above it), and resizable-panel.test, whose named
imports are the form that keeps CodeQL from reading a compiler-generated
`.vue` default export as undefined.

Both sides where each added something: .size-limit.json keeps demo's
ratcheted 1.1 KB footer-root budget and takes main's fourteen doc-*
entries; the ui-craft skill and the CLI plan keep demo's microcopy
routing without duplicating main's entry.

Generated artifacts were rebuilt rather than hand-resolved — theme dist
via build:tokens, catalog.json via catalog:build — and both carry each
side's tokens and all 258 exports.
isaque-bock-azion added a commit that referenced this pull request Sep 9, 2026
…ansition (#932)

* fix(webkit): place overlay panels with translate so re-anchors can transition

Three fixes to `usePlacement`, all of them about a panel that moves after it is
already up.

THE PANEL IS PLACED WITH `translate`, NOT `top`/`left`. A translate is a
compositor property, so re-anchoring an OPEN panel can be transitioned into
place instead of snapping a layout property. And `translate` is a property of
its own, so it composes with the `transform: scale(...)` the open/close
keyframes animate rather than overwriting it — which a `transform:
translate3d(...)` here would.

`anchored` GATES THAT TRANSITION. It turns true one frame AFTER the opening
placements land, so the entrance stays the panel's own scale animation and only
later re-anchors glide. Without it a panel would travel in from the off-screen
seed. It resets on close but the panel is NOT moved off-screen there: it is
still in the DOM playing its leave animation.

THE PANEL IS MEASURED WITH `offsetWidth`/`offsetHeight`, NOT
`getBoundingClientRect`. The rect reports the TRANSFORMED box and the panel
opens under `scale(0.9) → scale(1)`: read mid-animation it was up to 10% narrow,
which placed a centred tooltip 4.1px off its trigger and left it there. The
layout box does not depend on which frame of the entrance it is read on.

A ResizeObserver RE-ANCHORS AN OPEN PANEL WHOSE CONTENT CHANGES SIZE. A centred
panel's position is computed from its measured width, so a panel that resizes
after placement keeps coordinates for the width it used to have — and nothing
else catches it, since the window has not resized and the page has not scrolled.
Measured on the copy control, whose tooltip swaps "Copy code" for "Copied" while
the pointer is still on it: the panel lost 21px of width, slid 4px, and ended
10.4px off centre in one frame. A transform does not affect the observed box and
placement only writes position, so this cannot feed back into itself.

Preserves #891 (`fix(webkit): stop re-anchoring the popover panel when its own
content scrolls`), which landed on main after this work started. The inner-scroll
guard and its regression test are byte-identical in behaviour; only the comments
differ, and the ones kept here are supersets that add why repositioning on an
inner scroll also cost two bounding rects and an inline-style write per frame.

* test(storybook): regenerate the dropdown and tooltip baselines for the translate anchoring

* test(storybook): regenerate the dropdown and tooltip mobile/tablet baselines on the merged tree

Both main (regular type weight, shared leading column) and this branch (translate anchoring)
had regenerated these eight snapshots; the merge could keep only one side, so they were
regenerated from the merged code by the baseline workflow (run 34390189299).

---------

Co-authored-by: Isaque dos Santos <isaque.santos@azion.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants