diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 7721801eb60..5e47667392b 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -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; + /** + * 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; /** * Scroll by a specified X/Y distance in the component. * @param x The amount to scroll by on the horizontal axis. diff --git a/core/src/components/content/content.tsx b/core/src/components/content/content.tsx index 74e44c63597..361939f7c27 100644 --- a/core/src/components/content/content.tsx +++ b/core/src/components/content/content.tsx @@ -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 { + readTask(() => this.readDimensions()); + } + private readDimensions() { const page = getPageElement(this.el); const top = Math.max(this.el.offsetTop, 0); diff --git a/core/src/components/popover/popover.tsx b/core/src/components/popover/popover.tsx index 9b07ab2f275..af0e613297d 100644 --- a/core/src/components/popover/popover.tsx +++ b/core/src/components/popover/popover.tsx @@ -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