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 ,
@@ -108,6 +108,9 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
108108 /** Selector to be used when finding the elements on which to set the transform origin. */
109109 private _transformOriginSelector : string ;
110110
111+ /** Amount by which the overlay was pushed in each axis during the last time it was positioned. */
112+ private _previousPushAmount : { x : number , y : number } | null ;
113+
111114 /** Observable sequence of position changes. */
112115 positionChanges : Observable < ConnectedOverlayPositionChange > =
113116 this . _positionChanges . asObservable ( ) ;
@@ -269,6 +272,8 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
269272 }
270273
271274 detach ( ) {
275+ this . _lastPosition = null ;
276+ this . _previousPushAmount = null ;
272277 this . _resizeSubscription . unsubscribe ( ) ;
273278 }
274279
@@ -528,23 +533,37 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
528533 * the viewport, the top-left corner will be pushed on-screen (with overflow occuring on the
529534 * right and bottom).
530535 *
531- * @param start The starting point from which the overlay is pushed.
532- * @param overlay The overlay dimensions.
536+ * @param start Starting point from which the overlay is pushed.
537+ * @param overlay Dimensions of the overlay.
538+ * @param scrollPosition Current viewport scroll position.
533539 * @returns The point at which to position the overlay after pushing. This is effectively a new
534540 * originPoint.
535541 */
536- private _pushOverlayOnScreen ( start : Point , overlay : ClientRect ) : Point {
542+ private _pushOverlayOnScreen ( start : Point ,
543+ overlay : ClientRect ,
544+ scrollPosition : ViewportScrollPosition ) : Point {
545+ // If the position is locked and we've pushed the overlay already, reuse the previous push
546+ // amount, rather than pushing it again. If we were to continue pushing, the element would
547+ // remain in the viewport, which goes against the expectations when position locking is enabled.
548+ if ( this . _previousPushAmount && this . _positionLocked ) {
549+ return {
550+ x : start . x + this . _previousPushAmount . x ,
551+ y : start . y + this . _previousPushAmount . y
552+ } ;
553+ }
554+
537555 const viewport = this . _viewportRect ;
538556
539- // Determine how much the overlay goes outside the viewport on each side, which we'll use to
540- // decide which direction to push it.
557+ // Determine how much the overlay goes outside the viewport on each
558+ // side, which we'll use to decide which direction to push it.
541559 const overflowRight = Math . max ( start . x + overlay . width - viewport . right , 0 ) ;
542560 const overflowBottom = Math . max ( start . y + overlay . height - viewport . bottom , 0 ) ;
543- const overflowTop = Math . max ( viewport . top - start . y , 0 ) ;
544- const overflowLeft = Math . max ( viewport . left - start . x , 0 ) ;
561+ const overflowTop = Math . max ( viewport . top - scrollPosition . top - start . y , 0 ) ;
562+ const overflowLeft = Math . max ( viewport . left - scrollPosition . left - start . x , 0 ) ;
545563
546- // Amount by which to push the overlay in each direction such that it remains on-screen.
547- let pushX , pushY = 0 ;
564+ // Amount by which to push the overlay in each axis such that it remains on-screen.
565+ let pushX = 0 ;
566+ let pushY = 0 ;
548567
549568 // If the overlay fits completely within the bounds of the viewport, push it from whichever
550569 // direction is goes off-screen. Otherwise, push the top-left corner such that its in the
@@ -561,6 +580,8 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
561580 pushY = viewport . top - start . y ;
562581 }
563582
583+ this . _previousPushAmount = { x : pushX , y : pushY } ;
584+
564585 return {
565586 x : start . x + pushX ,
566587 y : start . y + pushY ,
@@ -776,8 +797,9 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
776797 const styles = { } as CSSStyleDeclaration ;
777798
778799 if ( this . _hasExactPosition ( ) ) {
779- extendStyles ( styles , this . _getExactOverlayY ( position , originPoint ) ) ;
780- extendStyles ( styles , this . _getExactOverlayX ( position , originPoint ) ) ;
800+ const scrollPosition = this . _viewportRuler . getViewportScrollPosition ( ) ;
801+ extendStyles ( styles , this . _getExactOverlayY ( position , originPoint , scrollPosition ) ) ;
802+ extendStyles ( styles , this . _getExactOverlayX ( position , originPoint , scrollPosition ) ) ;
781803 } else {
782804 styles . position = 'static' ;
783805 }
@@ -816,14 +838,16 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
816838 }
817839
818840 /** Gets the exact top/bottom for the overlay when not using flexible sizing or when pushing. */
819- private _getExactOverlayY ( position : ConnectedPosition , originPoint : Point ) {
841+ private _getExactOverlayY ( position : ConnectedPosition ,
842+ originPoint : Point ,
843+ scrollPosition : ViewportScrollPosition ) {
820844 // Reset any existing styles. This is necessary in case the
821845 // preferred position has changed since the last `apply`.
822846 let styles = { top : null , bottom : null } as CSSStyleDeclaration ;
823847 let overlayPoint = this . _getOverlayPoint ( originPoint , this . _overlayRect , position ) ;
824848
825849 if ( this . _isPushed ) {
826- overlayPoint = this . _pushOverlayOnScreen ( overlayPoint , this . _overlayRect ) ;
850+ overlayPoint = this . _pushOverlayOnScreen ( overlayPoint , this . _overlayRect , scrollPosition ) ;
827851 }
828852
829853 // We want to set either `top` or `bottom` based on whether the overlay wants to appear
@@ -841,14 +865,16 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
841865 }
842866
843867 /** Gets the exact left/right for the overlay when not using flexible sizing or when pushing. */
844- private _getExactOverlayX ( position : ConnectedPosition , originPoint : Point ) {
868+ private _getExactOverlayX ( position : ConnectedPosition ,
869+ originPoint : Point ,
870+ scrollPosition : ViewportScrollPosition ) {
845871 // Reset any existing styles. This is necessary in case the preferred position has
846872 // changed since the last `apply`.
847873 let styles = { left : null , right : null } as CSSStyleDeclaration ;
848874 let overlayPoint = this . _getOverlayPoint ( originPoint , this . _overlayRect , position ) ;
849875
850876 if ( this . _isPushed ) {
851- overlayPoint = this . _pushOverlayOnScreen ( overlayPoint , this . _overlayRect ) ;
877+ overlayPoint = this . _pushOverlayOnScreen ( overlayPoint , this . _overlayRect , scrollPosition ) ;
852878 }
853879
854880 // We want to set either `left` or `right` based on whether the overlay wants to appear "before"
0 commit comments