Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
11 changes: 11 additions & 0 deletions core/src/components/content/content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
41 changes: 41 additions & 0 deletions core/src/components/popover/popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -361,6 +362,11 @@ export class Popover implements ComponentInterface, PopoverInterface {
if (destroyTriggerInteraction) {
destroyTriggerInteraction();
}

if (this.headerResizeObserver) {
this.headerResizeObserver.disconnect();
this.headerResizeObserver = undefined;
}
}

componentWillLoad() {
Expand Down Expand Up @@ -491,6 +497,8 @@ export class Popover implements ComponentInterface, PopoverInterface {
inline
);

this.recalculateContentOnHeaderReady();

if (!this.keyboardEvents) {
this.configureKeyboardInteraction();
}
Expand Down Expand Up @@ -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
Expand Down
Loading