8
8
9
9
import { PositionStrategy } from './position-strategy' ;
10
10
import { ElementRef } from '@angular/core' ;
11
- import { ViewportRuler , CdkScrollable } from '@angular/cdk/scrolling' ;
11
+ import { ViewportRuler , CdkScrollable , ViewportScrollPosition } from '@angular/cdk/scrolling' ;
12
12
import {
13
13
ConnectedOverlayPositionChange ,
14
14
ConnectionPositionPair ,
@@ -111,6 +111,9 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
111
111
/** Amount of subscribers to the `positionChanges` stream. */
112
112
private _positionChangeSubscriptions = 0 ;
113
113
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
+
114
117
/** Observable sequence of position changes. */
115
118
positionChanges : Observable < ConnectedOverlayPositionChange > = Observable . create ( observer => {
116
119
const subscription = this . _positionChanges . subscribe ( observer ) ;
@@ -279,6 +282,8 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
279
282
}
280
283
281
284
detach ( ) {
285
+ this . _lastPosition = null ;
286
+ this . _previousPushAmount = null ;
282
287
this . _resizeSubscription . unsubscribe ( ) ;
283
288
}
284
289
@@ -538,39 +543,55 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
538
543
* the viewport, the top-left corner will be pushed on-screen (with overflow occuring on the
539
544
* right and bottom).
540
545
*
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.
543
549
* @returns The point at which to position the overlay after pushing. This is effectively a new
544
550
* originPoint.
545
551
*/
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
+
547
565
const viewport = this . _viewportRect ;
548
566
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.
551
569
const overflowRight = Math . max ( start . x + overlay . width - viewport . right , 0 ) ;
552
570
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 ) ;
555
573
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 ;
558
577
559
578
// If the overlay fits completely within the bounds of the viewport, push it from whichever
560
579
// direction is goes off-screen. Otherwise, push the top-left corner such that its in the
561
580
// 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 ) {
563
582
pushX = overflowLeft || - overflowRight ;
564
583
} else {
565
- pushX = viewport . left - start . x ;
584
+ pushX = start . x < this . _viewportMargin ? ( viewport . left - scrollPosition . left ) - start . x : 0 ;
566
585
}
567
586
568
- if ( overlay . height <= viewport . height ) {
587
+ if ( overlay . height < viewport . height ) {
569
588
pushY = overflowTop || - overflowBottom ;
570
589
} else {
571
- pushY = viewport . top - start . y ;
590
+ pushY = start . y < this . _viewportMargin ? ( viewport . top - scrollPosition . top ) - start . y : 0 ;
572
591
}
573
592
593
+ this . _previousPushAmount = { x : pushX , y : pushY } ;
594
+
574
595
return {
575
596
x : start . x + pushX ,
576
597
y : start . y + pushY ,
@@ -789,8 +810,9 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
789
810
const styles = { } as CSSStyleDeclaration ;
790
811
791
812
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 ) ) ;
794
816
} else {
795
817
styles . position = 'static' ;
796
818
}
@@ -829,14 +851,16 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
829
851
}
830
852
831
853
/** 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 ) {
833
857
// Reset any existing styles. This is necessary in case the
834
858
// preferred position has changed since the last `apply`.
835
859
let styles = { top : null , bottom : null } as CSSStyleDeclaration ;
836
860
let overlayPoint = this . _getOverlayPoint ( originPoint , this . _overlayRect , position ) ;
837
861
838
862
if ( this . _isPushed ) {
839
- overlayPoint = this . _pushOverlayOnScreen ( overlayPoint , this . _overlayRect ) ;
863
+ overlayPoint = this . _pushOverlayOnScreen ( overlayPoint , this . _overlayRect , scrollPosition ) ;
840
864
}
841
865
842
866
// 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 {
854
878
}
855
879
856
880
/** 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 ) {
858
884
// Reset any existing styles. This is necessary in case the preferred position has
859
885
// changed since the last `apply`.
860
886
let styles = { left : null , right : null } as CSSStyleDeclaration ;
861
887
let overlayPoint = this . _getOverlayPoint ( originPoint , this . _overlayRect , position ) ;
862
888
863
889
if ( this . _isPushed ) {
864
- overlayPoint = this . _pushOverlayOnScreen ( overlayPoint , this . _overlayRect ) ;
890
+ overlayPoint = this . _pushOverlayOnScreen ( overlayPoint , this . _overlayRect , scrollPosition ) ;
865
891
}
866
892
867
893
// We want to set either `left` or `right` based on whether the overlay wants to appear "before"
0 commit comments