diff --git a/core/src/components/content/content.tsx b/core/src/components/content/content.tsx index 361939f7c27..a4aa2882bc8 100644 --- a/core/src/components/content/content.tsx +++ b/core/src/components/content/content.tsx @@ -25,6 +25,8 @@ import type { ScrollBaseDetail, ScrollDetail } from './content-interface'; }) export class Content implements ComponentInterface { private watchDog: ReturnType | null = null; + private mutationObserver: MutationObserver | null = null; + private resizeObserver: ResizeObserver | null = null; private isScrolling = false; private lastScroll = 0; private queued = false; @@ -168,10 +170,12 @@ export class Content implements ComponentInterface { closestTabs.addEventListener('ionTabBarLoaded', this.tabsLoadCallback); } } + this.connectObservers(); } disconnectedCallback() { this.onScrollEnd(); + this.disconnectObservers(); if (hasLazyBuild(this.el)) { /** @@ -420,6 +424,93 @@ export class Content implements ComponentInterface { return promise; } + /** + * We need to observe the parent element to detect when + * or elements are added/removed + * or resized. This ensures the content offset is recalculated + * dynamically. + */ + private connectObservers() { + if (!Build.isBrowser) { + return; + } + + const parent = this.el.parentElement; + if (!parent) { + return; + } + + if ('ResizeObserver' in window) { + let timeout: any; + this.resizeObserver = new ResizeObserver(() => { + clearTimeout(timeout); + timeout = setTimeout(() => this.resize(), 100); + }); + } + + if ('MutationObserver' in window) { + this.mutationObserver = new MutationObserver((mutations) => { + let shouldUpdate = false; + + for (const mutation of mutations) { + if (mutation.type === 'childList') { + mutation.addedNodes.forEach((node: Node) => { + if (node instanceof HTMLElement && (node.tagName === 'ION-HEADER' || node.tagName === 'ION-FOOTER')) { + shouldUpdate = true; + } + }); + + mutation.removedNodes.forEach((node: Node) => { + if (node instanceof HTMLElement && (node.tagName === 'ION-HEADER' || node.tagName === 'ION-FOOTER')) { + shouldUpdate = true; + } + }); + } + } + + if (shouldUpdate) { + this.refreshResizeObserver(); + this.resize(); + } + }); + + this.mutationObserver.observe(parent, { childList: true }); + } + + this.refreshResizeObserver(); + } + + private disconnectObservers() { + if (this.mutationObserver) { + this.mutationObserver.disconnect(); + this.mutationObserver = null; + } + if (this.resizeObserver) { + this.resizeObserver.disconnect(); + this.resizeObserver = null; + } + } + + private refreshResizeObserver() { + if (!this.resizeObserver || !this.el.parentElement) { + return; + } + + this.resizeObserver.disconnect(); + + const headers = this.el.parentElement.querySelectorAll('ion-header'); + const footers = this.el.parentElement.querySelectorAll('ion-footer'); + + const observer = this.resizeObserver; + + if (observer === null || observer === undefined) { + return; + } + + headers.forEach((header) => observer.observe(header)); + footers.forEach((footer) => observer.observe(footer)); + } + private onScrollStart() { this.isScrolling = true; this.ionScrollStart.emit({ diff --git a/core/src/components/content/test/auto-offset/content.e2e.ts b/core/src/components/content/test/auto-offset/content.e2e.ts new file mode 100644 index 00000000000..0580e3e7a12 --- /dev/null +++ b/core/src/components/content/test/auto-offset/content.e2e.ts @@ -0,0 +1,48 @@ +import { expect } from '@playwright/test'; +import { test, configs } from '@utils/test/playwright'; + +configs({ modes: ['ios'] }).forEach(({ title, screenshot, config }) => { + test.describe(title('content: auto offset'), () => { + test('should not have visual regressions', async ({ page }) => { + await page.goto(`/src/components/content/test/auto-offset`, config); + await page.setIonViewport(); + await expect(page).toHaveScreenshot(screenshot(`content-auto-offset-initial`)); + }); + + test('should update offsets when header height changes', async ({ page }) => { + await page.goto(`/src/components/content/test/auto-offset`, config); + await page.setIonViewport(); + + const content = page.locator('ion-content'); + const before = await content.evaluate((el: HTMLElement) => getComputedStyle(el).getPropertyValue('--offset-top')); + + await page.click('#expand-header-btn'); + + await expect(content).not.toHaveCSS('--offset-top', before); + + await expect(page).toHaveScreenshot(screenshot(`content-auto-offset-header-updated`)); + }); + + test('should update offsets when footer height changes', async ({ page }) => { + await page.goto(`/src/components/content/test/auto-offset`, config); + await page.setIonViewport(); + + const content = page.locator('ion-content'); + const before = await content.evaluate((el: HTMLElement) => + getComputedStyle(el).getPropertyValue('--offset-bottom') + ); + + await page.click('#expand-footer-btn'); + + await expect(content).not.toHaveCSS('--offset-bottom', before); + + const after = await content.evaluate((el: HTMLElement) => + getComputedStyle(el).getPropertyValue('--offset-bottom') + ); + + expect(after).not.toBe(before); + + await expect(page).toHaveScreenshot(screenshot(`content-auto-offset-footer-updated`)); + }); + }); +}); diff --git a/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-footer-updated-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-footer-updated-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..3f9957f19a1 Binary files /dev/null and b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-footer-updated-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-footer-updated-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-footer-updated-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..a5c61844404 Binary files /dev/null and b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-footer-updated-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-footer-updated-ios-ltr-Mobile-Safari-linux.png b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-footer-updated-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..de1e35843c4 Binary files /dev/null and b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-footer-updated-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-footer-updated-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-footer-updated-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..cd585838bbf Binary files /dev/null and b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-footer-updated-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-footer-updated-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-footer-updated-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..1710b3d807b Binary files /dev/null and b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-footer-updated-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-footer-updated-ios-rtl-Mobile-Safari-linux.png b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-footer-updated-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 00000000000..6752ff84cac Binary files /dev/null and b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-footer-updated-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-header-updated-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-header-updated-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..93a4640de4d Binary files /dev/null and b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-header-updated-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-header-updated-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-header-updated-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..7066e66a236 Binary files /dev/null and b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-header-updated-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-header-updated-ios-ltr-Mobile-Safari-linux.png b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-header-updated-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..42af1d9de08 Binary files /dev/null and b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-header-updated-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-header-updated-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-header-updated-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..ac06ebbfc9b Binary files /dev/null and b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-header-updated-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-header-updated-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-header-updated-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..3d70ed20036 Binary files /dev/null and b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-header-updated-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-header-updated-ios-rtl-Mobile-Safari-linux.png b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-header-updated-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 00000000000..be341c4a88b Binary files /dev/null and b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-header-updated-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-initial-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-initial-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..d67eb0caf0b Binary files /dev/null and b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-initial-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-initial-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-initial-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..8ec5ef9aee4 Binary files /dev/null and b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-initial-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-initial-ios-ltr-Mobile-Safari-linux.png b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-initial-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..a2ae7b501c6 Binary files /dev/null and b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-initial-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-initial-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-initial-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..105c1bd500e Binary files /dev/null and b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-initial-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-initial-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-initial-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..6c0ee16a171 Binary files /dev/null and b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-initial-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-initial-ios-rtl-Mobile-Safari-linux.png b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-initial-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 00000000000..f3938a51a79 Binary files /dev/null and b/core/src/components/content/test/auto-offset/content.e2e.ts-snapshots/content-auto-offset-initial-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/content/test/auto-offset/index.html b/core/src/components/content/test/auto-offset/index.html new file mode 100644 index 00000000000..6c5727f5d75 --- /dev/null +++ b/core/src/components/content/test/auto-offset/index.html @@ -0,0 +1,58 @@ + + + + + Content - Auto Offset + + + + + + + + + + + + + Auto Offset Test + + + + + + +

+ +
+ + + + + Footer + + +
+ + +