Skip to content

fix(overlay): popover tooltip renders in ioswebkit#6194

Open
Rajdeepc wants to merge 4 commits intomainfrom
fix/overlay-ios-visualviewport-positioning
Open

fix(overlay): popover tooltip renders in ioswebkit#6194
Rajdeepc wants to merge 4 commits intomainfrom
fix/overlay-ios-visualviewport-positioning

Conversation

@Rajdeepc
Copy link
Copy Markdown
Contributor

@Rajdeepc Rajdeepc commented Apr 22, 2026

Description

On iOS Safari and WKWebView hosts (such as the Adobe Express iOS app), sp-popover opened via Overlay.open() or the trigger Lit directive lands visualViewport.offsetTop pixels below its trigger — typically 30–40 px — whenever the layout viewport diverges from the visual viewport (URL bar showing, pinch-zoom, virtual keyboard open, host-app bottom sheet).
This PR fixes the offset and keeps the overlay anchored as the viewports realign.

What changed

  • PlacementController.computePlacement — on WebKit, subtract visualViewport.offsetLeft / offsetTop from the computed translate before applying it. Floating UI calculates (x, y) from getBoundingClientRect() (layout viewport) but the overlay paints in the top layer relative to the visual viewport; this closes the gap.
  • PlacementController.placeOverlay — on WebKit, subscribe to window.visualViewport resize and scroll, and recompute placement on each event, so an open overlay re-anchors when the URL bar shows/hides, the keyboard opens, etc. Floating UI's autoUpdate does not observe visualViewport. Listeners are torn down on close.
  • Both branches are gated on isWebKit() so Chromium and Firefox are unaffected.
  • New regression Storybook story under Overlay → Bug Repros → iOS Popover Positioning that mirrors the bug-report screenshot and prints live visualViewport telemetry for on-device verification.
  • New WebKit-only unit test that asserts the visualViewport resize and scroll listeners are attached on open and removed on close.
  • Changeset added (patch to @spectrum-web-components/overlay).

Motivation and context

Reported by Adobe Express iOS engineering: popovers anchored to toolbar buttons render below their triggers in the iOS app. The bug reproduces in plain iOS Safari any time the URL bar is visible and disappears when the page is scrolled enough that the URL bar collapses. Root cause is a coordinate-system mismatch between Floating UI's layout-viewport input and the top layer's visual-viewport rendering on iOS WebKit.

Related issue(s)

  • fixes SWC-2031

Screenshots (if appropriate)

Before (iOS, URL bar showing) After
attach the original screenshot from the Slack/GitHub report attach a screenshot from the new iOS Popover Positioning story after the fix

Author's checklist

  • I have read the CONTRIBUTING and PULL_REQUESTS documents.
  • I have reviewed at the Accessibility Practices for this feature, see: Aria Practices
  • I have added automated tests to cover my changes.
  • I have included a well-written changeset if my change needs to be published.
  • I have included updated documentation if my change required it.

Reviewer's checklist

  • Includes a Github Issue with appropriate flag or Jira ticket number without a link
  • Includes thoughtfully written changeset if changes suggested include patch, minor, or major features
  • Automated tests cover all use cases and follow best practices for writing
  • Validated on all supported browsers
  • All VRTs are approved before the author can update Golden Hash

Manual review test cases

  • iOS Safari — popover anchors to trigger with URL bar visible
    1. Open this story
    2. Go to iOS device mode and Tap the "..." button on any row while the URL bar is showing (visualViewport.offsetTop > 0 in the telemetry panel).
    3. Expect the popover to anchor flush to the button (no vertical gap).

Device review

  • Did it pass in Desktop?
  • Did it pass in (emulated) Mobile?
  • Did it pass in (emulated) iPad?

Accessibility testing checklist

  • Keyboard (required — document steps below)
    1. Open Overlay → Bug Repros → iOS Popover Positioning in desktop Safari.
    2. Tab to a row's "..." button and press Enter / Space to open the popover; press Escape to dismiss.
    3. Expect focus to move into the popover on open and return to the trigger on close — no change from current behavior; this fix is positional only.
  • Screen reader (required — document steps below)
    1. With VoiceOver on, navigate to a row's "..." button on iOS Safari and activate it.
    2. Expect VoiceOver to announce the popover's role/name as it does on main; re-anchoring fires only sp-update-overlays and does not announce.
    3. Confirm there are no duplicate or unexpected announcements when the URL bar shows/hides while the popover is open.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 22, 2026

📚 Branch Preview Links

🔍 First Generation Visual Regression Test Results

When a visual regression test fails (or has previously failed while working on this branch), its results can be found in the following URLs:

Deployed to Azure Blob Storage: pr-6194

If the changes are expected, update the current_golden_images_cache hash in the circleci config to accept the new images. Instructions are included in that file.
If the changes are unexpected, you can investigate the cause of the differences and update the code accordingly.

@Rajdeepc Rajdeepc self-assigned this Apr 22, 2026
Copy link
Copy Markdown
Contributor

@rubencarvalho rubencarvalho left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! I have two nits, which prob are not blocking! 😄

