@ionic/core 8.7.17-dev.11767636336.113b441d → 8.7.17-dev.11767641184.14a165bc
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 +144 -11
- package/components/popover.js +60 -10
- package/dist/cjs/ion-modal.cjs.entry.js +144 -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 +136 -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 +144 -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-7268efa5.entry.js +4 -0
- package/dist/ionic/p-fedca459.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 +29 -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 +207 -38
- package/hydrate/index.mjs +207 -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
|
@@ -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,124 @@ 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 mode = getIonMode(this);
|
|
645
|
+
const isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined;
|
|
646
|
+
// Card modals only exist in iOS mode - in MD mode, presentingElement is ignored
|
|
647
|
+
const isCardModal = presentingElement !== undefined && mode === 'ios';
|
|
648
|
+
const isTablet = window.innerWidth >= 768;
|
|
649
|
+
// Sheet modals always touch bottom edge, never top/left/right
|
|
650
|
+
if (isSheetModal) {
|
|
651
|
+
style.setProperty('--ion-safe-area-top', '0px');
|
|
652
|
+
style.setProperty('--ion-safe-area-left', '0px');
|
|
653
|
+
style.setProperty('--ion-safe-area-right', '0px');
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
// Card modals have rounded top corners
|
|
657
|
+
if (isCardModal) {
|
|
658
|
+
style.setProperty('--ion-safe-area-top', '0px');
|
|
659
|
+
if (isTablet) {
|
|
660
|
+
// On tablets, card modals are inset from all edges
|
|
661
|
+
this.zeroAllSafeAreas();
|
|
662
|
+
}
|
|
663
|
+
else {
|
|
664
|
+
// On phones, card modals still extend to the bottom edge
|
|
665
|
+
style.setProperty('--ion-safe-area-left', '0px');
|
|
666
|
+
style.setProperty('--ion-safe-area-right', '0px');
|
|
667
|
+
this.applyFullscreenSafeArea();
|
|
668
|
+
}
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
// Phone-sized fullscreen modals inherit safe areas and use wrapper padding
|
|
672
|
+
if (!isTablet) {
|
|
673
|
+
this.applyFullscreenSafeArea();
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
// Check if tablet modal is fullscreen via CSS custom properties
|
|
677
|
+
const computedStyle = getComputedStyle(this.el);
|
|
678
|
+
const width = computedStyle.getPropertyValue('--width').trim();
|
|
679
|
+
const height = computedStyle.getPropertyValue('--height').trim();
|
|
680
|
+
const isFullscreen = width === '100%' && height === '100%';
|
|
681
|
+
if (isFullscreen) {
|
|
682
|
+
this.applyFullscreenSafeArea();
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
// Centered dialog doesn't touch edges
|
|
686
|
+
this.zeroAllSafeAreas();
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Applies safe-area handling for fullscreen modals.
|
|
691
|
+
* Adds wrapper padding when no footer is present to prevent
|
|
692
|
+
* content from overlapping system navigation areas.
|
|
693
|
+
*/
|
|
694
|
+
applyFullscreenSafeArea() {
|
|
695
|
+
this.skipSafeAreaCoordinateDetection = true;
|
|
696
|
+
const hasFooter = this.el.querySelector('ion-footer') !== null;
|
|
697
|
+
if (!hasFooter && this.wrapperEl) {
|
|
698
|
+
this.wrapperEl.style.setProperty('padding-bottom', 'var(--ion-safe-area-bottom, 0px)');
|
|
699
|
+
this.wrapperEl.style.setProperty('box-sizing', 'border-box');
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Sets all safe-area CSS variables to 0px for modals that
|
|
704
|
+
* don't touch screen edges.
|
|
705
|
+
*/
|
|
706
|
+
zeroAllSafeAreas() {
|
|
707
|
+
const style = this.el.style;
|
|
708
|
+
style.setProperty('--ion-safe-area-top', '0px');
|
|
709
|
+
style.setProperty('--ion-safe-area-bottom', '0px');
|
|
710
|
+
style.setProperty('--ion-safe-area-left', '0px');
|
|
711
|
+
style.setProperty('--ion-safe-area-right', '0px');
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Gets the root safe-area values from the document element.
|
|
715
|
+
* These represent the actual device safe areas before any overlay overrides.
|
|
716
|
+
*/
|
|
717
|
+
getRootSafeAreaValues() {
|
|
718
|
+
const rootStyle = getComputedStyle(document.documentElement);
|
|
719
|
+
return {
|
|
720
|
+
top: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-top')) || 0,
|
|
721
|
+
bottom: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-bottom')) || 0,
|
|
722
|
+
left: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-left')) || 0,
|
|
723
|
+
right: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-right')) || 0,
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Updates safe-area CSS variable overrides based on whether the modal
|
|
728
|
+
* extends into each safe-area region. Called after animation
|
|
729
|
+
* and during gestures to handle dynamic position changes.
|
|
730
|
+
*/
|
|
731
|
+
updateSafeAreaOverrides() {
|
|
732
|
+
if (this.skipSafeAreaCoordinateDetection) {
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
const wrapper = this.wrapperEl;
|
|
736
|
+
if (!wrapper) {
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
const rect = wrapper.getBoundingClientRect();
|
|
740
|
+
const safeAreas = this.getRootSafeAreaValues();
|
|
741
|
+
const extendsIntoTop = rect.top < safeAreas.top;
|
|
742
|
+
const extendsIntoBottom = rect.bottom > window.innerHeight - safeAreas.bottom;
|
|
743
|
+
const extendsIntoLeft = rect.left < safeAreas.left;
|
|
744
|
+
const extendsIntoRight = rect.right > window.innerWidth - safeAreas.right;
|
|
745
|
+
const style = this.el.style;
|
|
746
|
+
extendsIntoTop ? style.removeProperty('--ion-safe-area-top') : style.setProperty('--ion-safe-area-top', '0px');
|
|
747
|
+
extendsIntoBottom
|
|
748
|
+
? style.removeProperty('--ion-safe-area-bottom')
|
|
749
|
+
: style.setProperty('--ion-safe-area-bottom', '0px');
|
|
750
|
+
extendsIntoLeft ? style.removeProperty('--ion-safe-area-left') : style.setProperty('--ion-safe-area-left', '0px');
|
|
751
|
+
extendsIntoRight
|
|
752
|
+
? style.removeProperty('--ion-safe-area-right')
|
|
753
|
+
: style.setProperty('--ion-safe-area-right', '0px');
|
|
754
|
+
}
|
|
628
755
|
sheetOnDismiss() {
|
|
629
756
|
/**
|
|
630
757
|
* While the gesture animation is finishing
|
|
@@ -717,6 +844,8 @@ export class Modal {
|
|
|
717
844
|
}
|
|
718
845
|
this.currentBreakpoint = undefined;
|
|
719
846
|
this.animation = undefined;
|
|
847
|
+
// Reset safe-area detection flag for potential re-presentation
|
|
848
|
+
this.skipSafeAreaCoordinateDetection = false;
|
|
720
849
|
unlock();
|
|
721
850
|
return dismissed;
|
|
722
851
|
}
|
|
@@ -974,20 +1103,20 @@ export class Modal {
|
|
|
974
1103
|
const isCardModal = presentingElement !== undefined && mode === 'ios';
|
|
975
1104
|
const isHandleCycle = handleBehavior === 'cycle';
|
|
976
1105
|
const isSheetModalWithHandle = isSheetModal && showHandle;
|
|
977
|
-
return (h(Host, Object.assign({ key: '
|
|
1106
|
+
return (h(Host, Object.assign({ key: '07ebca6a70eb99f8a2236e1d66a03097a7bb67d8', "no-router": true,
|
|
978
1107
|
// Allow the modal to be navigable when the handle is focusable
|
|
979
1108
|
tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
|
|
980
1109
|
zIndex: `${20000 + this.overlayIndex}`,
|
|
981
|
-
}, 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: '
|
|
1110
|
+
}, 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: '1b6850d9b9f6e8f3865b49e0a14399e2ef43a5d6', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: 'eab52c0ebccb820781e92392dc6fa90db93525d5', class: "modal-shadow" }), h("div", Object.assign({ key: 'ab9448cabdf03a633319999771ce1ca1edce5699',
|
|
982
1111
|
/*
|
|
983
1112
|
role and aria-modal must be used on the
|
|
984
1113
|
same element. They must also be set inside the
|
|
985
1114
|
shadow DOM otherwise ion-button will not be highlighted
|
|
986
1115
|
when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
|
|
987
1116
|
*/
|
|
988
|
-
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '
|
|
1117
|
+
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: 'b2b8c0a8d8add0d43e928dd3d3519f184551e62b', class: "modal-handle",
|
|
989
1118
|
// Prevents the handle from receiving keyboard focus when it does not cycle
|
|
990
|
-
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: '
|
|
1119
|
+
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: 'b994f206765f7b340971d378f00999c0da334102', onSlotchange: this.onSlotChange }))));
|
|
991
1120
|
}
|
|
992
1121
|
static get is() { return "ion-modal"; }
|
|
993
1122
|
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,
|
|
@@ -12,7 +12,6 @@ export class TabBar {
|
|
|
12
12
|
constructor() {
|
|
13
13
|
this.keyboardCtrl = null;
|
|
14
14
|
this.didLoad = false;
|
|
15
|
-
this.isComponentConnected = false;
|
|
16
15
|
this.keyboardVisible = false;
|
|
17
16
|
/**
|
|
18
17
|
* If `true`, the tab bar will be translucent.
|
|
@@ -47,8 +46,7 @@ export class TabBar {
|
|
|
47
46
|
}
|
|
48
47
|
}
|
|
49
48
|
async connectedCallback() {
|
|
50
|
-
this.
|
|
51
|
-
const keyboardCtrl = await createKeyboardController(async (keyboardOpen, waitForResize) => {
|
|
49
|
+
this.keyboardCtrl = await createKeyboardController(async (keyboardOpen, waitForResize) => {
|
|
52
50
|
/**
|
|
53
51
|
* If the keyboard is hiding, then we need to wait
|
|
54
52
|
* for the webview to resize. Otherwise, the tab bar
|
|
@@ -59,33 +57,21 @@ export class TabBar {
|
|
|
59
57
|
}
|
|
60
58
|
this.keyboardVisible = keyboardOpen; // trigger re-render by updating state
|
|
61
59
|
});
|
|
62
|
-
/**
|
|
63
|
-
* Destroy the keyboard controller if the component was
|
|
64
|
-
* disconnected during async initialization to prevent memory leaks.
|
|
65
|
-
*/
|
|
66
|
-
if (this.isComponentConnected) {
|
|
67
|
-
this.keyboardCtrl = keyboardCtrl;
|
|
68
|
-
}
|
|
69
|
-
else {
|
|
70
|
-
keyboardCtrl.destroy();
|
|
71
|
-
}
|
|
72
60
|
}
|
|
73
61
|
disconnectedCallback() {
|
|
74
|
-
this.isComponentConnected = false;
|
|
75
62
|
if (this.keyboardCtrl) {
|
|
76
63
|
this.keyboardCtrl.destroy();
|
|
77
|
-
this.keyboardCtrl = null;
|
|
78
64
|
}
|
|
79
65
|
}
|
|
80
66
|
render() {
|
|
81
67
|
const { color, translucent, keyboardVisible } = this;
|
|
82
68
|
const mode = getIonMode(this);
|
|
83
69
|
const shouldHide = keyboardVisible && this.el.getAttribute('slot') !== 'top';
|
|
84
|
-
return (h(Host, { key: '
|
|
70
|
+
return (h(Host, { key: '388ec37ce308035bab78d6c9a016bb616e9517a9', role: "tablist", "aria-hidden": shouldHide ? 'true' : null, class: createColorClasses(color, {
|
|
85
71
|
[mode]: true,
|
|
86
72
|
'tab-bar-translucent': translucent,
|
|
87
73
|
'tab-bar-hidden': shouldHide,
|
|
88
|
-
}) }, h("slot", { key: '
|
|
74
|
+
}) }, h("slot", { key: 'ce10ade2b86725e24f3254516483eeedd8ecb16a' })));
|
|
89
75
|
}
|
|
90
76
|
static get is() { return "ion-tab-bar"; }
|
|
91
77
|
static get encapsulation() { return "shadow"; }
|
package/dist/docs.json
CHANGED