diff --git a/src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts b/src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts index 8d517776d2f5..b65b44bb9094 100644 --- a/src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts +++ b/src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts @@ -1361,6 +1361,33 @@ describe('FlexibleConnectedPositionStrategy', () => { expect(boundingBox.style.right).toBe('0px'); }); + it('should calculate the bottom offset correctly with a viewport margin', () => { + const viewportMargin = 5; + + originElement.style.top = `${OVERLAY_HEIGHT / 2}px`; + originElement.style.right = '200px'; + + positionStrategy + .withFlexibleHeight() + .withViewportMargin(viewportMargin) + .withPositions([ + { + originX: 'start', + originY: 'top', + overlayX: 'start', + overlayY: 'bottom' + } + ]); + + attachOverlay({positionStrategy}); + + const originRect = originElement.getBoundingClientRect(); + const overlayRect = overlayRef.overlayElement.getBoundingClientRect(); + + expect(Math.floor(overlayRect.bottom)).toBe(Math.floor(originRect.top)); + expect(Math.floor(overlayRect.top)).toBe(viewportMargin); + }); + }); describe('onPositionChange with scrollable view properties', () => { diff --git a/src/cdk/overlay/position/flexible-connected-position-strategy.ts b/src/cdk/overlay/position/flexible-connected-position-strategy.ts index 4792318a89d4..0ad1375488f0 100644 --- a/src/cdk/overlay/position/flexible-connected-position-strategy.ts +++ b/src/cdk/overlay/position/flexible-connected-position-strategy.ts @@ -580,6 +580,7 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy { */ private _calculateBoundingBoxRect(origin: Point, position: ConnectedPosition): BoundingBoxRect { const viewport = this._viewportRect; + const isRtl = this._isRtl(); let height, top, bottom; if (position.overlayY === 'top') { @@ -587,9 +588,11 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy { top = origin.y; height = viewport.bottom - origin.y; } else if (position.overlayY === 'bottom') { - // Overlay is opening "upward" and thus is bound by the top viewport edge. - bottom = viewport.height - origin.y + this._viewportMargin; - height = viewport.height - bottom; + // Overlay is opening "upward" and thus is bound by the top viewport edge. We need to add + // the viewport margin back in, because the viewport rect is narrowed down to remove the + // margin, whereas the `origin` position is calculated based on its `ClientRect`. + bottom = viewport.height - origin.y + this._viewportMargin * 2; + height = viewport.height - bottom + this._viewportMargin; } else { // If neither top nor bottom, it means that the overlay // is vertically centered on the origin point. @@ -607,13 +610,13 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy { // The overlay is opening 'right-ward' (the content flows to the right). const isBoundedByRightViewportEdge = - (position.overlayX === 'start' && !this._isRtl()) || - (position.overlayX === 'end' && this._isRtl()); + (position.overlayX === 'start' && !isRtl) || + (position.overlayX === 'end' && isRtl); // The overlay is opening 'left-ward' (the content flows to the left). const isBoundedByLeftViewportEdge = - (position.overlayX === 'end' && !this._isRtl()) || - (position.overlayX === 'start' && this._isRtl()); + (position.overlayX === 'end' && !isRtl) || + (position.overlayX === 'start' && isRtl); let width, left, right; @@ -886,7 +889,7 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy { return { top: scrollPosition.top + this._viewportMargin, left: scrollPosition.left + this._viewportMargin, - right: scrollPosition.left + width - this._viewportMargin, + right: scrollPosition.left + width - this._viewportMargin, bottom: scrollPosition.top + height - this._viewportMargin, width: width - (2 * this._viewportMargin), height: height - (2 * this._viewportMargin),