Skip to content

Commit 2a78c90

Browse files
committed
feat(cdk/overlay): Allow passing separate X and Y values for the viewportMargin
The overlay directive now accepts two additional (optional parameters) [viewportMarginX] and [viewportMarginY]. You can use these to pass separate margin values for the viewport.
1 parent df7104f commit 2a78c90

File tree

3 files changed

+119
-21
lines changed

3 files changed

+119
-21
lines changed

Diff for: src/cdk/overlay/overlay-directives.ts

+17-2
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,21 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
177177
/** The custom class to add to the overlay pane element. */
178178
@Input('cdkConnectedOverlayPanelClass') panelClass: string | string[];
179179

180-
/** Margin between the overlay and the viewport edges. */
180+
/** Margin (both horizontal and vertical) between the overlay and the viewport edges. */
181181
@Input('cdkConnectedOverlayViewportMargin') viewportMargin: number = 0;
182182

183+
/** Margin between the overlay and the left viewport edge. */
184+
@Input('cdkConnectedOverlayViewportMarginLeft') viewportMarginLeft: number = 0;
185+
186+
/** Margin between the overlay and the right viewport edge. */
187+
@Input('cdkConnectedOverlayViewportMarginRight') viewportMarginRight: number = 0;
188+
189+
/** Margin between the overlay and the top viewport edge. */
190+
@Input('cdkConnectedOverlayViewportMarginTop') viewportMarginTop: number = 0;
191+
192+
/** Margin between the overlay and the bottom viewport edge. */
193+
@Input('cdkConnectedOverlayViewportMarginBottom') viewportMarginBottom: number = 0;
194+
183195
/** Strategy to be used when handling scroll events while the overlay is open. */
184196
@Input('cdkConnectedOverlayScrollStrategy') scrollStrategy: ScrollStrategy;
185197

@@ -378,7 +390,10 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
378390
.withFlexibleDimensions(this.flexibleDimensions)
379391
.withPush(this.push)
380392
.withGrowAfterOpen(this.growAfterOpen)
381-
.withViewportMargin(this.viewportMargin)
393+
.withViewportMarginTop(this.viewportMarginTop ?? this.viewportMargin ?? 0)
394+
.withViewportMarginRight(this.viewportMarginRight ?? this.viewportMargin ?? 0)
395+
.withViewportMarginBottom(this.viewportMarginBottom ?? this.viewportMargin ?? 0)
396+
.withViewportMarginLeft(this.viewportMarginLeft ?? this.viewportMargin ?? 0)
382397
.withLockedPosition(this.lockPosition)
383398
.withTransformOriginOn(this.transformOriginSelector);
384399
}

Diff for: src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts

+47
Original file line numberDiff line numberDiff line change
@@ -1367,6 +1367,53 @@ describe('FlexibleConnectedPositionStrategy', () => {
13671367
expect(Math.floor(overlayRect.top)).toBe(15);
13681368
});
13691369

