88
99import { PositionStrategy } from './position-strategy' ;
1010import { ElementRef } from '@angular/core' ;
11- import { ViewportRuler , CdkScrollable } from '@angular/cdk/scrolling' ;
11+ import { ViewportRuler , CdkScrollable , ViewportScrollPosition } from '@angular/cdk/scrolling' ;
1212import {
1313 ConnectedOverlayPositionChange ,
1414 ConnectionPositionPair ,
@@ -111,6 +111,9 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
111111 /** Amount of subscribers to the `positionChanges` stream. */
112112 private _positionChangeSubscriptions = 0 ;
113113
114+ /** Amount by which the overlay was pushed in each axis during the last time it was positioned. */
115+ private _previousPushAmount : { x : number , y : number } | null ;
116+
114117 /** Observable sequence of position changes. */
115118 positionChanges : Observable < ConnectedOverlayPositionChange > = Observable . create ( observer => {
116119 const subscription = this . _positionChanges . subscribe ( observer ) ;
@@ -279,6 +282,8 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
279282 }
280283
281284 detach ( ) {
285+ this . _lastPosition = null ;
286+ this . _previousPushAmount = null ;
282287 this . _resizeSubscription . unsubscribe ( ) ;
283288 }
284289
@@ -538,39 +543,55 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
538543 * the viewport, the top-left corner will be pushed on-screen (with overflow occuring on the
539544 * right and bottom).
540545 *
541- * @param start The starting point from which the overlay is pushed.
542- * @param overlay The overlay dimensions.
546+ * @param start Starting point from which the overlay is pushed.
547+ * @param overlay Dimensions of the overlay.
548+ * @param scrollPosition Current viewport scroll position.
543549 * @returns The point at which to position the overlay after pushing. This is effectively a new
544550 * originPoint.
545551 */
546- private _pushOverlayOnScreen ( start : Point , overlay : ClientRect ) : Point {
552+ private _pushOverlayOnScreen ( start : Point ,
553+ overlay : ClientRect ,
554+ scrollPosition : ViewportScrollPosition ) : Point {
555+ // If the position is locked and we've pushed the overlay already, reuse the previous push
556+ // amount, rather than pushing it again. If we were to continue pushing, the element would
557+ // remain in the viewport, which goes against the expectations when position locking is enabled.
558+ if ( this . _previousPushAmount && this . _positionLocked ) {
559+ return {
560+ x : start . x + this . _previousPushAmount . x ,
561+ y : start . y + this . _previousPushAmount . y
562+ } ;
563+ }
564+
547565 const viewport = this . _viewportRect ;
548566
549- // Determine how much the overlay goes outside the viewport on each side, which we'll use to
550- // decide which direction to push it.
567+ // Determine how much the overlay goes outside the viewport on each
568+ // side, which we'll use to decide which direction to push it.
551569 const overflowRight = Math . max ( start . x + overlay . width - viewport . right , 0 ) ;
552570 const overflowBottom = Math . max ( start . y + overlay . height - viewport . bottom , 0 ) ;
553- const overflowTop = Math . max ( viewport . top - start . y , 0 ) ;
554- const overflowLeft = Math . max ( viewport . left - start . x , 0 ) ;
571+ const overflowTop = Math . max ( viewport . top - scrollPosition . top - start . y , 0 ) ;
572+ const overflowLeft = Math . max ( viewport . left - scrollPosition . left - start . x , 0 ) ;
555573
556- // Amount by which to push the overlay in each direction such that it remains on-screen.
557- let pushX , pushY = 0 ;
574+ // Amount by which to push the overlay in each axis such that it remains on-screen.
575+ let pushX = 0 ;
576+ let pushY = 0 ;
558577
559578 // If the overlay fits completely within the bounds of the viewport, push it from whichever
560579 // direction is goes off-screen. Otherwise, push the top-left corner such that its in the
561580 // viewport and allow for the trailing end of the overlay to go out of bounds.
562- if ( overlay . width <= viewport . width ) {
581+ if ( overlay . width < viewport . width ) {
563582 pushX = overflowLeft || - overflowRight ;
564583 } else {
565- pushX = viewport . left - start . x ;
584+ pushX = start . x < this . _viewportMargin ? ( viewport . left - scrollPosition . left ) - start . x : 0 ;
566585 }
567586
568- if ( overlay . height <= viewport . height ) {
587+ if ( overlay . height < viewport . height ) {
569588 pushY = overflowTop || - overflowBottom ;
570589 } else {
571- pushY = viewport . top - start . y ;
590+ pushY = start . y < this . _viewportMargin ? ( viewport . top - scrollPosition . top ) - start . y : 0 ;
572591 }
573592
593+ this . _previousPushAmount = { x : pushX , y : pushY } ;
594+
574595 return {
575596 x : start . x + pushX ,
576597 y : start . y + pushY ,
@@ -789,8 +810,9 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
789810 const styles = { } as CSSStyleDeclaration ;
790811
791812 if ( this . _hasExactPosition ( ) ) {
792- extendStyles ( styles , this . _getExactOverlayY ( position , originPoint ) ) ;
793- extendStyles ( styles , this . _getExactOverlayX ( position , originPoint ) ) ;
813+ const scrollPosition = this . _viewportRuler . getViewportScrollPosition ( ) ;
814+ extendStyles ( styles , this . _getExactOverlayY ( position , originPoint , scrollPosition ) ) ;
815+ extendStyles ( styles , this . _getExactOverlayX ( position , originPoint , scrollPosition ) ) ;
794816 } else {
795817 styles . position = 'static' ;
796818 }
@@ -829,14 +851,16 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
829851 }
830852
831853 /** Gets the exact top/bottom for the overlay when not using flexible sizing or when pushing. */
832- private _getExactOverlayY ( position : ConnectedPosition , originPoint : Point ) {
854+ private _getExactOverlayY ( position : ConnectedPosition ,
855+ originPoint : Point ,
856+ scrollPosition : ViewportScrollPosition ) {
833857 // Reset any existing styles. This is necessary in case the
834858 // preferred position has changed since the last `apply`.
835859 let styles = { top : null , bottom : null } as CSSStyleDeclaration ;
836860 let overlayPoint = this . _getOverlayPoint ( originPoint , this . _overlayRect , position ) ;
837861
838862 if ( this . _isPushed ) {
839- overlayPoint = this . _pushOverlayOnScreen ( overlayPoint , this . _overlayRect ) ;
863+ overlayPoint = this . _pushOverlayOnScreen ( overlayPoint , this . _overlayRect , scrollPosition ) ;
840864 }
841865
842866 // We want to set either `top` or `bottom` based on whether the overlay wants to appear
@@ -854,14 +878,16 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
854878 }
855879
856880 /** Gets the exact left/right for the overlay when not using flexible sizing or when pushing. */
857- private _getExactOverlayX ( position : ConnectedPosition , originPoint : Point ) {
881+ private _getExactOverlayX ( position : ConnectedPosition ,
882+ originPoint : Point ,
883+ scrollPosition : ViewportScrollPosition ) {
858884 // Reset any existing styles. This is necessary in case the preferred position has
859885 // changed since the last `apply`.
860886 let styles = { left : null , right : null } as CSSStyleDeclaration ;
861887 let overlayPoint = this . _getOverlayPoint ( originPoint , this . _overlayRect , position ) ;
862888
863889 if ( this . _isPushed ) {
864- overlayPoint = this . _pushOverlayOnScreen ( overlayPoint , this . _overlayRect ) ;
890+ overlayPoint = this . _pushOverlayOnScreen ( overlayPoint , this . _overlayRect , scrollPosition ) ;
865891 }
866892
867893 // We want to set either `left` or `right` based on whether the overlay wants to appear "before"
0 commit comments