@ionic/core 8.7.17-dev.11767895575.16ea7cef → 8.7.17-dev.11767897190.1ef0f479
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 +96 -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 +107 -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 +94 -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 +96 -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-d9fd799f.entry.js +4 -0
- package/dist/ionic/p-ec9ca3fe.entry.js +4 -0
- package/dist/types/components/content/content.d.ts +24 -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 +385 -52
- package/hydrate/index.mjs +385 -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,86 @@ 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
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Updates hasHeader/hasFooter based on current DOM state.
|
|
304
|
+
* Checks both direct siblings and elements wrapped in custom components
|
|
305
|
+
* (e.g., <my-header><ion-header>...</ion-header></my-header>).
|
|
306
|
+
*/
|
|
307
|
+
updateSiblingDetection() {
|
|
308
|
+
const parent = this.el.parentElement;
|
|
309
|
+
if (parent) {
|
|
310
|
+
// First check for direct ion-header/ion-footer siblings
|
|
311
|
+
this.hasHeader = parent.querySelector(':scope > ion-header') !== null;
|
|
312
|
+
this.hasFooter = parent.querySelector(':scope > ion-footer') !== null;
|
|
313
|
+
// If not found, check if any sibling contains them (wrapped components)
|
|
314
|
+
if (!this.hasHeader) {
|
|
315
|
+
this.hasHeader = this.siblingContainsElement(parent, 'ion-header');
|
|
316
|
+
}
|
|
317
|
+
if (!this.hasFooter) {
|
|
318
|
+
this.hasFooter = this.siblingContainsElement(parent, 'ion-footer');
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
// If no footer found, check if we're inside ion-tabs which has ion-tab-bar
|
|
322
|
+
if (!this.hasFooter) {
|
|
323
|
+
const tabs = this.el.closest('ion-tabs');
|
|
324
|
+
if (tabs) {
|
|
325
|
+
this.hasFooter = tabs.querySelector(':scope > ion-tab-bar') !== null;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Checks if any sibling element of ion-content contains the specified element.
|
|
331
|
+
* Only searches one level deep to avoid finding elements in nested pages.
|
|
332
|
+
*/
|
|
333
|
+
siblingContainsElement(parent, tagName) {
|
|
334
|
+
for (const sibling of parent.children) {
|
|
335
|
+
// Skip ion-content itself
|
|
336
|
+
if (sibling === this.el)
|
|
337
|
+
continue;
|
|
338
|
+
// Check if this sibling contains the target element as an immediate child
|
|
339
|
+
if (sibling.querySelector(`:scope > ${tagName}`) !== null) {
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
264
345
|
disconnectedCallback() {
|
|
346
|
+
var _a;
|
|
265
347
|
this.onScrollEnd();
|
|
348
|
+
// Clean up mutation observer to prevent memory leaks
|
|
349
|
+
(_a = this.parentMutationObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
350
|
+
this.parentMutationObserver = undefined;
|
|
266
351
|
if (helpers.hasLazyBuild(this.el)) {
|
|
267
352
|
/**
|
|
268
353
|
* The event listener and tabs caches need to
|
|
@@ -509,26 +594,28 @@ const Content = class {
|
|
|
509
594
|
}
|
|
510
595
|
}
|
|
511
596
|
render() {
|
|
512
|
-
const { fixedSlotPlacement, inheritedAttributes, isMainContent, scrollX, scrollY, el } = this;
|
|
597
|
+
const { fixedSlotPlacement, hasFooter, hasHeader, inheritedAttributes, isMainContent, scrollX, scrollY, el } = this;
|
|
513
598
|
const rtl = dir.isRTL(el) ? 'rtl' : 'ltr';
|
|
514
599
|
const mode = ionicGlobal.getIonMode(this);
|
|
515
600
|
const forceOverscroll = this.shouldForceOverscroll();
|
|
516
601
|
const transitionShadow = mode === 'ios';
|
|
517
602
|
this.resize();
|
|
518
|
-
return (index.h(index.Host, Object.assign({ key: '
|
|
603
|
+
return (index.h(index.Host, Object.assign({ key: 'f7218f733e4022a30875441bd949747537d28aa1', role: isMainContent ? 'main' : undefined, class: theme.createColorClasses(this.color, {
|
|
519
604
|
[mode]: true,
|
|
520
605
|
'content-sizing': theme.hostContext('ion-popover', this.el),
|
|
521
606
|
overscroll: forceOverscroll,
|
|
522
607
|
[`content-${rtl}`]: true,
|
|
608
|
+
'safe-area-top': isMainContent && !hasHeader,
|
|
609
|
+
'safe-area-bottom': isMainContent && !hasFooter,
|
|
523
610
|
}), style: {
|
|
524
611
|
'--offset-top': `${this.cTop}px`,
|
|
525
612
|
'--offset-bottom': `${this.cBottom}px`,
|
|
526
|
-
} }, inheritedAttributes), index.h("div", { key: '
|
|
613
|
+
} }, inheritedAttributes), index.h("div", { key: 'b735ec68c18c0b99c3595bb194029830e6542cde', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? index.h("slot", { name: "fixed" }) : null, index.h("div", { key: 'e76c00d030342d44ade6648c3f9e32ca990787ba', class: {
|
|
527
614
|
'inner-scroll': true,
|
|
528
615
|
'scroll-x': scrollX,
|
|
529
616
|
'scroll-y': scrollY,
|
|
530
617
|
overscroll: (scrollX || scrollY) && forceOverscroll,
|
|
531
|
-
}, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, index.h("slot", { key: '
|
|
618
|
+
}, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, index.h("slot", { key: '9049be4cea9b5da5ec1e1012248b05286fddeb7a' })), 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
619
|
}
|
|
533
620
|
get el() { return index.getElement(this); }
|
|
534
621
|
};
|
|
@@ -646,16 +733,16 @@ const Footer = class {
|
|
|
646
733
|
this.destroyCollapsibleFooter();
|
|
647
734
|
if (hasFade) {
|
|
648
735
|
const pageEl = this.el.closest('ion-app,ion-page,.ion-page,page-inner');
|
|
649
|
-
const contentEl = pageEl ? index$
|
|
736
|
+
const contentEl = pageEl ? index$2.findIonContent(pageEl) : null;
|
|
650
737
|
if (!contentEl) {
|
|
651
|
-
index$
|
|
738
|
+
index$2.printIonContentErrorMsg(this.el);
|
|
652
739
|
return;
|
|
653
740
|
}
|
|
654
741
|
this.setupFadeFooter(contentEl);
|
|
655
742
|
}
|
|
656
743
|
};
|
|
657
744
|
this.setupFadeFooter = async (contentEl) => {
|
|
658
|
-
const scrollEl = (this.scrollEl = await index$
|
|
745
|
+
const scrollEl = (this.scrollEl = await index$2.getScrollElement(contentEl));
|
|
659
746
|
/**
|
|
660
747
|
* Handle fading of toolbars on scroll
|
|
661
748
|
*/
|
|
@@ -968,7 +1055,7 @@ const Header = class {
|
|
|
968
1055
|
*/
|
|
969
1056
|
this.translucent = false;
|
|
970
1057
|
this.setupFadeHeader = async (contentEl, condenseHeader) => {
|
|
971
|
-
const scrollEl = (this.scrollEl = await index$
|
|
1058
|
+
const scrollEl = (this.scrollEl = await index$2.getScrollElement(contentEl));
|
|
972
1059
|
/**
|
|
973
1060
|
* Handle fading of toolbars on scroll
|
|
974
1061
|
*/
|
|
@@ -1002,7 +1089,7 @@ const Header = class {
|
|
|
1002
1089
|
this.destroyCollapsibleHeader();
|
|
1003
1090
|
if (hasCondense) {
|
|
1004
1091
|
const pageEl = this.el.closest('ion-app,ion-page,.ion-page,page-inner');
|
|
1005
|
-
const contentEl = pageEl ? index$
|
|
1092
|
+
const contentEl = pageEl ? index$2.findIonContent(pageEl) : null;
|
|
1006
1093
|
// Cloned elements are always needed in iOS transition
|
|
1007
1094
|
index.writeTask(() => {
|
|
1008
1095
|
const title = cloneElement('ion-title');
|
|
@@ -1013,9 +1100,9 @@ const Header = class {
|
|
|
1013
1100
|
}
|
|
1014
1101
|
else if (hasFade) {
|
|
1015
1102
|
const pageEl = this.el.closest('ion-app,ion-page,.ion-page,page-inner');
|
|
1016
|
-
const contentEl = pageEl ? index$
|
|
1103
|
+
const contentEl = pageEl ? index$2.findIonContent(pageEl) : null;
|
|
1017
1104
|
if (!contentEl) {
|
|
1018
|
-
index$
|
|
1105
|
+
index$2.printIonContentErrorMsg(this.el);
|
|
1019
1106
|
return;
|
|
1020
1107
|
}
|
|
1021
1108
|
const condenseHeader = contentEl.querySelector('ion-header[collapse="condense"]');
|
|
@@ -1038,13 +1125,13 @@ const Header = class {
|
|
|
1038
1125
|
}
|
|
1039
1126
|
async setupCondenseHeader(contentEl, pageEl) {
|
|
1040
1127
|
if (!contentEl || !pageEl) {
|
|
1041
|
-
index$
|
|
1128
|
+
index$2.printIonContentErrorMsg(this.el);
|
|
1042
1129
|
return;
|
|
1043
1130
|
}
|
|
1044
1131
|
if (typeof IntersectionObserver === 'undefined') {
|
|
1045
1132
|
return;
|
|
1046
1133
|
}
|
|
1047
|
-
this.scrollEl = await index$
|
|
1134
|
+
this.scrollEl = await index$2.getScrollElement(contentEl);
|
|
1048
1135
|
const headers = pageEl.querySelectorAll('ion-header');
|
|
1049
1136
|
this.collapsibleMainHeader = Array.from(headers).find((header) => header.collapse !== 'condense');
|
|
1050
1137
|
if (!this.collapsibleMainHeader) {
|
|
@@ -1242,7 +1329,7 @@ const RouterOutlet = class {
|
|
|
1242
1329
|
const { el, mode } = this;
|
|
1243
1330
|
const animated = this.animated && index.config.getBoolean('animated', true);
|
|
1244
1331
|
const animationBuilder = opts.animationBuilder || this.animation || index.config.get('navAnimation');
|
|
1245
|
-
await index$
|
|
1332
|
+
await index$3.transition(Object.assign(Object.assign({ mode,
|
|
1246
1333
|
animated,
|
|
1247
1334
|
enteringEl,
|
|
1248
1335
|
leavingEl, baseEl: el,
|