Skip to content

Commit 4fe98a4

Browse files
committed
fix(content): apply safe-area insets when header/footer absent
1 parent 7c197c2 commit 4fe98a4

File tree

2 files changed

+51
-1
lines changed

2 files changed

+51
-1
lines changed

core/src/components/content/content.scss

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,23 @@
235235
}
236236

237237

238+
// Content: Safe Area
239+
// --------------------------------------------------
240+
// When content has no sibling header, offset from top safe-area.
241+
// When content has no sibling footer/tab-bar, offset from bottom safe-area.
242+
// This prevents content from overlapping device safe areas (status bar, nav bar).
243+
244+
:host(.safe-area-top) #background-content,
245+
:host(.safe-area-top) .inner-scroll {
246+
top: var(--ion-safe-area-top, 0px);
247+
}
248+
249+
:host(.safe-area-bottom) #background-content,
250+
:host(.safe-area-bottom) .inner-scroll {
251+
bottom: var(--ion-safe-area-bottom, 0px);
252+
}
253+
254+
238255
// Content: Fixed
239256
// --------------------------------------------------
240257

core/src/components/content/content.tsx

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ export class Content implements ComponentInterface {
3636
private resizeTimeout: ReturnType<typeof setTimeout> | null = null;
3737
private inheritedAttributes: Attributes = {};
3838

39+
/**
40+
* Track whether this content has sibling header/footer elements.
41+
* When absent, we need to apply safe-area padding directly.
42+
*/
43+
private hasHeader = false;
44+
private hasFooter = false;
45+
3946
private tabsElement: HTMLElement | null = null;
4047
private tabsLoadCallback?: () => void;
4148

@@ -134,6 +141,9 @@ export class Content implements ComponentInterface {
134141
connectedCallback() {
135142
this.isMainContent = this.el.closest('ion-menu, ion-popover, ion-modal') === null;
136143

144+
// Detect sibling header/footer for safe-area handling
145+
this.detectSiblingElements();
146+
137147
/**
138148
* The fullscreen content offsets need to be
139149
* computed after the tab bar has loaded. Since
@@ -170,6 +180,27 @@ export class Content implements ComponentInterface {
170180
}
171181
}
172182

183+
/**
184+
* Detects sibling ion-header and ion-footer elements.
185+
* When these are absent, content needs to handle safe-area padding directly.
186+
*/
187+
private detectSiblingElements() {
188+
// Check parent element for sibling header/footer.
189+
const parent = this.el.parentElement;
190+
if (parent) {
191+
this.hasHeader = parent.querySelector(':scope > ion-header') !== null;
192+
this.hasFooter = parent.querySelector(':scope > ion-footer') !== null;
193+
}
194+
195+
// If no footer found, check if we're inside ion-tabs which has ion-tab-bar
196+
if (!this.hasFooter) {
197+
const tabs = this.el.closest('ion-tabs');
198+
if (tabs) {
199+
this.hasFooter = tabs.querySelector(':scope > ion-tab-bar') !== null;
200+
}
201+
}
202+
}
203+
173204
disconnectedCallback() {
174205
this.onScrollEnd();
175206

@@ -449,7 +480,7 @@ export class Content implements ComponentInterface {
449480
}
450481

451482
render() {
452-
const { fixedSlotPlacement, inheritedAttributes, isMainContent, scrollX, scrollY, el } = this;
483+
const { fixedSlotPlacement, hasFooter, hasHeader, inheritedAttributes, isMainContent, scrollX, scrollY, el } = this;
453484
const rtl = isRTL(el) ? 'rtl' : 'ltr';
454485
const mode = getIonMode(this);
455486
const forceOverscroll = this.shouldForceOverscroll();
@@ -465,6 +496,8 @@ export class Content implements ComponentInterface {
465496
'content-sizing': hostContext('ion-popover', this.el),
466497
overscroll: forceOverscroll,
467498
[`content-${rtl}`]: true,
499+
'safe-area-top': isMainContent && !hasHeader,
500+
'safe-area-bottom': isMainContent && !hasFooter,
468501
})}
469502
style={{
470503
'--offset-top': `${this.cTop}px`,

0 commit comments

Comments
 (0)