From 7cd4c8df9dfbc4a5d92c8d8f941c86fe655ee039 Mon Sep 17 00:00:00 2001 From: Gustav Bylund Date: Mon, 5 Jul 2021 16:57:22 +0200 Subject: [PATCH] fix(cdk/overlay): scrolling broken in some overlay sizes When the positioning of an overlay is calculated, the overlay pane is moved to the top right corner, and given an unbounded size. Based on the calculated size, a position is attempted where the entire element can fit next to the origin element without needing to scroll. If an overlay pane is created with a size large enough that it needs scrolling in any valid position (top/bottom/left/right) but small enough that it can fit in the viewport without needing to scroll, the scrolling of that pane will break. When the pane is moved to the top left corner for size calculation, the pane will lose it's scroll position since it no longer has a scrollbar. This fix introduces a maximum constraint on the size of the overlay pane during size calculation, where the maximum available vertical and horizontal space is used as the constraint. --- .../flexible-connected-position-strategy.ts | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/cdk/overlay/position/flexible-connected-position-strategy.ts b/src/cdk/overlay/position/flexible-connected-position-strategy.ts index f7fc206ddd07..83517b1fedbd 100644 --- a/src/cdk/overlay/position/flexible-connected-position-strategy.ts +++ b/src/cdk/overlay/position/flexible-connected-position-strategy.ts @@ -199,18 +199,33 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy { this._clearPanelClasses(); this._resetOverlayElementStyles(); - this._resetBoundingBoxStyles(); + // We need the bounding rects for the origin and the overlay to determine how to position // the overlay relative to the origin. // We use the viewport rect to determine whether a position would go off-screen. this._viewportRect = this._getNarrowedViewportRect(); this._originRect = this._getOriginRect(); + const originRect = this._originRect; + const viewportRect = this._viewportRect; + + // Calculate the maximum sizes the overlay can have when connected + const topSpace = originRect.top - this._viewportMargin; + const bottomSpace = viewportRect.height - originRect.bottom - this._viewportMargin; + const maxVertical = Math.max(topSpace, bottomSpace); + + const leftSpace = originRect.left - this._viewportMargin; + const rightSpace = viewportRect.width - originRect.right - this._viewportMargin; + const maxHorizontal = Math.max(leftSpace, rightSpace); + + // Reset the bounding box, but constrain its max size to be the max size the pane will have later + this._resetBoundingBoxStyles(maxVertical, maxHorizontal); + + // We need the bounding rects for the overlay to determine how to position + // the overlay relative to the origin. this._overlayRect = this._pane.getBoundingClientRect(); - const originRect = this._originRect; const overlayRect = this._overlayRect; - const viewportRect = this._viewportRect; // Positions where the overlay will fit with flexible dimensions. const flexibleFits: FlexibleFit[] = []; @@ -841,14 +856,12 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy { } /** Resets the styles for the bounding box so that a new positioning can be computed. */ - private _resetBoundingBoxStyles() { + private _resetBoundingBoxStyles(maxVertical: number, maxHorizontal: number) { extendStyles(this._boundingBox!.style, { top: '0', left: '0', - right: '0', - bottom: '0', - height: '', - width: '', + height: `${maxVertical}px`, + width: `${maxHorizontal}px`, alignItems: '', justifyContent: '', } as CSSStyleDeclaration);