11import type { ComponentInterface , EventEmitter } from '@stencil/core' ;
22import { Build , Component , Element , Event , Host , Listen , Method , Prop , forceUpdate , h , readTask } from '@stencil/core' ;
3- import { win } from '@utils/browser' ;
43import { componentOnReady , hasLazyBuild , inheritAriaAttributes } from '@utils/helpers' ;
54import type { Attributes } from '@utils/helpers' ;
65import { isPlatform } from '@utils/platform' ;
@@ -37,19 +36,6 @@ export class Content implements ComponentInterface {
3736 private resizeTimeout : ReturnType < typeof setTimeout > | null = null ;
3837 private inheritedAttributes : Attributes = { } ;
3938
40- /**
41- * Track whether this content has sibling header/footer elements.
42- * When absent, we need to apply safe-area padding directly.
43- */
44- private hasHeader = false ;
45- private hasFooter = false ;
46-
47- /** Watches for dynamic header/footer changes in parent element */
48- private parentMutationObserver ?: MutationObserver ;
49-
50- /** Watches for dynamic tab bar changes in ion-tabs */
51- private tabsMutationObserver ?: MutationObserver ;
52-
5339 private tabsElement : HTMLElement | null = null ;
5440 private tabsLoadCallback ?: ( ) => void ;
5541
@@ -146,13 +132,7 @@ export class Content implements ComponentInterface {
146132 }
147133
148134 connectedCallback ( ) {
149- // Content is "main" if not inside menu/popover/modal and not nested in another ion-content
150- this . isMainContent =
151- this . el . closest ( 'ion-menu, ion-popover, ion-modal' ) === null &&
152- this . el . parentElement ?. closest ( 'ion-content' ) === null ;
153-
154- // Detect sibling header/footer for safe-area handling
155- this . detectSiblingElements ( ) ;
135+ this . isMainContent = this . el . closest ( 'ion-menu, ion-popover, ion-modal' ) === null ;
156136
157137 /**
158138 * The fullscreen content offsets need to be
@@ -184,109 +164,15 @@ export class Content implements ComponentInterface {
184164 * bubbles, we can catch any instances of child tab bars loading by listening
185165 * on IonTabs.
186166 */
187- this . tabsLoadCallback = ( ) => {
188- this . resize ( ) ;
189- // Re-detect footer when tab bar loads (it may not exist during initial detection)
190- this . updateSiblingDetection ( ) ;
191- forceUpdate ( this ) ;
192- } ;
167+ this . tabsLoadCallback = ( ) => this . resize ( ) ;
193168 closestTabs . addEventListener ( 'ionTabBarLoaded' , this . tabsLoadCallback ) ;
194169 }
195170 }
196171 }
197172
198- /**
199- * Detects sibling ion-header and ion-footer elements and sets up
200- * a mutation observer to handle dynamic changes (e.g., conditional rendering).
201- */
202- private detectSiblingElements ( ) {
203- this . updateSiblingDetection ( ) ;
204-
205- // Watch for dynamic header/footer changes (common in React conditional rendering)
206- const parent = this . el . parentElement ;
207- if ( parent && ! this . parentMutationObserver && win !== undefined && 'MutationObserver' in win ) {
208- this . parentMutationObserver = new MutationObserver ( ( ) => {
209- const prevHasHeader = this . hasHeader ;
210- const prevHasFooter = this . hasFooter ;
211- this . updateSiblingDetection ( ) ;
212- // Only trigger re-render if header/footer detection actually changed
213- if ( prevHasHeader !== this . hasHeader || prevHasFooter !== this . hasFooter ) {
214- forceUpdate ( this ) ;
215- }
216- } ) ;
217- this . parentMutationObserver . observe ( parent , { childList : true } ) ;
218- }
219-
220- // Watch for dynamic tab bar changes in ion-tabs (common in Angular conditional rendering)
221- const tabs = this . el . closest ( 'ion-tabs' ) ;
222- if ( tabs && ! this . tabsMutationObserver && win !== undefined && 'MutationObserver' in win ) {
223- this . tabsMutationObserver = new MutationObserver ( ( ) => {
224- const prevHasFooter = this . hasFooter ;
225- this . updateSiblingDetection ( ) ;
226- // Only trigger re-render if footer detection actually changed
227- if ( prevHasFooter !== this . hasFooter ) {
228- forceUpdate ( this ) ;
229- }
230- } ) ;
231- this . tabsMutationObserver . observe ( tabs , { childList : true } ) ;
232- }
233- }
234-
235- /**
236- * Updates hasHeader/hasFooter based on current DOM state.
237- * Checks both direct siblings and elements wrapped in custom components
238- * (e.g., <my-header><ion-header>...</ion-header></my-header>).
239- */
240- private updateSiblingDetection ( ) {
241- const parent = this . el . parentElement ;
242- if ( parent ) {
243- // First check for direct ion-header/ion-footer siblings
244- this . hasHeader = parent . querySelector ( ':scope > ion-header' ) !== null ;
245- this . hasFooter = parent . querySelector ( ':scope > ion-footer' ) !== null ;
246-
247- // If not found, check if any sibling contains them (wrapped components)
248- if ( ! this . hasHeader ) {
249- this . hasHeader = this . siblingContainsElement ( parent , 'ion-header' ) ;
250- }
251- if ( ! this . hasFooter ) {
252- this . hasFooter = this . siblingContainsElement ( parent , 'ion-footer' ) ;
253- }
254- }
255-
256- // If no footer found, check if we're inside ion-tabs which has ion-tab-bar
257- if ( ! this . hasFooter ) {
258- const tabs = this . el . closest ( 'ion-tabs' ) ;
259- if ( tabs ) {
260- this . hasFooter = tabs . querySelector ( ':scope > ion-tab-bar' ) !== null ;
261- }
262- }
263- }
264-
265- /**
266- * Checks if any sibling element of ion-content contains the specified element.
267- * Only searches one level deep to avoid finding elements in nested pages.
268- */
269- private siblingContainsElement ( parent : Element , tagName : string ) : boolean {
270- for ( const sibling of parent . children ) {
271- // Skip ion-content itself
272- if ( sibling === this . el ) continue ;
273- // Check if this sibling contains the target element as an immediate child
274- if ( sibling . querySelector ( `:scope > ${ tagName } ` ) !== null ) {
275- return true ;
276- }
277- }
278- return false ;
279- }
280-
281173 disconnectedCallback ( ) {
282174 this . onScrollEnd ( ) ;
283175
284- // Clean up mutation observers to prevent memory leaks
285- this . parentMutationObserver ?. disconnect ( ) ;
286- this . parentMutationObserver = undefined ;
287- this . tabsMutationObserver ?. disconnect ( ) ;
288- this . tabsMutationObserver = undefined ;
289-
290176 if ( hasLazyBuild ( this . el ) ) {
291177 /**
292178 * The event listener and tabs caches need to
@@ -563,7 +449,7 @@ export class Content implements ComponentInterface {
563449 }
564450
565451 render ( ) {
566- const { fixedSlotPlacement, hasFooter , hasHeader , inheritedAttributes, isMainContent, scrollX, scrollY, el } = this ;
452+ const { fixedSlotPlacement, inheritedAttributes, isMainContent, scrollX, scrollY, el } = this ;
567453 const rtl = isRTL ( el ) ? 'rtl' : 'ltr' ;
568454 const mode = getIonMode ( this ) ;
569455 const forceOverscroll = this . shouldForceOverscroll ( ) ;
@@ -579,10 +465,6 @@ export class Content implements ComponentInterface {
579465 'content-sizing' : hostContext ( 'ion-popover' , this . el ) ,
580466 overscroll : forceOverscroll ,
581467 [ `content-${ rtl } ` ] : true ,
582- 'safe-area-top' : isMainContent && ! hasHeader ,
583- 'safe-area-bottom' : isMainContent && ! hasFooter ,
584- 'safe-area-left' : isMainContent ,
585- 'safe-area-right' : isMainContent ,
586468 } ) }
587469 style = { {
588470 '--offset-top' : `${ this . cTop } px` ,
0 commit comments