@ionic/core 8.7.18-dev.11770316772.1e7b64e5 → 8.7.18-dev.11770674094.18396f54
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/ion-refresher.js +3 -32
- package/components/modal.js +248 -13
- package/components/popover.js +152 -30
- package/dist/cjs/ion-modal.cjs.entry.js +248 -13
- package/dist/cjs/ion-popover.cjs.entry.js +152 -30
- package/dist/cjs/ion-refresher_2.cjs.entry.js +3 -32
- package/dist/collection/components/modal/modal.ios.css +0 -4
- package/dist/collection/components/modal/modal.js +127 -11
- package/dist/collection/components/modal/modal.md.css +0 -4
- package/dist/collection/components/modal/safe-area-utils.js +123 -0
- package/dist/collection/components/popover/animations/ios.enter.js +33 -8
- package/dist/collection/components/popover/animations/md.enter.js +26 -6
- package/dist/collection/components/popover/utils.js +96 -19
- package/dist/collection/components/refresher/refresher.js +4 -70
- package/dist/docs.json +4 -49
- package/dist/esm/ion-modal.entry.js +248 -13
- package/dist/esm/ion-popover.entry.js +152 -30
- package/dist/esm/ion-refresher_2.entry.js +3 -32
- package/dist/html.html-data.json +1 -1
- package/dist/ionic/ionic.esm.js +1 -1
- package/dist/ionic/p-36d78f36.entry.js +4 -0
- package/dist/ionic/p-639dd543.entry.js +4 -0
- package/dist/ionic/p-cc3446d5.entry.js +4 -0
- package/dist/types/components/modal/modal.d.ts +23 -0
- package/dist/types/components/modal/safe-area-utils.d.ts +55 -0
- package/dist/types/components/popover/utils.d.ts +23 -1
- package/dist/types/components/refresher/refresher-interface.d.ts +0 -7
- package/dist/types/components/refresher/refresher.d.ts +3 -16
- package/dist/types/components.d.ts +5 -16
- package/dist/types/interface.d.ts +1 -1
- package/hydrate/index.js +403 -75
- package/hydrate/index.mjs +403 -75
- package/package.json +1 -1
- package/dist/ionic/p-90f2d3ff.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
|
@@ -13,6 +13,58 @@ import { e as deepReady, w as waitForMount } from './index2.js';
|
|
|
13
13
|
import { c as createAnimation } from './animation.js';
|
|
14
14
|
import { d as defineCustomElement$1 } from './backdrop.js';
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Shared per-frame cache for safe-area insets. Avoids creating a temporary
|
|
18
|
+
* DOM element and forcing a synchronous reflow on every call within the same
|
|
19
|
+
* frame (e.g., multiple popovers presenting simultaneously). Invalidated
|
|
20
|
+
* after the next animation frame so values stay fresh across orientation
|
|
21
|
+
* changes and viewport resizes.
|
|
22
|
+
*/
|
|
23
|
+
let cachedInsets = null;
|
|
24
|
+
let cacheInvalidationScheduled = false;
|
|
25
|
+
/**
|
|
26
|
+
* Resolves the current --ion-safe-area-* CSS custom property values
|
|
27
|
+
* to actual pixel numbers. A temporary element is needed because
|
|
28
|
+
* getComputedStyle on :root returns the declared value of custom
|
|
29
|
+
* properties (e.g. "env(safe-area-inset-top)") rather than a
|
|
30
|
+
* resolved number. By assigning them to standard padding properties,
|
|
31
|
+
* the browser resolves the value.
|
|
32
|
+
*
|
|
33
|
+
* Results are cached for the current frame to avoid repeated reflows.
|
|
34
|
+
*/
|
|
35
|
+
const getSafeAreaInsets = (doc) => {
|
|
36
|
+
if (cachedInsets !== null) {
|
|
37
|
+
return cachedInsets;
|
|
38
|
+
}
|
|
39
|
+
if (doc.body === null) {
|
|
40
|
+
return { top: 0, bottom: 0, left: 0, right: 0 };
|
|
41
|
+
}
|
|
42
|
+
const el = doc.createElement('div');
|
|
43
|
+
el.style.cssText =
|
|
44
|
+
'position:fixed;visibility:hidden;pointer-events:none;top:0;left:0;' +
|
|
45
|
+
'padding-top:var(--ion-safe-area-top,0px);' +
|
|
46
|
+
'padding-bottom:var(--ion-safe-area-bottom,0px);' +
|
|
47
|
+
'padding-left:var(--ion-safe-area-left,0px);' +
|
|
48
|
+
'padding-right:var(--ion-safe-area-right,0px);';
|
|
49
|
+
doc.body.appendChild(el);
|
|
50
|
+
const style = getComputedStyle(el);
|
|
51
|
+
const insets = {
|
|
52
|
+
top: parseFloat(style.paddingTop) || 0,
|
|
53
|
+
bottom: parseFloat(style.paddingBottom) || 0,
|
|
54
|
+
left: parseFloat(style.paddingLeft) || 0,
|
|
55
|
+
right: parseFloat(style.paddingRight) || 0,
|
|
56
|
+
};
|
|
57
|
+
el.remove();
|
|
58
|
+
cachedInsets = insets;
|
|
59
|
+
if (!cacheInvalidationScheduled) {
|
|
60
|
+
cacheInvalidationScheduled = true;
|
|
61
|
+
raf(() => {
|
|
62
|
+
cachedInsets = null;
|
|
63
|
+
cacheInvalidationScheduled = false;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return insets;
|
|
67
|
+
};
|
|
16
68
|
/**
|
|
17
69
|
* Returns the dimensions of the popover
|
|
18
70
|
* arrow on `ios` mode. If arrow is disabled
|
|
@@ -648,8 +700,10 @@ const calculatePopoverCenterAlign = (side, triggerBoundingBox, contentWidth, con
|
|
|
648
700
|
* Adjusts popover positioning coordinates
|
|
649
701
|
* such that popover does not appear offscreen
|
|
650
702
|
* or overlapping safe area bounds.
|
|
703
|
+
*
|
|
704
|
+
* @internal - This is not part of the public API.
|
|
651
705
|
*/
|
|
652
|
-
const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyWidth, bodyHeight, contentWidth, contentHeight,
|
|
706
|
+
const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyWidth, bodyHeight, contentWidth, contentHeight, safeArea, contentOriginX, contentOriginY, triggerCoordinates, coordArrowTop = 0, coordArrowLeft = 0, arrowHeight = 0) => {
|
|
653
707
|
let arrowTop = coordArrowTop;
|
|
654
708
|
const arrowLeft = coordArrowLeft;
|
|
655
709
|
let left = coordLeft;
|
|
@@ -659,16 +713,19 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
|
|
|
659
713
|
let originY = contentOriginY;
|
|
660
714
|
let checkSafeAreaLeft = false;
|
|
661
715
|
let checkSafeAreaRight = false;
|
|
716
|
+
let checkSafeAreaTop = false;
|
|
717
|
+
let checkSafeAreaBottom = false;
|
|
662
718
|
const triggerTop = triggerCoordinates
|
|
663
719
|
? triggerCoordinates.top + triggerCoordinates.height
|
|
664
720
|
: bodyHeight / 2 - contentHeight / 2;
|
|
665
721
|
const triggerHeight = triggerCoordinates ? triggerCoordinates.height : 0;
|
|
666
722
|
let addPopoverBottomClass = false;
|
|
723
|
+
const hideArrow = false;
|
|
667
724
|
/**
|
|
668
725
|
* Adjust popover so it does not
|
|
669
726
|
* go off the left of the screen.
|
|
670
727
|
*/
|
|
671
|
-
if (left < bodyPadding +
|
|
728
|
+
if (left < bodyPadding + safeArea.left) {
|
|
672
729
|
left = bodyPadding;
|
|
673
730
|
checkSafeAreaLeft = true;
|
|
674
731
|
originX = 'left';
|
|
@@ -677,41 +734,58 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
|
|
|
677
734
|
* go off the right of the screen.
|
|
678
735
|
*/
|
|
679
736
|
}
|
|
680
|
-
else if (contentWidth + bodyPadding + left +
|
|
737
|
+
else if (contentWidth + bodyPadding + left + safeArea.right > bodyWidth) {
|
|
681
738
|
checkSafeAreaRight = true;
|
|
682
739
|
left = bodyWidth - contentWidth - bodyPadding;
|
|
683
740
|
originX = 'right';
|
|
684
741
|
}
|
|
685
742
|
/**
|
|
686
743
|
* Adjust popover so it does not
|
|
687
|
-
* go off the
|
|
744
|
+
* go off the bottom of the screen
|
|
745
|
+
* or overlap the bottom safe area.
|
|
688
746
|
* If popover is on the left or the right of
|
|
689
747
|
* the trigger, then we should not adjust top
|
|
690
748
|
* margins.
|
|
691
749
|
*/
|
|
692
|
-
if (triggerTop + triggerHeight + contentHeight > bodyHeight
|
|
693
|
-
|
|
750
|
+
if (triggerTop + triggerHeight + contentHeight > bodyHeight - safeArea.bottom &&
|
|
751
|
+
(side === 'top' || side === 'bottom')) {
|
|
752
|
+
/**
|
|
753
|
+
* Calculate where the popover top would be if flipped
|
|
754
|
+
* above the trigger. Check whether that position clears
|
|
755
|
+
* the top safe area with room for bodyPadding.
|
|
756
|
+
*/
|
|
757
|
+
const idealFlipTop = triggerTop - contentHeight - triggerHeight - (arrowHeight - 1);
|
|
758
|
+
if (idealFlipTop >= safeArea.top + bodyPadding) {
|
|
694
759
|
/**
|
|
695
|
-
*
|
|
696
|
-
*
|
|
697
|
-
*
|
|
698
|
-
* off the bottom of the screen. However, we do not want to move
|
|
699
|
-
* the popover up so much that it goes off the top of the screen.
|
|
700
|
-
*
|
|
701
|
-
* We chose 12 here so that the popover position looks a bit nicer as
|
|
702
|
-
* it is not right up against the edge of the screen.
|
|
760
|
+
* Popover fits above the trigger without overlapping
|
|
761
|
+
* the top safe area. Use the ideal position directly —
|
|
762
|
+
* no safe-area CSS vars needed since it clears both edges.
|
|
703
763
|
*/
|
|
704
|
-
top =
|
|
764
|
+
top = idealFlipTop;
|
|
705
765
|
arrowTop = top + contentHeight;
|
|
706
766
|
originY = 'bottom';
|
|
707
767
|
addPopoverBottomClass = true;
|
|
708
|
-
/**
|
|
709
|
-
* If not enough room for popover to appear
|
|
710
|
-
* above trigger, then cut it off.
|
|
711
|
-
*/
|
|
712
768
|
}
|
|
713
769
|
else {
|
|
770
|
+
/**
|
|
771
|
+
* Can't flip above the trigger. Constrain the bottom
|
|
772
|
+
* edge while keeping the top near the trigger. This
|
|
773
|
+
* creates a scrollable area anchored to the trigger.
|
|
774
|
+
*/
|
|
714
775
|
bottom = bodyPadding;
|
|
776
|
+
checkSafeAreaBottom = true;
|
|
777
|
+
/**
|
|
778
|
+
* When the trigger is near the bottom of the screen,
|
|
779
|
+
* the calculated top may be at or past the bottom
|
|
780
|
+
* constraint, leaving zero visible height. In that
|
|
781
|
+
* case, pull top up to the top safe area so the
|
|
782
|
+
* popover fills the available space between safe areas.
|
|
783
|
+
*/
|
|
784
|
+
const bottomEdge = bodyHeight - safeArea.bottom - bodyPadding;
|
|
785
|
+
if (top >= bottomEdge) {
|
|
786
|
+
top = safeArea.top + bodyPadding;
|
|
787
|
+
checkSafeAreaTop = true;
|
|
788
|
+
}
|
|
715
789
|
}
|
|
716
790
|
}
|
|
717
791
|
return {
|
|
@@ -722,9 +796,12 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
|
|
|
722
796
|
originY,
|
|
723
797
|
checkSafeAreaLeft,
|
|
724
798
|
checkSafeAreaRight,
|
|
799
|
+
checkSafeAreaTop,
|
|
800
|
+
checkSafeAreaBottom,
|
|
725
801
|
arrowTop,
|
|
726
802
|
arrowLeft,
|
|
727
803
|
addPopoverBottomClass,
|
|
804
|
+
hideArrow,
|
|
728
805
|
};
|
|
729
806
|
};
|
|
730
807
|
const shouldShowArrow = (side, didAdjustBounds = false, ev, trigger) => {
|
|
@@ -754,6 +831,15 @@ const shouldShowArrow = (side, didAdjustBounds = false, ev, trigger) => {
|
|
|
754
831
|
};
|
|
755
832
|
|
|
756
833
|
const POPOVER_IOS_BODY_PADDING = 5;
|
|
834
|
+
/**
|
|
835
|
+
* Minimum edge margin for iOS popovers ensures visual spacing from screen
|
|
836
|
+
* edges on devices without safe areas (e.g., older iPhones without notches).
|
|
837
|
+
* Previously this was a hardcoded `safeAreaMargin = 25` that served dual
|
|
838
|
+
* purpose: safe-area avoidance AND visual spacing. Now that actual safe-area
|
|
839
|
+
* insets are read dynamically, this floor preserves the visual spacing when
|
|
840
|
+
* safe-area values are 0.
|
|
841
|
+
*/
|
|
842
|
+
const POPOVER_IOS_MIN_EDGE_MARGIN = 25;
|
|
757
843
|
/**
|
|
758
844
|
* iOS Popover Enter Animation
|
|
759
845
|
*/
|
|
@@ -779,8 +865,16 @@ const iosEnterAnimation = (baseEl, opts) => {
|
|
|
779
865
|
};
|
|
780
866
|
const results = getPopoverPosition(isRTL, contentWidth, contentHeight, arrowWidth, arrowHeight, reference, side, align, defaultPosition, trigger, ev);
|
|
781
867
|
const padding = size === 'cover' ? 0 : POPOVER_IOS_BODY_PADDING;
|
|
782
|
-
const
|
|
783
|
-
const
|
|
868
|
+
const rawSafeArea = getSafeAreaInsets(doc);
|
|
869
|
+
const safeArea = size === 'cover'
|
|
870
|
+
? { top: 0, bottom: 0, left: 0, right: 0 }
|
|
871
|
+
: {
|
|
872
|
+
top: Math.max(rawSafeArea.top, POPOVER_IOS_MIN_EDGE_MARGIN),
|
|
873
|
+
bottom: Math.max(rawSafeArea.bottom, POPOVER_IOS_MIN_EDGE_MARGIN),
|
|
874
|
+
left: Math.max(rawSafeArea.left, POPOVER_IOS_MIN_EDGE_MARGIN),
|
|
875
|
+
right: Math.max(rawSafeArea.right, POPOVER_IOS_MIN_EDGE_MARGIN),
|
|
876
|
+
};
|
|
877
|
+
const { originX, originY, top, left, bottom, checkSafeAreaLeft, checkSafeAreaRight, checkSafeAreaTop, checkSafeAreaBottom, arrowTop, arrowLeft, addPopoverBottomClass} = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, safeArea, results.originX, results.originY, results.referenceCoordinates, results.arrowTop, results.arrowLeft, arrowHeight);
|
|
784
878
|
const baseAnimation = createAnimation();
|
|
785
879
|
const backdropAnimation = createAnimation();
|
|
786
880
|
const contentAnimation = createAnimation();
|
|
@@ -811,10 +905,14 @@ const iosEnterAnimation = (baseEl, opts) => {
|
|
|
811
905
|
baseEl.classList.add('popover-bottom');
|
|
812
906
|
}
|
|
813
907
|
if (bottom !== undefined) {
|
|
814
|
-
|
|
908
|
+
let bottomValue = `${bottom}px`;
|
|
909
|
+
if (checkSafeAreaBottom) {
|
|
910
|
+
bottomValue = `${bottom}px + var(--ion-safe-area-bottom, 0px)`;
|
|
911
|
+
}
|
|
912
|
+
contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
|
|
815
913
|
}
|
|
816
|
-
const safeAreaLeft = ' + var(--ion-safe-area-left,
|
|
817
|
-
const safeAreaRight = ' - var(--ion-safe-area-right,
|
|
914
|
+
const safeAreaLeft = ' + var(--ion-safe-area-left, 0px)';
|
|
915
|
+
const safeAreaRight = ' - var(--ion-safe-area-right, 0px)';
|
|
818
916
|
let leftValue = `${left}px`;
|
|
819
917
|
if (checkSafeAreaLeft) {
|
|
820
918
|
leftValue = `${left}px${safeAreaLeft}`;
|
|
@@ -822,7 +920,11 @@ const iosEnterAnimation = (baseEl, opts) => {
|
|
|
822
920
|
if (checkSafeAreaRight) {
|
|
823
921
|
leftValue = `${left}px${safeAreaRight}`;
|
|
824
922
|
}
|
|
825
|
-
|
|
923
|
+
let topValue = `${top}px`;
|
|
924
|
+
if (checkSafeAreaTop) {
|
|
925
|
+
topValue = `${top}px + var(--ion-safe-area-top, 0px)`;
|
|
926
|
+
}
|
|
927
|
+
contentEl.style.setProperty('top', `calc(${topValue} + var(--offset-y, 0))`);
|
|
826
928
|
contentEl.style.setProperty('left', `calc(${leftValue} + var(--offset-x, 0))`);
|
|
827
929
|
contentEl.style.setProperty('transform-origin', `${originY} ${originX}`);
|
|
828
930
|
if (arrowEl !== null) {
|
|
@@ -898,7 +1000,23 @@ const mdEnterAnimation = (baseEl, opts) => {
|
|
|
898
1000
|
};
|
|
899
1001
|
const results = getPopoverPosition(isRTL, contentWidth, contentHeight, 0, 0, reference, side, align, defaultPosition, trigger, ev);
|
|
900
1002
|
const padding = size === 'cover' ? 0 : POPOVER_MD_BODY_PADDING;
|
|
901
|
-
|
|
1003
|
+
// MD mode now applies safe-area insets (previously passed 0, ignoring all safe areas).
|
|
1004
|
+
// This is needed for Android edge-to-edge (API 36+) where system bars overlap content.
|
|
1005
|
+
const safeArea = size === 'cover' ? { top: 0, bottom: 0, left: 0, right: 0 } : getSafeAreaInsets(doc);
|
|
1006
|
+
const { originX, originY, top, left, bottom, checkSafeAreaLeft, checkSafeAreaRight, checkSafeAreaTop, checkSafeAreaBottom, addPopoverBottomClass, } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, safeArea, results.originX, results.originY, results.referenceCoordinates);
|
|
1007
|
+
const safeAreaLeftCalc = ' + var(--ion-safe-area-left, 0px)';
|
|
1008
|
+
const safeAreaRightCalc = ' - var(--ion-safe-area-right, 0px)';
|
|
1009
|
+
let leftValue = `${left}px`;
|
|
1010
|
+
if (checkSafeAreaLeft) {
|
|
1011
|
+
leftValue = `${left}px${safeAreaLeftCalc}`;
|
|
1012
|
+
}
|
|
1013
|
+
if (checkSafeAreaRight) {
|
|
1014
|
+
leftValue = `${left}px${safeAreaRightCalc}`;
|
|
1015
|
+
}
|
|
1016
|
+
let topValue = `${top}px`;
|
|
1017
|
+
if (checkSafeAreaTop) {
|
|
1018
|
+
topValue = `${top}px + var(--ion-safe-area-top, 0px)`;
|
|
1019
|
+
}
|
|
902
1020
|
const baseAnimation = createAnimation();
|
|
903
1021
|
const backdropAnimation = createAnimation();
|
|
904
1022
|
const wrapperAnimation = createAnimation();
|
|
@@ -915,13 +1033,17 @@ const mdEnterAnimation = (baseEl, opts) => {
|
|
|
915
1033
|
contentAnimation
|
|
916
1034
|
.addElement(contentEl)
|
|
917
1035
|
.beforeStyles({
|
|
918
|
-
top: `calc(${
|
|
919
|
-
left: `calc(${
|
|
1036
|
+
top: `calc(${topValue} + var(--offset-y, 0px))`,
|
|
1037
|
+
left: `calc(${leftValue} + var(--offset-x, 0px))`,
|
|
920
1038
|
'transform-origin': `${originY} ${originX}`,
|
|
921
1039
|
})
|
|
922
1040
|
.beforeAddWrite(() => {
|
|
923
1041
|
if (bottom !== undefined) {
|
|
924
|
-
|
|
1042
|
+
let bottomValue = `${bottom}px`;
|
|
1043
|
+
if (checkSafeAreaBottom) {
|
|
1044
|
+
bottomValue = `${bottom}px + var(--ion-safe-area-bottom, 0px)`;
|
|
1045
|
+
}
|
|
1046
|
+
contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
|
|
925
1047
|
}
|
|
926
1048
|
})
|
|
927
1049
|
.fromTo('transform', 'scale(0.8)', 'scale(1)');
|
|
@@ -933,7 +1055,7 @@ const mdEnterAnimation = (baseEl, opts) => {
|
|
|
933
1055
|
if (size === 'cover') {
|
|
934
1056
|
baseEl.style.setProperty('--width', `${contentWidth}px`);
|
|
935
1057
|
}
|
|
936
|
-
if (
|
|
1058
|
+
if (addPopoverBottomClass) {
|
|
937
1059
|
baseEl.classList.add('popover-bottom');
|
|
938
1060
|
}
|
|
939
1061
|
})
|