fix(popover): recalculate the content dimensions after the header has fully loaded (#30853)

Issue number: internal

---------

## What is the current behavior?
A translucent header in a popover does not consistently render as
translucent upon presenting due to the `offset-top` of the content being
set to `0`.

## What is the new behavior?
Watch the header for height changes using `ResizeObserver` and
recalculate the content dimensions when the header height is greater
than `0`.

## Does this introduce a breaking change?
- [ ] Yes
- [x] No

---------

Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
This commit is contained in:
Brandy Smith
2025-12-10 15:56:59 -05:00
committed by GitHub
parent 6643f6a115
commit 99dcf3810a
3 changed files with 56 additions and 0 deletions

View File

@@ -868,6 +868,10 @@ export namespace Components {
* Get the element where the actual scrolling takes place. This element can be used to subscribe to `scroll` events or manually modify `scrollTop`. However, it's recommended to use the API provided by `ion-content`: i.e. Using `ionScroll`, `ionScrollStart`, `ionScrollEnd` for scrolling events and `scrollToPoint()` to scroll the content into a certain point.
*/
"getScrollElement": () => Promise<HTMLElement>;
/**
* Recalculate content dimensions. Called by overlays (e.g., popover) when sibling elements like headers or footers have finished rendering and their heights are available, ensuring accurate offset-top calculations.
*/
"recalculateDimensions": () => Promise<void>;
/**
* Scroll by a specified X/Y distance in the component.
* @param x The amount to scroll by on the horizontal axis.

View File

@@ -254,6 +254,17 @@ export class Content implements ComponentInterface {
}
}
/**
* Recalculate content dimensions. Called by overlays (e.g., popover) when
* sibling elements like headers or footers have finished rendering and their
* heights are available, ensuring accurate offset-top calculations.
* @internal
*/
@Method()
async recalculateDimensions(): Promise<void> {
readTask(() => this.readDimensions());
}
private readDimensions() {
const page = getPageElement(this.el);
const top = Math.max(this.el.offsetTop, 0);

View File

@@ -64,6 +64,7 @@ export class Popover implements ComponentInterface, PopoverInterface {
private destroyTriggerInteraction?: () => void;
private destroyKeyboardInteraction?: () => void;
private destroyDismissInteraction?: () => void;
private headerResizeObserver?: ResizeObserver;
private inline = false;
private workingDelegate?: FrameworkDelegate;
@@ -361,6 +362,11 @@ export class Popover implements ComponentInterface, PopoverInterface {
if (destroyTriggerInteraction) {
destroyTriggerInteraction();
}
if (this.headerResizeObserver) {
this.headerResizeObserver.disconnect();
this.headerResizeObserver = undefined;
}
}
componentWillLoad() {
@@ -491,6 +497,8 @@ export class Popover implements ComponentInterface, PopoverInterface {
inline
);
this.recalculateContentOnHeaderReady();
if (!this.keyboardEvents) {
this.configureKeyboardInteraction();
}
@@ -540,6 +548,39 @@ export class Popover implements ComponentInterface, PopoverInterface {
unlock();
}
/**
* Watch the header for height changes and trigger content dimension
* recalculation when the header has a height > 0. This sets the offset-top
* of the content to the height of the header correctly.
*/
private recalculateContentOnHeaderReady() {
const popoverContent = this.el.shadowRoot?.querySelector('.popover-content');
if (!popoverContent) {
return;
}
const contentContainer = this.usersElement || popoverContent;
const header = contentContainer.querySelector('ion-header') as HTMLElement | null;
const contentElements = contentContainer.querySelectorAll('ion-content');
if (!header || contentElements.length === 0) {
return;
}
this.headerResizeObserver = new ResizeObserver(async () => {
if (header.offsetHeight > 0) {
this.headerResizeObserver?.disconnect();
this.headerResizeObserver = undefined;
for (const contentEl of contentElements) {
await contentEl.recalculateDimensions();
}
}
});
this.headerResizeObserver.observe(header);
}
/**
* Dismiss the popover overlay after it has been presented.
* This is a no-op if the overlay has not been presented yet. If you want