feat(popover): initial checkin of popover positioning

This commit is contained in:
Brandy Carney
2017-08-14 13:36:28 -04:00
parent f7417d1e13
commit e214502c6c
3 changed files with 134 additions and 35 deletions

View File

@ -38,11 +38,11 @@ ion-popover {
}
.popover-content ion-content,
.popover-content .scroll-content {
.popover-content ion-scroll {
contain: none;
}
.popover-content .scroll-content {
.popover-content ion-scroll {
position: relative;
}

View File

@ -51,6 +51,96 @@ export class Popover {
});
}
// TODO currently only positions iOS
private positionView(nativeEle: HTMLElement, ev: any, mode: string) {
// Default to material design mode unless mode is ios
const popoverMode = (mode === 'ios') ? mode : 'md';
const popoverProps = popoverViewProps[popoverMode];
const popoverPadding = popoverProps.bodyPadding;
// Declare the popover elements
let popoverWrapperEle = nativeEle.querySelector('.popover-wrapper') as HTMLElement;
let popoverContentEle = nativeEle.querySelector('.popover-content') as HTMLElement;
let popoverArrowEle = nativeEle.querySelector('.popover-arrow') as HTMLElement;
// Grab the default origin from the properties
let originY = 'top';
let originX = 'left';
// Popover content width and height
let popoverDim = popoverContentEle.getBoundingClientRect();
let popoverWidth = popoverDim.width;
let popoverHeight = popoverDim.height;
// Window body width and height
// TODO need to check if portrait/landscape?
let bodyWidth = window.screen.width;
let bodyHeight = window.screen.height;
// If ev was passed, use that for target element
let targetDim = ev && ev.target && ev.target.getBoundingClientRect();
let targetTop = (targetDim && 'top' in targetDim) ? targetDim.top : (bodyHeight / 2) - (popoverHeight / 2);
let targetLeft = (targetDim && 'left' in targetDim) ? targetDim.left : (bodyWidth / 2);
let targetWidth = targetDim && targetDim.width || 0;
let targetHeight = targetDim && targetDim.height || 0;
// The arrow that shows above the popover on iOS
let arrowDim = popoverArrowEle.getBoundingClientRect();
var arrowWidth = arrowDim.width;
var arrowHeight = arrowDim.height;
// If no ev was passed, hide the arrow
if (!targetDim) {
popoverArrowEle.style.display = 'none';
}
let arrowCSS = {
top: targetTop + targetHeight,
left: targetLeft + (targetWidth / 2) - (arrowWidth / 2)
};
let popoverCSS = {
top: targetTop + targetHeight + (arrowHeight - 1),
left: targetLeft + (targetWidth / 2) - (popoverWidth / 2)
};
// If the popover left is less than the padding it is off screen
// to the left so adjust it, else if the width of the popover
// exceeds the body width it is off screen to the right so adjust
if (popoverCSS.left < popoverPadding) {
popoverCSS.left = popoverPadding;
} else if (popoverWidth + popoverPadding + popoverCSS.left > bodyWidth) {
popoverCSS.left = bodyWidth - popoverWidth - popoverPadding;
originX = 'right';
}
// If the popover when popped down stretches past bottom of screen,
// make it pop up if there's room above
if (targetTop + targetHeight + popoverHeight > bodyHeight && targetTop - popoverHeight > 0) {
arrowCSS.top = targetTop - (arrowHeight + 1);
popoverCSS.top = targetTop - popoverHeight - (arrowHeight - 1);
nativeEle.className = nativeEle.className + ' popover-bottom';
originY = 'bottom';
// If there isn't room for it to pop up above the target cut it off
} else if (targetTop + targetHeight + popoverHeight > bodyHeight) {
popoverContentEle.style.bottom = popoverPadding + '%';
}
popoverArrowEle.style.top = arrowCSS.top + 'px';
popoverArrowEle.style.left = arrowCSS.left + 'px';
popoverContentEle.style.top = popoverCSS.top + 'px';
popoverContentEle.style.left = popoverCSS.left + 'px';
popoverContentEle.style.transformOrigin = originY + ' ' + originX;
// Since the transition starts before styling is done we
// want to wait for the styles to apply before showing the wrapper
popoverWrapperEle.style.opacity = '1';
}
private _present(resolve: Function) {
if (this.animation) {
this.animation.destroy();
@ -74,6 +164,7 @@ export class Popover {
animation.onFinish((a: any) => {
a.destroy();
this.ionViewDidEnter();
this.positionView(this.el, this.ev, this.mode);
resolve();
}).play();
});
@ -145,21 +236,11 @@ export class Popover {
render() {
const ThisComponent = this.component;
let userCssClasses = 'popover-content';
if (this.cssClass) {
userCssClasses += ` ${this.cssClass}`;
}
const dialogClasses = createThemedClasses(
this.mode,
this.color,
'popover-wrapper'
);
const thisComponentClasses = createThemedClasses(
this.mode,
this.color,
userCssClasses
);
return [
<ion-backdrop
@ -172,7 +253,7 @@ export class Popover {
<div class="popover-viewport">
<ThisComponent
props={this.componentProps}
class={thisComponentClasses}
class={this.cssClass}
/>
</div>
</div>
@ -197,3 +278,17 @@ export interface PopoverEvent {
popover: Popover;
};
}
export const popoverViewProps: any = {
ios: {
bodyPadding: 2,
showArrow: true
},
md: {
bodyPadding: 12,
showArrow: false
}
}
// TODO FIX
const POPOVER_MD_BODY_PADDING = 12;

View File

@ -3,25 +3,35 @@
<head>
<meta charset="UTF-8">
<title>Ionic Slides Basic</title>
<title>Ionic Popover</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<script src="/dist/ionic.js"></script>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar color="primary">
<ion-buttons slot="start">
<ion-button onclick="presentPopover('profile-page', event)">
<ion-icon slot="icon-only" name="person"></ion-icon>
</ion-button>
</ion-buttons>
<ion-title>Popover</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-button onclick="presentPopover(event)">Show Popover</ion-button>
<ion-content padding>
<ion-button block onclick="presentPopover('profile-page', event)">Show Popover</ion-button>
<ion-popover-controller></ion-popover-controller>
</ion-content>
</ion-app>
<script>
function presentPopover(event) {
function presentPopover(componentName, event) {
var popoverController = document.querySelector('ion-popover-controller');
popoverController.create({
component: 'profile-page',
component: componentName,
ev: event
}).then(popover => {
popover.present();
@ -37,27 +47,21 @@
});
shadowRoot.innerHTML = `
<ion-header>
<ion-toolbar>
My Profile
</ion-toolbar>
</ion-header>
<ion-content padding>
<p>
<ion-button>Dismiss</ion-button>
</p>
<ion-content>
<ion-list>
<ion-list-header>Ionic</ion-list-header>
<ion-item onclick="connectedCallback(event)">Dismiss</ion-item>
<ion-item><ion-label>Item 0</ion-label></ion-item>
</ion-list>
</ion-content>`;
}
connectedCallback() {
var btn = this.shadowRoot.querySelector('ion-button');
btn.addEventListener('click', (uiEvent) => {
var ev = new CustomEvent('ionDismiss', {
composed: true,
bubbles: true
});
uiEvent.target.dispatchEvent(ev);
connectedCallback(event) {
var ev = new CustomEvent('ionDismiss', {
composed: true,
bubbles: true
});
event.target.dispatchEvent(ev);
}
}