Skip to content

Commit 99dcf38

Browse files
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 <[email protected]>
1 parent 6643f6a commit 99dcf38

File tree

3 files changed

+56
-0
lines changed

3 files changed

+56
-0
lines changed

core/src/components.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,10 @@ export namespace Components {
868868
* 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.
869869
*/
870870
"getScrollElement": () => Promise<HTMLElement>;
871+
/**
872+
* 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.
873+
*/
874+
"recalculateDimensions": () => Promise<void>;
871875
/**
872876
* Scroll by a specified X/Y distance in the component.
873877
* @param x The amount to scroll by on the horizontal axis.

core/src/components/content/content.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,17 @@ export class Content implements ComponentInterface {
254254
}
255255
}
256256

257+
/**
258+
* Recalculate content dimensions. Called by overlays (e.g., popover) when
259+
* sibling elements like headers or footers have finished rendering and their
260+
* heights are available, ensuring accurate offset-top calculations.
261+
* @internal
262+
*/
263+
@Method()
264+
async recalculateDimensions(): Promise<void> {
265+
readTask(() => this.readDimensions());
266+
}
267+
257268
private readDimensions() {
258269
const page = getPageElement(this.el);
259270
const top = Math.max(this.el.offsetTop, 0);

core/src/components/popover/popover.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export class Popover implements ComponentInterface, PopoverInterface {
6464
private destroyTriggerInteraction?: () => void;
6565
private destroyKeyboardInteraction?: () => void;
6666
private destroyDismissInteraction?: () => void;
67+
private headerResizeObserver?: ResizeObserver;
6768

6869
private inline = false;
6970
private workingDelegate?: FrameworkDelegate;
@@ -361,6 +362,11 @@ export class Popover implements ComponentInterface, PopoverInterface {
361362
if (destroyTriggerInteraction) {
362363
destroyTriggerInteraction();
363364
}
365+
366+
if (this.headerResizeObserver) {
367+
this.headerResizeObserver.disconnect();
368+
this.headerResizeObserver = undefined;
369+
}
364370
}
365371

366372
componentWillLoad() {
@@ -491,6 +497,8 @@ export class Popover implements ComponentInterface, PopoverInterface {
491497
inline
492498
);
493499

500+
this.recalculateContentOnHeaderReady();
501+
494502
if (!this.keyboardEvents) {
495503
this.configureKeyboardInteraction();
496504
}
@@ -540,6 +548,39 @@ export class Popover implements ComponentInterface, PopoverInterface {
540548
unlock();
541549
}
542550

551+
/**
552+
* Watch the header for height changes and trigger content dimension
553+
* recalculation when the header has a height > 0. This sets the offset-top
554+
* of the content to the height of the header correctly.
555+
*/
556+
private recalculateContentOnHeaderReady() {
557+
const popoverContent = this.el.shadowRoot?.querySelector('.popover-content');
558+
if (!popoverContent) {
559+
return;
560+
}
561+
562+
const contentContainer = this.usersElement || popoverContent;
563+
564+
const header = contentContainer.querySelector('ion-header') as HTMLElement | null;
565+
const contentElements = contentContainer.querySelectorAll('ion-content');
566+
567+
if (!header || contentElements.length === 0) {
568+
return;
569+
}
570+
571+
this.headerResizeObserver = new ResizeObserver(async () => {
572+
if (header.offsetHeight > 0) {
573+
this.headerResizeObserver?.disconnect();
574+
this.headerResizeObserver = undefined;
575+
for (const contentEl of contentElements) {
576+
await contentEl.recalculateDimensions();
577+
}
578+
}
579+
});
580+
581+
this.headerResizeObserver.observe(header);
582+
}
583+
543584
/**
544585
* Dismiss the popover overlay after it has been presented.
545586
* This is a no-op if the overlay has not been presented yet. If you want

0 commit comments

Comments
 (0)