1370+
it('should set separate margins when pushing the overlay into the viewport', () => {
1371+
originElement.style.top = `${-OVERLAY_HEIGHT}px`;
1372+
originElement.style.left = `${-OVERLAY_WIDTH / 2}px`;
1373+
1374+
positionStrategy
1375+
.withViewportMarginTop(15)
1376+
.withViewportMarginBottom(30)
1377+
.withViewportMarginLeft(10)
1378+
.withPositions([
1379+
{
1380+
originX: 'start',
1381+
originY: 'bottom',
1382+
overlayX: 'start',
1383+
overlayY: 'top',
1384+
},
1385+
]);
1386+
1387+
attachOverlay({positionStrategy});
1388+
1389+
const overlayRect = overlayRef.overlayElement.getBoundingClientRect();
1390+
expect(Math.floor(overlayRect.left)).toBe(10);
1391+
expect(Math.floor(overlayRect.top)).toBe(15);
1392+
expect(Math.floor(overlayRect.bottom)).toBe(30);
1393+
});
1394+
1395+
it('should only set the margins that were provided when pushing the overlay into the viewport from both axes', () => {
1396+
originElement.style.top = `${-OVERLAY_HEIGHT / 2}px`;
1397+
originElement.style.left = `${-OVERLAY_WIDTH / 2}px`;
1398+
1399+
positionStrategy.withViewportMarginLeft(30).withPositions([
1400+
{
1401+
originX: 'start',
1402+
originY: 'bottom',
1403+
overlayX: 'start',
1404+
overlayY: 'top',
1405+
},
1406+
]);
1407+
1408+
attachOverlay({positionStrategy});
1409+
1410+
const overlayRect = overlayRef.overlayElement.getBoundingClientRect();
1411+
expect(Math.floor(overlayRect.left)).toBe(30);
1412+
expect(Math.floor(overlayRect.right)).toBe(0);
1413+
expect(Math.floor(overlayRect.top)).toBe(0);
1414+
expect(Math.floor(overlayRect.bottom)).toBe(0);
1415+
});
1416+
13701417
it('should not mess with the left offset when pushing from the top', () => {
13711418
originElement.style.top = `${-OVERLAY_HEIGHT * 2}px`;
13721419
originElement.style.left = '200px';

Diff for: src/cdk/overlay/position/flexible-connected-position-strategy.ts

+55-19
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,17 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
8888
/** Cached container dimensions */
8989
private _containerRect: Dimensions;
9090

91-
/** Amount of space that must be maintained between the overlay and the edge of the viewport. */
92-
private _viewportMargin = 0;
91+
/** Amount of space that must be maintained between the overlay and the top edge of the viewport. */
92+
private _viewportMarginTop = 0;
93+
94+
/** Amount of space that must be maintained between the overlay and the bottom edge of the viewport. */
95+
private _viewportMarginBottom = 0;
96+
97+
/** Amount of space that must be maintained between the overlay and the left edge of the viewport. */
98+
private _viewportMarginLeft = 0;
99+
100+
/** Amount of space that must be maintained between the overlay and the right edge of the viewport. */
101+
private _viewportMarginRight = 0;
93102

94103
/** The Scrollable containers used to check scrollable view properties on position change. */
95104
private _scrollables: CdkScrollable[] = [];
@@ -411,11 +420,38 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
411420
}
412421

413422
/**
414-
* Sets a minimum distance the overlay may be positioned to the edge of the viewport.
423+
* Sets a minimum distance the overlay may be positioned from the left edge of the viewport.
424+
* @param margin Required margin between the overlay and the viewport edge in pixels.
425+
*/
426+
withViewportMarginLeft(margin: number): this {
427+
this._viewportMarginLeft = margin;
428+
return this;
429+
}
430+
431+
/**
432+
* Sets a minimum distance the overlay may be positioned from the right edge of the viewport.
415433
* @param margin Required margin between the overlay and the viewport edge in pixels.
416434
*/
417-
withViewportMargin(margin: number): this {
418-
this._viewportMargin = margin;
435+
withViewportMarginRight(margin: number): this {
436+
this._viewportMarginRight = margin;
437+
return this;
438+
}
439+
440+
/**
441+
* Sets a minimum distance the overlay may be positioned from the top edge of the viewport.
442+
* @param margin Required vertical margin between the overlay and the viewport edge in pixels.
443+
*/
444+
withViewportMarginTop(margin: number): this {
445+
this._viewportMarginTop = margin;
446+
return this;
447+
}
448+
449+
/**
450+
* Sets a minimum distance the overlay may be positioned from the bottom edge of the viewport.
451+
* @param margin Required vertical margin between the overlay and the viewport edge in pixels.
452+
*/
453+
withViewportMarginBottom(margin: number): this {
454+
this._viewportMarginBottom = margin;
419455
return this;
420456
}
421457

@@ -682,13 +718,13 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
682718
if (overlay.width <= viewport.width) {
683719
pushX = overflowLeft || -overflowRight;
684720
} else {
685-
pushX = start.x < this._viewportMargin ? viewport.left - scrollPosition.left - start.x : 0;
721+
pushX = start.x < this._viewportMarginLeft ? viewport.left - scrollPosition.left - start.x : 0;
686722
}
687723

688724
if (overlay.height <= viewport.height) {
689725
pushY = overflowTop || -overflowBottom;
690726
} else {
691-
pushY = start.y < this._viewportMargin ? viewport.top - scrollPosition.top - start.y : 0;
727+
pushY = start.y < this._viewportMarginTop ? viewport.top - scrollPosition.top - start.y : 0;
692728
}
693729

694730
this._previousPushAmount = {x: pushX, y: pushY};
@@ -777,13 +813,13 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
777813
if (position.overlayY === 'top') {
778814
// Overlay is opening "downward" and thus is bound by the bottom viewport edge.
779815
top = origin.y;
780-
height = viewport.height - top + this._viewportMargin;
816+
height = viewport.height - top + this._viewportMarginBottom;
781817
} else if (position.overlayY === 'bottom') {
782818
// Overlay is opening "upward" and thus is bound by the top viewport edge. We need to add
783819
// the viewport margin back in, because the viewport rect is narrowed down to remove the
784820
// margin, whereas the `origin` position is calculated based on its `DOMRect`.
785-
bottom = viewport.height - origin.y + this._viewportMargin * 2;
786-
height = viewport.height - bottom + this._viewportMargin;
821+
bottom = viewport.height - origin.y + this._viewportMarginTop + this._viewportMarginBottom;
822+
height = viewport.height - bottom + this._viewportMarginTop;
787823
} else {
788824
// If neither top nor bottom, it means that the overlay is vertically centered on the
789825
// origin point. Note that we want the position relative to the viewport, rather than
@@ -815,11 +851,11 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
815851
let width: number, left: number, right: number;
816852

817853
if (isBoundedByLeftViewportEdge) {
818-
right = viewport.width - origin.x + this._viewportMargin * 2;
819-
width = origin.x - this._viewportMargin;
854+
right = viewport.width - origin.x + this._viewportMarginLeft + this._viewportMarginRight;
855+
width = origin.x - this._viewportMarginLeft;
820856
} else if (isBoundedByRightViewportEdge) {
821857
left = origin.x;
822-
width = viewport.right - origin.x;
858+
width = viewport.right - origin.x - this._viewportMarginRight;
823859
} else {
824860
// If neither start nor end, it means that the overlay is horizontally centered on the
825861
// origin point. Note that we want the position relative to the viewport, rather than
@@ -1098,12 +1134,12 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
10981134
const scrollPosition = this._viewportRuler.getViewportScrollPosition();
10991135

11001136
return {
1101-
top: scrollPosition.top + this._viewportMargin,
1102-
left: scrollPosition.left + this._viewportMargin,
1103-
right: scrollPosition.left + width - this._viewportMargin,
1104-
bottom: scrollPosition.top + height - this._viewportMargin,
1105-
width: width - 2 * this._viewportMargin,
1106-
height: height - 2 * this._viewportMargin,
1137+
top: scrollPosition.top + this._viewportMarginTop,
1138+
left: scrollPosition.left + this._viewportMarginLeft,
1139+
right: scrollPosition.left + width - this._viewportMarginRight,
1140+
bottom: scrollPosition.top + height - this._viewportMarginBottom,
1141+
width: width - this._viewportMarginLeft - this._viewportMarginRight,
1142+
height: height - this._viewportMarginTop - this._viewportMarginBottom,
11071143
};
11081144
}
11091145

0 commit comments

Comments
 (0)