fix(popover): adjust position to account for approximate safe area

This commit is contained in:
amandaesmith3
2024-02-16 12:52:47 -06:00
parent 7033a28ebd
commit 0d7497abe0
4 changed files with 86 additions and 25 deletions

View File

@@ -53,7 +53,6 @@ export const iosEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation =>
);
const padding = size === 'cover' ? 0 : POPOVER_IOS_BODY_PADDING;
const margin = size === 'cover' ? 0 : 25;
const {
originX,
@@ -61,8 +60,6 @@ export const iosEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation =>
top,
left,
bottom,
checkSafeAreaLeft,
checkSafeAreaRight,
arrowTop,
arrowLeft,
addPopoverBottomClass,
@@ -75,7 +72,6 @@ export const iosEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation =>
bodyHeight,
contentWidth,
contentHeight,
margin,
results.originX,
results.originY,
results.referenceCoordinates,
@@ -122,20 +118,8 @@ export const iosEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation =>
contentEl.style.setProperty('bottom', `${bottom}px`);
}
const safeAreaLeft = ' + var(--ion-safe-area-left, 0)';
const safeAreaRight = ' - var(--ion-safe-area-right, 0)';
let leftValue = `${left}px`;
if (checkSafeAreaLeft) {
leftValue = `${left}px${safeAreaLeft}`;
}
if (checkSafeAreaRight) {
leftValue = `${left}px${safeAreaRight}`;
}
contentEl.style.setProperty('top', `calc(${top}px + var(--offset-y, 0))`);
contentEl.style.setProperty('left', `calc(${leftValue} + var(--offset-x, 0))`);
contentEl.style.setProperty('left', `calc(${left}px + var(--offset-x, 0))`);
contentEl.style.setProperty('transform-origin', `${originY} ${originX}`);
if (arrowEl !== null) {

View File

@@ -56,7 +56,6 @@ export const mdEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation =>
bodyHeight,
contentWidth,
contentHeight,
0,
results.originX,
results.originY,
results.referenceCoordinates

View File

@@ -16,18 +16,43 @@
import { popoverController } from '../../../../dist/ionic/index.esm.js';
window.popoverController = popoverController;
</script>
<style>
.safe-area-cover {
background-color: white;
position: absolute;
top: 5%;
bottom: 5%;
left: 0;
right: 0;
@media (orientation: landscape) {
left: 5%;
right: 5%;
}
}
</style>
</head>
<body>
<ion-app>
<ion-content>
<p style="text-align: center">Click everywhere to open the popover.</p>
<div style="text-align: center">
<p>Click everywhere to open the popover.</p>
<ion-checkbox id="safe-area-cb">Show Safe Area Approximation</ion-checkbox>
</div>
<div class="safe-area-cover"></div>
</ion-content>
</ion-app>
<script>
document.querySelector('ion-content').addEventListener('click', handleButtonClick);
document.querySelector('#safe-area-cb').addEventListener('ionChange', toggleSafeArea);
async function handleButtonClick(ev) {
if (ev.target.tagName === 'ION-CHECKBOX') return;
const popover = await popoverController.create({
component: 'popover-example-page',
event: ev,
@@ -37,6 +62,16 @@
popover.present();
}
function toggleSafeArea(ev) {
const content = document.querySelector('ion-content');
if (ev.detail.checked) {
content.style.setProperty('--background', 'lightblue');
} else {
content.style.removeProperty('--background');
}
}
customElements.define(
'popover-example-page',
class PopoverContent extends HTMLElement {

View File

@@ -1,3 +1,4 @@
import { win } from '@utils/browser';
import { getElementRoot, raf } from '@utils/helpers';
import type { PopoverSize, PositionAlign, PositionReference, PositionSide, TriggerAction } from './popover-interface';
@@ -814,7 +815,6 @@ export const calculateWindowAdjustment = (
bodyHeight: number,
contentWidth: number,
contentHeight: number,
safeAreaMargin: number,
contentOriginX: string,
contentOriginY: string,
triggerCoordinates?: ReferenceCoordinates,
@@ -837,24 +837,61 @@ export const calculateWindowAdjustment = (
const triggerHeight = triggerCoordinates ? triggerCoordinates.height : 0;
let addPopoverBottomClass = false;
/**
* Approximate the safe area margins. Getting exact values would necessitate
* using window.getComputedStyle(), which is very expensive, so we use "close
* enough" values for now.
*
* 5% is derived from the iPhone 14 top safe area margin (47pt / 844 pt ~= 0.05).
* Source: https://useyourloaf.com/blog/iphone-14-screen-sizes/
*
* TODO(FW-5982): Investigate a more robust solution that uses the actual
* safe area margins through alternate means.
*/
let horizontalSafeAreaApprox = 0;
let verticalSafeAreaApprox = 0;
if (win !== undefined && win.matchMedia !== undefined) {
verticalSafeAreaApprox = win.innerHeight * 0.05;
/**
* We only want to check horizontal safe area on landscape.
* Most devices do not have horizontal safe area margins in
* portrait mode, so enforcing it would lead to popovers
* being misaligned with the trigger when we don't want them
* to move.
*/
if (win.matchMedia('(orientation: landscape)').matches) {
horizontalSafeAreaApprox = win.innerWidth * 0.05;
}
}
/**
* Adjust popover so it does not
* go off the left of the screen.
*/
if (left < bodyPadding + safeAreaMargin) {
left = bodyPadding;
if (left < bodyPadding + horizontalSafeAreaApprox) {
left = bodyPadding + horizontalSafeAreaApprox;
checkSafeAreaLeft = true;
originX = 'left';
/**
* Adjust popover so it does not
* go off the right of the screen.
*/
} else if (contentWidth + bodyPadding + left + safeAreaMargin > bodyWidth) {
} else if (contentWidth + bodyPadding + left + horizontalSafeAreaApprox > bodyWidth) {
checkSafeAreaRight = true;
left = bodyWidth - contentWidth - bodyPadding;
left = bodyWidth - contentWidth - bodyPadding - horizontalSafeAreaApprox;
originX = 'right';
}
/**
* Ensure the popover doesn't sit above the safe area approxmation.
* If popover is on the left or right of the trigger, we should not
* adjust top margins.
*/
if (side === 'top' || side === 'bottom') {
top = Math.max(top, verticalSafeAreaApprox + bodyPadding);
}
/**
* Adjust popover so it does not
* go off the top of the screen.
@@ -862,7 +899,7 @@ export const calculateWindowAdjustment = (
* the trigger, then we should not adjust top
* margins.
*/
if (triggerTop + triggerHeight + contentHeight > bodyHeight && (side === 'top' || side === 'bottom')) {
if (triggerTop + triggerHeight + contentHeight + verticalSafeAreaApprox > bodyHeight && (side === 'top' || side === 'bottom')) {
if (triggerTop - contentHeight > 0) {
/**
* While we strive to align the popover with the trigger
@@ -875,6 +912,12 @@ export const calculateWindowAdjustment = (
* it is not right up against the edge of the screen.
*/
top = Math.max(12, triggerTop - contentHeight - triggerHeight - (arrowHeight - 1));
/**
* Ensure the popover doesn't sit below the safe area approxmation.
*/
top = Math.min(top, bodyHeight - verticalSafeAreaApprox - contentHeight - bodyPadding);
arrowTop = top + contentHeight;
originY = 'bottom';
addPopoverBottomClass = true;