mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
fix(content): detect dynamic tab bar changes for safe-area handling
This commit is contained in:
@@ -47,6 +47,9 @@ export class Content implements ComponentInterface {
|
||||
/** Watches for dynamic header/footer changes in parent element */
|
||||
private parentMutationObserver?: MutationObserver;
|
||||
|
||||
/** Watches for dynamic tab bar changes in ion-tabs */
|
||||
private tabsMutationObserver?: MutationObserver;
|
||||
|
||||
private tabsElement: HTMLElement | null = null;
|
||||
private tabsLoadCallback?: () => void;
|
||||
|
||||
@@ -213,6 +216,20 @@ export class Content implements ComponentInterface {
|
||||
});
|
||||
this.parentMutationObserver.observe(parent, { childList: true });
|
||||
}
|
||||
|
||||
// Watch for dynamic tab bar changes in ion-tabs (common in Angular conditional rendering)
|
||||
const tabs = this.el.closest('ion-tabs');
|
||||
if (tabs && !this.tabsMutationObserver && win !== undefined && 'MutationObserver' in win) {
|
||||
this.tabsMutationObserver = new MutationObserver(() => {
|
||||
const prevHasFooter = this.hasFooter;
|
||||
this.updateSiblingDetection();
|
||||
// Only trigger re-render if footer detection actually changed
|
||||
if (prevHasFooter !== this.hasFooter) {
|
||||
forceUpdate(this);
|
||||
}
|
||||
});
|
||||
this.tabsMutationObserver.observe(tabs, { childList: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -264,9 +281,11 @@ export class Content implements ComponentInterface {
|
||||
disconnectedCallback() {
|
||||
this.onScrollEnd();
|
||||
|
||||
// Clean up mutation observer to prevent memory leaks
|
||||
// Clean up mutation observers to prevent memory leaks
|
||||
this.parentMutationObserver?.disconnect();
|
||||
this.parentMutationObserver = undefined;
|
||||
this.tabsMutationObserver?.disconnect();
|
||||
this.tabsMutationObserver = undefined;
|
||||
|
||||
if (hasLazyBuild(this.el)) {
|
||||
/**
|
||||
|
||||
@@ -164,5 +164,53 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
// Should have safe-area-top again
|
||||
await expect(content).toHaveClass(/safe-area-top/, { timeout: 1000 });
|
||||
});
|
||||
|
||||
test('content inside ion-tabs with tab bar should not have safe-area-bottom', async ({ page }, testInfo) => {
|
||||
testInfo.annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/ionic-team/ionic-framework/issues/30900',
|
||||
});
|
||||
|
||||
const content = page.locator('#content-dynamic-tabs');
|
||||
// Tab bar is present, so content should not have safe-area-bottom
|
||||
await expect(content).not.toHaveClass(/safe-area-bottom/);
|
||||
});
|
||||
|
||||
test('dynamic tab bar removal should update safe-area classes', async ({ page }, testInfo) => {
|
||||
testInfo.annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/ionic-team/ionic-framework/issues/30900',
|
||||
});
|
||||
|
||||
const content = page.locator('#content-dynamic-tabs');
|
||||
|
||||
// Initially tab bar is present, so no safe-area-bottom
|
||||
await expect(content).not.toHaveClass(/safe-area-bottom/);
|
||||
|
||||
// Remove tab bar
|
||||
await page.evaluate(() => (window as any).removeTabBar());
|
||||
|
||||
// Should have safe-area-bottom now
|
||||
await expect(content).toHaveClass(/safe-area-bottom/, { timeout: 1000 });
|
||||
});
|
||||
|
||||
test('dynamic tab bar addition should update safe-area classes', async ({ page }, testInfo) => {
|
||||
testInfo.annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/ionic-team/ionic-framework/issues/30900',
|
||||
});
|
||||
|
||||
const content = page.locator('#content-dynamic-tabs');
|
||||
|
||||
// Remove tab bar first
|
||||
await page.evaluate(() => (window as any).removeTabBar());
|
||||
await expect(content).toHaveClass(/safe-area-bottom/, { timeout: 1000 });
|
||||
|
||||
// Add tab bar back
|
||||
await page.evaluate(() => (window as any).addTabBar());
|
||||
|
||||
// Should not have safe-area-bottom anymore
|
||||
await expect(content).not.toHaveClass(/safe-area-bottom/, { timeout: 1000 });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -163,6 +163,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test 10: Dynamic tab bar - for testing ion-tabs mutation observer -->
|
||||
<div id="test-dynamic-tabs" class="test-section">
|
||||
<ion-tabs id="dynamic-tabs">
|
||||
<div class="ion-page" id="dynamic-tabs-page">
|
||||
<ion-content id="content-dynamic-tabs">
|
||||
<p>Content with dynamic tab bar</p>
|
||||
</ion-content>
|
||||
</div>
|
||||
<ion-tab-bar id="dynamic-tab-bar" slot="bottom">
|
||||
<ion-tab-button tab="tab1">
|
||||
<ion-label>Tab 1</ion-label>
|
||||
</ion-tab-button>
|
||||
</ion-tab-bar>
|
||||
</ion-tabs>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function addHeader() {
|
||||
const page = document.getElementById('dynamic-page');
|
||||
@@ -186,6 +202,24 @@
|
||||
function openModal() {
|
||||
document.getElementById('test-modal').isOpen = true;
|
||||
}
|
||||
|
||||
function addTabBar() {
|
||||
const tabs = document.getElementById('dynamic-tabs');
|
||||
if (!tabs.querySelector('ion-tab-bar')) {
|
||||
const tabBar = document.createElement('ion-tab-bar');
|
||||
tabBar.id = 'dynamic-tab-bar';
|
||||
tabBar.slot = 'bottom';
|
||||
tabBar.innerHTML = '<ion-tab-button tab="tab1"><ion-label>Tab 1</ion-label></ion-tab-button>';
|
||||
tabs.appendChild(tabBar);
|
||||
}
|
||||
}
|
||||
|
||||
function removeTabBar() {
|
||||
const tabBar = document.getElementById('dynamic-tab-bar');
|
||||
if (tabBar) {
|
||||
tabBar.remove();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</ion-app>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user