@ionic/core 8.7.17-dev.11767641184.14a165bc → 8.7.17-dev.11767717752.14fe98a4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,7 @@ import { b as getIonMode, a as isPlatform } from './ionic-global.js';
7
7
  import { i as isRTL } from './dir.js';
8
8
  import { c as createColorClasses, h as hostContext } from './theme.js';
9
9
 
10
- const contentCss = ":host{--background:var(--ion-background-color, #fff);--color:var(--ion-text-color, #000);--padding-top:0px;--padding-bottom:0px;--padding-start:0px;--padding-end:0px;--keyboard-offset:0px;--offset-top:0px;--offset-bottom:0px;--overflow:auto;display:block;position:relative;-ms-flex:1;flex:1;width:100%;height:100%;margin:0 !important;padding:0 !important;font-family:var(--ion-font-family, inherit);contain:size style}:host(.ion-color) .inner-scroll{background:var(--ion-color-base);color:var(--ion-color-contrast)}#background-content{left:0px;right:0px;top:calc(var(--offset-top) * -1);bottom:calc(var(--offset-bottom) * -1);position:absolute;background:var(--background)}.inner-scroll{left:0px;right:0px;top:calc(var(--offset-top) * -1);bottom:calc(var(--offset-bottom) * -1);-webkit-padding-start:var(--padding-start);padding-inline-start:var(--padding-start);-webkit-padding-end:var(--padding-end);padding-inline-end:var(--padding-end);padding-top:calc(var(--padding-top) + var(--offset-top));padding-bottom:calc(var(--padding-bottom) + var(--keyboard-offset) + var(--offset-bottom));position:absolute;color:var(--color);-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;-ms-touch-action:pan-x pan-y pinch-zoom;touch-action:pan-x pan-y pinch-zoom}.scroll-y,.scroll-x{-webkit-overflow-scrolling:touch;z-index:0;will-change:scroll-position}.scroll-y{overflow-y:var(--overflow);overscroll-behavior-y:contain}.scroll-x{overflow-x:var(--overflow);overscroll-behavior-x:contain}.overscroll::before,.overscroll::after{position:absolute;width:1px;height:1px;content:\"\"}.overscroll::before{bottom:-1px}.overscroll::after{top:-1px}:host(.content-sizing){display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-height:0;contain:none}:host(.content-sizing) .inner-scroll{position:relative;top:0;bottom:0;margin-top:calc(var(--offset-top) * -1);margin-bottom:calc(var(--offset-bottom) * -1)}.transition-effect{display:none;position:absolute;width:100%;height:100vh;opacity:0;pointer-events:none}:host(.content-ltr) .transition-effect{left:-100%;}:host(.content-rtl) .transition-effect{right:-100%;}.transition-cover{position:absolute;right:0;width:100%;height:100%;background:black;opacity:0.1}.transition-shadow{display:block;position:absolute;width:100%;height:100%;-webkit-box-shadow:inset -9px 0 9px 0 rgba(0, 0, 100, 0.03);box-shadow:inset -9px 0 9px 0 rgba(0, 0, 100, 0.03)}:host(.content-ltr) .transition-shadow{right:0;}:host(.content-rtl) .transition-shadow{left:0;-webkit-transform:scaleX(-1);transform:scaleX(-1)}::slotted([slot=fixed]){position:absolute;-webkit-transform:translateZ(0);transform:translateZ(0)}";
10
+ const contentCss = ":host{--background:var(--ion-background-color, #fff);--color:var(--ion-text-color, #000);--padding-top:0px;--padding-bottom:0px;--padding-start:0px;--padding-end:0px;--keyboard-offset:0px;--offset-top:0px;--offset-bottom:0px;--overflow:auto;display:block;position:relative;-ms-flex:1;flex:1;width:100%;height:100%;margin:0 !important;padding:0 !important;font-family:var(--ion-font-family, inherit);contain:size style}:host(.ion-color) .inner-scroll{background:var(--ion-color-base);color:var(--ion-color-contrast)}#background-content{left:0px;right:0px;top:calc(var(--offset-top) * -1);bottom:calc(var(--offset-bottom) * -1);position:absolute;background:var(--background)}.inner-scroll{left:0px;right:0px;top:calc(var(--offset-top) * -1);bottom:calc(var(--offset-bottom) * -1);-webkit-padding-start:var(--padding-start);padding-inline-start:var(--padding-start);-webkit-padding-end:var(--padding-end);padding-inline-end:var(--padding-end);padding-top:calc(var(--padding-top) + var(--offset-top));padding-bottom:calc(var(--padding-bottom) + var(--keyboard-offset) + var(--offset-bottom));position:absolute;color:var(--color);-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;-ms-touch-action:pan-x pan-y pinch-zoom;touch-action:pan-x pan-y pinch-zoom}.scroll-y,.scroll-x{-webkit-overflow-scrolling:touch;z-index:0;will-change:scroll-position}.scroll-y{overflow-y:var(--overflow);overscroll-behavior-y:contain}.scroll-x{overflow-x:var(--overflow);overscroll-behavior-x:contain}.overscroll::before,.overscroll::after{position:absolute;width:1px;height:1px;content:\"\"}.overscroll::before{bottom:-1px}.overscroll::after{top:-1px}:host(.content-sizing){display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-height:0;contain:none}:host(.content-sizing) .inner-scroll{position:relative;top:0;bottom:0;margin-top:calc(var(--offset-top) * -1);margin-bottom:calc(var(--offset-bottom) * -1)}.transition-effect{display:none;position:absolute;width:100%;height:100vh;opacity:0;pointer-events:none}:host(.content-ltr) .transition-effect{left:-100%;}:host(.content-rtl) .transition-effect{right:-100%;}.transition-cover{position:absolute;right:0;width:100%;height:100%;background:black;opacity:0.1}.transition-shadow{display:block;position:absolute;width:100%;height:100%;-webkit-box-shadow:inset -9px 0 9px 0 rgba(0, 0, 100, 0.03);box-shadow:inset -9px 0 9px 0 rgba(0, 0, 100, 0.03)}:host(.content-ltr) .transition-shadow{right:0;}:host(.content-rtl) .transition-shadow{left:0;-webkit-transform:scaleX(-1);transform:scaleX(-1)}:host(.safe-area-top) #background-content,:host(.safe-area-top) .inner-scroll{top:var(--ion-safe-area-top, 0px)}:host(.safe-area-bottom) #background-content,:host(.safe-area-bottom) .inner-scroll{bottom:var(--ion-safe-area-bottom, 0px)}::slotted([slot=fixed]){position:absolute;-webkit-transform:translateZ(0);transform:translateZ(0)}";
11
11
 
