|
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,
|
@@ -112,6 +112,9 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
|
112 | 112 | /** Amount of subscribers to the `positionChanges` stream. */
|
113 | 113 | private _positionChangeSubscriptions = 0;
|
114 | 114 |
|
| 115 | + /** Amount by which the overlay was pushed in each axis during the last time it was positioned. */ |
| 116 | + private _previousPushAmount: {x: number, y: number} | null; |
| 117 | + |
115 | 118 | /** Observable sequence of position changes. */
|
116 | 119 | positionChanges: Observable<ConnectedOverlayPositionChange> = Observable.create(observer => {
|
117 | 120 | const subscription = this._positionChanges.subscribe(observer);
|
@@ -282,6 +285,8 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
|
282 | 285 | }
|
283 | 286 |
|
284 | 287 | detach() {
|
| 288 | + this._lastPosition = null; |
| 289 | + this._previousPushAmount = null; |
285 | 290 | this._resizeSubscription.unsubscribe();
|
286 | 291 | }
|
287 | 292 |
|
@@ -541,39 +546,55 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
|
541 | 546 | * the viewport, the top-left corner will be pushed on-screen (with overflow occuring on the
|
542 | 547 | * right and bottom).
|
543 | 548 | *
|
544 |
| - * @param start The starting point from which the overlay is pushed. |
545 |
| - * @param overlay The overlay dimensions. |
| 549 | + * @param start Starting point from which the overlay is pushed. |
| 550 | + * @param overlay Dimensions of the overlay. |
| 551 | + * @param scrollPosition Current viewport scroll position. |
546 | 552 | * @returns The point at which to position the overlay after pushing. This is effectively a new
|
547 | 553 | * originPoint.
|
548 | 554 | */
|
549 |
| - private _pushOverlayOnScreen(start: Point, overlay: ClientRect): Point { |
| 555 | + private _pushOverlayOnScreen(start: Point, |
| 556 | + overlay: ClientRect, |
| 557 | + scrollPosition: ViewportScrollPosition): Point { |
| 558 | + // If the position is locked and we've pushed the overlay already, reuse the previous push |
| 559 | + // amount, rather than pushing it again. If we were to continue pushing, the element would |
| 560 | + // remain in the viewport, which goes against the expectations when position locking is enabled. |
| 561 | + if (this._previousPushAmount && this._positionLocked) { |
| 562 | + return { |
| 563 | + x: start.x + this._previousPushAmount.x, |
| 564 | + y: start.y + this._previousPushAmount.y |
| 565 | + }; |
| 566 | + } |
| 567 | + |
550 | 568 | const viewport = this._viewportRect;
|
551 | 569 |
|
552 |
| - // Determine how much the overlay goes outside the viewport on each side, which we'll use to |
553 |
| - // decide which direction to push it. |
| 570 | + // Determine how much the overlay goes outside the viewport on each |
| 571 | + // side, which we'll use to decide which direction to push it. |
554 | 572 | const overflowRight = Math.max(start.x + overlay.width - viewport.right, 0);
|
555 | 573 | const overflowBottom = Math.max(start.y + overlay.height - viewport.bottom, 0);
|
556 |
| - const overflowTop = Math.max(viewport.top - start.y, 0); |
557 |
| - const overflowLeft = Math.max(viewport.left - start.x, 0); |
| 574 | + const overflowTop = Math.max(viewport.top - scrollPosition.top - start.y, 0); |
| 575 | + const overflowLeft = Math.max(viewport.left - scrollPosition.left - start.x, 0); |
558 | 576 |
|
559 |
| - // Amount by which to push the overlay in each direction such that it remains on-screen. |
560 |
| - let pushX, pushY = 0; |
| 577 | + // Amount by which to push the overlay in each axis such that it remains on-screen. |
| 578 | + let pushX = 0; |
| 579 | + let pushY = 0; |
561 | 580 |
|
562 | 581 | // If the overlay fits completely within the bounds of the viewport, push it from whichever
|
563 | 582 | // direction is goes off-screen. Otherwise, push the top-left corner such that its in the
|
564 | 583 | // viewport and allow for the trailing end of the overlay to go out of bounds.
|
565 |
| - if (overlay.width <= viewport.width) { |
| 584 | + if (overlay.width < viewport.width) { |
566 | 585 | pushX = overflowLeft || -overflowRight;
|
567 | 586 | } else {
|
568 |
| - pushX = viewport.left - start.x; |
| 587 | + pushX = start.x < this._viewportMargin ? (viewport.left - scrollPosition.left) - start.x : 0; |
569 | 588 | }
|
570 | 589 |
|
571 |
| - if (overlay.height <= viewport.height) { |
| 590 | + if (overlay.height < viewport.height) { |
572 | 591 | pushY = overflowTop || -overflowBottom;
|
573 | 592 | } else {
|
574 |
| - pushY = viewport.top - start.y; |
| 593 | + pushY = start.y < this._viewportMargin ? (viewport.top - scrollPosition.top) - start.y : 0; |
575 | 594 | }
|
576 | 595 |
|
| 596 | + this._previousPushAmount = {x: pushX, y: pushY}; |
| 597 | + |
577 | 598 | return {
|
578 | 599 | x: start.x + pushX,
|
579 | 600 | y: start.y + pushY,
|
@@ -792,8 +813,9 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
|
792 | 813 | const styles = {} as CSSStyleDeclaration;
|
793 | 814 |
|
794 | 815 | if (this._hasExactPosition()) {
|
795 |
| - extendStyles(styles, this._getExactOverlayY(position, originPoint)); |
796 |
| - extendStyles(styles, this._getExactOverlayX(position, originPoint)); |
| 816 | + const scrollPosition = this._viewportRuler.getViewportScrollPosition(); |
| 817 | + extendStyles(styles, this._getExactOverlayY(position, originPoint, scrollPosition)); |
| 818 | + extendStyles(styles, this._getExactOverlayX(position, originPoint, scrollPosition)); |
797 | 819 | } else {
|
798 | 820 | styles.position = 'static';
|
799 | 821 | }
|
@@ -832,14 +854,16 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
|
832 | 854 | }
|
833 | 855 |
|
834 | 856 | /** Gets the exact top/bottom for the overlay when not using flexible sizing or when pushing. */
|
835 |
| - private _getExactOverlayY(position: ConnectedPosition, originPoint: Point) { |
| 857 | + private _getExactOverlayY(position: ConnectedPosition, |
| 858 | + originPoint: Point, |
| 859 | + scrollPosition: ViewportScrollPosition) { |
836 | 860 | // Reset any existing styles. This is necessary in case the
|
837 | 861 | // preferred position has changed since the last `apply`.
|
838 | 862 | let styles = {top: null, bottom: null} as CSSStyleDeclaration;
|
839 | 863 | let overlayPoint = this._getOverlayPoint(originPoint, this._overlayRect, position);
|
840 | 864 |
|
841 | 865 | if (this._isPushed) {
|
842 |
| - overlayPoint = this._pushOverlayOnScreen(overlayPoint, this._overlayRect); |
| 866 | + overlayPoint = this._pushOverlayOnScreen(overlayPoint, this._overlayRect, scrollPosition); |
843 | 867 | }
|
844 | 868 |
|
845 | 869 | // @deletion-target 7.0.0 Currently the `_overlayContainer` is optional in order to avoid a
|
@@ -869,14 +893,16 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
|
869 | 893 | }
|
870 | 894 |
|
871 | 895 | /** Gets the exact left/right for the overlay when not using flexible sizing or when pushing. */
|
872 |
| - private _getExactOverlayX(position: ConnectedPosition, originPoint: Point) { |
| 896 | + private _getExactOverlayX(position: ConnectedPosition, |
| 897 | + originPoint: Point, |
| 898 | + scrollPosition: ViewportScrollPosition) { |
873 | 899 | // Reset any existing styles. This is necessary in case the preferred position has
|
874 | 900 | // changed since the last `apply`.
|
875 | 901 | let styles = {left: null, right: null} as CSSStyleDeclaration;
|
876 | 902 | let overlayPoint = this._getOverlayPoint(originPoint, this._overlayRect, position);
|
877 | 903 |
|
878 | 904 | if (this._isPushed) {
|
879 |
| - overlayPoint = this._pushOverlayOnScreen(overlayPoint, this._overlayRect); |
| 905 | + overlayPoint = this._pushOverlayOnScreen(overlayPoint, this._overlayRect, scrollPosition); |
880 | 906 | }
|
881 | 907 |
|
882 | 908 | // We want to set either `left` or `right` based on whether the overlay wants to appear "before"
|
|
0 commit comments