// drift away from its trigger on iOS WebKit until the next resize.
// See `computePlacement` for the corresponding offset compensation.
const visualViewport = window.visualViewport;
if (visualViewport && isWebKit()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this match all webkit? i.e. Safari Desktop too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isWebKit() is intentionally broad here because the top-layer / popover-API rendering paints relative to visualViewport on all WebKit, so desktop Safari has the same bug under pinch-zoom. I've updated the inline comment to drop the "iOS" framing so the gate's intent is clearer.

// See `computePlacement` for the corresponding offset compensation.
const visualViewport = window.visualViewport;
if (visualViewport && isWebKit()) {
visualViewport.addEventListener('resize', this.updatePlacement);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be a bit more conservative here? Can you test and see how it feels with or without this being passive + maybe wrapping the handler in a raf?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both are good calls. Listeners are now { passive: true } (we never preventDefault in updatePlacement) and the handler is rAF-coalesced so a burst of resize/scroll during a URL-bar collapse or pinch gesture compresses to one Floating UI compute per frame. Felt smoother in the iOS Simulator on the repro story; no behavior change on desktop.

@Rajdeepc Rajdeepc marked this pull request as ready for review April 23, 2026 05:46
@Rajdeepc Rajdeepc requested a review from a team as a code owner April 23, 2026 05:46
@Rajdeepc Rajdeepc added bug Something isn't working Status:Ready for review PR ready for review or re-review. iOS Issues or PRs related specifically to iOS devices, including iPad. labels Apr 23, 2026
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 23, 2026

🦋 Changeset detected

Latest commit: 3ecf315

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 83 packages
Name Type
@spectrum-web-components/overlay Patch
@spectrum-web-components/action-menu Patch
@spectrum-web-components/combobox Patch
@spectrum-web-components/contextual-help Patch
@spectrum-web-components/menu Patch
@spectrum-web-components/picker Patch
@spectrum-web-components/popover Patch
@spectrum-web-components/textfield Patch
@spectrum-web-components/tooltip Patch
@spectrum-web-components/story-decorator Patch
@spectrum-web-components/bundle Patch
@spectrum-web-components/truncated Patch
@spectrum-web-components/breadcrumbs Patch
@spectrum-web-components/custom-vars-viewer Patch
@spectrum-web-components/action-bar Patch
@spectrum-web-components/card Patch
@spectrum-web-components/coachmark Patch
@spectrum-web-components/color-field Patch
@spectrum-web-components/number-field Patch
@spectrum-web-components/search Patch
documentation Patch
@spectrum-web-components/vrt-compare Patch
@spectrum-web-components/slider Patch
@spectrum-web-components/accordion Patch
@spectrum-web-components/action-button Patch
@spectrum-web-components/action-group Patch
@spectrum-web-components/alert-banner Patch
@spectrum-web-components/alert-dialog Patch
@spectrum-web-components/asset Patch
@spectrum-web-components/avatar Patch
@spectrum-web-components/badge Patch
@spectrum-web-components/button-group Patch
@spectrum-web-components/button Patch
@spectrum-web-components/checkbox Patch
@spectrum-web-components/clear-button Patch
@spectrum-web-components/close-button Patch
@spectrum-web-components/color-area Patch
@spectrum-web-components/color-handle Patch
@spectrum-web-components/color-loupe Patch
@spectrum-web-components/color-slider Patch
@spectrum-web-components/color-wheel Patch
@spectrum-web-components/dialog Patch
@spectrum-web-components/divider Patch
@spectrum-web-components/dropzone Patch
@spectrum-web-components/field-group Patch
@spectrum-web-components/field-label Patch
@spectrum-web-components/help-text Patch
@spectrum-web-components/icon Patch
@spectrum-web-components/icons-ui Patch
@spectrum-web-components/icons-workflow Patch
@spectrum-web-components/icons Patch
@spectrum-web-components/iconset Patch
@spectrum-web-components/illustrated-message Patch
@spectrum-web-components/infield-button Patch
@spectrum-web-components/link Patch
@spectrum-web-components/meter Patch
@spectrum-web-components/modal Patch
@spectrum-web-components/picker-button Patch
@spectrum-web-components/progress-bar Patch
@spectrum-web-components/progress-circle Patch
@spectrum-web-components/radio Patch
@spectrum-web-components/sidenav Patch
@spectrum-web-components/split-view Patch
@spectrum-web-components/status-light Patch
@spectrum-web-components/swatch Patch
@spectrum-web-components/switch Patch
@spectrum-web-components/table Patch
@spectrum-web-components/tabs Patch
@spectrum-web-components/tags Patch
@spectrum-web-components/thumbnail Patch
@spectrum-web-components/toast Patch
@spectrum-web-components/top-nav Patch
@spectrum-web-components/tray Patch
@spectrum-web-components/underlay Patch
@spectrum-web-components/base Patch
@spectrum-web-components/grid Patch
@spectrum-web-components/opacity-checkerboard Patch
@spectrum-web-components/reactive-controllers Patch
@spectrum-web-components/shared Patch
@spectrum-web-components/styles Patch
@spectrum-web-components/theme Patch
@spectrum-web-components/eslint-plugin Patch
@spectrum-web-components/stylelint-header-plugin Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@Rajdeepc Rajdeepc requested a review from rubencarvalho April 23, 2026 05:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working iOS Issues or PRs related specifically to iOS devices, including iPad. Status:Ready for review PR ready for review or re-review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants