@ionic/core 8.7.16-dev.11767111768.1552e96d → 8.7.16-dev.11767205737.13557925
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/modal.js +121 -11
- package/components/popover.js +60 -10
- package/dist/cjs/ion-modal.cjs.entry.js +121 -11
- package/dist/cjs/ion-popover.cjs.entry.js +60 -10
- 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 +113 -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/docs.json +1 -1
- package/dist/esm/ion-modal.entry.js +121 -11
- package/dist/esm/ion-popover.entry.js +60 -10
- package/dist/ionic/ionic.esm.js +1 -1
- package/dist/ionic/p-301ad219.entry.js +4 -0
- package/dist/ionic/p-481858c3.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/hydrate/index.js +181 -21
- package/hydrate/index.mjs +181 -21
- package/package.json +1 -1
- package/dist/ionic/p-b28af559.entry.js +0 -4
- package/dist/ionic/p-f9061316.entry.js +0 -4
|
@@ -42,6 +42,8 @@ export class Modal {
|
|
|
42
42
|
this.inline = false;
|
|
43
43
|
// Whether or not modal is being dismissed via gesture
|
|
44
44
|
this.gestureAnimationDismissing = false;
|
|
45
|
+
// Whether to skip coordinate-based safe-area detection (for fullscreen phone modals)
|
|
46
|
+
this.skipSafeAreaCoordinateDetection = false;
|
|
45
47
|
this.presented = false;
|
|
46
48
|
/** @internal */
|
|
47
49
|
this.hasController = false;
|
|
@@ -232,7 +234,9 @@ export class Modal {
|
|
|
232
234
|
}
|
|
233
235
|
}
|
|
234
236
|
onWindowResize() {
|
|
235
|
-
//
|
|
237
|
+
// Update safe-area overrides for all modal types on resize
|
|
238
|
+
this.updateSafeAreaOverrides();
|
|
239
|
+
// Only handle view transition for iOS card modals when no custom animations are provided
|
|
236
240
|
if (getIonMode(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) {
|
|
237
241
|
return;
|
|
238
242
|
}
|
|
@@ -414,6 +418,8 @@ export class Modal {
|
|
|
414
418
|
else if (!this.keepContentsMounted) {
|
|
415
419
|
await waitForMount();
|
|
416
420
|
}
|
|
421
|
+
// Predict safe-area needs based on modal configuration to avoid visual snap
|
|
422
|
+
this.setInitialSafeAreaOverrides(presentingElement);
|
|
417
423
|
writeTask(() => this.el.classList.add('show-modal'));
|
|
418
424
|
const hasCardModal = presentingElement !== undefined;
|
|
419
425
|
/**
|
|
@@ -475,6 +481,8 @@ export class Modal {
|
|
|
475
481
|
else if (hasCardModal) {
|
|
476
482
|
this.initSwipeToClose();
|
|
477
483
|
}
|
|
484
|
+
// Now that animation is complete, update safe-area based on actual position
|
|
485
|
+
this.updateSafeAreaOverrides();
|
|
478
486
|
// Initialize view transition listener for iOS card modals
|
|
479
487
|
this.initViewTransitionListener();
|
|
480
488
|
// Initialize parent removal observer
|
|
@@ -526,7 +534,7 @@ export class Modal {
|
|
|
526
534
|
await this.dismiss(undefined, GESTURE);
|
|
527
535
|
this.gestureAnimationDismissing = false;
|
|
528
536
|
});
|
|
529
|
-
});
|
|
537
|
+
}, () => this.updateSafeAreaOverrides());
|
|
530
538
|
this.gesture.enable(true);
|
|
531
539
|
}
|
|
532
540
|
initSheetGesture() {
|
|
@@ -547,7 +555,8 @@ export class Modal {
|
|
|
547
555
|
this.currentBreakpoint = breakpoint;
|
|
548
556
|
this.ionBreakpointDidChange.emit({ breakpoint });
|
|
549
557
|
}
|
|
550
|
-
|
|
558
|
+
this.updateSafeAreaOverrides();
|
|
559
|
+
}, () => this.updateSafeAreaOverrides());
|
|
551
560
|
this.gesture = gesture;
|
|
552
561
|
this.moveSheetToBreakpoint = moveSheetToBreakpoint;
|
|
553
562
|
this.gesture.enable(true);
|
|
@@ -625,6 +634,97 @@ export class Modal {
|
|
|
625
634
|
// Clear the cached reference
|
|
626
635
|
this.cachedPageParent = undefined;
|
|
627
636
|
}
|
|
637
|
+
/**
|
|
638
|
+
* Sets initial safe-area overrides based on modal configuration before
|
|
639
|
+
* the modal becomes visible. This predicts whether the modal will touch
|
|
640
|
+
* screen edges to avoid a visual snap after animation completes.
|
|
641
|
+
*/
|
|
642
|
+
setInitialSafeAreaOverrides(presentingElement) {
|
|
643
|
+
const style = this.el.style;
|
|
644
|
+
const isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined;
|
|
645
|
+
const isCardModal = presentingElement !== undefined;
|
|
646
|
+
const isTablet = window.innerWidth >= 768;
|
|
647
|
+
// Sheet modals always touch bottom edge, never top/left/right
|
|
648
|
+
if (isSheetModal) {
|
|
649
|
+
style.setProperty('--ion-safe-area-top', '0px');
|
|
650
|
+
style.setProperty('--ion-safe-area-left', '0px');
|
|
651
|
+
style.setProperty('--ion-safe-area-right', '0px');
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
// Card modals are inset from all edges
|
|
655
|
+
if (isCardModal) {
|
|
656
|
+
this.zeroAllSafeAreas();
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
// Phone-sized fullscreen modals inherit safe areas and use wrapper padding
|
|
660
|
+
if (!isTablet) {
|
|
661
|
+
this.applyFullscreenSafeArea();
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
// Check if tablet modal is fullscreen via CSS custom properties
|
|
665
|
+
const computedStyle = getComputedStyle(this.el);
|
|
666
|
+
const width = computedStyle.getPropertyValue('--width').trim();
|
|
667
|
+
const height = computedStyle.getPropertyValue('--height').trim();
|
|
668
|
+
const isFullscreen = width === '100%' && height === '100%';
|
|
669
|
+
if (isFullscreen) {
|
|
670
|
+
this.applyFullscreenSafeArea();
|
|
671
|
+
}
|
|
672
|
+
else {
|
|
673
|
+
// Centered dialog doesn't touch edges
|
|
674
|
+
this.zeroAllSafeAreas();
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Applies safe-area handling for fullscreen modals.
|
|
679
|
+
* Adds wrapper padding when no footer is present to prevent
|
|
680
|
+
* content from overlapping system navigation areas.
|
|
681
|
+
*/
|
|
682
|
+
applyFullscreenSafeArea() {
|
|
683
|
+
this.skipSafeAreaCoordinateDetection = true;
|
|
684
|
+
const hasFooter = this.el.querySelector('ion-footer') !== null;
|
|
685
|
+
if (!hasFooter && this.wrapperEl) {
|
|
686
|
+
this.wrapperEl.style.setProperty('padding-bottom', 'var(--ion-safe-area-bottom, 0px)');
|
|
687
|
+
this.wrapperEl.style.setProperty('box-sizing', 'border-box');
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Sets all safe-area CSS variables to 0px for modals that
|
|
692
|
+
* don't touch screen edges.
|
|
693
|
+
*/
|
|
694
|
+
zeroAllSafeAreas() {
|
|
695
|
+
const style = this.el.style;
|
|
696
|
+
style.setProperty('--ion-safe-area-top', '0px');
|
|
697
|
+
style.setProperty('--ion-safe-area-bottom', '0px');
|
|
698
|
+
style.setProperty('--ion-safe-area-left', '0px');
|
|
699
|
+
style.setProperty('--ion-safe-area-right', '0px');
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Updates safe-area CSS variable overrides based on whether the modal
|
|
703
|
+
* is touching each edge of the viewport. Called after animation
|
|
704
|
+
* and during gestures to handle dynamic position changes.
|
|
705
|
+
*/
|
|
706
|
+
updateSafeAreaOverrides() {
|
|
707
|
+
if (this.skipSafeAreaCoordinateDetection) {
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
const wrapper = this.wrapperEl;
|
|
711
|
+
if (!wrapper) {
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
const rect = wrapper.getBoundingClientRect();
|
|
715
|
+
const threshold = 2;
|
|
716
|
+
const touchingTop = rect.top <= threshold;
|
|
717
|
+
const touchingBottom = rect.bottom >= window.innerHeight - threshold;
|
|
718
|
+
const touchingLeft = rect.left <= threshold;
|
|
719
|
+
const touchingRight = rect.right >= window.innerWidth - threshold;
|
|
720
|
+
const style = this.el.style;
|
|
721
|
+
touchingTop ? style.removeProperty('--ion-safe-area-top') : style.setProperty('--ion-safe-area-top', '0px');
|
|
722
|
+
touchingBottom
|
|
723
|
+
? style.removeProperty('--ion-safe-area-bottom')
|
|
724
|
+
: style.setProperty('--ion-safe-area-bottom', '0px');
|
|
725
|
+
touchingLeft ? style.removeProperty('--ion-safe-area-left') : style.setProperty('--ion-safe-area-left', '0px');
|
|
726
|
+
touchingRight ? style.removeProperty('--ion-safe-area-right') : style.setProperty('--ion-safe-area-right', '0px');
|
|
727
|
+
}
|
|
628
728
|
sheetOnDismiss() {
|
|
629
729
|
/**
|
|
630
730
|
* While the gesture animation is finishing
|
|
@@ -717,6 +817,8 @@ export class Modal {
|
|
|
717
817
|
}
|
|
718
818
|
this.currentBreakpoint = undefined;
|
|
719
819
|
this.animation = undefined;
|
|
820
|
+
// Reset safe-area detection flag for potential re-presentation
|
|
821
|
+
this.skipSafeAreaCoordinateDetection = false;
|
|
720
822
|
unlock();
|
|
721
823
|
return dismissed;
|
|
722
824
|
}
|
|
@@ -798,6 +900,10 @@ export class Modal {
|
|
|
798
900
|
this.currentViewIsPortrait = window.innerWidth < 768;
|
|
799
901
|
}
|
|
800
902
|
handleViewTransition() {
|
|
903
|
+
// Only run view transitions when the modal is presented
|
|
904
|
+
if (!this.presented) {
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
801
907
|
const isPortrait = window.innerWidth < 768;
|
|
802
908
|
// Only transition if view state actually changed
|
|
803
909
|
if (this.currentViewIsPortrait === isPortrait) {
|
|
@@ -970,20 +1076,20 @@ export class Modal {
|
|
|
970
1076
|
const isCardModal = presentingElement !== undefined && mode === 'ios';
|
|
971
1077
|
const isHandleCycle = handleBehavior === 'cycle';
|
|
972
1078
|
const isSheetModalWithHandle = isSheetModal && showHandle;
|
|
973
|
-
return (h(Host, Object.assign({ key: '
|
|
1079
|
+
return (h(Host, Object.assign({ key: 'b93c9d20985ca4800b34c0165957b12b0a9d6789', "no-router": true,
|
|
974
1080
|
// Allow the modal to be navigable when the handle is focusable
|
|
975
1081
|
tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
|
|
976
1082
|
zIndex: `${20000 + this.overlayIndex}`,
|
|
977
|
-
}, 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: '
|
|
1083
|
+
}, 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: '732b9355b3c0b051d19e1e0cedd7c49e3ad6fdda', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '874dcbb38da5a9feecafc5d3d1cb9f140f7fe48c', class: "modal-shadow" }), h("div", Object.assign({ key: '0d0c28581d49aeb0f764387a76ad54f68c35a6d5',
|
|
978
1084
|
/*
|
|
979
1085
|
role and aria-modal must be used on the
|
|
980
1086
|
same element. They must also be set inside the
|
|
981
1087
|
shadow DOM otherwise ion-button will not be highlighted
|
|
982
1088
|
when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
|
|
983
1089
|
*/
|
|
984
|
-
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '
|
|
1090
|
+
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: 'a27a7ecc9857b4dc2f8a6f64bb01487ac578f5aa', class: "modal-handle",
|
|
985
1091
|
// Prevents the handle from receiving keyboard focus when it does not cycle
|
|
986
|
-
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: '
|
|
1092
|
+
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: '2ca0a826798dbc5cfe06e94b6a2d0ef375d018bc', onSlotchange: this.onSlotChange }))));
|
|
987
1093
|
}
|
|
988
1094
|
static get is() { return "ion-modal"; }
|
|
989
1095
|
static get encapsulation() { return "shadow"; }
|
|
@@ -135,10 +135,6 @@ ion-backdrop {
|
|
|
135
135
|
:host {
|
|
136
136
|
--width: 600px;
|
|
137
137
|
--height: 500px;
|
|
138
|
-
--ion-safe-area-top: 0px;
|
|
139
|
-
--ion-safe-area-bottom: 0px;
|
|
140
|
-
--ion-safe-area-right: 0px;
|
|
141
|
-
--ion-safe-area-left: 0px;
|
|
142
138
|
}
|
|
143
139
|
}
|
|
144
140
|
@media only screen and (min-width: 768px) and (min-height: 768px) {
|
|
@@ -31,7 +31,7 @@ export const iosEnterAnimation = (baseEl, opts) => {
|
|
|
31
31
|
const results = getPopoverPosition(isRTL, contentWidth, contentHeight, arrowWidth, arrowHeight, reference, side, align, defaultPosition, trigger, ev);
|
|
32
32
|
const padding = size === 'cover' ? 0 : POPOVER_IOS_BODY_PADDING;
|
|
33
33
|
const margin = size === 'cover' ? 0 : 25;
|
|
34
|
-
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);
|
|
34
|
+
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);
|
|
35
35
|
const baseAnimation = createAnimation();
|
|
36
36
|
const backdropAnimation = createAnimation();
|
|
37
37
|
const contentAnimation = createAnimation();
|
|
@@ -61,19 +61,35 @@ export const iosEnterAnimation = (baseEl, opts) => {
|
|
|
61
61
|
if (addPopoverBottomClass) {
|
|
62
62
|
baseEl.classList.add('popover-bottom');
|
|
63
63
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Safe area CSS variable adjustments.
|
|
66
|
+
* When the popover is positioned near an edge, we add the corresponding
|
|
67
|
+
* safe-area inset to ensure the popover doesn't overlap with system UI
|
|
68
|
+
* (status bars, home indicators, navigation bars on Android API 36+, etc.)
|
|
69
|
+
*/
|
|
70
|
+
const safeAreaTop = ' + var(--ion-safe-area-top, 0)';
|
|
71
|
+
const safeAreaBottom = ' + var(--ion-safe-area-bottom, 0)';
|
|
67
72
|
const safeAreaLeft = ' + var(--ion-safe-area-left, 0)';
|
|
68
73
|
const safeAreaRight = ' - var(--ion-safe-area-right, 0)';
|
|
74
|
+
let topValue = `${top}px`;
|
|
75
|
+
let bottomValue = bottom !== undefined ? `${bottom}px` : undefined;
|
|
69
76
|
let leftValue = `${left}px`;
|
|
77
|
+
if (checkSafeAreaTop) {
|
|
78
|
+
topValue = `${top}px${safeAreaTop}`;
|
|
79
|
+
}
|
|
80
|
+
if (checkSafeAreaBottom && bottomValue !== undefined) {
|
|
81
|
+
bottomValue = `${bottom}px${safeAreaBottom}`;
|
|
82
|
+
}
|
|
70
83
|
if (checkSafeAreaLeft) {
|
|
71
84
|
leftValue = `${left}px${safeAreaLeft}`;
|
|
72
85
|
}
|
|
73
86
|
if (checkSafeAreaRight) {
|
|
74
87
|
leftValue = `${left}px${safeAreaRight}`;
|
|
75
88
|
}
|
|
76
|
-
|
|
89
|
+
if (bottomValue !== undefined) {
|
|
90
|
+
contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
|
|
91
|
+
}
|
|
92
|
+
contentEl.style.setProperty('top', `calc(${topValue} + var(--offset-y, 0))`);
|
|
77
93
|
contentEl.style.setProperty('left', `calc(${leftValue} + var(--offset-x, 0))`);
|
|
78
94
|
contentEl.style.setProperty('transform-origin', `${originY} ${originX}`);
|
|
79
95
|
if (arrowEl !== null) {
|
|
@@ -28,7 +28,23 @@ export const mdEnterAnimation = (baseEl, opts) => {
|
|
|
28
28
|
};
|
|
29
29
|
const results = getPopoverPosition(isRTL, contentWidth, contentHeight, 0, 0, reference, side, align, defaultPosition, trigger, ev);
|
|
30
30
|
const padding = size === 'cover' ? 0 : POPOVER_MD_BODY_PADDING;
|
|
31
|
-
const { originX, originY, top, left, bottom } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, 0, results.originX, results.originY, results.referenceCoordinates);
|
|
31
|
+
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);
|
|
32
|
+
/**
|
|
33
|
+
* Safe area CSS variable adjustments.
|
|
34
|
+
* When the popover is positioned near an edge, we add the corresponding
|
|
35
|
+
* safe-area inset to ensure the popover doesn't overlap with system UI
|
|
36
|
+
* (status bars, home indicators, navigation bars on Android API 36+, etc.)
|
|
37
|
+
*/
|
|
38
|
+
const safeAreaTop = ' + var(--ion-safe-area-top, 0)';
|
|
39
|
+
const safeAreaBottom = ' + var(--ion-safe-area-bottom, 0)';
|
|
40
|
+
let topValue = `${top}px`;
|
|
41
|
+
let bottomValue = bottom !== undefined ? `${bottom}px` : undefined;
|
|
42
|
+
if (checkSafeAreaTop) {
|
|
43
|
+
topValue = `${top}px${safeAreaTop}`;
|
|
44
|
+
}
|
|
45
|
+
if (checkSafeAreaBottom && bottomValue !== undefined) {
|
|
46
|
+
bottomValue = `${bottom}px${safeAreaBottom}`;
|
|
47
|
+
}
|
|
32
48
|
const baseAnimation = createAnimation();
|
|
33
49
|
const backdropAnimation = createAnimation();
|
|
34
50
|
const wrapperAnimation = createAnimation();
|
|
@@ -45,13 +61,13 @@ export const mdEnterAnimation = (baseEl, opts) => {
|
|
|
45
61
|
contentAnimation
|
|
46
62
|
.addElement(contentEl)
|
|
47
63
|
.beforeStyles({
|
|
48
|
-
top: `calc(${
|
|
64
|
+
top: `calc(${topValue} + var(--offset-y, 0px))`,
|
|
49
65
|
left: `calc(${left}px + var(--offset-x, 0px))`,
|
|
50
66
|
'transform-origin': `${originY} ${originX}`,
|
|
51
67
|
})
|
|
52
68
|
.beforeAddWrite(() => {
|
|
53
|
-
if (
|
|
54
|
-
contentEl.style.setProperty('bottom',
|
|
69
|
+
if (bottomValue !== undefined) {
|
|
70
|
+
contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
|
|
55
71
|
}
|
|
56
72
|
})
|
|
57
73
|
.fromTo('transform', 'scale(0.8)', 'scale(1)');
|
|
@@ -648,6 +648,8 @@ export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding
|
|
|
648
648
|
let bottom;
|
|
649
649
|
let originX = contentOriginX;
|
|
650
650
|
let originY = contentOriginY;
|
|
651
|
+
let checkSafeAreaTop = false;
|
|
652
|
+
let checkSafeAreaBottom = false;
|
|
651
653
|
let checkSafeAreaLeft = false;
|
|
652
654
|
let checkSafeAreaRight = false;
|
|
653
655
|
const triggerTop = triggerCoordinates
|
|
@@ -692,10 +694,18 @@ export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding
|
|
|
692
694
|
* We chose 12 here so that the popover position looks a bit nicer as
|
|
693
695
|
* it is not right up against the edge of the screen.
|
|
694
696
|
*/
|
|
695
|
-
top = Math.max(
|
|
697
|
+
top = Math.max(bodyPadding, triggerTop - contentHeight - triggerHeight - (arrowHeight - 1));
|
|
696
698
|
arrowTop = top + contentHeight;
|
|
697
699
|
originY = 'bottom';
|
|
698
700
|
addPopoverBottomClass = true;
|
|
701
|
+
/**
|
|
702
|
+
* If the popover is positioned near the top edge, account for safe area.
|
|
703
|
+
* This ensures the popover doesn't overlap with status bars or notches.
|
|
704
|
+
*/
|
|
705
|
+
if (top <= bodyPadding + safeAreaMargin) {
|
|
706
|
+
checkSafeAreaTop = true;
|
|
707
|
+
top = bodyPadding;
|
|
708
|
+
}
|
|
699
709
|
/**
|
|
700
710
|
* If not enough room for popover to appear
|
|
701
711
|
* above trigger, then cut it off.
|
|
@@ -703,6 +713,12 @@ export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding
|
|
|
703
713
|
}
|
|
704
714
|
else {
|
|
705
715
|
bottom = bodyPadding;
|
|
716
|
+
/**
|
|
717
|
+
* When the popover is pinned to the bottom, account for safe area.
|
|
718
|
+
* This ensures the popover doesn't overlap with home indicators
|
|
719
|
+
* or navigation bars (e.g., Android API 36+ edge-to-edge).
|
|
720
|
+
*/
|
|
721
|
+
checkSafeAreaBottom = true;
|
|
706
722
|
}
|
|
707
723
|
}
|
|
708
724
|
return {
|
|
@@ -711,6 +727,8 @@ export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding
|
|
|
711
727
|
bottom,
|
|
712
728
|
originX,
|
|
713
729
|
originY,
|
|
730
|
+
checkSafeAreaTop,
|
|
731
|
+
checkSafeAreaBottom,
|
|
714
732
|
checkSafeAreaLeft,
|
|
715
733
|
checkSafeAreaRight,
|
|
716
734
|
arrowTop,
|
package/dist/docs.json
CHANGED
|
@@ -247,7 +247,7 @@ const calculateSpringStep = (t) => {
|
|
|
247
247
|
const SwipeToCloseDefaults = {
|
|
248
248
|
MIN_PRESENTING_SCALE: 0.915,
|
|
249
249
|
};
|
|
250
|
-
const createSwipeToCloseGesture = (el, animation, statusBarStyle, onDismiss) => {
|
|
250
|
+
const createSwipeToCloseGesture = (el, animation, statusBarStyle, onDismiss, onGestureMove) => {
|
|
251
251
|
/**
|
|
252
252
|
* The step value at which a card modal
|
|
253
253
|
* is eligible for dismissing via gesture.
|
|
@@ -404,6 +404,8 @@ const createSwipeToCloseGesture = (el, animation, statusBarStyle, onDismiss) =>
|
|
|
404
404
|
const processedStep = isAttemptingDismissWithCanDismiss ? calculateSpringStep(step / maxStep) : step;
|
|
405
405
|
const clampedStep = clamp(0.0001, processedStep, maxStep);
|
|
406
406
|
animation.progressStep(clampedStep);
|
|
407
|
+
// Notify modal of position change for safe-area updates
|
|
408
|
+
onGestureMove === null || onGestureMove === void 0 ? void 0 : onGestureMove();
|
|
407
409
|
/**
|
|
408
410
|
* When swiping down half way, the status bar style
|
|
409
411
|
* should be reset to its default value.
|
|
@@ -947,7 +949,7 @@ const mdLeaveAnimation = (baseEl, opts) => {
|
|
|
947
949
|
return baseAnimation;
|
|
948
950
|
};
|
|
949
951
|
|
|
950
|
-
const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, backdropBreakpoint, animation, breakpoints = [], expandToScroll, getCurrentBreakpoint, onDismiss, onBreakpointChange) => {
|
|
952
|
+
const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, backdropBreakpoint, animation, breakpoints = [], expandToScroll, getCurrentBreakpoint, onDismiss, onBreakpointChange, onGestureMove) => {
|
|
951
953
|
// Defaults for the sheet swipe animation
|
|
952
954
|
const defaultBackdrop = [
|
|
953
955
|
{ offset: 0, opacity: 'var(--backdrop-opacity)' },
|
|
@@ -1278,6 +1280,8 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
|
|
|
1278
1280
|
: step;
|
|
1279
1281
|
offset = clamp(0.0001, processedStep, maxStep);
|
|
1280
1282
|
animation.progressStep(offset);
|
|
1283
|
+
// Notify modal of position change for safe-area updates
|
|
1284
|
+
onGestureMove === null || onGestureMove === void 0 ? void 0 : onGestureMove();
|
|
1281
1285
|
};
|
|
1282
1286
|
const onEnd = (detail) => {
|
|
1283
1287
|
/**
|
|
@@ -1472,9 +1476,9 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
|
|
|
1472
1476
|
};
|
|
1473
1477
|
};
|
|
1474
1478
|
|
|
1475
|
-
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
|
|
1479
|
+
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}";
|
|
1476
1480
|
|
|
1477
|
-
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
|
|
1481
|
+
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}";
|
|
1478
1482
|
|
|
1479
1483
|
const Modal = class {
|
|
1480
1484
|
constructor(hostRef) {
|
|
@@ -1497,6 +1501,8 @@ const Modal = class {
|
|
|
1497
1501
|
this.inline = false;
|
|
1498
1502
|
// Whether or not modal is being dismissed via gesture
|
|
1499
1503
|
this.gestureAnimationDismissing = false;
|
|
1504
|
+
// Whether to skip coordinate-based safe-area detection (for fullscreen phone modals)
|
|
1505
|
+
this.skipSafeAreaCoordinateDetection = false;
|
|
1500
1506
|
this.presented = false;
|
|
1501
1507
|
/** @internal */
|
|
1502
1508
|
this.hasController = false;
|
|
@@ -1687,7 +1693,9 @@ const Modal = class {
|
|
|
1687
1693
|
}
|
|
1688
1694
|
}
|
|
1689
1695
|
onWindowResize() {
|
|
1690
|
-
//
|
|
1696
|
+
// Update safe-area overrides for all modal types on resize
|
|
1697
|
+
this.updateSafeAreaOverrides();
|
|
1698
|
+
// Only handle view transition for iOS card modals when no custom animations are provided
|
|
1691
1699
|
if (getIonMode(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) {
|
|
1692
1700
|
return;
|
|
1693
1701
|
}
|
|
@@ -1869,6 +1877,8 @@ const Modal = class {
|
|
|
1869
1877
|
else if (!this.keepContentsMounted) {
|
|
1870
1878
|
await waitForMount();
|
|
1871
1879
|
}
|
|
1880
|
+
// Predict safe-area needs based on modal configuration to avoid visual snap
|
|
1881
|
+
this.setInitialSafeAreaOverrides(presentingElement);
|
|
1872
1882
|
writeTask(() => this.el.classList.add('show-modal'));
|
|
1873
1883
|
const hasCardModal = presentingElement !== undefined;
|
|
1874
1884
|
/**
|
|
@@ -1930,6 +1940,8 @@ const Modal = class {
|
|
|
1930
1940
|
else if (hasCardModal) {
|
|
1931
1941
|
this.initSwipeToClose();
|
|
1932
1942
|
}
|
|
1943
|
+
// Now that animation is complete, update safe-area based on actual position
|
|
1944
|
+
this.updateSafeAreaOverrides();
|
|
1933
1945
|
// Initialize view transition listener for iOS card modals
|
|
1934
1946
|
this.initViewTransitionListener();
|
|
1935
1947
|
// Initialize parent removal observer
|
|
@@ -1981,7 +1993,7 @@ const Modal = class {
|
|
|
1981
1993
|
await this.dismiss(undefined, GESTURE);
|
|
1982
1994
|
this.gestureAnimationDismissing = false;
|
|
1983
1995
|
});
|
|
1984
|
-
});
|
|
1996
|
+
}, () => this.updateSafeAreaOverrides());
|
|
1985
1997
|
this.gesture.enable(true);
|
|
1986
1998
|
}
|
|
1987
1999
|
initSheetGesture() {
|
|
@@ -2002,7 +2014,8 @@ const Modal = class {
|
|
|
2002
2014
|
this.currentBreakpoint = breakpoint;
|
|
2003
2015
|
this.ionBreakpointDidChange.emit({ breakpoint });
|
|
2004
2016
|
}
|
|
2005
|
-
|
|
2017
|
+
this.updateSafeAreaOverrides();
|
|
2018
|
+
}, () => this.updateSafeAreaOverrides());
|
|
2006
2019
|
this.gesture = gesture;
|
|
2007
2020
|
this.moveSheetToBreakpoint = moveSheetToBreakpoint;
|
|
2008
2021
|
this.gesture.enable(true);
|
|
@@ -2080,6 +2093,97 @@ const Modal = class {
|
|
|
2080
2093
|
// Clear the cached reference
|
|
2081
2094
|
this.cachedPageParent = undefined;
|
|
2082
2095
|
}
|
|
2096
|
+
/**
|
|
2097
|
+
* Sets initial safe-area overrides based on modal configuration before
|
|
2098
|
+
* the modal becomes visible. This predicts whether the modal will touch
|
|
2099
|
+
* screen edges to avoid a visual snap after animation completes.
|
|
2100
|
+
*/
|
|
2101
|
+
setInitialSafeAreaOverrides(presentingElement) {
|
|
2102
|
+
const style = this.el.style;
|
|
2103
|
+
const isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined;
|
|
2104
|
+
const isCardModal = presentingElement !== undefined;
|
|
2105
|
+
const isTablet = window.innerWidth >= 768;
|
|
2106
|
+
// Sheet modals always touch bottom edge, never top/left/right
|
|
2107
|
+
if (isSheetModal) {
|
|
2108
|
+
style.setProperty('--ion-safe-area-top', '0px');
|
|
2109
|
+
style.setProperty('--ion-safe-area-left', '0px');
|
|
2110
|
+
style.setProperty('--ion-safe-area-right', '0px');
|
|
2111
|
+
return;
|
|
2112
|
+
}
|
|
2113
|
+
// Card modals are inset from all edges
|
|
2114
|
+
if (isCardModal) {
|
|
2115
|
+
this.zeroAllSafeAreas();
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
2118
|
+
// Phone-sized fullscreen modals inherit safe areas and use wrapper padding
|
|
2119
|
+
if (!isTablet) {
|
|
2120
|
+
this.applyFullscreenSafeArea();
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
2123
|
+
// Check if tablet modal is fullscreen via CSS custom properties
|
|
2124
|
+
const computedStyle = getComputedStyle(this.el);
|
|
2125
|
+
const width = computedStyle.getPropertyValue('--width').trim();
|
|
2126
|
+
const height = computedStyle.getPropertyValue('--height').trim();
|
|
2127
|
+
const isFullscreen = width === '100%' && height === '100%';
|
|
2128
|
+
if (isFullscreen) {
|
|
2129
|
+
this.applyFullscreenSafeArea();
|
|
2130
|
+
}
|
|
2131
|
+
else {
|
|
2132
|
+
// Centered dialog doesn't touch edges
|
|
2133
|
+
this.zeroAllSafeAreas();
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
/**
|
|
2137
|
+
* Applies safe-area handling for fullscreen modals.
|
|
2138
|
+
* Adds wrapper padding when no footer is present to prevent
|
|
2139
|
+
* content from overlapping system navigation areas.
|
|
2140
|
+
*/
|
|
2141
|
+
applyFullscreenSafeArea() {
|
|
2142
|
+
this.skipSafeAreaCoordinateDetection = true;
|
|
2143
|
+
const hasFooter = this.el.querySelector('ion-footer') !== null;
|
|
2144
|
+
if (!hasFooter && this.wrapperEl) {
|
|
2145
|
+
this.wrapperEl.style.setProperty('padding-bottom', 'var(--ion-safe-area-bottom, 0px)');
|
|
2146
|
+
this.wrapperEl.style.setProperty('box-sizing', 'border-box');
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
/**
|
|
2150
|
+
* Sets all safe-area CSS variables to 0px for modals that
|
|
2151
|
+
* don't touch screen edges.
|
|
2152
|
+
*/
|
|
2153
|
+
zeroAllSafeAreas() {
|
|
2154
|
+
const style = this.el.style;
|
|
2155
|
+
style.setProperty('--ion-safe-area-top', '0px');
|
|
2156
|
+
style.setProperty('--ion-safe-area-bottom', '0px');
|
|
2157
|
+
style.setProperty('--ion-safe-area-left', '0px');
|
|
2158
|
+
style.setProperty('--ion-safe-area-right', '0px');
|
|
2159
|
+
}
|
|
2160
|
+
/**
|
|
2161
|
+
* Updates safe-area CSS variable overrides based on whether the modal
|
|
2162
|
+
* is touching each edge of the viewport. Called after animation
|
|
2163
|
+
* and during gestures to handle dynamic position changes.
|
|
2164
|
+
*/
|
|
2165
|
+
updateSafeAreaOverrides() {
|
|
2166
|
+
if (this.skipSafeAreaCoordinateDetection) {
|
|
2167
|
+
return;
|
|
2168
|
+
}
|
|
2169
|
+
const wrapper = this.wrapperEl;
|
|
2170
|
+
if (!wrapper) {
|
|
2171
|
+
return;
|
|
2172
|
+
}
|
|
2173
|
+
const rect = wrapper.getBoundingClientRect();
|
|
2174
|
+
const threshold = 2;
|
|
2175
|
+
const touchingTop = rect.top <= threshold;
|
|
2176
|
+
const touchingBottom = rect.bottom >= window.innerHeight - threshold;
|
|
2177
|
+
const touchingLeft = rect.left <= threshold;
|
|
2178
|
+
const touchingRight = rect.right >= window.innerWidth - threshold;
|
|
2179
|
+
const style = this.el.style;
|
|
2180
|
+
touchingTop ? style.removeProperty('--ion-safe-area-top') : style.setProperty('--ion-safe-area-top', '0px');
|
|
2181
|
+
touchingBottom
|
|
2182
|
+
? style.removeProperty('--ion-safe-area-bottom')
|
|
2183
|
+
: style.setProperty('--ion-safe-area-bottom', '0px');
|
|
2184
|
+
touchingLeft ? style.removeProperty('--ion-safe-area-left') : style.setProperty('--ion-safe-area-left', '0px');
|
|
2185
|
+
touchingRight ? style.removeProperty('--ion-safe-area-right') : style.setProperty('--ion-safe-area-right', '0px');
|
|
2186
|
+
}
|
|
2083
2187
|
sheetOnDismiss() {
|
|
2084
2188
|
/**
|
|
2085
2189
|
* While the gesture animation is finishing
|
|
@@ -2172,6 +2276,8 @@ const Modal = class {
|
|
|
2172
2276
|
}
|
|
2173
2277
|
this.currentBreakpoint = undefined;
|
|
2174
2278
|
this.animation = undefined;
|
|
2279
|
+
// Reset safe-area detection flag for potential re-presentation
|
|
2280
|
+
this.skipSafeAreaCoordinateDetection = false;
|
|
2175
2281
|
unlock();
|
|
2176
2282
|
return dismissed;
|
|
2177
2283
|
}
|
|
@@ -2253,6 +2359,10 @@ const Modal = class {
|
|
|
2253
2359
|
this.currentViewIsPortrait = window.innerWidth < 768;
|
|
2254
2360
|
}
|
|
2255
2361
|
handleViewTransition() {
|
|
2362
|
+
// Only run view transitions when the modal is presented
|
|
2363
|
+
if (!this.presented) {
|
|
2364
|
+
return;
|
|
2365
|
+
}
|
|
2256
2366
|
const isPortrait = window.innerWidth < 768;
|
|
2257
2367
|
// Only transition if view state actually changed
|
|
2258
2368
|
if (this.currentViewIsPortrait === isPortrait) {
|
|
@@ -2417,20 +2527,20 @@ const Modal = class {
|
|
|
2417
2527
|
const isCardModal = presentingElement !== undefined && mode === 'ios';
|
|
2418
2528
|
const isHandleCycle = handleBehavior === 'cycle';
|
|
2419
2529
|
const isSheetModalWithHandle = isSheetModal && showHandle;
|
|
2420
|
-
return (h(Host, Object.assign({ key: '
|
|
2530
|
+
return (h(Host, Object.assign({ key: 'b93c9d20985ca4800b34c0165957b12b0a9d6789', "no-router": true,
|
|
2421
2531
|
// Allow the modal to be navigable when the handle is focusable
|
|
2422
2532
|
tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
|
|
2423
2533
|
zIndex: `${20000 + this.overlayIndex}`,
|
|
2424
|
-
}, 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: '
|
|
2534
|
+
}, 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: '732b9355b3c0b051d19e1e0cedd7c49e3ad6fdda', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '874dcbb38da5a9feecafc5d3d1cb9f140f7fe48c', class: "modal-shadow" }), h("div", Object.assign({ key: '0d0c28581d49aeb0f764387a76ad54f68c35a6d5',
|
|
2425
2535
|
/*
|
|
2426
2536
|
role and aria-modal must be used on the
|
|
2427
2537
|
same element. They must also be set inside the
|
|
2428
2538
|
shadow DOM otherwise ion-button will not be highlighted
|
|
2429
2539
|
when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
|
|
2430
2540
|
*/
|
|
2431
|
-
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '
|
|
2541
|
+
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: 'a27a7ecc9857b4dc2f8a6f64bb01487ac578f5aa', class: "modal-handle",
|
|
2432
2542
|
// Prevents the handle from receiving keyboard focus when it does not cycle
|
|
2433
|
-
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: '
|
|
2543
|
+
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: '2ca0a826798dbc5cfe06e94b6a2d0ef375d018bc', onSlotchange: this.onSlotChange }))));
|
|
2434
2544
|
}
|
|
2435
2545
|
get el() { return getElement(this); }
|
|
2436
2546
|
static get watchers() { return {
|