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(); const backdropAnimation = new Animation();
backdropAnimation.addElement(baseElm.querySelector('.popover-backdrop')); 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); backdropAnimation.fromTo('opacity', 0.01, 0.08);
return baseAnimation return baseAnimation
.addElement(baseElm) .addElement(baseElm)
.easing('ease') .easing('ease')
.duration(100) .duration(100)
.add(backdropAnimation) .add(backdropAnimation);
.add(wrapperAnimation);
} }

View File

@ -52,92 +52,127 @@ export class Popover {
} }
// TODO currently only positions iOS private positionPopover(nativeEl: HTMLElement, ev: any, props: any) {
private positionView(nativeEle: HTMLElement, ev: any, mode: string) { console.debug('Position popover', nativeEl, ev, props);
// 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 // Declare the popover elements
let popoverWrapperEle = nativeEle.querySelector('.popover-wrapper') as HTMLElement; let contentEl = nativeEl.querySelector('.popover-content') as HTMLElement;
let popoverContentEle = nativeEle.querySelector('.popover-content') as HTMLElement; let arrowEl = nativeEl.querySelector('.popover-arrow') as HTMLElement;
let popoverArrowEle = nativeEle.querySelector('.popover-arrow') as HTMLElement;
// Grab the default origin from the properties // Set the default transform origin direction
let originY = 'top'; let origin = {
let originX = 'left'; y: 'top',
x: 'left'
}
// Popover content width and height // Popover content width and height
let popoverDim = popoverContentEle.getBoundingClientRect(); const popover = {
let popoverWidth = popoverDim.width; width: contentEl.getBoundingClientRect().width,
let popoverHeight = popoverDim.height; height: contentEl.getBoundingClientRect().height
}
// Window body width and height // Window body width and height
// TODO need to check if portrait/landscape? // TODO need to check if portrait/landscape?
let bodyWidth = window.screen.width; const body = {
let bodyHeight = window.screen.height; width: window.screen.width,
height: window.screen.height
}
// If ev was passed, use that for target element // If ev was passed, use that for target element
let targetDim = ev && ev.target && ev.target.getBoundingClientRect(); let targetDim = ev && ev.target && ev.target.getBoundingClientRect();
let targetTop = (targetDim && 'top' in targetDim) ? targetDim.top : (bodyHeight / 2) - (popoverHeight / 2); // The target is the object that dispatched the event that was passed
let targetLeft = (targetDim && 'left' in targetDim) ? targetDim.left : (bodyWidth / 2); let target = {
let targetWidth = targetDim && targetDim.width || 0; top: (targetDim && 'top' in targetDim) ? targetDim.top : (body.height / 2) - (popover.height / 2),
let targetHeight = targetDim && targetDim.height || 0; 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 // The arrow that shows above the popover on iOS
let arrowDim = popoverArrowEle.getBoundingClientRect(); let arrowDim = arrowEl.getBoundingClientRect();
var arrowWidth = arrowDim.width;
var arrowHeight = arrowDim.height; const arrow = {
width: arrowDim.width,
height: arrowDim.height
}
// If no ev was passed, hide the arrow // If no ev was passed, hide the arrow
if (!targetDim) { if (!targetDim) {
popoverArrowEle.style.display = 'none'; arrowEl.style.display = 'none';
} }
let arrowCSS = { let arrowCSS = {
top: targetTop + targetHeight, top: target.top + target.height,
left: targetLeft + (targetWidth / 2) - (arrowWidth / 2) left: target.left + (target.width / 2) - (arrow.width / 2)
}; };
let popoverCSS = { let popoverCSS = {
top: targetTop + targetHeight + (arrowHeight - 1), top: target.top + target.height + (arrow.height - 1),
left: targetLeft + (targetWidth / 2) - (popoverWidth / 2) 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 // 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 // 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 // exceeds the body width it is off screen to the right so adjust
if (popoverCSS.left < popoverPadding) { if (popoverCSS.left < props.padding) {
popoverCSS.left = popoverPadding; popoverCSS.left = props.padding;
} else if (popoverWidth + popoverPadding + popoverCSS.left > bodyWidth) { } else if (popover.width + props.padding + popoverCSS.left > body.width) {
popoverCSS.left = bodyWidth - popoverWidth - popoverPadding; popoverCSS.left = body.width - popover.width - props.padding;
originX = 'right'; origin.x = 'right';
} }
// If the popover when popped down stretches past bottom of screen, // If the popover when popped down stretches past bottom of screen,
// make it pop up if there's room above // make it pop up if there's room above
if (targetTop + targetHeight + popoverHeight > bodyHeight && targetTop - popoverHeight > 0) { if (this.showFromBottom(target, popover, body)) {
arrowCSS.top = targetTop - (arrowHeight + 1); nativeEl.className = nativeEl.className + ' popover-bottom';
popoverCSS.top = targetTop - popoverHeight - (arrowHeight - 1); origin.y = 'bottom';
nativeEle.className = nativeEle.className + ' popover-bottom';
originY = 'bottom'; popoverCSS.top = target.top - popover.height;
// If there isn't room for it to pop up above the target cut it off
} else if (targetTop + targetHeight + popoverHeight > bodyHeight) { if (props.showArrow) {
popoverContentEle.style.bottom = popoverPadding + '%'; 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'; arrowEl.style.top = arrowCSS.top + 'px';
popoverArrowEle.style.left = arrowCSS.left + 'px'; arrowEl.style.left = arrowCSS.left + 'px';
popoverContentEle.style.top = popoverCSS.top + 'px'; contentEl.style.top = popoverCSS.top + 'px';
popoverContentEle.style.left = popoverCSS.left + '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 // Since the transition starts before styling is done we
// want to wait for the styles to apply before showing the wrapper // 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'; popoverWrapperEle.style.opacity = '1';
} }
@ -164,7 +199,7 @@ export class Popover {
animation.onFinish((a: any) => { animation.onFinish((a: any) => {
a.destroy(); a.destroy();
this.ionViewDidEnter(); this.ionViewDidEnter();
this.positionView(this.el, this.ev, this.mode); this.positionPopover(this.el, this.ev, POPOVER_POSITION_PROPERTIES[this.mode]);
resolve(); resolve();
}).play(); }).play();
}); });
@ -279,16 +314,23 @@ export interface PopoverEvent {
}; };
} }
export const popoverViewProps: any = { export const POPOVER_POSITION_PROPERTIES: any = {
ios: { ios: {
bodyPadding: 2, padding: 2,
showArrow: true unit: '%',
showArrow: true,
centerTarget: true
}, },
md: { md: {
bodyPadding: 12, padding: 12,
showArrow: false 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-popover-controller></ion-popover-controller>
</ion-content> </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> </ion-app>
<script> <script>
function presentPopover(componentName, event) { function presentPopover(componentName, event) {
@ -61,7 +72,8 @@
composed: true, composed: true,
bubbles: true bubbles: true
}); });
event.target.dispatchEvent(ev); console.log('event', event);
// event.target.dispatchEvent(ev);
} }
} }