12
12
  const Content = /*@__PURE__*/ proxyCustomElement(class Content extends HTMLElement {
13
13
  constructor(registerHost) {
@@ -28,6 +28,12 @@ const Content = /*@__PURE__*/ proxyCustomElement(class Content extends HTMLEleme
28
28
  this.isMainContent = true;
29
29
  this.resizeTimeout = null;
30
30
  this.inheritedAttributes = {};
31
+ /**
32
+ * Track whether this content has sibling header/footer elements.
33
+ * When absent, we need to apply safe-area padding directly.
34
+ */
35
+ this.hasHeader = false;
36
+ this.hasFooter = false;
31
37
  this.tabsElement = null;
32
38
  // Detail is used in a hot loop in the scroll event, by allocating it here
33
39
  // V8 will be able to inline any read/write to it since it's a monomorphic class.
@@ -83,6 +89,8 @@ const Content = /*@__PURE__*/ proxyCustomElement(class Content extends HTMLEleme
83
89
  }
84
90
  connectedCallback() {
85
91
  this.isMainContent = this.el.closest('ion-menu, ion-popover, ion-modal') === null;
92
+ // Detect sibling header/footer for safe-area handling
93
+ this.detectSiblingElements();
86
94
  /**
87
95
  * The fullscreen content offsets need to be
88
96
  * computed after the tab bar has loaded. Since
@@ -118,6 +126,25 @@ const Content = /*@__PURE__*/ proxyCustomElement(class Content extends HTMLEleme
118
126
  }
119
127
  }
120
128
  }
129
+ /**
130
+ * Detects sibling ion-header and ion-footer elements.
131
+ * When these are absent, content needs to handle safe-area padding directly.
132
+ */
133
+ detectSiblingElements() {
134
+ // Check parent element for sibling header/footer.
135
+ const parent = this.el.parentElement;
136
+ if (parent) {
137
+ this.hasHeader = parent.querySelector(':scope > ion-header') !== null;
138
+ this.hasFooter = parent.querySelector(':scope > ion-footer') !== null;
139
+ }
140
+ // If no footer found, check if we're inside ion-tabs which has ion-tab-bar
141
+ if (!this.hasFooter) {
142
+ const tabs = this.el.closest('ion-tabs');
143
+ if (tabs) {
144
+ this.hasFooter = tabs.querySelector(':scope > ion-tab-bar') !== null;
145
+ }
146
+ }
147
+ }
121
148
  disconnectedCallback() {
122
149
  this.onScrollEnd();
123
150
  if (hasLazyBuild(this.el)) {
@@ -366,26 +393,28 @@ const Content = /*@__PURE__*/ proxyCustomElement(class Content extends HTMLEleme
366
393
  }
367
394
  }
368
395
  render() {
369
- const { fixedSlotPlacement, inheritedAttributes, isMainContent, scrollX, scrollY, el } = this;
396
+ const { fixedSlotPlacement, hasFooter, hasHeader, inheritedAttributes, isMainContent, scrollX, scrollY, el } = this;
370
397
  const rtl = isRTL(el) ? 'rtl' : 'ltr';
371
398
  const mode = getIonMode(this);
372
399
  const forceOverscroll = this.shouldForceOverscroll();
373
400
  const transitionShadow = mode === 'ios';
374
401
  this.resize();
375
- return (h(Host, Object.assign({ key: 'cd8781f848d8dc926fe66f43d43c49564425a507', role: isMainContent ? 'main' : undefined, class: createColorClasses(this.color, {
402
+ return (h(Host, Object.assign({ key: '83665c0e35e4f4117709606e47d27ad36e343458', role: isMainContent ? 'main' : undefined, class: createColorClasses(this.color, {
376
403
  [mode]: true,
377
404
  'content-sizing': hostContext('ion-popover', this.el),
378
405
  overscroll: forceOverscroll,
379
406
  [`content-${rtl}`]: true,
407
+ 'safe-area-top': isMainContent && !hasHeader,
408
+ 'safe-area-bottom': isMainContent && !hasFooter,
380
409
  }), style: {
381
410
  '--offset-top': `${this.cTop}px`,
382
411
  '--offset-bottom': `${this.cBottom}px`,
383
- } }, inheritedAttributes), h("div", { key: '95b112d7cae30f22ef778ceffb88edb4d941c170', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? h("slot", { name: "fixed" }) : null, h("div", { key: '2fdfcbc39fb66f11b6191911f2941c660f4c12e5', class: {
412
+ } }, inheritedAttributes), h("div", { key: '75d7cf9315bc8dfb150c3b5bcc356a1f9c793b5a', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? h("slot", { name: "fixed" }) : null, h("div", { key: 'f68bc5843c93ed6f32e998b80c5edc553c77a2d7', class: {
384
413
  'inner-scroll': true,
385
414
  'scroll-x': scrollX,
386
415
  'scroll-y': scrollY,
387
416
  overscroll: (scrollX || scrollY) && forceOverscroll,
388
- }, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, h("slot", { key: '6bc77e0054ec8e21635a7f2abfe0ca46e0962e03' })), transitionShadow ? (h("div", { class: "transition-effect" }, h("div", { class: "transition-cover" }), h("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? h("slot", { name: "fixed" }) : null));
417
+ }, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, h("slot", { key: '8d4b2be00f036f6a24bfa65e6324ca715dd93b60' })), transitionShadow ? (h("div", { class: "transition-effect" }, h("div", { class: "transition-cover" }), h("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? h("slot", { name: "fixed" }) : null));
389
418
  }
390
419
  get el() { return this; }
391
420
  static get style() { return contentCss; }
@@ -730,6 +730,19 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
730
730
  checkSafeAreaBottom = true;
731
731
  }
732
732
  }
733
+ /**
734
+ * Final check: If the popover extends into any safe-area region,
735
+ * ensure the corresponding flag is set regardless of side.
736
+ * This handles cases where a side-positioned popover (left/right)
737
+ * still needs bottom safe-area padding because it extends into that region.
738
+ */
739
+ const popoverBottom = bottom !== undefined ? bodyHeight - bottom : top + contentHeight;
740
+ if (popoverBottom + safeAreaMargin > bodyHeight) {
741
+ checkSafeAreaBottom = true;
742
+ }
743
+ if (top < safeAreaMargin) {
744
+ checkSafeAreaTop = true;
745
+ }
733
746
  return {
734
747
  top,
735
748
  left,
@@ -154,7 +154,7 @@ Buttons.style = {
154
154
  md: buttonsMdCss
155
155
  };
156
156
 
157
- const contentCss = ":host{--background:var(--ion-background-color, #fff);--color:var(--ion-text-color, #000);--padding-top:0px;--padding-bottom:0px;--padding-start:0px;--padding-end:0px;--keyboard-offset:0px;--offset-top:0px;--offset-bottom:0px;--overflow:auto;display:block;position:relative;-ms-flex:1;flex:1;width:100%;height:100%;margin:0 !important;padding:0 !important;font-family:var(--ion-font-family, inherit);contain:size style}:host(.ion-color) .inner-scroll{background:var(--ion-color-base);color:var(--ion-color-contrast)}#background-content{left:0px;right:0px;top:calc(var(--offset-top) * -1);bottom:calc(var(--offset-bottom) * -1);position:absolute;background:var(--background)}.inner-scroll{left:0px;right:0px;top:calc(var(--offset-top) * -1);bottom:calc(var(--offset-bottom) * -1);-webkit-padding-start:var(--padding-start);padding-inline-start:var(--padding-start);-webkit-padding-end:var(--padding-end);padding-inline-end:var(--padding-end);padding-top:calc(var(--padding-top) + var(--offset-top));padding-bottom:calc(var(--padding-bottom) + var(--keyboard-offset) + var(--offset-bottom));position:absolute;color:var(--color);-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;-ms-touch-action:pan-x pan-y pinch-zoom;touch-action:pan-x pan-y pinch-zoom}.scroll-y,.scroll-x{-webkit-overflow-scrolling:touch;z-index:0;will-change:scroll-position}.scroll-y{overflow-y:var(--overflow);overscroll-behavior-y:contain}.scroll-x{overflow-x:var(--overflow);overscroll-behavior-x:contain}.overscroll::before,.overscroll::after{position:absolute;width:1px;height:1px;content:\"\"}.overscroll::before{bottom:-1px}.overscroll::after{top:-1px}:host(.content-sizing){display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-height:0;contain:none}:host(.content-sizing) .inner-scroll{position:relative;top:0;bottom:0;margin-top:calc(var(--offset-top) * -1);margin-bottom:calc(var(--offset-bottom) * -1)}.transition-effect{display:none;position:absolute;width:100%;height:100vh;opacity:0;pointer-events:none}:host(.content-ltr) .transition-effect{left:-100%;}:host(.content-rtl) .transition-effect{right:-100%;}.transition-cover{position:absolute;right:0;width:100%;height:100%;background:black;opacity:0.1}.transition-shadow{display:block;position:absolute;width:100%;height:100%;-webkit-box-shadow:inset -9px 0 9px 0 rgba(0, 0, 100, 0.03);box-shadow:inset -9px 0 9px 0 rgba(0, 0, 100, 0.03)}:host(.content-ltr) .transition-shadow{right:0;}:host(.content-rtl) .transition-shadow{left:0;-webkit-transform:scaleX(-1);transform:scaleX(-1)}::slotted([slot=fixed]){position:absolute;-webkit-transform:translateZ(0);transform:translateZ(0)}";
157
+ const contentCss = ":host{--background:var(--ion-background-color, #fff);--color:var(--ion-text-color, #000);--padding-top:0px;--padding-bottom:0px;--padding-start:0px;--padding-end:0px;--keyboard-offset:0px;--offset-top:0px;--offset-bottom:0px;--overflow:auto;display:block;position:relative;-ms-flex:1;flex:1;width:100%;height:100%;margin:0 !important;padding:0 !important;font-family:var(--ion-font-family, inherit);contain:size style}:host(.ion-color) .inner-scroll{background:var(--ion-color-base);color:var(--ion-color-contrast)}#background-content{left:0px;right:0px;top:calc(var(--offset-top) * -1);bottom:calc(var(--offset-bottom) * -1);position:absolute;background:var(--background)}.inner-scroll{left:0px;right:0px;top:calc(var(--offset-top) * -1);bottom:calc(var(--offset-bottom) * -1);-webkit-padding-start:var(--padding-start);padding-inline-start:var(--padding-start);-webkit-padding-end:var(--padding-end);padding-inline-end:var(--padding-end);padding-top:calc(var(--padding-top) + var(--offset-top));padding-bottom:calc(var(--padding-bottom) + var(--keyboard-offset) + var(--offset-bottom));position:absolute;color:var(--color);-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;-ms-touch-action:pan-x pan-y pinch-zoom;touch-action:pan-x pan-y pinch-zoom}.scroll-y,.scroll-x{-webkit-overflow-scrolling:touch;z-index:0;will-change:scroll-position}.scroll-y{overflow-y:var(--overflow);overscroll-behavior-y:contain}.scroll-x{overflow-x:var(--overflow);overscroll-behavior-x:contain}.overscroll::before,.overscroll::after{position:absolute;width:1px;height:1px;content:\"\"}.overscroll::before{bottom:-1px}.overscroll::after{top:-1px}:host(.content-sizing){display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-height:0;contain:none}:host(.content-sizing) .inner-scroll{position:relative;top:0;bottom:0;margin-top:calc(var(--offset-top) * -1);margin-bottom:calc(var(--offset-bottom) * -1)}.transition-effect{display:none;position:absolute;width:100%;height:100vh;opacity:0;pointer-events:none}:host(.content-ltr) .transition-effect{left:-100%;}:host(.content-rtl) .transition-effect{right:-100%;}.transition-cover{position:absolute;right:0;width:100%;height:100%;background:black;opacity:0.1}.transition-shadow{display:block;position:absolute;width:100%;height:100%;-webkit-box-shadow:inset -9px 0 9px 0 rgba(0, 0, 100, 0.03);box-shadow:inset -9px 0 9px 0 rgba(0, 0, 100, 0.03)}:host(.content-ltr) .transition-shadow{right:0;}:host(.content-rtl) .transition-shadow{left:0;-webkit-transform:scaleX(-1);transform:scaleX(-1)}:host(.safe-area-top) #background-content,:host(.safe-area-top) .inner-scroll{top:var(--ion-safe-area-top, 0px)}:host(.safe-area-bottom) #background-content,:host(.safe-area-bottom) .inner-scroll{bottom:var(--ion-safe-area-bottom, 0px)}::slotted([slot=fixed]){position:absolute;-webkit-transform:translateZ(0);transform:translateZ(0)}";
158
158
 
159
159
  const Content = class {
160
160
  constructor(hostRef) {
@@ -171,6 +171,12 @@ const Content = class {
171
171
  this.isMainContent = true;
172
172
  this.resizeTimeout = null;
173
173
  this.inheritedAttributes = {};
174
+ /**
175
+ * Track whether this content has sibling header/footer elements.
176
+ * When absent, we need to apply safe-area padding directly.
177
+ */
178
+ this.hasHeader = false;
179
+ this.hasFooter = false;
174
180
  this.tabsElement = null;
175
181
  // Detail is used in a hot loop in the scroll event, by allocating it here
176
182
  // V8 will be able to inline any read/write to it since it's a monomorphic class.
@@ -226,6 +232,8 @@ const Content = class {
226
232
  }
227
233
  connectedCallback() {
228
234
  this.isMainContent = this.el.closest('ion-menu, ion-popover, ion-modal') === null;
235
+ // Detect sibling header/footer for safe-area handling
236
+ this.detectSiblingElements();
229
237
  /**
230
238
  * The fullscreen content offsets need to be
231
239
  * computed after the tab bar has loaded. Since
@@ -261,6 +269,25 @@ const Content = class {
261
269
  }
262
270
  }
263
271
  }
272
+ /**
273
+ * Detects sibling ion-header and ion-footer elements.
274
+ * When these are absent, content needs to handle safe-area padding directly.
275
+ */
276
+ detectSiblingElements() {
277
+ // Check parent element for sibling header/footer.
278
+ const parent = this.el.parentElement;
279
+ if (parent) {
280
+ this.hasHeader = parent.querySelector(':scope > ion-header') !== null;
281
+ this.hasFooter = parent.querySelector(':scope > ion-footer') !== null;
282
+ }
283
+ // If no footer found, check if we're inside ion-tabs which has ion-tab-bar
284
+ if (!this.hasFooter) {
285
+ const tabs = this.el.closest('ion-tabs');
286
+ if (tabs) {
287
+ this.hasFooter = tabs.querySelector(':scope > ion-tab-bar') !== null;
288
+ }
289
+ }
290
+ }
264
291
  disconnectedCallback() {
265
292
  this.onScrollEnd();
266
293
  if (helpers.hasLazyBuild(this.el)) {
@@ -509,26 +536,28 @@ const Content = class {
509
536
  }
510
537
  }
511
538
  render() {
512
- const { fixedSlotPlacement, inheritedAttributes, isMainContent, scrollX, scrollY, el } = this;
539
+ const { fixedSlotPlacement, hasFooter, hasHeader, inheritedAttributes, isMainContent, scrollX, scrollY, el } = this;
513
540
  const rtl = dir.isRTL(el) ? 'rtl' : 'ltr';
514
541
  const mode = ionicGlobal.getIonMode(this);
515
542
  const forceOverscroll = this.shouldForceOverscroll();
516
543
  const transitionShadow = mode === 'ios';
517
544
  this.resize();
518
- return (index.h(index.Host, Object.assign({ key: 'cd8781f848d8dc926fe66f43d43c49564425a507', role: isMainContent ? 'main' : undefined, class: theme.createColorClasses(this.color, {
545
+ return (index.h(index.Host, Object.assign({ key: '83665c0e35e4f4117709606e47d27ad36e343458', role: isMainContent ? 'main' : undefined, class: theme.createColorClasses(this.color, {
519
546
  [mode]: true,
520
547
  'content-sizing': theme.hostContext('ion-popover', this.el),
521
548
  overscroll: forceOverscroll,
522
549
  [`content-${rtl}`]: true,
550
+ 'safe-area-top': isMainContent && !hasHeader,
551
+ 'safe-area-bottom': isMainContent && !hasFooter,
523
552
  }), style: {
524
553
  '--offset-top': `${this.cTop}px`,
525
554
  '--offset-bottom': `${this.cBottom}px`,
526
- } }, inheritedAttributes), index.h("div", { key: '95b112d7cae30f22ef778ceffb88edb4d941c170', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? index.h("slot", { name: "fixed" }) : null, index.h("div", { key: '2fdfcbc39fb66f11b6191911f2941c660f4c12e5', class: {
555
+ } }, inheritedAttributes), index.h("div", { key: '75d7cf9315bc8dfb150c3b5bcc356a1f9c793b5a', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? index.h("slot", { name: "fixed" }) : null, index.h("div", { key: 'f68bc5843c93ed6f32e998b80c5edc553c77a2d7', class: {
527
556
  'inner-scroll': true,
528
557
  'scroll-x': scrollX,
529
558
  'scroll-y': scrollY,
530
559
  overscroll: (scrollX || scrollY) && forceOverscroll,
531
- }, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, index.h("slot", { key: '6bc77e0054ec8e21635a7f2abfe0ca46e0962e03' })), transitionShadow ? (index.h("div", { class: "transition-effect" }, index.h("div", { class: "transition-cover" }), index.h("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? index.h("slot", { name: "fixed" }) : null));
560
+ }, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, index.h("slot", { key: '8d4b2be00f036f6a24bfa65e6324ca715dd93b60' })), transitionShadow ? (index.h("div", { class: "transition-effect" }, index.h("div", { class: "transition-cover" }), index.h("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? index.h("slot", { name: "fixed" }) : null));
532
561
  }
533
562
  get el() { return index.getElement(this); }
534
563
  };
@@ -733,6 +733,19 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
733
733
  checkSafeAreaBottom = true;
734
734
  }
735
735
  }
736
+ /**
737
+ * Final check: If the popover extends into any safe-area region,
738
+ * ensure the corresponding flag is set regardless of side.
739
+ * This handles cases where a side-positioned popover (left/right)
740
+ * still needs bottom safe-area padding because it extends into that region.
741
+ */
742
+ const popoverBottom = bottom !== undefined ? bodyHeight - bottom : top + contentHeight;
743
+ if (popoverBottom + safeAreaMargin > bodyHeight) {
744
+ checkSafeAreaBottom = true;
745
+ }
746
+ if (top < safeAreaMargin) {
747
+ checkSafeAreaTop = true;
748
+ }
736
749
  return {
737
750
  top,
738
751
  left,
@@ -263,6 +263,16 @@
263
263
  transform: scaleX(-1);
264
264
  }
265
265
 
266
+ :host(.safe-area-top) #background-content,
267
+ :host(.safe-area-top) .inner-scroll {
268
+ top: var(--ion-safe-area-top, 0px);
269
+ }
270
+
271
+ :host(.safe-area-bottom) #background-content,
272
+ :host(.safe-area-bottom) .inner-scroll {
273
+ bottom: var(--ion-safe-area-bottom, 0px);
274
+ }
275
+
266
276
  ::slotted([slot=fixed]) {
267
277
  position: absolute;
268
278
  /**
@@ -25,6 +25,12 @@ export class Content {
25
25
  this.isMainContent = true;
26
26
  this.resizeTimeout = null;
27
27
  this.inheritedAttributes = {};
28
+ /**
29
+ * Track whether this content has sibling header/footer elements.
30
+ * When absent, we need to apply safe-area padding directly.
31
+ */
32
+ this.hasHeader = false;
33
+ this.hasFooter = false;
28
34
  this.tabsElement = null;
29
35
  // Detail is used in a hot loop in the scroll event, by allocating it here
30
36
  // V8 will be able to inline any read/write to it since it's a monomorphic class.
@@ -80,6 +86,8 @@ export class Content {
80
86
  }
81
87
  connectedCallback() {
82
88
  this.isMainContent = this.el.closest('ion-menu, ion-popover, ion-modal') === null;
89
+ // Detect sibling header/footer for safe-area handling
90
+ this.detectSiblingElements();
83
91
  /**
84
92
  * The fullscreen content offsets need to be
85
93
  * computed after the tab bar has loaded. Since
@@ -115,6 +123,25 @@ export class Content {
115
123
  }
116
124
  }
117
125
  }
126
+ /**
127
+ * Detects sibling ion-header and ion-footer elements.
128
+ * When these are absent, content needs to handle safe-area padding directly.
129
+ */
130
+ detectSiblingElements() {
131
+ // Check parent element for sibling header/footer.
132
+ const parent = this.el.parentElement;
133
+ if (parent) {
134
+ this.hasHeader = parent.querySelector(':scope > ion-header') !== null;
135
+ this.hasFooter = parent.querySelector(':scope > ion-footer') !== null;
136
+ }
137
+ // If no footer found, check if we're inside ion-tabs which has ion-tab-bar
138
+ if (!this.hasFooter) {
139
+ const tabs = this.el.closest('ion-tabs');
140
+ if (tabs) {
141
+ this.hasFooter = tabs.querySelector(':scope > ion-tab-bar') !== null;
142
+ }
143
+ }
144
+ }
118
145
  disconnectedCallback() {
119
146
  this.onScrollEnd();
120
147
  if (hasLazyBuild(this.el)) {
@@ -363,26 +390,28 @@ export class Content {
363
390
  }
364
391
  }
365
392
  render() {
366
- const { fixedSlotPlacement, inheritedAttributes, isMainContent, scrollX, scrollY, el } = this;
393
+ const { fixedSlotPlacement, hasFooter, hasHeader, inheritedAttributes, isMainContent, scrollX, scrollY, el } = this;
367
394
  const rtl = isRTL(el) ? 'rtl' : 'ltr';
368
395
  const mode = getIonMode(this);
369
396
  const forceOverscroll = this.shouldForceOverscroll();
370
397
  const transitionShadow = mode === 'ios';
371
398
  this.resize();
372
- return (h(Host, Object.assign({ key: 'cd8781f848d8dc926fe66f43d43c49564425a507', role: isMainContent ? 'main' : undefined, class: createColorClasses(this.color, {
399
+ return (h(Host, Object.assign({ key: '83665c0e35e4f4117709606e47d27ad36e343458', role: isMainContent ? 'main' : undefined, class: createColorClasses(this.color, {
373
400
  [mode]: true,
374
401
  'content-sizing': hostContext('ion-popover', this.el),
375
402
  overscroll: forceOverscroll,
376
403
  [`content-${rtl}`]: true,
404
+ 'safe-area-top': isMainContent && !hasHeader,
405
+ 'safe-area-bottom': isMainContent && !hasFooter,
377
406
  }), style: {
378
407
  '--offset-top': `${this.cTop}px`,
379
408
  '--offset-bottom': `${this.cBottom}px`,
380
- } }, inheritedAttributes), h("div", { key: '95b112d7cae30f22ef778ceffb88edb4d941c170', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? h("slot", { name: "fixed" }) : null, h("div", { key: '2fdfcbc39fb66f11b6191911f2941c660f4c12e5', class: {
409
+ } }, inheritedAttributes), h("div", { key: '75d7cf9315bc8dfb150c3b5bcc356a1f9c793b5a', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? h("slot", { name: "fixed" }) : null, h("div", { key: 'f68bc5843c93ed6f32e998b80c5edc553c77a2d7', class: {
381
410
  'inner-scroll': true,
382
411
  'scroll-x': scrollX,
383
412
  'scroll-y': scrollY,
384
413
  overscroll: (scrollX || scrollY) && forceOverscroll,
385
- }, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, h("slot", { key: '6bc77e0054ec8e21635a7f2abfe0ca46e0962e03' })), transitionShadow ? (h("div", { class: "transition-effect" }, h("div", { class: "transition-cover" }), h("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? h("slot", { name: "fixed" }) : null));
414
+ }, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, h("slot", { key: '8d4b2be00f036f6a24bfa65e6324ca715dd93b60' })), transitionShadow ? (h("div", { class: "transition-effect" }, h("div", { class: "transition-cover" }), h("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? h("slot", { name: "fixed" }) : null));
386
415
  }
387
416
  static get is() { return "ion-content"; }
388
417
  static get encapsulation() { return "shadow"; }
@@ -721,6 +721,19 @@ export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding
721
721
  checkSafeAreaBottom = true;
722
722
  }
723
723
  }
724
+ /**
725
+ * Final check: If the popover extends into any safe-area region,
726
+ * ensure the corresponding flag is set regardless of side.
727
+ * This handles cases where a side-positioned popover (left/right)
728
+ * still needs bottom safe-area padding because it extends into that region.
729
+ */
730
+ const popoverBottom = bottom !== undefined ? bodyHeight - bottom : top + contentHeight;
731
+ if (popoverBottom + safeAreaMargin > bodyHeight) {
732
+ checkSafeAreaBottom = true;
733
+ }
734
+ if (top < safeAreaMargin) {
735
+ checkSafeAreaTop = true;
736
+ }
724
737
  return {
725
738
  top,
726
739
  left,
package/dist/docs.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2026-01-05T19:28:16",
2
+ "timestamp": "2026-01-06T16:44:21",
3
3
  "compiler": {
4
4
  "name": "@stencil/core",
5
5
  "version": "4.38.0",
@@ -152,7 +152,7 @@ Buttons.style = {
152
152
  md: buttonsMdCss
153
153
  };
154
154
 
155
- const contentCss = ":host{--background:var(--ion-background-color, #fff);--color:var(--ion-text-color, #000);--padding-top:0px;--padding-bottom:0px;--padding-start:0px;--padding-end:0px;--keyboard-offset:0px;--offset-top:0px;--offset-bottom:0px;--overflow:auto;display:block;position:relative;-ms-flex:1;flex:1;width:100%;height:100%;margin:0 !important;padding:0 !important;font-family:var(--ion-font-family, inherit);contain:size style}:host(.ion-color) .inner-scroll{background:var(--ion-color-base);color:var(--ion-color-contrast)}#background-content{left:0px;right:0px;top:calc(var(--offset-top) * -1);bottom:calc(var(--offset-bottom) * -1);position:absolute;background:var(--background)}.inner-scroll{left:0px;right:0px;top:calc(var(--offset-top) * -1);bottom:calc(var(--offset-bottom) * -1);-webkit-padding-start:var(--padding-start);padding-inline-start:var(--padding-start);-webkit-padding-end:var(--padding-end);padding-inline-end:var(--padding-end);padding-top:calc(var(--padding-top) + var(--offset-top));padding-bottom:calc(var(--padding-bottom) + var(--keyboard-offset) + var(--offset-bottom));position:absolute;color:var(--color);-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;-ms-touch-action:pan-x pan-y pinch-zoom;touch-action:pan-x pan-y pinch-zoom}.scroll-y,.scroll-x{-webkit-overflow-scrolling:touch;z-index:0;will-change:scroll-position}.scroll-y{overflow-y:var(--overflow);overscroll-behavior-y:contain}.scroll-x{overflow-x:var(--overflow);overscroll-behavior-x:contain}.overscroll::before,.overscroll::after{position:absolute;width:1px;height:1px;content:\"\"}.overscroll::before{bottom:-1px}.overscroll::after{top:-1px}:host(.content-sizing){display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-height:0;contain:none}:host(.content-sizing) .inner-scroll{position:relative;top:0;bottom:0;margin-top:calc(var(--offset-top) * -1);margin-bottom:calc(var(--offset-bottom) * -1)}.transition-effect{display:none;position:absolute;width:100%;height:100vh;opacity:0;pointer-events:none}:host(.content-ltr) .transition-effect{left:-100%;}:host(.content-rtl) .transition-effect{right:-100%;}.transition-cover{position:absolute;right:0;width:100%;height:100%;background:black;opacity:0.1}.transition-shadow{display:block;position:absolute;width:100%;height:100%;-webkit-box-shadow:inset -9px 0 9px 0 rgba(0, 0, 100, 0.03);box-shadow:inset -9px 0 9px 0 rgba(0, 0, 100, 0.03)}:host(.content-ltr) .transition-shadow{right:0;}:host(.content-rtl) .transition-shadow{left:0;-webkit-transform:scaleX(-1);transform:scaleX(-1)}::slotted([slot=fixed]){position:absolute;-webkit-transform:translateZ(0);transform:translateZ(0)}";
155
+ const contentCss = ":host{--background:var(--ion-background-color, #fff);--color:var(--ion-text-color, #000);--padding-top:0px;--padding-bottom:0px;--padding-start:0px;--padding-end:0px;--keyboard-offset:0px;--offset-top:0px;--offset-bottom:0px;--overflow:auto;display:block;position:relative;-ms-flex:1;flex:1;width:100%;height:100%;margin:0 !important;padding:0 !important;font-family:var(--ion-font-family, inherit);contain:size style}:host(.ion-color) .inner-scroll{background:var(--ion-color-base);color:var(--ion-color-contrast)}#background-content{left:0px;right:0px;top:calc(var(--offset-top) * -1);bottom:calc(var(--offset-bottom) * -1);position:absolute;background:var(--background)}.inner-scroll{left:0px;right:0px;top:calc(var(--offset-top) * -1);bottom:calc(var(--offset-bottom) * -1);-webkit-padding-start:var(--padding-start);padding-inline-start:var(--padding-start);-webkit-padding-end:var(--padding-end);padding-inline-end:var(--padding-end);padding-top:calc(var(--padding-top) + var(--offset-top));padding-bottom:calc(var(--padding-bottom) + var(--keyboard-offset) + var(--offset-bottom));position:absolute;color:var(--color);-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;-ms-touch-action:pan-x pan-y pinch-zoom;touch-action:pan-x pan-y pinch-zoom}.scroll-y,.scroll-x{-webkit-overflow-scrolling:touch;z-index:0;will-change:scroll-position}.scroll-y{overflow-y:var(--overflow);overscroll-behavior-y:contain}.scroll-x{overflow-x:var(--overflow);overscroll-behavior-x:contain}.overscroll::before,.overscroll::after{position:absolute;width:1px;height:1px;content:\"\"}.overscroll::before{bottom:-1px}.overscroll::after{top:-1px}:host(.content-sizing){display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-height:0;contain:none}:host(.content-sizing) .inner-scroll{position:relative;top:0;bottom:0;margin-top:calc(var(--offset-top) * -1);margin-bottom:calc(var(--offset-bottom) * -1)}.transition-effect{display:none;position:absolute;width:100%;height:100vh;opacity:0;pointer-events:none}:host(.content-ltr) .transition-effect{left:-100%;}:host(.content-rtl) .transition-effect{right:-100%;}.transition-cover{position:absolute;right:0;width:100%;height:100%;background:black;opacity:0.1}.transition-shadow{display:block;position:absolute;width:100%;height:100%;-webkit-box-shadow:inset -9px 0 9px 0 rgba(0, 0, 100, 0.03);box-shadow:inset -9px 0 9px 0 rgba(0, 0, 100, 0.03)}:host(.content-ltr) .transition-shadow{right:0;}:host(.content-rtl) .transition-shadow{left:0;-webkit-transform:scaleX(-1);transform:scaleX(-1)}:host(.safe-area-top) #background-content,:host(.safe-area-top) .inner-scroll{top:var(--ion-safe-area-top, 0px)}:host(.safe-area-bottom) #background-content,:host(.safe-area-bottom) .inner-scroll{bottom:var(--ion-safe-area-bottom, 0px)}::slotted([slot=fixed]){position:absolute;-webkit-transform:translateZ(0);transform:translateZ(0)}";
156
156
 
157
157
  const Content = class {
158
158
  constructor(hostRef) {
@@ -169,6 +169,12 @@ const Content = class {
169
169
  this.isMainContent = true;
170
170
  this.resizeTimeout = null;
171
171
  this.inheritedAttributes = {};
172
+ /**
173
+ * Track whether this content has sibling header/footer elements.
174
+ * When absent, we need to apply safe-area padding directly.
175
+ */
176
+ this.hasHeader = false;
177
+ this.hasFooter = false;
172
178
  this.tabsElement = null;
173
179
  // Detail is used in a hot loop in the scroll event, by allocating it here
174
180
  // V8 will be able to inline any read/write to it since it's a monomorphic class.
@@ -224,6 +230,8 @@ const Content = class {
224
230
  }
225
231
  connectedCallback() {
226
232
  this.isMainContent = this.el.closest('ion-menu, ion-popover, ion-modal') === null;
233
+ // Detect sibling header/footer for safe-area handling
234
+ this.detectSiblingElements();
227
235
  /**
228
236
  * The fullscreen content offsets need to be
229
237
  * computed after the tab bar has loaded. Since
@@ -259,6 +267,25 @@ const Content = class {
259
267
  }
260
268
  }
261
269
  }
270
+ /**
271
+ * Detects sibling ion-header and ion-footer elements.
272
+ * When these are absent, content needs to handle safe-area padding directly.
273
+ */
274
+ detectSiblingElements() {
275
+ // Check parent element for sibling header/footer.
276
+ const parent = this.el.parentElement;
277
+ if (parent) {
278
+ this.hasHeader = parent.querySelector(':scope > ion-header') !== null;
279
+ this.hasFooter = parent.querySelector(':scope > ion-footer') !== null;
280
+ }
281
+ // If no footer found, check if we're inside ion-tabs which has ion-tab-bar
282
+ if (!this.hasFooter) {
283
+ const tabs = this.el.closest('ion-tabs');
284
+ if (tabs) {
285
+ this.hasFooter = tabs.querySelector(':scope > ion-tab-bar') !== null;
286
+ }
287
+ }
288
+ }
262
289
  disconnectedCallback() {
263
290
  this.onScrollEnd();
264
291
  if (hasLazyBuild(this.el)) {
@@ -507,26 +534,28 @@ const Content = class {
507
534
  }
508
535
  }
509
536
  render() {
510
- const { fixedSlotPlacement, inheritedAttributes, isMainContent, scrollX, scrollY, el } = this;
537
+ const { fixedSlotPlacement, hasFooter, hasHeader, inheritedAttributes, isMainContent, scrollX, scrollY, el } = this;
511
538
  const rtl = isRTL(el) ? 'rtl' : 'ltr';
512
539
  const mode = getIonMode(this);
513
540
  const forceOverscroll = this.shouldForceOverscroll();
514
541
  const transitionShadow = mode === 'ios';
515
542
  this.resize();
516
- return (h(Host, Object.assign({ key: 'cd8781f848d8dc926fe66f43d43c49564425a507', role: isMainContent ? 'main' : undefined, class: createColorClasses(this.color, {
543
+ return (h(Host, Object.assign({ key: '83665c0e35e4f4117709606e47d27ad36e343458', role: isMainContent ? 'main' : undefined, class: createColorClasses(this.color, {
517
544
  [mode]: true,
518
545
  'content-sizing': hostContext('ion-popover', this.el),
519
546
  overscroll: forceOverscroll,
520
547
  [`content-${rtl}`]: true,
548
+ 'safe-area-top': isMainContent && !hasHeader,
549
+ 'safe-area-bottom': isMainContent && !hasFooter,
521
550
  }), style: {
522
551
  '--offset-top': `${this.cTop}px`,
523
552
  '--offset-bottom': `${this.cBottom}px`,
524
- } }, inheritedAttributes), h("div", { key: '95b112d7cae30f22ef778ceffb88edb4d941c170', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? h("slot", { name: "fixed" }) : null, h("div", { key: '2fdfcbc39fb66f11b6191911f2941c660f4c12e5', class: {
553
+ } }, inheritedAttributes), h("div", { key: '75d7cf9315bc8dfb150c3b5bcc356a1f9c793b5a', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? h("slot", { name: "fixed" }) : null, h("div", { key: 'f68bc5843c93ed6f32e998b80c5edc553c77a2d7', class: {
525
554
  'inner-scroll': true,
526
555
  'scroll-x': scrollX,
527
556
  'scroll-y': scrollY,
528
557
  overscroll: (scrollX || scrollY) && forceOverscroll,
529
- }, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, h("slot", { key: '6bc77e0054ec8e21635a7f2abfe0ca46e0962e03' })), transitionShadow ? (h("div", { class: "transition-effect" }, h("div", { class: "transition-cover" }), h("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? h("slot", { name: "fixed" }) : null));
558
+ }, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, h("slot", { key: '8d4b2be00f036f6a24bfa65e6324ca715dd93b60' })), transitionShadow ? (h("div", { class: "transition-effect" }, h("div", { class: "transition-cover" }), h("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? h("slot", { name: "fixed" }) : null));
530
559
  }
531
560
  get el() { return getElement(this); }
532
561
  };
@@ -731,6 +731,19 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
731
731
  checkSafeAreaBottom = true;
732
732
  }
733
733
  }
734
+ /**
735
+ * Final check: If the popover extends into any safe-area region,
736
+ * ensure the corresponding flag is set regardless of side.
737
+ * This handles cases where a side-positioned popover (left/right)
738
+ * still needs bottom safe-area padding because it extends into that region.
739
+ */
740
+ const popoverBottom = bottom !== undefined ? bodyHeight - bottom : top + contentHeight;
741
+ if (popoverBottom + safeAreaMargin > bodyHeight) {
742
+ checkSafeAreaBottom = true;
743
+ }
744
+ if (top < safeAreaMargin) {
745
+ checkSafeAreaTop = true;
746
+ }
734
747
  return {
735
748
  top,
736
749
  left,