@ionic/core 8.7.17-dev.11767895575.16ea7cef → 8.7.17-dev.11767980394.1d9266a9
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.
- package/components/content.js +111 -8
- package/components/ion-tab-bar.js +3 -23
- package/components/modal.js +213 -12
- package/components/popover.js +83 -11
- package/dist/cjs/ion-app_8.cjs.entry.js +122 -20
- package/dist/cjs/ion-modal.cjs.entry.js +213 -12
- package/dist/cjs/ion-popover.cjs.entry.js +83 -11
- package/dist/cjs/ion-tab-bar_2.cjs.entry.js +3 -23
- package/dist/collection/components/content/content.css +10 -0
- package/dist/collection/components/content/content.js +109 -6
- package/dist/collection/components/modal/gestures/sheet.js +3 -1
- package/dist/collection/components/modal/gestures/swipe-to-close.js +3 -1
- package/dist/collection/components/modal/modal.ios.css +0 -4
- package/dist/collection/components/modal/modal.js +205 -7
- package/dist/collection/components/modal/modal.md.css +0 -4
- package/dist/collection/components/popover/animations/ios.enter.js +21 -5
- package/dist/collection/components/popover/animations/md.enter.js +30 -5
- package/dist/collection/components/popover/utils.js +32 -1
- package/dist/collection/components/tab-bar/tab-bar.js +3 -23
- package/dist/docs.json +1 -1
- package/dist/esm/ion-app_8.entry.js +111 -9
- package/dist/esm/ion-modal.entry.js +213 -12
- package/dist/esm/ion-popover.entry.js +83 -11
- package/dist/esm/ion-tab-bar_2.entry.js +3 -23
- package/dist/ionic/ionic.esm.js +1 -1
- package/dist/ionic/p-7268efa5.entry.js +4 -0
- package/dist/ionic/p-968a55d1.entry.js +4 -0
- package/dist/ionic/p-b55c44c8.entry.js +4 -0
- package/dist/ionic/p-ec9ca3fe.entry.js +4 -0
- package/dist/types/components/content/content.d.ts +26 -0
- package/dist/types/components/modal/gestures/sheet.d.ts +1 -1
- package/dist/types/components/modal/gestures/swipe-to-close.d.ts +1 -1
- package/dist/types/components/modal/modal.d.ts +45 -0
- package/dist/types/components/popover/utils.d.ts +2 -0
- package/dist/types/components/tab-bar/tab-bar.d.ts +0 -1
- package/hydrate/index.js +395 -52
- package/hydrate/index.mjs +395 -52
- package/package.json +1 -1
- package/dist/ionic/p-172a579f.entry.js +0 -4
- package/dist/ionic/p-732b2fd6.entry.js +0 -4
- package/dist/ionic/p-91840a80.entry.js +0 -4
- package/dist/ionic/p-f9061316.entry.js +0 -4
package/components/popover.js
CHANGED
|
@@ -657,6 +657,8 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
|
|
|
657
657
|
let bottom;
|
|
658
658
|
let originX = contentOriginX;
|
|
659
659
|
let originY = contentOriginY;
|
|
660
|
+
let checkSafeAreaTop = false;
|
|
661
|
+
let checkSafeAreaBottom = false;
|
|
660
662
|
let checkSafeAreaLeft = false;
|
|
661
663
|
let checkSafeAreaRight = false;
|
|
662
664
|
const triggerTop = triggerCoordinates
|
|
@@ -701,10 +703,18 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
|
|
|
701
703
|
* We chose 12 here so that the popover position looks a bit nicer as
|
|
702
704
|
* it is not right up against the edge of the screen.
|
|
703
705
|
*/
|
|
704
|
-
top = Math.max(
|
|
706
|
+
top = Math.max(bodyPadding, triggerTop - contentHeight - triggerHeight - (arrowHeight - 1));
|
|
705
707
|
arrowTop = top + contentHeight;
|
|
706
708
|
originY = 'bottom';
|
|
707
709
|
addPopoverBottomClass = true;
|
|
710
|
+
/**
|
|
711
|
+
* If the popover is positioned near the top edge, account for safe area.
|
|
712
|
+
* This ensures the popover doesn't overlap with status bars or notches.
|
|
713
|
+
*/
|
|
714
|
+
if (top <= bodyPadding + safeAreaMargin) {
|
|
715
|
+
checkSafeAreaTop = true;
|
|
716
|
+
top = bodyPadding;
|
|
717
|
+
}
|
|
708
718
|
/**
|
|
709
719
|
* If not enough room for popover to appear
|
|
710
720
|
* above trigger, then cut it off.
|
|
@@ -712,14 +722,35 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
|
|
|
712
722
|
}
|
|
713
723
|
else {
|
|
714
724
|
bottom = bodyPadding;
|
|
725
|
+
/**
|
|
726
|
+
* When the popover is pinned to the bottom, account for safe area.
|
|
727
|
+
* This ensures the popover doesn't overlap with home indicators
|
|
728
|
+
* or navigation bars (e.g., Android API 36+ edge-to-edge).
|
|
729
|
+
*/
|
|
730
|
+
checkSafeAreaBottom = true;
|
|
715
731
|
}
|
|
716
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
|
+
}
|
|
717
746
|
return {
|
|
718
747
|
top,
|
|
719
748
|
left,
|
|
720
749
|
bottom,
|
|
721
750
|
originX,
|
|
722
751
|
originY,
|
|
752
|
+
checkSafeAreaTop,
|
|
753
|
+
checkSafeAreaBottom,
|
|
723
754
|
checkSafeAreaLeft,
|
|
724
755
|
checkSafeAreaRight,
|
|
725
756
|
arrowTop,
|
|
@@ -780,7 +811,7 @@ const iosEnterAnimation = (baseEl, opts) => {
|
|
|
780
811
|
const results = getPopoverPosition(isRTL, contentWidth, contentHeight, arrowWidth, arrowHeight, reference, side, align, defaultPosition, trigger, ev);
|
|
781
812
|
const padding = size === 'cover' ? 0 : POPOVER_IOS_BODY_PADDING;
|
|
782
813
|
const margin = size === 'cover' ? 0 : 25;
|
|
783
|
-
const { originX, originY, top, left, bottom, checkSafeAreaLeft, checkSafeAreaRight, arrowTop, arrowLeft, addPopoverBottomClass, } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, margin, results.originX, results.originY, results.referenceCoordinates, results.arrowTop, results.arrowLeft, arrowHeight);
|
|
814
|
+
const { originX, originY, top, left, bottom, checkSafeAreaTop, checkSafeAreaBottom, checkSafeAreaLeft, checkSafeAreaRight, arrowTop, arrowLeft, addPopoverBottomClass, } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, margin, results.originX, results.originY, results.referenceCoordinates, results.arrowTop, results.arrowLeft, arrowHeight);
|
|
784
815
|
const baseAnimation = createAnimation();
|
|
785
816
|
const backdropAnimation = createAnimation();
|
|
786
817
|
const contentAnimation = createAnimation();
|
|
@@ -810,19 +841,35 @@ const iosEnterAnimation = (baseEl, opts) => {
|
|
|
810
841
|
if (addPopoverBottomClass) {
|
|
811
842
|
baseEl.classList.add('popover-bottom');
|
|
812
843
|
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
844
|
+
/**
|
|
845
|
+
* Safe area CSS variable adjustments.
|
|
846
|
+
* When the popover is positioned near an edge, we add the corresponding
|
|
847
|
+
* safe-area inset to ensure the popover doesn't overlap with system UI
|
|
848
|
+
* (status bars, home indicators, navigation bars on Android API 36+, etc.)
|
|
849
|
+
*/
|
|
850
|
+
const safeAreaTop = ' + var(--ion-safe-area-top, 0)';
|
|
851
|
+
const safeAreaBottom = ' + var(--ion-safe-area-bottom, 0)';
|
|
816
852
|
const safeAreaLeft = ' + var(--ion-safe-area-left, 0)';
|
|
817
853
|
const safeAreaRight = ' - var(--ion-safe-area-right, 0)';
|
|
854
|
+
let topValue = `${top}px`;
|
|
855
|
+
let bottomValue = bottom !== undefined ? `${bottom}px` : undefined;
|
|
818
856
|
let leftValue = `${left}px`;
|
|
857
|
+
if (checkSafeAreaTop) {
|
|
858
|
+
topValue = `${top}px${safeAreaTop}`;
|
|
859
|
+
}
|
|
860
|
+
if (checkSafeAreaBottom && bottomValue !== undefined) {
|
|
861
|
+
bottomValue = `${bottom}px${safeAreaBottom}`;
|
|
862
|
+
}
|
|
819
863
|
if (checkSafeAreaLeft) {
|
|
820
864
|
leftValue = `${left}px${safeAreaLeft}`;
|
|
821
865
|
}
|
|
822
866
|
if (checkSafeAreaRight) {
|
|
823
867
|
leftValue = `${left}px${safeAreaRight}`;
|
|
824
868
|
}
|
|
825
|
-
|
|
869
|
+
if (bottomValue !== undefined) {
|
|
870
|
+
contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
|
|
871
|
+
}
|
|
872
|
+
contentEl.style.setProperty('top', `calc(${topValue} + var(--offset-y, 0))`);
|
|
826
873
|
contentEl.style.setProperty('left', `calc(${leftValue} + var(--offset-x, 0))`);
|
|
827
874
|
contentEl.style.setProperty('transform-origin', `${originY} ${originX}`);
|
|
828
875
|
if (arrowEl !== null) {
|
|
@@ -898,7 +945,32 @@ const mdEnterAnimation = (baseEl, opts) => {
|
|
|
898
945
|
};
|
|
899
946
|
const results = getPopoverPosition(isRTL, contentWidth, contentHeight, 0, 0, reference, side, align, defaultPosition, trigger, ev);
|
|
900
947
|
const padding = size === 'cover' ? 0 : POPOVER_MD_BODY_PADDING;
|
|
901
|
-
const { originX, originY, top, left, bottom } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, 0, results.originX, results.originY, results.referenceCoordinates);
|
|
948
|
+
const { originX, originY, top, left, bottom, checkSafeAreaTop, checkSafeAreaBottom, checkSafeAreaLeft, checkSafeAreaRight, } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, 0, results.originX, results.originY, results.referenceCoordinates);
|
|
949
|
+
/**
|
|
950
|
+
* Safe area CSS variable adjustments.
|
|
951
|
+
* When the popover is positioned near an edge, we add the corresponding
|
|
952
|
+
* safe-area inset to ensure the popover doesn't overlap with system UI
|
|
953
|
+
* (status bars, home indicators, navigation bars on Android API 36+, etc.)
|
|
954
|
+
*/
|
|
955
|
+
const safeAreaTop = ' + var(--ion-safe-area-top, 0)';
|
|
956
|
+
const safeAreaBottom = ' + var(--ion-safe-area-bottom, 0)';
|
|
957
|
+
const safeAreaLeft = ' + var(--ion-safe-area-left, 0)';
|
|
958
|
+
const safeAreaRight = ' - var(--ion-safe-area-right, 0)';
|
|
959
|
+
let topValue = `${top}px`;
|
|
960
|
+
let bottomValue = bottom !== undefined ? `${bottom}px` : undefined;
|
|
961
|
+
let leftValue = `${left}px`;
|
|
962
|
+
if (checkSafeAreaTop) {
|
|
963
|
+
topValue = `${top}px${safeAreaTop}`;
|
|
964
|
+
}
|
|
965
|
+
if (checkSafeAreaBottom && bottomValue !== undefined) {
|
|
966
|
+
bottomValue = `${bottom}px${safeAreaBottom}`;
|
|
967
|
+
}
|
|
968
|
+
if (checkSafeAreaLeft) {
|
|
969
|
+
leftValue = `${left}px${safeAreaLeft}`;
|
|
970
|
+
}
|
|
971
|
+
if (checkSafeAreaRight) {
|
|
972
|
+
leftValue = `${left}px${safeAreaRight}`;
|
|
973
|
+
}
|
|
902
974
|
const baseAnimation = createAnimation();
|
|
903
975
|
const backdropAnimation = createAnimation();
|
|
904
976
|
const wrapperAnimation = createAnimation();
|
|
@@ -915,13 +987,13 @@ const mdEnterAnimation = (baseEl, opts) => {
|
|
|
915
987
|
contentAnimation
|
|
916
988
|
.addElement(contentEl)
|
|
917
989
|
.beforeStyles({
|
|
918
|
-
top: `calc(${
|
|
919
|
-
left: `calc(${
|
|
990
|
+
top: `calc(${topValue} + var(--offset-y, 0px))`,
|
|
991
|
+
left: `calc(${leftValue} + var(--offset-x, 0px))`,
|
|
920
992
|
'transform-origin': `${originY} ${originX}`,
|
|
921
993
|
})
|
|
922
994
|
.beforeAddWrite(() => {
|
|
923
|
-
if (
|
|
924
|
-
contentEl.style.setProperty('bottom',
|
|
995
|
+
if (bottomValue !== undefined) {
|
|
996
|
+
contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
|
|
925
997
|
}
|
|
926
998
|
})
|
|
927
999
|
.fromTo('transform', 'scale(0.8)', 'scale(1)');
|
|
@@ -6,16 +6,16 @@
|
|
|
6
6
|
var index = require('./index-D6Wc6v08.js');
|
|
7
7
|
var hardwareBackButton = require('./hardware-back-button-VCK4V3mG.js');
|
|
8
8
|
var ionicGlobal = require('./ionic-global-HMVqOFGO.js');
|
|
9
|
+
var index$1 = require('./index-DkNv4J_i.js');
|
|
9
10
|
var helpers = require('./helpers-DrTqNghc.js');
|
|
10
11
|
var dir = require('./dir-Cn0z1rJH.js');
|
|
11
12
|
var theme = require('./theme-CeDs6Hcv.js');
|
|
12
|
-
var index$
|
|
13
|
+
var index$2 = require('./index-CO6eryBo.js');
|
|
13
14
|
var keyboardController = require('./keyboard-controller-GXBiBRKS.js');
|
|
14
15
|
var cubicBezier = require('./cubic-bezier-DAjy1V-e.js');
|
|
15
16
|
var frameworkDelegate = require('./framework-delegate-DMJRBuDi.js');
|
|
16
17
|
var lockController = require('./lock-controller-aDB9wrEf.js');
|
|
17
|
-
var index$
|
|
18
|
-
require('./index-DkNv4J_i.js');
|
|
18
|
+
var index$3 = require('./index-094mMFB-.js');
|
|
19
19
|
require('./keyboard-UuAS4D_9.js');
|
|
20
20
|
require('./capacitor-DmA66EwP.js');
|
|
21
21
|
|
|
@@ -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.
|
|
@@ -225,7 +231,13 @@ const Content = class {
|
|
|
225
231
|
this.inheritedAttributes = helpers.inheritAriaAttributes(this.el);
|
|
226
232
|
}
|
|
227
233
|
connectedCallback() {
|
|
228
|
-
|
|
234
|
+
var _a;
|
|
235
|
+
// Content is "main" if not inside menu/popover/modal and not nested in another ion-content
|
|
236
|
+
this.isMainContent =
|
|
237
|
+
this.el.closest('ion-menu, ion-popover, ion-modal') === null &&
|
|
238
|
+
((_a = this.el.parentElement) === null || _a === void 0 ? void 0 : _a.closest('ion-content')) === null;
|
|
239
|
+
// Detect sibling header/footer for safe-area handling
|
|
240
|
+
this.detectSiblingElements();
|
|
229
241
|
/**
|
|
230
242
|
* The fullscreen content offsets need to be
|
|
231
243
|
* computed after the tab bar has loaded. Since
|
|
@@ -256,13 +268,101 @@ const Content = class {
|
|
|
256
268
|
* bubbles, we can catch any instances of child tab bars loading by listening
|
|
257
269
|
* on IonTabs.
|
|
258
270
|
*/
|
|
259
|
-
this.tabsLoadCallback = () =>
|
|
271
|
+
this.tabsLoadCallback = () => {
|
|
272
|
+
this.resize();
|
|
273
|
+
// Re-detect footer when tab bar loads (it may not exist during initial detection)
|
|
274
|
+
this.updateSiblingDetection();
|
|
275
|
+
index.forceUpdate(this);
|
|
276
|
+
};
|
|
260
277
|
closestTabs.addEventListener('ionTabBarLoaded', this.tabsLoadCallback);
|
|
261
278
|
}
|
|
262
279
|
}
|
|
263
280
|
}
|
|
281
|
+
/**
|
|
282
|
+
* Detects sibling ion-header and ion-footer elements and sets up
|
|
283
|
+
* a mutation observer to handle dynamic changes (e.g., conditional rendering).
|
|
284
|
+
*/
|
|
285
|
+
detectSiblingElements() {
|
|
286
|
+
this.updateSiblingDetection();
|
|
287
|
+
// Watch for dynamic header/footer changes (common in React conditional rendering)
|
|
288
|
+
const parent = this.el.parentElement;
|
|
289
|
+
if (parent && !this.parentMutationObserver && index$1.win !== undefined && 'MutationObserver' in index$1.win) {
|
|
290
|
+
this.parentMutationObserver = new MutationObserver(() => {
|
|
291
|
+
const prevHasHeader = this.hasHeader;
|
|
292
|
+
const prevHasFooter = this.hasFooter;
|
|
293
|
+
this.updateSiblingDetection();
|
|
294
|
+
// Only trigger re-render if header/footer detection actually changed
|
|
295
|
+
if (prevHasHeader !== this.hasHeader || prevHasFooter !== this.hasFooter) {
|
|
296
|
+
index.forceUpdate(this);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
this.parentMutationObserver.observe(parent, { childList: true });
|
|
300
|
+
}
|
|
301
|
+
// Watch for dynamic tab bar changes in ion-tabs (common in Angular conditional rendering)
|
|
302
|
+
const tabs = this.el.closest('ion-tabs');
|
|
303
|
+
if (tabs && !this.tabsMutationObserver && index$1.win !== undefined && 'MutationObserver' in index$1.win) {
|
|
304
|
+
this.tabsMutationObserver = new MutationObserver(() => {
|
|
305
|
+
const prevHasFooter = this.hasFooter;
|
|
306
|
+
this.updateSiblingDetection();
|
|
307
|
+
// Only trigger re-render if footer detection actually changed
|
|
308
|
+
if (prevHasFooter !== this.hasFooter) {
|
|
309
|
+
index.forceUpdate(this);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
this.tabsMutationObserver.observe(tabs, { childList: true });
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Updates hasHeader/hasFooter based on current DOM state.
|
|
317
|
+
* Checks both direct siblings and elements wrapped in custom components
|
|
318
|
+
* (e.g., <my-header><ion-header>...</ion-header></my-header>).
|
|
319
|
+
*/
|
|
320
|
+
updateSiblingDetection() {
|
|
321
|
+
const parent = this.el.parentElement;
|
|
322
|
+
if (parent) {
|
|
323
|
+
// First check for direct ion-header/ion-footer siblings
|
|
324
|
+
this.hasHeader = parent.querySelector(':scope > ion-header') !== null;
|
|
325
|
+
this.hasFooter = parent.querySelector(':scope > ion-footer') !== null;
|
|
326
|
+
// If not found, check if any sibling contains them (wrapped components)
|
|
327
|
+
if (!this.hasHeader) {
|
|
328
|
+
this.hasHeader = this.siblingContainsElement(parent, 'ion-header');
|
|
329
|
+
}
|
|
330
|
+
if (!this.hasFooter) {
|
|
331
|
+
this.hasFooter = this.siblingContainsElement(parent, 'ion-footer');
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
// If no footer found, check if we're inside ion-tabs which has ion-tab-bar
|
|
335
|
+
if (!this.hasFooter) {
|
|
336
|
+
const tabs = this.el.closest('ion-tabs');
|
|
337
|
+
if (tabs) {
|
|
338
|
+
this.hasFooter = tabs.querySelector(':scope > ion-tab-bar') !== null;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Checks if any sibling element of ion-content contains the specified element.
|
|
344
|
+
* Only searches one level deep to avoid finding elements in nested pages.
|
|
345
|
+
*/
|
|
346
|
+
siblingContainsElement(parent, tagName) {
|
|
347
|
+
for (const sibling of parent.children) {
|
|
348
|
+
// Skip ion-content itself
|
|
349
|
+
if (sibling === this.el)
|
|
350
|
+
continue;
|
|
351
|
+
// Check if this sibling contains the target element as an immediate child
|
|
352
|
+
if (sibling.querySelector(`:scope > ${tagName}`) !== null) {
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
264
358
|
disconnectedCallback() {
|
|
359
|
+
var _a, _b;
|
|
265
360
|
this.onScrollEnd();
|
|
361
|
+
// Clean up mutation observers to prevent memory leaks
|
|
362
|
+
(_a = this.parentMutationObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
363
|
+
this.parentMutationObserver = undefined;
|
|
364
|
+
(_b = this.tabsMutationObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
|
|
365
|
+
this.tabsMutationObserver = undefined;
|
|
266
366
|
if (helpers.hasLazyBuild(this.el)) {
|
|
267
367
|
/**
|
|
268
368
|
* The event listener and tabs caches need to
|
|
@@ -509,26 +609,28 @@ const Content = class {
|
|
|
509
609
|
}
|
|
510
610
|
}
|
|
511
611
|
render() {
|
|
512
|
-
const { fixedSlotPlacement, inheritedAttributes, isMainContent, scrollX, scrollY, el } = this;
|
|
612
|
+
const { fixedSlotPlacement, hasFooter, hasHeader, inheritedAttributes, isMainContent, scrollX, scrollY, el } = this;
|
|
513
613
|
const rtl = dir.isRTL(el) ? 'rtl' : 'ltr';
|
|
514
614
|
const mode = ionicGlobal.getIonMode(this);
|
|
515
615
|
const forceOverscroll = this.shouldForceOverscroll();
|
|
516
616
|
const transitionShadow = mode === 'ios';
|
|
517
617
|
this.resize();
|
|
518
|
-
return (index.h(index.Host, Object.assign({ key: '
|
|
618
|
+
return (index.h(index.Host, Object.assign({ key: '8b40855d7b3257fac16b0c945dc54b75a8a36b1f', role: isMainContent ? 'main' : undefined, class: theme.createColorClasses(this.color, {
|
|
519
619
|
[mode]: true,
|
|
520
620
|
'content-sizing': theme.hostContext('ion-popover', this.el),
|
|
521
621
|
overscroll: forceOverscroll,
|
|
522
622
|
[`content-${rtl}`]: true,
|
|
623
|
+
'safe-area-top': isMainContent && !hasHeader,
|
|
624
|
+
'safe-area-bottom': isMainContent && !hasFooter,
|
|
523
625
|
}), style: {
|
|
524
626
|
'--offset-top': `${this.cTop}px`,
|
|
525
627
|
'--offset-bottom': `${this.cBottom}px`,
|
|
526
|
-
} }, inheritedAttributes), index.h("div", { key: '
|
|
628
|
+
} }, inheritedAttributes), index.h("div", { key: '1ec3de99f33ed7a252a637c14887043c96046536', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? index.h("slot", { name: "fixed" }) : null, index.h("div", { key: 'dae33d14c489a55fcb6eceabbec3cb3b04726f34', class: {
|
|
527
629
|
'inner-scroll': true,
|
|
528
630
|
'scroll-x': scrollX,
|
|
529
631
|
'scroll-y': scrollY,
|
|
530
632
|
overscroll: (scrollX || scrollY) && forceOverscroll,
|
|
531
|
-
}, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, index.h("slot", { key: '
|
|
633
|
+
}, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, index.h("slot", { key: 'e99873d5fcc573e5d6ccad0f7c79192e99f4b749' })), 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
634
|
}
|
|
533
635
|
get el() { return index.getElement(this); }
|
|
534
636
|
};
|
|
@@ -646,16 +748,16 @@ const Footer = class {
|
|
|
646
748
|
this.destroyCollapsibleFooter();
|
|
647
749
|
if (hasFade) {
|
|
648
750
|
const pageEl = this.el.closest('ion-app,ion-page,.ion-page,page-inner');
|
|
649
|
-
const contentEl = pageEl ? index$
|
|
751
|
+
const contentEl = pageEl ? index$2.findIonContent(pageEl) : null;
|
|
650
752
|
if (!contentEl) {
|
|
651
|
-
index$
|
|
753
|
+
index$2.printIonContentErrorMsg(this.el);
|
|
652
754
|
return;
|
|
653
755
|
}
|
|
654
756
|
this.setupFadeFooter(contentEl);
|
|
655
757
|
}
|
|
656
758
|
};
|
|
657
759
|
this.setupFadeFooter = async (contentEl) => {
|
|
658
|
-
const scrollEl = (this.scrollEl = await index$
|
|
760
|
+
const scrollEl = (this.scrollEl = await index$2.getScrollElement(contentEl));
|
|
659
761
|
/**
|
|
660
762
|
* Handle fading of toolbars on scroll
|
|
661
763
|
*/
|
|
@@ -968,7 +1070,7 @@ const Header = class {
|
|
|
968
1070
|
*/
|
|
969
1071
|
this.translucent = false;
|
|
970
1072
|
this.setupFadeHeader = async (contentEl, condenseHeader) => {
|
|
971
|
-
const scrollEl = (this.scrollEl = await index$
|
|
1073
|
+
const scrollEl = (this.scrollEl = await index$2.getScrollElement(contentEl));
|
|
972
1074
|
/**
|
|
973
1075
|
* Handle fading of toolbars on scroll
|
|
974
1076
|
*/
|
|
@@ -1002,7 +1104,7 @@ const Header = class {
|
|
|
1002
1104
|
this.destroyCollapsibleHeader();
|
|
1003
1105
|
if (hasCondense) {
|
|
1004
1106
|
const pageEl = this.el.closest('ion-app,ion-page,.ion-page,page-inner');
|
|
1005
|
-
const contentEl = pageEl ? index$
|
|
1107
|
+
const contentEl = pageEl ? index$2.findIonContent(pageEl) : null;
|
|
1006
1108
|
// Cloned elements are always needed in iOS transition
|
|
1007
1109
|
index.writeTask(() => {
|
|
1008
1110
|
const title = cloneElement('ion-title');
|
|
@@ -1013,9 +1115,9 @@ const Header = class {
|
|
|
1013
1115
|
}
|
|
1014
1116
|
else if (hasFade) {
|
|
1015
1117
|
const pageEl = this.el.closest('ion-app,ion-page,.ion-page,page-inner');
|
|
1016
|
-
const contentEl = pageEl ? index$
|
|
1118
|
+
const contentEl = pageEl ? index$2.findIonContent(pageEl) : null;
|
|
1017
1119
|
if (!contentEl) {
|
|
1018
|
-
index$
|
|
1120
|
+
index$2.printIonContentErrorMsg(this.el);
|
|
1019
1121
|
return;
|
|
1020
1122
|
}
|
|
1021
1123
|
const condenseHeader = contentEl.querySelector('ion-header[collapse="condense"]');
|
|
@@ -1038,13 +1140,13 @@ const Header = class {
|
|
|
1038
1140
|
}
|
|
1039
1141
|
async setupCondenseHeader(contentEl, pageEl) {
|
|
1040
1142
|
if (!contentEl || !pageEl) {
|
|
1041
|
-
index$
|
|
1143
|
+
index$2.printIonContentErrorMsg(this.el);
|
|
1042
1144
|
return;
|
|
1043
1145
|
}
|
|
1044
1146
|
if (typeof IntersectionObserver === 'undefined') {
|
|
1045
1147
|
return;
|
|
1046
1148
|
}
|
|
1047
|
-
this.scrollEl = await index$
|
|
1149
|
+
this.scrollEl = await index$2.getScrollElement(contentEl);
|
|
1048
1150
|
const headers = pageEl.querySelectorAll('ion-header');
|
|
1049
1151
|
this.collapsibleMainHeader = Array.from(headers).find((header) => header.collapse !== 'condense');
|
|
1050
1152
|
if (!this.collapsibleMainHeader) {
|
|
@@ -1242,7 +1344,7 @@ const RouterOutlet = class {
|
|
|
1242
1344
|
const { el, mode } = this;
|
|
1243
1345
|
const animated = this.animated && index.config.getBoolean('animated', true);
|
|
1244
1346
|
const animationBuilder = opts.animationBuilder || this.animation || index.config.get('navAnimation');
|
|
1245
|
-
await index$
|
|
1347
|
+
await index$3.transition(Object.assign(Object.assign({ mode,
|
|
1246
1348
|
animated,
|
|
1247
1349
|
enteringEl,
|
|
1248
1350
|
leavingEl, baseEl: el,
|