@ionic/core 8.7.17-dev.11767636336.113b441d → 8.7.17-dev.11767639327.19c404a6
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-tab-bar.js +3 -17
- package/components/modal.js +127 -11
- package/components/popover.js +60 -10
- package/dist/cjs/ion-modal.cjs.entry.js +127 -11
- package/dist/cjs/ion-popover.cjs.entry.js +60 -10
- package/dist/cjs/ion-tab-bar_2.cjs.entry.js +3 -17
- 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 +119 -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 +20 -4
- package/dist/collection/components/popover/utils.js +19 -1
- package/dist/collection/components/tab-bar/tab-bar.js +3 -17
- package/dist/docs.json +1 -1
- package/dist/esm/ion-modal.entry.js +127 -11
- package/dist/esm/ion-popover.entry.js +60 -10
- package/dist/esm/ion-tab-bar_2.entry.js +3 -17
- package/dist/ionic/ionic.esm.js +1 -1
- package/dist/ionic/p-301ad219.entry.js +4 -0
- package/dist/ionic/p-71fa7adc.entry.js +4 -0
- package/dist/ionic/p-7268efa5.entry.js +4 -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 +24 -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 +190 -38
- package/hydrate/index.mjs +190 -38
- package/package.json +1 -1
- package/dist/ionic/p-0d0eb444.entry.js +0 -4
- package/dist/ionic/p-91840a80.entry.js +0 -4
- package/dist/ionic/p-f9061316.entry.js +0 -4
|
@@ -21,7 +21,6 @@ const TabBar = /*@__PURE__*/ proxyCustomElement(class TabBar extends HTMLElement
|
|
|
21
21
|
this.ionTabBarLoaded = createEvent(this, "ionTabBarLoaded", 7);
|
|
22
22
|
this.keyboardCtrl = null;
|
|
23
23
|
this.didLoad = false;
|
|
24
|
-
this.isComponentConnected = false;
|
|
25
24
|
this.keyboardVisible = false;
|
|
26
25
|
/**
|
|
27
26
|
* If `true`, the tab bar will be translucent.
|
|
@@ -56,8 +55,7 @@ const TabBar = /*@__PURE__*/ proxyCustomElement(class TabBar extends HTMLElement
|
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
57
|
async connectedCallback() {
|
|
59
|
-
this.
|
|
60
|
-
const keyboardCtrl = await createKeyboardController(async (keyboardOpen, waitForResize) => {
|
|
58
|
+
this.keyboardCtrl = await createKeyboardController(async (keyboardOpen, waitForResize) => {
|
|
61
59
|
/**
|
|
62
60
|
* If the keyboard is hiding, then we need to wait
|
|
63
61
|
* for the webview to resize. Otherwise, the tab bar
|
|
@@ -68,33 +66,21 @@ const TabBar = /*@__PURE__*/ proxyCustomElement(class TabBar extends HTMLElement
|
|
|
68
66
|
}
|
|
69
67
|
this.keyboardVisible = keyboardOpen; // trigger re-render by updating state
|
|
70
68
|
});
|
|
71
|
-
/**
|
|
72
|
-
* Destroy the keyboard controller if the component was
|
|
73
|
-
* disconnected during async initialization to prevent memory leaks.
|
|
74
|
-
*/
|
|
75
|
-
if (this.isComponentConnected) {
|
|
76
|
-
this.keyboardCtrl = keyboardCtrl;
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
keyboardCtrl.destroy();
|
|
80
|
-
}
|
|
81
69
|
}
|
|
82
70
|
disconnectedCallback() {
|
|
83
|
-
this.isComponentConnected = false;
|
|
84
71
|
if (this.keyboardCtrl) {
|
|
85
72
|
this.keyboardCtrl.destroy();
|
|
86
|
-
this.keyboardCtrl = null;
|
|
87
73
|
}
|
|
88
74
|
}
|
|
89
75
|
render() {
|
|
90
76
|
const { color, translucent, keyboardVisible } = this;
|
|
91
77
|
const mode = getIonMode(this);
|
|
92
78
|
const shouldHide = keyboardVisible && this.el.getAttribute('slot') !== 'top';
|
|
93
|
-
return (h(Host, { key: '
|
|
79
|
+
return (h(Host, { key: '388ec37ce308035bab78d6c9a016bb616e9517a9', role: "tablist", "aria-hidden": shouldHide ? 'true' : null, class: createColorClasses(color, {
|
|
94
80
|
[mode]: true,
|
|
95
81
|
'tab-bar-translucent': translucent,
|
|
96
82
|
'tab-bar-hidden': shouldHide,
|
|
97
|
-
}) }, h("slot", { key: '
|
|
83
|
+
}) }, h("slot", { key: 'ce10ade2b86725e24f3254516483eeedd8ecb16a' })));
|
|
98
84
|
}
|
|
99
85
|
get el() { return this; }
|
|
100
86
|
static get watchers() { return {
|
package/components/modal.js
CHANGED
|
@@ -246,7 +246,7 @@ const calculateSpringStep = (t) => {
|
|
|
246
246
|
const SwipeToCloseDefaults = {
|
|
247
247
|
MIN_PRESENTING_SCALE: 0.915,
|
|
248
248
|
};
|
|
249
|
-
const createSwipeToCloseGesture = (el, animation, statusBarStyle, onDismiss) => {
|
|
249
|
+
const createSwipeToCloseGesture = (el, animation, statusBarStyle, onDismiss, onGestureMove) => {
|
|
250
250
|
/**
|
|
251
251
|
* The step value at which a card modal
|
|
252
252
|
* is eligible for dismissing via gesture.
|
|
@@ -403,6 +403,8 @@ const createSwipeToCloseGesture = (el, animation, statusBarStyle, onDismiss) =>
|
|
|
403
403
|
const processedStep = isAttemptingDismissWithCanDismiss ? calculateSpringStep(step / maxStep) : step;
|
|
404
404
|
const clampedStep = clamp(0.0001, processedStep, maxStep);
|
|
405
405
|
animation.progressStep(clampedStep);
|
|
406
|
+
// Notify modal of position change for safe-area updates
|
|
407
|
+
onGestureMove === null || onGestureMove === void 0 ? void 0 : onGestureMove();
|
|
406
408
|
/**
|
|
407
409
|
* When swiping down half way, the status bar style
|
|
408
410
|
* should be reset to its default value.
|
|
@@ -946,7 +948,7 @@ const mdLeaveAnimation = (baseEl, opts) => {
|
|
|
946
948
|
return baseAnimation;
|
|
947
949
|
};
|
|
948
950
|
|
|
949
|
-
const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, backdropBreakpoint, animation, breakpoints = [], expandToScroll, getCurrentBreakpoint, onDismiss, onBreakpointChange) => {
|
|
951
|
+
const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, backdropBreakpoint, animation, breakpoints = [], expandToScroll, getCurrentBreakpoint, onDismiss, onBreakpointChange, onGestureMove) => {
|
|
950
952
|
// Defaults for the sheet swipe animation
|
|
951
953
|
const defaultBackdrop = [
|
|
952
954
|
{ offset: 0, opacity: 'var(--backdrop-opacity)' },
|
|
@@ -1277,6 +1279,8 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
|
|
|
1277
1279
|
: step;
|
|
1278
1280
|
offset = clamp(0.0001, processedStep, maxStep);
|
|
1279
1281
|
animation.progressStep(offset);
|
|
1282
|
+
// Notify modal of position change for safe-area updates
|
|
1283
|
+
onGestureMove === null || onGestureMove === void 0 ? void 0 : onGestureMove();
|
|
1280
1284
|
};
|
|
1281
1285
|
const onEnd = (detail) => {
|
|
1282
1286
|
/**
|
|
@@ -1471,9 +1475,9 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
|
|
|
1471
1475
|
};
|
|
1472
1476
|
};
|
|
1473
1477
|
|
|
1474
|
-
const modalIosCss = ":host{--width:100%;--min-width:auto;--max-width:auto;--height:100%;--min-height:auto;--max-height:auto;--overflow:hidden;--border-radius:0;--border-width:0;--border-style:none;--border-color:transparent;--background:var(--ion-background-color, #fff);--box-shadow:none;--backdrop-opacity:0;left:0;right:0;top:0;bottom:0;display:-ms-flexbox;display:flex;position:absolute;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;outline:none;color:var(--ion-text-color, #000);contain:strict}.modal-wrapper,ion-backdrop{pointer-events:auto}:host(.overlay-hidden){display:none}.modal-wrapper,.modal-shadow{border-radius:var(--border-radius);width:var(--width);min-width:var(--min-width);max-width:var(--max-width);height:var(--height);min-height:var(--min-height);max-height:var(--max-height);border-width:var(--border-width);border-style:var(--border-style);border-color:var(--border-color);background:var(--background);-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow);overflow:var(--overflow);z-index:10}.modal-shadow{position:absolute;background:transparent}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--width:600px;--height:500px
|
|
1478
|
+
const modalIosCss = ":host{--width:100%;--min-width:auto;--max-width:auto;--height:100%;--min-height:auto;--max-height:auto;--overflow:hidden;--border-radius:0;--border-width:0;--border-style:none;--border-color:transparent;--background:var(--ion-background-color, #fff);--box-shadow:none;--backdrop-opacity:0;left:0;right:0;top:0;bottom:0;display:-ms-flexbox;display:flex;position:absolute;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;outline:none;color:var(--ion-text-color, #000);contain:strict}.modal-wrapper,ion-backdrop{pointer-events:auto}:host(.overlay-hidden){display:none}.modal-wrapper,.modal-shadow{border-radius:var(--border-radius);width:var(--width);min-width:var(--min-width);max-width:var(--max-width);height:var(--height);min-height:var(--min-height);max-height:var(--max-height);border-width:var(--border-width);border-style:var(--border-style);border-color:var(--border-color);background:var(--background);-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow);overflow:var(--overflow);z-index:10}.modal-shadow{position:absolute;background:transparent}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--width:600px;--height:500px}}@media only screen and (min-width: 768px) and (min-height: 768px){:host{--width:600px;--height:600px}}.modal-handle{left:0px;right:0px;top:5px;border-radius:8px;-webkit-margin-start:auto;margin-inline-start:auto;-webkit-margin-end:auto;margin-inline-end:auto;position:absolute;width:36px;height:5px;-webkit-transform:translateZ(0);transform:translateZ(0);border:0;background:var(--ion-color-step-350, var(--ion-background-color-step-350, #c0c0be));cursor:pointer;z-index:11}.modal-handle::before{-webkit-padding-start:4px;padding-inline-start:4px;-webkit-padding-end:4px;padding-inline-end:4px;padding-top:4px;padding-bottom:4px;position:absolute;width:36px;height:5px;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);content:\"\"}:host(.modal-sheet){--height:calc(100% - (var(--ion-safe-area-top) + 10px))}:host(.modal-sheet) .modal-wrapper,:host(.modal-sheet) .modal-shadow{position:absolute;bottom:0}:host(.modal-sheet.modal-no-expand-scroll) ion-footer{position:absolute;bottom:0;width:var(--width)}:host{--backdrop-opacity:var(--ion-backdrop-opacity, 0.4)}:host(.modal-card),:host(.modal-sheet){--border-radius:10px}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--border-radius:10px}}.modal-wrapper{-webkit-transform:translate3d(0, 100%, 0);transform:translate3d(0, 100%, 0)}@media screen and (max-width: 767px){@supports (width: max(0px, 1px)){:host(.modal-card){--height:calc(100% - max(30px, var(--ion-safe-area-top)) - 10px)}}@supports not (width: max(0px, 1px)){:host(.modal-card){--height:calc(100% - 40px)}}:host(.modal-card) .modal-wrapper{border-start-start-radius:var(--border-radius);border-start-end-radius:var(--border-radius);border-end-end-radius:0;border-end-start-radius:0}:host(.modal-card){--backdrop-opacity:0;--width:100%;-ms-flex-align:end;align-items:flex-end}:host(.modal-card) .modal-shadow{display:none}:host(.modal-card) ion-backdrop{pointer-events:none}}@media screen and (min-width: 768px){:host(.modal-card){--width:calc(100% - 120px);--height:calc(100% - (120px + var(--ion-safe-area-top) + var(--ion-safe-area-bottom)));--max-width:720px;--max-height:1000px;--backdrop-opacity:0;--box-shadow:0px 0px 30px 10px rgba(0, 0, 0, 0.1);-webkit-transition:all 0.5s ease-in-out;transition:all 0.5s ease-in-out}:host(.modal-card) .modal-wrapper{-webkit-box-shadow:none;box-shadow:none}:host(.modal-card) .modal-shadow{-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow)}}:host(.modal-sheet) .modal-wrapper{border-start-start-radius:var(--border-radius);border-start-end-radius:var(--border-radius);border-end-end-radius:0;border-end-start-radius:0}";
|
|
1475
1479
|
|
|
1476
|
-
const modalMdCss = ":host{--width:100%;--min-width:auto;--max-width:auto;--height:100%;--min-height:auto;--max-height:auto;--overflow:hidden;--border-radius:0;--border-width:0;--border-style:none;--border-color:transparent;--background:var(--ion-background-color, #fff);--box-shadow:none;--backdrop-opacity:0;left:0;right:0;top:0;bottom:0;display:-ms-flexbox;display:flex;position:absolute;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;outline:none;color:var(--ion-text-color, #000);contain:strict}.modal-wrapper,ion-backdrop{pointer-events:auto}:host(.overlay-hidden){display:none}.modal-wrapper,.modal-shadow{border-radius:var(--border-radius);width:var(--width);min-width:var(--min-width);max-width:var(--max-width);height:var(--height);min-height:var(--min-height);max-height:var(--max-height);border-width:var(--border-width);border-style:var(--border-style);border-color:var(--border-color);background:var(--background);-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow);overflow:var(--overflow);z-index:10}.modal-shadow{position:absolute;background:transparent}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--width:600px;--height:500px
|
|
1480
|
+
const modalMdCss = ":host{--width:100%;--min-width:auto;--max-width:auto;--height:100%;--min-height:auto;--max-height:auto;--overflow:hidden;--border-radius:0;--border-width:0;--border-style:none;--border-color:transparent;--background:var(--ion-background-color, #fff);--box-shadow:none;--backdrop-opacity:0;left:0;right:0;top:0;bottom:0;display:-ms-flexbox;display:flex;position:absolute;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;outline:none;color:var(--ion-text-color, #000);contain:strict}.modal-wrapper,ion-backdrop{pointer-events:auto}:host(.overlay-hidden){display:none}.modal-wrapper,.modal-shadow{border-radius:var(--border-radius);width:var(--width);min-width:var(--min-width);max-width:var(--max-width);height:var(--height);min-height:var(--min-height);max-height:var(--max-height);border-width:var(--border-width);border-style:var(--border-style);border-color:var(--border-color);background:var(--background);-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow);overflow:var(--overflow);z-index:10}.modal-shadow{position:absolute;background:transparent}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--width:600px;--height:500px}}@media only screen and (min-width: 768px) and (min-height: 768px){:host{--width:600px;--height:600px}}.modal-handle{left:0px;right:0px;top:5px;border-radius:8px;-webkit-margin-start:auto;margin-inline-start:auto;-webkit-margin-end:auto;margin-inline-end:auto;position:absolute;width:36px;height:5px;-webkit-transform:translateZ(0);transform:translateZ(0);border:0;background:var(--ion-color-step-350, var(--ion-background-color-step-350, #c0c0be));cursor:pointer;z-index:11}.modal-handle::before{-webkit-padding-start:4px;padding-inline-start:4px;-webkit-padding-end:4px;padding-inline-end:4px;padding-top:4px;padding-bottom:4px;position:absolute;width:36px;height:5px;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);content:\"\"}:host(.modal-sheet){--height:calc(100% - (var(--ion-safe-area-top) + 10px))}:host(.modal-sheet) .modal-wrapper,:host(.modal-sheet) .modal-shadow{position:absolute;bottom:0}:host(.modal-sheet.modal-no-expand-scroll) ion-footer{position:absolute;bottom:0;width:var(--width)}:host{--backdrop-opacity:var(--ion-backdrop-opacity, 0.32)}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--border-radius:2px;--box-shadow:0 28px 48px rgba(0, 0, 0, 0.4)}}.modal-wrapper{-webkit-transform:translate3d(0, 40px, 0);transform:translate3d(0, 40px, 0);opacity:0.01}";
|
|
1477
1481
|
|
|
1478
1482
|
const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
1479
1483
|
constructor(registerHost) {
|
|
@@ -1500,6 +1504,8 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
1500
1504
|
this.inline = false;
|
|
1501
1505
|
// Whether or not modal is being dismissed via gesture
|
|
1502
1506
|
this.gestureAnimationDismissing = false;
|
|
1507
|
+
// Whether to skip coordinate-based safe-area detection (for fullscreen phone modals)
|
|
1508
|
+
this.skipSafeAreaCoordinateDetection = false;
|
|
1503
1509
|
this.presented = false;
|
|
1504
1510
|
/** @internal */
|
|
1505
1511
|
this.hasController = false;
|
|
@@ -1690,7 +1696,9 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
1690
1696
|
}
|
|
1691
1697
|
}
|
|
1692
1698
|
onWindowResize() {
|
|
1693
|
-
//
|
|
1699
|
+
// Update safe-area overrides for all modal types on resize
|
|
1700
|
+
this.updateSafeAreaOverrides();
|
|
1701
|
+
// Only handle view transition for iOS card modals when no custom animations are provided
|
|
1694
1702
|
if (getIonMode(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) {
|
|
1695
1703
|
return;
|
|
1696
1704
|
}
|
|
@@ -1872,6 +1880,8 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
1872
1880
|
else if (!this.keepContentsMounted) {
|
|
1873
1881
|
await waitForMount();
|
|
1874
1882
|
}
|
|
1883
|
+
// Predict safe-area needs based on modal configuration to avoid visual snap
|
|
1884
|
+
this.setInitialSafeAreaOverrides(presentingElement);
|
|
1875
1885
|
writeTask(() => this.el.classList.add('show-modal'));
|
|
1876
1886
|
const hasCardModal = presentingElement !== undefined;
|
|
1877
1887
|
/**
|
|
@@ -1933,6 +1943,8 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
1933
1943
|
else if (hasCardModal) {
|
|
1934
1944
|
this.initSwipeToClose();
|
|
1935
1945
|
}
|
|
1946
|
+
// Now that animation is complete, update safe-area based on actual position
|
|
1947
|
+
this.updateSafeAreaOverrides();
|
|
1936
1948
|
// Initialize view transition listener for iOS card modals
|
|
1937
1949
|
this.initViewTransitionListener();
|
|
1938
1950
|
// Initialize parent removal observer
|
|
@@ -1984,7 +1996,7 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
1984
1996
|
await this.dismiss(undefined, GESTURE);
|
|
1985
1997
|
this.gestureAnimationDismissing = false;
|
|
1986
1998
|
});
|
|
1987
|
-
});
|
|
1999
|
+
}, () => this.updateSafeAreaOverrides());
|
|
1988
2000
|
this.gesture.enable(true);
|
|
1989
2001
|
}
|
|
1990
2002
|
initSheetGesture() {
|
|
@@ -2005,7 +2017,8 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
2005
2017
|
this.currentBreakpoint = breakpoint;
|
|
2006
2018
|
this.ionBreakpointDidChange.emit({ breakpoint });
|
|
2007
2019
|
}
|
|
2008
|
-
|
|
2020
|
+
this.updateSafeAreaOverrides();
|
|
2021
|
+
}, () => this.updateSafeAreaOverrides());
|
|
2009
2022
|
this.gesture = gesture;
|
|
2010
2023
|
this.moveSheetToBreakpoint = moveSheetToBreakpoint;
|
|
2011
2024
|
this.gesture.enable(true);
|
|
@@ -2083,6 +2096,107 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
2083
2096
|
// Clear the cached reference
|
|
2084
2097
|
this.cachedPageParent = undefined;
|
|
2085
2098
|
}
|
|
2099
|
+
/**
|
|
2100
|
+
* Sets initial safe-area overrides based on modal configuration before
|
|
2101
|
+
* the modal becomes visible. This predicts whether the modal will touch
|
|
2102
|
+
* screen edges to avoid a visual snap after animation completes.
|
|
2103
|
+
*/
|
|
2104
|
+
setInitialSafeAreaOverrides(presentingElement) {
|
|
2105
|
+
const style = this.el.style;
|
|
2106
|
+
const isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined;
|
|
2107
|
+
const isCardModal = presentingElement !== undefined;
|
|
2108
|
+
const isTablet = window.innerWidth >= 768;
|
|
2109
|
+
// Sheet modals always touch bottom edge, never top/left/right
|
|
2110
|
+
if (isSheetModal) {
|
|
2111
|
+
style.setProperty('--ion-safe-area-top', '0px');
|
|
2112
|
+
style.setProperty('--ion-safe-area-left', '0px');
|
|
2113
|
+
style.setProperty('--ion-safe-area-right', '0px');
|
|
2114
|
+
return;
|
|
2115
|
+
}
|
|
2116
|
+
// Card modals have rounded top corners
|
|
2117
|
+
if (isCardModal) {
|
|
2118
|
+
style.setProperty('--ion-safe-area-top', '0px');
|
|
2119
|
+
if (isTablet) {
|
|
2120
|
+
// On tablets, card modals are inset from all edges
|
|
2121
|
+
this.zeroAllSafeAreas();
|
|
2122
|
+
}
|
|
2123
|
+
else {
|
|
2124
|
+
// On phones, card modals still extend to the bottom edge
|
|
2125
|
+
style.setProperty('--ion-safe-area-left', '0px');
|
|
2126
|
+
style.setProperty('--ion-safe-area-right', '0px');
|
|
2127
|
+
this.applyFullscreenSafeArea();
|
|
2128
|
+
}
|
|
2129
|
+
return;
|
|
2130
|
+
}
|
|
2131
|
+
// Phone-sized fullscreen modals inherit safe areas and use wrapper padding
|
|
2132
|
+
if (!isTablet) {
|
|
2133
|
+
this.applyFullscreenSafeArea();
|
|
2134
|
+
return;
|
|
2135
|
+
}
|
|
2136
|
+
// Check if tablet modal is fullscreen via CSS custom properties
|
|
2137
|
+
const computedStyle = getComputedStyle(this.el);
|
|
2138
|
+
const width = computedStyle.getPropertyValue('--width').trim();
|
|
2139
|
+
const height = computedStyle.getPropertyValue('--height').trim();
|
|
2140
|
+
const isFullscreen = width === '100%' && height === '100%';
|
|
2141
|
+
if (isFullscreen) {
|
|
2142
|
+
this.applyFullscreenSafeArea();
|
|
2143
|
+
}
|
|
2144
|
+
else {
|
|
2145
|
+
// Centered dialog doesn't touch edges
|
|
2146
|
+
this.zeroAllSafeAreas();
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
/**
|
|
2150
|
+
* Applies safe-area handling for fullscreen modals.
|
|
2151
|
+
* Adds wrapper padding when no footer is present to prevent
|
|
2152
|
+
* content from overlapping system navigation areas.
|
|
2153
|
+
*/
|
|
2154
|
+
applyFullscreenSafeArea() {
|
|
2155
|
+
this.skipSafeAreaCoordinateDetection = true;
|
|
2156
|
+
const hasFooter = this.el.querySelector('ion-footer') !== null;
|
|
2157
|
+
if (!hasFooter && this.wrapperEl) {
|
|
2158
|
+
this.wrapperEl.style.setProperty('padding-bottom', 'var(--ion-safe-area-bottom, 0px)');
|
|
2159
|
+
this.wrapperEl.style.setProperty('box-sizing', 'border-box');
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
/**
|
|
2163
|
+
* Sets all safe-area CSS variables to 0px for modals that
|
|
2164
|
+
* don't touch screen edges.
|
|
2165
|
+
*/
|
|
2166
|
+
zeroAllSafeAreas() {
|
|
2167
|
+
const style = this.el.style;
|
|
2168
|
+
style.setProperty('--ion-safe-area-top', '0px');
|
|
2169
|
+
style.setProperty('--ion-safe-area-bottom', '0px');
|
|
2170
|
+
style.setProperty('--ion-safe-area-left', '0px');
|
|
2171
|
+
style.setProperty('--ion-safe-area-right', '0px');
|
|
2172
|
+
}
|
|
2173
|
+
/**
|
|
2174
|
+
* Updates safe-area CSS variable overrides based on whether the modal
|
|
2175
|
+
* is touching each edge of the viewport. Called after animation
|
|
2176
|
+
* and during gestures to handle dynamic position changes.
|
|
2177
|
+
*/
|
|
2178
|
+
updateSafeAreaOverrides() {
|
|
2179
|
+
if (this.skipSafeAreaCoordinateDetection) {
|
|
2180
|
+
return;
|
|
2181
|
+
}
|
|
2182
|
+
const wrapper = this.wrapperEl;
|
|
2183
|
+
if (!wrapper) {
|
|
2184
|
+
return;
|
|
2185
|
+
}
|
|
2186
|
+
const rect = wrapper.getBoundingClientRect();
|
|
2187
|
+
const threshold = 2;
|
|
2188
|
+
const touchingTop = rect.top <= threshold;
|
|
2189
|
+
const touchingBottom = rect.bottom >= window.innerHeight - threshold;
|
|
2190
|
+
const touchingLeft = rect.left <= threshold;
|
|
2191
|
+
const touchingRight = rect.right >= window.innerWidth - threshold;
|
|
2192
|
+
const style = this.el.style;
|
|
2193
|
+
touchingTop ? style.removeProperty('--ion-safe-area-top') : style.setProperty('--ion-safe-area-top', '0px');
|
|
2194
|
+
touchingBottom
|
|
2195
|
+
? style.removeProperty('--ion-safe-area-bottom')
|
|
2196
|
+
: style.setProperty('--ion-safe-area-bottom', '0px');
|
|
2197
|
+
touchingLeft ? style.removeProperty('--ion-safe-area-left') : style.setProperty('--ion-safe-area-left', '0px');
|
|
2198
|
+
touchingRight ? style.removeProperty('--ion-safe-area-right') : style.setProperty('--ion-safe-area-right', '0px');
|
|
2199
|
+
}
|
|
2086
2200
|
sheetOnDismiss() {
|
|
2087
2201
|
/**
|
|
2088
2202
|
* While the gesture animation is finishing
|
|
@@ -2175,6 +2289,8 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
2175
2289
|
}
|
|
2176
2290
|
this.currentBreakpoint = undefined;
|
|
2177
2291
|
this.animation = undefined;
|
|
2292
|
+
// Reset safe-area detection flag for potential re-presentation
|
|
2293
|
+
this.skipSafeAreaCoordinateDetection = false;
|
|
2178
2294
|
unlock();
|
|
2179
2295
|
return dismissed;
|
|
2180
2296
|
}
|
|
@@ -2424,20 +2540,20 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
2424
2540
|
const isCardModal = presentingElement !== undefined && mode === 'ios';
|
|
2425
2541
|
const isHandleCycle = handleBehavior === 'cycle';
|
|
2426
2542
|
const isSheetModalWithHandle = isSheetModal && showHandle;
|
|
2427
|
-
return (h(Host, Object.assign({ key: '
|
|
2543
|
+
return (h(Host, Object.assign({ key: '6642b8a32dad449f26ac4ed60814d69afc97bf30', "no-router": true,
|
|
2428
2544
|
// Allow the modal to be navigable when the handle is focusable
|
|
2429
2545
|
tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
|
|
2430
2546
|
zIndex: `${20000 + this.overlayIndex}`,
|
|
2431
|
-
}, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: '
|
|
2547
|
+
}, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: '8adf79ea72c0f622190ff366980621067c35795b', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '34f14fcd4970c04aee3b7a5305f9f220ff377f37', class: "modal-shadow" }), h("div", Object.assign({ key: 'a3a8e3df6a1cfe061bbd99503655015ca8cd416d',
|
|
2432
2548
|
/*
|
|
2433
2549
|
role and aria-modal must be used on the
|
|
2434
2550
|
same element. They must also be set inside the
|
|
2435
2551
|
shadow DOM otherwise ion-button will not be highlighted
|
|
2436
2552
|
when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
|
|
2437
2553
|
*/
|
|
2438
|
-
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '
|
|
2554
|
+
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: 'a4ad2f6def8c20a36a9731553dfa5a98392151ef', class: "modal-handle",
|
|
2439
2555
|
// Prevents the handle from receiving keyboard focus when it does not cycle
|
|
2440
|
-
tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: '
|
|
2556
|
+
tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: '46fb6d94814dae4a9bcdf99387d57a17c6a6a95c', onSlotchange: this.onSlotChange }))));
|
|
2441
2557
|
}
|
|
2442
2558
|
get el() { return this; }
|
|
2443
2559
|
static get watchers() { return {
|
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,6 +722,12 @@ 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
|
}
|
|
717
733
|
return {
|
|
@@ -720,6 +736,8 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
|
|
|
720
736
|
bottom,
|
|
721
737
|
originX,
|
|
722
738
|
originY,
|
|
739
|
+
checkSafeAreaTop,
|
|
740
|
+
checkSafeAreaBottom,
|
|
723
741
|
checkSafeAreaLeft,
|
|
724
742
|
checkSafeAreaRight,
|
|
725
743
|
arrowTop,
|
|
@@ -780,7 +798,7 @@ const iosEnterAnimation = (baseEl, opts) => {
|
|
|
780
798
|
const results = getPopoverPosition(isRTL, contentWidth, contentHeight, arrowWidth, arrowHeight, reference, side, align, defaultPosition, trigger, ev);
|
|
781
799
|
const padding = size === 'cover' ? 0 : POPOVER_IOS_BODY_PADDING;
|
|
782
800
|
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);
|
|
801
|
+
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
802
|
const baseAnimation = createAnimation();
|
|
785
803
|
const backdropAnimation = createAnimation();
|
|
786
804
|
const contentAnimation = createAnimation();
|
|
@@ -810,19 +828,35 @@ const iosEnterAnimation = (baseEl, opts) => {
|
|
|
810
828
|
if (addPopoverBottomClass) {
|
|
811
829
|
baseEl.classList.add('popover-bottom');
|
|
812
830
|
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
831
|
+
/**
|
|
832
|
+
* Safe area CSS variable adjustments.
|
|
833
|
+
* When the popover is positioned near an edge, we add the corresponding
|
|
834
|
+
* safe-area inset to ensure the popover doesn't overlap with system UI
|
|
835
|
+
* (status bars, home indicators, navigation bars on Android API 36+, etc.)
|
|
836
|
+
*/
|
|
837
|
+
const safeAreaTop = ' + var(--ion-safe-area-top, 0)';
|
|
838
|
+
const safeAreaBottom = ' + var(--ion-safe-area-bottom, 0)';
|
|
816
839
|
const safeAreaLeft = ' + var(--ion-safe-area-left, 0)';
|
|
817
840
|
const safeAreaRight = ' - var(--ion-safe-area-right, 0)';
|
|
841
|
+
let topValue = `${top}px`;
|
|
842
|
+
let bottomValue = bottom !== undefined ? `${bottom}px` : undefined;
|
|
818
843
|
let leftValue = `${left}px`;
|
|
844
|
+
if (checkSafeAreaTop) {
|
|
845
|
+
topValue = `${top}px${safeAreaTop}`;
|
|
846
|
+
}
|
|
847
|
+
if (checkSafeAreaBottom && bottomValue !== undefined) {
|
|
848
|
+
bottomValue = `${bottom}px${safeAreaBottom}`;
|
|
849
|
+
}
|
|
819
850
|
if (checkSafeAreaLeft) {
|
|
820
851
|
leftValue = `${left}px${safeAreaLeft}`;
|
|
821
852
|
}
|
|
822
853
|
if (checkSafeAreaRight) {
|
|
823
854
|
leftValue = `${left}px${safeAreaRight}`;
|
|
824
855
|
}
|
|
825
|
-
|
|
856
|
+
if (bottomValue !== undefined) {
|
|
857
|
+
contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
|
|
858
|
+
}
|
|
859
|
+
contentEl.style.setProperty('top', `calc(${topValue} + var(--offset-y, 0))`);
|
|
826
860
|
contentEl.style.setProperty('left', `calc(${leftValue} + var(--offset-x, 0))`);
|
|
827
861
|
contentEl.style.setProperty('transform-origin', `${originY} ${originX}`);
|
|
828
862
|
if (arrowEl !== null) {
|
|
@@ -898,7 +932,23 @@ const mdEnterAnimation = (baseEl, opts) => {
|
|
|
898
932
|
};
|
|
899
933
|
const results = getPopoverPosition(isRTL, contentWidth, contentHeight, 0, 0, reference, side, align, defaultPosition, trigger, ev);
|
|
900
934
|
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);
|
|
935
|
+
const { originX, originY, top, left, bottom, checkSafeAreaTop, checkSafeAreaBottom } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, 0, results.originX, results.originY, results.referenceCoordinates);
|
|
936
|
+
/**
|
|
937
|
+
* Safe area CSS variable adjustments.
|
|
938
|
+
* When the popover is positioned near an edge, we add the corresponding
|
|
939
|
+
* safe-area inset to ensure the popover doesn't overlap with system UI
|
|
940
|
+
* (status bars, home indicators, navigation bars on Android API 36+, etc.)
|
|
941
|
+
*/
|
|
942
|
+
const safeAreaTop = ' + var(--ion-safe-area-top, 0)';
|
|
943
|
+
const safeAreaBottom = ' + var(--ion-safe-area-bottom, 0)';
|
|
944
|
+
let topValue = `${top}px`;
|
|
945
|
+
let bottomValue = bottom !== undefined ? `${bottom}px` : undefined;
|
|
946
|
+
if (checkSafeAreaTop) {
|
|
947
|
+
topValue = `${top}px${safeAreaTop}`;
|
|
948
|
+
}
|
|
949
|
+
if (checkSafeAreaBottom && bottomValue !== undefined) {
|
|
950
|
+
bottomValue = `${bottom}px${safeAreaBottom}`;
|
|
951
|
+
}
|
|
902
952
|
const baseAnimation = createAnimation();
|
|
903
953
|
const backdropAnimation = createAnimation();
|
|
904
954
|
const wrapperAnimation = createAnimation();
|
|
@@ -915,13 +965,13 @@ const mdEnterAnimation = (baseEl, opts) => {
|
|
|
915
965
|
contentAnimation
|
|
916
966
|
.addElement(contentEl)
|
|
917
967
|
.beforeStyles({
|
|
918
|
-
top: `calc(${
|
|
968
|
+
top: `calc(${topValue} + var(--offset-y, 0px))`,
|
|
919
969
|
left: `calc(${left}px + var(--offset-x, 0px))`,
|
|
920
970
|
'transform-origin': `${originY} ${originX}`,
|
|
921
971
|
})
|
|
922
972
|
.beforeAddWrite(() => {
|
|
923
|
-
if (
|
|
924
|
-
contentEl.style.setProperty('bottom',
|
|
973
|
+
if (bottomValue !== undefined) {
|
|
974
|
+
contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
|
|
925
975
|
}
|
|
926
976
|
})
|
|
927
977
|
.fromTo('transform', 'scale(0.8)', 'scale(1)');
|