From 4db72cfc2cc684a6e44f3df049a4eb51c0d63171 Mon Sep 17 00:00:00 2001
From: Brandy Carney <brandyscarney@gmail.com>
Date: Wed, 25 May 2016 12:12:18 -0400
Subject: [PATCH] feat(popover): fix long popovers that go off the page

Allow popovers to be of dynamic height and only cut them off when they
exceed the height of the body. Clean up styles for each mode.

references #5420
---
 demos/popover/main.html                     |  2 +-
 demos/popover/style.css                     |  4 ++
 src/components/popover/popover.ios.scss     |  8 +--
 src/components/popover/popover.md.scss      | 16 +++---
 src/components/popover/popover.scss         | 16 ++----
 src/components/popover/popover.ts           | 62 ++++++++++++---------
 src/components/popover/popover.wp.scss      | 14 ++---
 src/components/popover/test/basic/index.ts  | 28 ++++++++++
 src/components/popover/test/basic/main.html |  8 ++-
 src/components/popover/test/basic/style.css |  4 ++
 10 files changed, 101 insertions(+), 61 deletions(-)

diff --git a/demos/popover/main.html b/demos/popover/main.html
index a112d911c04..f52dafec3aa 100644
--- a/demos/popover/main.html
+++ b/demos/popover/main.html
@@ -17,6 +17,6 @@
 
     <div>Sed pellentesque ipsum eget ante hendrerit maximus. Aliquam id venenatis nulla. Nullam in nibh at enim vestibulum ullamcorper. Nam felis dolor, lobortis vel est non, condimentum malesuada nisl. In metus sapien, malesuada at nulla in, pretium aliquam turpis. Quisque elementum purus mi, sed tristique turpis ultricies in. Donec feugiat dolor non ultricies ultricies. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin ut purus et diam porta cursus vitae semper mi. Donec fringilla tellus orci. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nunc vitae commodo sem. Duis vehicula quam sit amet imperdiet facilisis. Pellentesque eget dignissim neque, et scelerisque libero. Maecenas molestie metus sed orci cursus, in venenatis justo dapibus.</div>
 
-    <p>Aenean rhoncus urna at interdum blandit. Donec ac massa nec libero vehicula tincidunt. Sed sit amet hendrerit risus. Aliquam vitae vestibulum ipsum, non feugiat orci. Vivamus eu rutrum elit. Nulla dapibus tortor non dignissim pretium. Nulla in luctus turpis. Etiam non mattis tortor, at aliquet ex. Nunc ut ante varius, auctor dui vel, volutpat elit. Nunc laoreet augue sit amet ultrices porta. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum pellentesque lobortis est, ut tincidunt ligula mollis sit amet. In porta risus arcu, quis pellentesque dolor mattis non. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;</p>
+    <div>Aenean rhoncus urna at interdum blandit. Donec ac massa nec libero vehicula tincidunt. Sed sit amet hendrerit risus. Aliquam vitae vestibulum ipsum, non feugiat orci. Vivamus eu rutrum elit. Nulla dapibus tortor non dignissim pretium. Nulla in luctus turpis. Etiam non mattis tortor, at aliquet ex. Nunc ut ante varius, auctor dui vel, volutpat elit. Nunc laoreet augue sit amet ultrices porta. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum pellentesque lobortis est, ut tincidunt ligula mollis sit amet. In porta risus arcu, quis pellentesque dolor mattis non. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;</div>
   </div>
 </ion-content>
diff --git a/demos/popover/style.css b/demos/popover/style.css
index d12421e3ce9..675528b09e5 100644
--- a/demos/popover/style.css
+++ b/demos/popover/style.css
@@ -54,6 +54,10 @@ ion-col {
   border: 1px solid #dedede;
 }
 
