MOBILE-3320 ios: Fix zoomed popover position

main
Noel De Martin 2021-06-29 18:47:01 +02:00
parent 3c4b4c3786
commit 9ded77f351
1 changed files with 102 additions and 1 deletions

View File

@ -14,7 +14,7 @@
import { Injectable, SimpleChange, ElementRef, KeyValueChanges } from '@angular/core';
import { IonContent } from '@ionic/angular';
import { ModalOptions, PopoverOptions, AlertOptions, AlertButton, TextFieldTypes } from '@ionic/core';
import { ModalOptions, PopoverOptions, AlertOptions, AlertButton, TextFieldTypes, getMode } from '@ionic/core';
import { Md5 } from 'ts-md5';
import { CoreApp } from '@services/app';
@ -37,6 +37,7 @@ import { CoreBSTooltipComponent } from '@components/bs-tooltip/bs-tooltip';
import { CoreViewerImageComponent } from '@features/viewer/components/image/image';
import { CoreFormFields, CoreForms } from '../../singletons/form';
import { CoreModalLateralTransitionEnter, CoreModalLateralTransitionLeave } from '@classes/modal-lateral-transition';
import { CoreZoomLevel } from '@features/settings/services/settings-helper';
/*
* "Utils" service with helper functions for UI, DOM elements and HTML code.
@ -1734,9 +1735,19 @@ export class CoreDomUtilsProvider {
): Promise<T | undefined> {
const popover = await PopoverController.create(popoverOptions);
const zoomLevel = await CoreConfig.get(CoreConstants.SETTINGS_ZOOM_LEVEL, CoreZoomLevel.NORMAL);
await popover.present();
// Fix popover position if zoom is applied.
if (zoomLevel !== CoreZoomLevel.NORMAL) {
switch (getMode()) {
case 'ios':
fixIOSPopoverPosition(popover, popoverOptions.event);
break;
}
}
// If onDidDismiss is nedded we can add a new param to the function to wait one function or the other.
const result = await popover.onWillDismiss<T>();
if (result?.data) {
@ -1849,6 +1860,96 @@ export class CoreDomUtilsProvider {
}
/**
* Fix the position of a popover that was created with a zoom level applied in iOS.
*
* This is necessary because Ionic's implementation gets the body dimensions from `element.ownerDocument.defaultView.innerXXX`,
* which doesn't return the correct dimensions when the `zoom` CSS property is being used. This is only necessary
* in iOS because Android already respects system font sizes, and setting the zoom level is unnecessary. Eventually, we
* should find an alternative implementation for iOS that doesn't require this workaround (also because the `zoom` CSS
* property is not standard and its usage is discouraged for production).
*
* This function has been copied in its entirety from Ionic's source code, only changing the aforementioned calculation
* of the body dimensions with `document.body.clientXXX`.
*
* @see https://github.com/ionic-team/ionic-framework/blob/v5.6.6/core/src/components/popover/animations/ios.enter.ts
*/
function fixIOSPopoverPosition(baseEl: HTMLElement, ev?: Event): void {
let originY = 'top';
let originX = 'left';
const POPOVER_IOS_BODY_PADDING = 5;
const contentEl = baseEl.querySelector('.popover-content') as HTMLElement;
const contentDimentions = contentEl.getBoundingClientRect();
const contentWidth = contentDimentions.width;
const contentHeight = contentDimentions.height;
const bodyWidth = document.body.clientWidth;
const bodyHeight = document.body.clientHeight;
const targetDim = ev && ev.target && (ev.target as HTMLElement).getBoundingClientRect();
const targetTop = targetDim != null && 'top' in targetDim ? targetDim.top : bodyHeight / 2 - contentHeight / 2;
const targetLeft = targetDim != null && 'left' in targetDim ? targetDim.left : bodyWidth / 2;
const targetWidth = (targetDim && targetDim.width) || 0;
const targetHeight = (targetDim && targetDim.height) || 0;
const arrowEl = baseEl.querySelector('.popover-arrow') as HTMLElement;
const arrowDim = arrowEl.getBoundingClientRect();
const arrowWidth = arrowDim.width;
const arrowHeight = arrowDim.height;
if (targetDim == null) {
arrowEl.style.display = 'none';
}
const arrowCSS = {
top: targetTop + targetHeight,
left: targetLeft + targetWidth / 2 - arrowWidth / 2,
};
const popoverCSS: { top: number; left: number } = {
top: targetTop + targetHeight + (arrowHeight - 1),
left: targetLeft + targetWidth / 2 - contentWidth / 2,
};
let checkSafeAreaLeft = false;
let checkSafeAreaRight = false;
if (popoverCSS.left < POPOVER_IOS_BODY_PADDING + 25) {
checkSafeAreaLeft = true;
popoverCSS.left = POPOVER_IOS_BODY_PADDING;
} else if (
contentWidth + POPOVER_IOS_BODY_PADDING + popoverCSS.left + 25 > bodyWidth
) {
checkSafeAreaRight = true;
popoverCSS.left = bodyWidth - contentWidth - POPOVER_IOS_BODY_PADDING;
originX = 'right';
}
if (targetTop + targetHeight + contentHeight > bodyHeight && targetTop - contentHeight > 0) {
arrowCSS.top = targetTop - (arrowHeight + 1);
popoverCSS.top = targetTop - contentHeight - (arrowHeight - 1);
baseEl.className = baseEl.className + ' popover-bottom';
originY = 'bottom';
} else if (targetTop + targetHeight + contentHeight > bodyHeight) {
contentEl.style.bottom = POPOVER_IOS_BODY_PADDING + '%';
}
arrowEl.style.top = arrowCSS.top + 'px';
arrowEl.style.left = arrowCSS.left + 'px';
contentEl.style.top = popoverCSS.top + 'px';
contentEl.style.left = popoverCSS.left + 'px';
if (checkSafeAreaLeft) {
contentEl.style.left = `calc(${popoverCSS.left}px + var(--ion-safe-area-left, 0px))`;
}
if (checkSafeAreaRight) {
contentEl.style.left = `calc(${popoverCSS.left}px - var(--ion-safe-area-right, 0px))`;
}
contentEl.style.transformOrigin = originY + ' ' + originX;
}
export const CoreDomUtils = makeSingleton(CoreDomUtilsProvider);
type AnchorOrMediaElement =