fix(popover): position for all modes in one function

This commit is contained in:
Brandy Carney
2017-08-14 18:04:23 -04:00
parent e214502c6c
commit ceac83addf
3 changed files with 112 additions and 63 deletions

View File

@ -10,16 +10,11 @@ export default function(Animation: Animation, baseElm: HTMLElement) {
const backdropAnimation = new Animation();
backdropAnimation.addElement(baseElm.querySelector('.popover-backdrop'));
const wrapperAnimation = new Animation();
wrapperAnimation.addElement(baseElm.querySelector('.popover-wrapper'));
wrapperAnimation.fromTo('opacity', 0.01, 1);
backdropAnimation.fromTo('opacity', 0.01, 0.08);
return baseAnimation
.addElement(baseElm)
.easing('ease')
.duration(100)
.add(backdropAnimation)
.add(wrapperAnimation);
.add(backdropAnimation);
}

View File

@ -52,92 +52,127 @@ 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;
private positionPopover(nativeEl: HTMLElement, ev: any, props: any) {
console.debug('Position popover', nativeEl, ev, props);
// 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;
let contentEl = nativeEl.querySelector('.popover-content') as HTMLElement;
let arrowEl = nativeEl.querySelector('.popover-arrow') as HTMLElement;
// Grab the default origin from the properties
let originY = 'top';
let originX = 'left';
// Set the default transform origin direction
let origin = {
y: 'top',
x: 'left'
}
// Popover content width and height
let popoverDim = popoverContentEle.getBoundingClientRect();
let popoverWidth = popoverDim.width;
let popoverHeight = popoverDim.height;
const popover = {
width: contentEl.getBoundingClientRect().width,
height: contentEl.getBoundingClientRect().height
}
// Window body width and height
// TODO need to check if portrait/landscape?
let bodyWidth = window.screen.width;
let bodyHeight = window.screen.height;
const body = {
width: window.screen.width,
height: 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 target is the object that dispatched the event that was passed
let target = {
top: (targetDim && 'top' in targetDim) ? targetDim.top : (body.height / 2) - (popover.height / 2),
left: (targetDim && 'left' in targetDim) ? targetDim.left : (body.width / 2) - (popover.width / 2),
width: targetDim && targetDim.width || 0,
height: targetDim && targetDim.height || 0
};
// If the popover should be centered to the target
if (props.centerTarget) {
target.left = (targetDim && 'left' in targetDim) ? targetDim.left : (body.width / 2);
}
// The arrow that shows above the popover on iOS
let arrowDim = popoverArrowEle.getBoundingClientRect();
var arrowWidth = arrowDim.width;
var arrowHeight = arrowDim.height;
let arrowDim = arrowEl.getBoundingClientRect();
const arrow = {
width: arrowDim.width,
height: arrowDim.height
}
// If no ev was passed, hide the arrow
if (!targetDim) {
popoverArrowEle.style.display = 'none';
arrowEl.style.display = 'none';
}
let arrowCSS = {
top: targetTop + targetHeight,
left: targetLeft + (targetWidth / 2) - (arrowWidth / 2)
top: target.top + target.height,
left: target.left + (target.width / 2) - (arrow.width / 2)
};
let popoverCSS = {
top: targetTop + targetHeight + (arrowHeight - 1),
left: targetLeft + (targetWidth / 2) - (popoverWidth / 2)
top: target.top + target.height + (arrow.height - 1),
left: target.left
};
// If the popover should be centered to the target
if (props.centerTarget) {
popoverCSS.left = target.left + (target.width / 2) - (popover.width / 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 (popoverCSS.left < props.padding) {
popoverCSS.left = props.padding;
} else if (popover.width + props.padding + popoverCSS.left > body.width) {
popoverCSS.left = body.width - popover.width - props.padding;
origin.x = '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 + '%';
if (this.showFromBottom(target, popover, body)) {
nativeEl.className = nativeEl.className + ' popover-bottom';
origin.y = 'bottom';
popoverCSS.top = target.top - popover.height;
if (props.showArrow) {
arrowCSS.top = target.top - (arrow.height + 1);
popoverCSS.top = target.top - popover.height - (arrow.height - 1);
}
// If the popover exceeds the viewport then cut the bottom off
} else if (this.exceedsViewport(target, popover, body)) {
contentEl.style.bottom = props.padding + props.unit;
}
popoverArrowEle.style.top = arrowCSS.top + 'px';
popoverArrowEle.style.left = arrowCSS.left + 'px';
arrowEl.style.top = arrowCSS.top + 'px';
arrowEl.style.left = arrowCSS.left + 'px';
popoverContentEle.style.top = popoverCSS.top + 'px';
popoverContentEle.style.left = popoverCSS.left + 'px';
contentEl.style.top = popoverCSS.top + 'px';
contentEl.style.left = popoverCSS.left + 'px';
popoverContentEle.style.transformOrigin = originY + ' ' + originX;
contentEl.style.transformOrigin = origin.y + ' ' + origin.x;
// Since the transition starts before styling is done we
// want to wait for the styles to apply before showing the wrapper
this.displayWrapper();
}
private showFromBottom(target: any, popover: any, body: any): boolean {
return target.top + target.height + popover.height > body.height && target.top - popover.height > 0;
}
private exceedsViewport(target: any, popover: any, body: any): boolean {
return target.top + target.height + popover.height > body.height;
}
private displayWrapper() {
let popoverWrapperEle = this.el.querySelector('.popover-wrapper') as HTMLElement;
popoverWrapperEle.style.opacity = '1';
}
@ -164,7 +199,7 @@ export class Popover {
animation.onFinish((a: any) => {
a.destroy();
this.ionViewDidEnter();
this.positionView(this.el, this.ev, this.mode);
this.positionPopover(this.el, this.ev, POPOVER_POSITION_PROPERTIES[this.mode]);
resolve();
}).play();
});
@ -279,16 +314,23 @@ export interface PopoverEvent {
};
}
export const popoverViewProps: any = {
export const POPOVER_POSITION_PROPERTIES: any = {
ios: {
bodyPadding: 2,
showArrow: true
padding: 2,
unit: '%',
showArrow: true,
centerTarget: true
},
md: {
bodyPadding: 12,
showArrow: false
padding: 12,
unit: 'px',
showArrow: false,
centerTarget: false
},
wp: {
padding: 12,
unit: 'px',
showArrow: false,
centerTarget: false
}
}
// TODO FIX
const POPOVER_MD_BODY_PADDING = 12;

View File

@ -26,6 +26,17 @@
<ion-popover-controller></ion-popover-controller>
</ion-content>
<ion-footer>
<ion-toolbar color="primary">
<ion-buttons slot="end">
<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-footer>
</ion-app>
<script>
function presentPopover(componentName, event) {
@ -61,7 +72,8 @@
composed: true,
bubbles: true
});
event.target.dispatchEvent(ev);
console.log('event', event);
// event.target.dispatchEvent(ev);
}
}