+.wp .dot {
+  border: 2px solid #ccc;
+}
+
 .hairlines .text-smaller,
 .hairlines .row-dots,
 .hairlines .dot {
diff --git a/src/components/popover/popover.ios.scss b/src/components/popover/popover.ios.scss
index 22cf5e098b4..288e1f02cb8 100644
--- a/src/components/popover/popover.ios.scss
+++ b/src/components/popover/popover.ios.scss
@@ -1,12 +1,11 @@
 @import "../../globals.core";
-@import "./popover";
 
 // iOS Popover
 // --------------------------------------------------
 
-$popover-ios-min-width:                 150px !default;
-$popover-ios-max-width:                 270px !default;
+$popover-ios-width:                     200px !default;
 $popover-ios-max-height:                90% !default;
+
 $popover-ios-border-radius:             10px !default;
 $popover-ios-text-color:                #000 !default;
 
@@ -16,8 +15,7 @@ $popover-ios-arrow-background:          $popover-ios-background !default;
 
 
 .popover-content {
-  min-width: $popover-ios-min-width;
-  max-width: $popover-ios-max-width;
+  width: $popover-ios-width;
 
   max-height: $popover-ios-max-height;
 
diff --git a/src/components/popover/popover.md.scss b/src/components/popover/popover.md.scss
index e7c7d669ac7..992b8b8753c 100644
--- a/src/components/popover/popover.md.scss
+++ b/src/components/popover/popover.md.scss
@@ -1,31 +1,29 @@
 @import "../../globals.core";
-@import "./popover";
 
 // Material Design Popover
 // --------------------------------------------------
 
-$popover-md-min-width:                 200px !default;
-$popover-md-max-width:                 270px !default;
+$popover-md-width:                     200px !default;
 $popover-md-max-height:                90% !default;
+
 $popover-md-border-radius:             2px !default;
-$popover-md-box-shadow-color:          rgba(0, 0, 0, .3) !default;
-$popover-md-box-shadow:                0px 3px 12px 2px $alert-md-box-shadow-color !default;
 $popover-md-text-color:                #000 !default;
-
 $popover-md-background:                #fafafa !default;
+$popover-md-box-shadow-color:          rgba(0, 0, 0, .3) !default;
+$popover-md-box-shadow:                0 3px 12px 2px $alert-md-box-shadow-color !default;
+
 $popover-md-item-background:           $popover-md-background !default;
 
 
 .popover-content {
-  min-width: $popover-md-min-width;
-  max-width: $popover-md-max-width;
+  width: $popover-md-width;
 
   max-height: $popover-md-max-height;
 
-  box-shadow: $popover-md-box-shadow;
   border-radius: $popover-md-border-radius;
   color: $popover-md-text-color;
   background: $popover-md-background;
+  box-shadow: $popover-md-box-shadow;
 }
 
 .popover-content .item {
diff --git a/src/components/popover/popover.scss b/src/components/popover/popover.scss
index 3163a36a203..7e42e075377 100644
--- a/src/components/popover/popover.scss
+++ b/src/components/popover/popover.scss
@@ -3,9 +3,6 @@
 // Popover
 // --------------------------------------------------
 
-$popover-width:                 200px !default;
-$popover-height:                250px !default;
-
 
 ion-popover {
   position: absolute;
@@ -22,8 +19,9 @@ ion-popover {
 }
 
 .popover-wrapper {
-  opacity: 0;
   z-index: $z-index-overlay-wrapper;
+
+  opacity: 0;
 }
 
 .popover-content {
@@ -32,19 +30,15 @@ ion-popover {
 
   display: flex;
 
-  overflow: hidden;
+  overflow: auto;
 
   flex-direction: column;
 
-  width: $popover-width;
-  min-width: $popover-width;
-
-  height: $popover-height;
-  max-height: $popover-height;
+  min-height: 200px;
 
   ion-page {
+    position: relative;
     display: flex;
-    overflow: auto;
   }
 }
 
diff --git a/src/components/popover/popover.ts b/src/components/popover/popover.ts
index 3d2b843cd69..1247e713859 100644
--- a/src/components/popover/popover.ts
+++ b/src/components/popover/popover.ts
@@ -7,6 +7,7 @@ import {Config} from '../../config/config';
 import {NavParams} from '../nav/nav-params';
 import {Platform} from '../../platform/platform';
 import {isPresent, isUndefined, isDefined} from '../../util/util';
+import {nativeRaf} from '../../util/dom';
 import {ViewController} from '../nav/view-controller';
 
 const POPOVER_BODY_PADDING = 2;
@@ -108,7 +109,7 @@ class PopoverCmp {
     this._loader.loadNextToLocation(this._navParams.data.componentType, this.viewport).then(componentRef => {
       this._viewCtrl.setInstance(componentRef.instance);
 
-      // manually fire onPageWillEnter() since ModalCmp's  onPageWillEnter already happened
+      // manually fire onPageWillEnter() since PopoverCmp's onPageWillEnter already happened
       this._viewCtrl.willEnter();
     });
   }
@@ -150,6 +151,7 @@ class PopoverTransition extends Transition {
     super(opts);
   }
 
+
   positionView(nativeEle: HTMLElement, ev) {
     // Popover content width and height
     let popoverEle = <HTMLElement>nativeEle.querySelector('.popover-content');
@@ -158,8 +160,6 @@ class PopoverTransition extends Transition {
     let popoverHeight = popoverDim.height;
 
     // Window body width and height
-    // let bodyWidth = this._platform.width();
-    // let bodyHeight = this._platform.height();
     let bodyWidth = window.innerWidth;
     let bodyHeight = window.innerHeight;
 
@@ -197,10 +197,13 @@ class PopoverTransition extends Transition {
 
     // If the popover when popped down stretches past bottom of screen,
     // make it pop up if there's room above
-    if (popoverCSS.top + POPOVER_BODY_PADDING + popoverHeight > bodyHeight && popoverCSS.top - popoverHeight > 0) {
+    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';
+    // If there isn't room for it to pop up above the target cut it off
+    } else if (targetTop + targetHeight + popoverHeight > bodyHeight) {
+      popoverEle.style.bottom = POPOVER_BODY_PADDING + '%';
     }
 
     arrowEle.style.top = arrowCSS.top + 'px';
@@ -212,31 +215,36 @@ class PopoverTransition extends Transition {
 }
 
 class PopoverPopIn extends PopoverTransition {
-  constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) {
+  constructor(private enteringView: ViewController, private leavingView: ViewController, private opts: TransitionOptions) {
     super(opts);
 
     let ele = enteringView.pageRef().nativeElement;
-    this.positionView(ele, opts.ev);
 
     let backdrop = new Animation(ele.querySelector('.backdrop'));
     let wrapper = new Animation(ele.querySelector('.popover-wrapper'));
 
     wrapper.fromTo('opacity', '0.01', '1');
-    backdrop.fromTo('opacity', '0.01', '0.1');
+    backdrop.fromTo('opacity', '0.01', '0.08');
 
     this
       .easing('ease')
       .duration(100)
-      .before.addClass('show-page')
       .add(backdrop)
       .add(wrapper);
   }
+
+  play() {
+    nativeRaf(() => {
+      this.positionView(this.enteringView.pageRef().nativeElement, this.opts.ev);
+      super.play();
+    });
+  }
 }
 Transition.register('popover-pop-in', PopoverPopIn);
 
 
 class PopoverPopOut extends PopoverTransition {
-  constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) {
+  constructor(private enteringView: ViewController, private leavingView: ViewController, private opts: TransitionOptions) {
     super(opts);
 
     let ele = leavingView.pageRef().nativeElement;
@@ -244,7 +252,7 @@ class PopoverPopOut extends PopoverTransition {
     let wrapper = new Animation(ele.querySelector('.popover-wrapper'));
 
     wrapper.fromTo('opacity', '1', '0');
-    backdrop.fromTo('opacity', '0.1', '0');
+    backdrop.fromTo('opacity', '0.08', '0');
 
     this
       .easing('ease')
@@ -257,45 +265,46 @@ Transition.register('popover-pop-out', PopoverPopOut);
 
 
 class PopoverMdPopIn extends PopoverTransition {
-  constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) {
+  constructor(private enteringView: ViewController, private leavingView: ViewController, private opts: TransitionOptions) {
     super(opts);
 
     let ele = enteringView.pageRef().nativeElement;
     this.positionView(ele, opts.ev);
 
-    let backdrop = new Animation(ele.querySelector('.backdrop'));
     let wrapper = new Animation(ele.querySelector('.popover-wrapper'));
 
     wrapper.fromTo('opacity', '0.01', '1');
-    backdrop.fromTo('opacity', '0', '0');
 
     this
       .easing('ease')
       .duration(100)
       .fadeIn()
-      .add(backdrop)
       .add(wrapper);
   }
+
+  play() {
+    nativeRaf(() => {
+      this.positionView(this.enteringView.pageRef().nativeElement, this.opts.ev);
+      super.play();
+    });
+  }
 }
 Transition.register('popover-md-pop-in', PopoverMdPopIn);
 
 
 class PopoverMdPopOut extends PopoverTransition {
-  constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) {
+  constructor(private enteringView: ViewController, private leavingView: ViewController, private opts: TransitionOptions) {
     super(opts);
 
     let ele = leavingView.pageRef().nativeElement;
-    let backdrop = new Animation(ele.querySelector('.backdrop'));
     let wrapper = new Animation(ele.querySelector('.popover-wrapper'));
 
     wrapper.fromTo('opacity', '1', '0');
-    backdrop.fromTo('opacity', '0', '0');
 
     this
       .easing('ease')
       .duration(500)
       .fadeIn()
-      .add(backdrop)
       .add(wrapper);
   }
 }
@@ -304,45 +313,46 @@ Transition.register('popover-md-pop-out', PopoverMdPopOut);
 
 
 class PopoverWpPopIn extends PopoverTransition {
-  constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) {
+  constructor(private enteringView: ViewController, private leavingView: ViewController, private opts: TransitionOptions) {
     super(opts);
 
     let ele = enteringView.pageRef().nativeElement;
     this.positionView(ele, opts.ev);
 
-    let backdrop = new Animation(ele.querySelector('.backdrop'));
     let wrapper = new Animation(ele.querySelector('.popover-wrapper'));
 
     wrapper.fromTo('opacity', '0.01', '1');
-    backdrop.fromTo('opacity', '0.01', '0.5');
 
     this
       .easing('ease')
       .duration(100)
       .fadeIn()
-      .add(backdrop)
       .add(wrapper);
   }
+
+  play() {
+    nativeRaf(() => {
+      this.positionView(this.enteringView.pageRef().nativeElement, this.opts.ev);
+      super.play();
+    });
+  }
 }
 Transition.register('popover-wp-pop-in', PopoverWpPopIn);
 
 
 class PopoverWpPopOut extends PopoverTransition {
-  constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) {
+  constructor(private enteringView: ViewController, private leavingView: ViewController, private opts: TransitionOptions) {
     super(opts);
 
     let ele = leavingView.pageRef().nativeElement;
-    let backdrop = new Animation(ele.querySelector('.backdrop'));
     let wrapper = new Animation(ele.querySelector('.popover-wrapper'));
 
     wrapper.fromTo('opacity', '1', '0');
-    backdrop.fromTo('opacity', '0.5', '0');
 
     this
       .easing('ease')
       .duration(500)
       .fadeIn()
-      .add(backdrop)
       .add(wrapper);
   }
 }
diff --git a/src/components/popover/popover.wp.scss b/src/components/popover/popover.wp.scss
index 8060d9ccebe..97df352c826 100644
--- a/src/components/popover/popover.wp.scss
+++ b/src/components/popover/popover.wp.scss
@@ -1,25 +1,25 @@
 @import "../../globals.core";
-@import "./popover";
 
 // Windows Popover
 // --------------------------------------------------
 
-$popover-wp-min-width:                 200px !default;
-$popover-wp-max-width:                 270px !default;
+$popover-wp-width:                     200px !default;
 $popover-wp-max-height:                90% !default;
-$popover-wp-border-radius:             2px !default;
-$popover-wp-text-color:                #000 !default;
 
+$popover-wp-border:                    2px solid #ccc !default;
+$popover-wp-border-radius:             0 !default;
+
+$popover-wp-text-color:                #000 !default;
 $popover-wp-background:                #f2f2f2 !default;
 $popover-wp-item-background:           $popover-wp-background !default;
 
 
 .popover-content {
-  min-width: $popover-wp-min-width;
-  max-width: $popover-wp-max-width;
+  width: $popover-wp-width;
 
   max-height: $popover-wp-max-height;
 
+  border: $popover-wp-border;
   border-radius: $popover-wp-border-radius;
   color: $popover-wp-text-color;
   background: $popover-wp-background;
diff --git a/src/components/popover/test/basic/index.ts b/src/components/popover/test/basic/index.ts
index 9cea540d017..16354e0b54d 100644
--- a/src/components/popover/test/basic/index.ts
+++ b/src/components/popover/test/basic/index.ts
@@ -153,6 +153,27 @@ class PopoverListPage {
 }
 
 
+@Page({
+  template: `
+    <ion-list>
+      <ion-list-header>Ionic</ion-list-header>
+      <button ion-item *ngFor="let item of items">Item {{item}}</button>
+    </ion-list>
+  `
+})
+class PopoverLongListPage {
+  items = [];
+
+  constructor(private viewCtrl: ViewController) {}
+
+  ngOnInit() {
+    for(let i = 1; i < 21; i++) {
+      this.items.push(i);
+    }
+  }
+}
+
+
 @Page({
   templateUrl: 'main.html'
 })
@@ -171,6 +192,13 @@ class E2EPage {
     });
   }
 
+  presentLongListPopover(ev) {
+    let popover = Popover.create(PopoverLongListPage);
+    this.nav.present(popover, {
+      ev: ev
+    });
+  }
+
   presentRadioPopover(ev) {
     let popover = Popover.create(PopoverRadioPage, {
       contentEle: this.content.nativeElement,
diff --git a/src/components/popover/test/basic/main.html b/src/components/popover/test/basic/main.html
index 345185a677d..8b379d8eabe 100644
--- a/src/components/popover/test/basic/main.html
+++ b/src/components/popover/test/basic/main.html
@@ -3,7 +3,7 @@
     <button (click)="presentRadioPopover($event)">
       <ion-icon name="person"></ion-icon>
     </button>
-    <button (click)="presentListPopover($event)">
+    <button (click)="presentLongListPopover($event)">
       <ion-icon name="search"></ion-icon>
     </button>
   </ion-buttons>
@@ -24,6 +24,10 @@
     Present List Popover
   </button>
 
+  <button block secondary (click)="presentLongListPopover($event)">
+    Present Long List Popover
+  </button>
+
   <div #popoverText class="text-to-change">
     <div>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris vel ipsum in purus mollis dictum eget vitae purus. Nulla ultrices est odio, a maximus velit pretium ac. Donec vel elementum mi. Proin elementum pulvinar neque, in lacinia nibh tempus auctor. Nam sapien velit, commodo ac nibh a, maximus ullamcorper nunc. Integer luctus tortor dignissim, dictum neque at, scelerisque purus. Vivamus nec erat vel magna posuere euismod. Sed ac augue eu tellus tincidunt fermentum eget sit amet nunc. Donec sit amet mi libero. Cras nunc arcu, ultrices nec sapien eu, convallis posuere libero. Pellentesque vulputate lacus eros, at lobortis lorem egestas et. Vestibulum tempus quam in efficitur lobortis. Maecenas consectetur consequat sem pharetra aliquet. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.</div>
 
@@ -33,7 +37,7 @@
 
     <div>Sed pellentesque ipsum eget ante hendrerit maximus. Aliquam id venenatis nulla. Nullam in nibh at enim vestibulum ullamcorper. Nam felis dolor, lobortis vel est non, condimentum malesuada nisl. In metus sapien, malesuada at nulla in, pretium aliquam turpis. Quisque elementum purus mi, sed tristique turpis ultricies in. Donec feugiat dolor non ultricies ultricies. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin ut purus et diam porta cursus vitae semper mi. Donec fringilla tellus orci. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nunc vitae commodo sem. Duis vehicula quam sit amet imperdiet facilisis. Pellentesque eget dignissim neque, et scelerisque libero. Maecenas molestie metus sed orci cursus, in venenatis justo dapibus.</div>
 
-    <p>Aenean rhoncus urna at interdum blandit. Donec ac massa nec libero vehicula tincidunt. Sed sit amet hendrerit risus. Aliquam vitae vestibulum ipsum, non feugiat orci. Vivamus eu rutrum elit. Nulla dapibus tortor non dignissim pretium. Nulla in luctus turpis. Etiam non mattis tortor, at aliquet ex. Nunc ut ante varius, auctor dui vel, volutpat elit. Nunc laoreet augue sit amet ultrices porta. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum pellentesque lobortis est, ut tincidunt ligula mollis sit amet. In porta risus arcu, quis pellentesque dolor mattis non. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;</p>
+    <div>Aenean rhoncus urna at interdum blandit. Donec ac massa nec libero vehicula tincidunt. Sed sit amet hendrerit risus. Aliquam vitae vestibulum ipsum, non feugiat orci. Vivamus eu rutrum elit. Nulla dapibus tortor non dignissim pretium. Nulla in luctus turpis. Etiam non mattis tortor, at aliquet ex. Nunc ut ante varius, auctor dui vel, volutpat elit. Nunc laoreet augue sit amet ultrices porta. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum pellentesque lobortis est, ut tincidunt ligula mollis sit amet. In porta risus arcu, quis pellentesque dolor mattis non. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;</div>
   </div>
 </ion-content>
 
diff --git a/src/components/popover/test/basic/style.css b/src/components/popover/test/basic/style.css
index d12421e3ce9..675528b09e5 100644
--- a/src/components/popover/test/basic/style.css
+++ b/src/components/popover/test/basic/style.css
@@ -54,6 +54,10 @@ ion-col {
   border: 1px solid #dedede;
 }
 
+.wp .dot {
+  border: 2px solid #ccc;
+}
+
 .hairlines .text-smaller,
 .hairlines .row-dots,
 .hairlines .dot {