Skip to content

Commit 37b5e76

Browse files
author
Luke
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 37b5e76

File tree

3 files changed

+111
-19
lines changed

3 files changed

+111
-19
lines changed

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

+9-2
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,15 @@ 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+
/** Horizontal margin between the overlay and the viewport edges. */
184+
@Input('cdkConnectedOverlayViewportMarginX') viewportMarginX: number = 0;
185+
186+
/** Vertical margin between the overlay and the viewport edges. */
187+
@Input('cdkConnectedOverlayViewportMarginY') viewportMarginY: number = 0;
188+
183189
/** Strategy to be used when handling scroll events while the overlay is open. */
184190
@Input('cdkConnectedOverlayScrollStrategy') scrollStrategy: ScrollStrategy;
185191

@@ -378,7 +384,8 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
378384
.withFlexibleDimensions(this.flexibleDimensions)
379385
.withPush(this.push)
380386
.withGrowAfterOpen(this.growAfterOpen)
381-
.withViewportMargin(this.viewportMargin)
387+
.withViewportMarginX(this.viewportMarginX ?? this.viewportMargin ?? 0)
388+
.withViewportMarginY(this.viewportMarginY ?? this.viewportMargin ?? 0)
382389
.withLockedPosition(this.lockPosition)
383390
.withTransformOriginOn(this.transformOriginSelector);
384391
}

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

+63
Original file line numberDiff line numberDiff line change
@@ -1367,6 +1367,69 @@ 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 from both axes', () => {
1371+
originElement.style.top = `${-OVERLAY_HEIGHT}px`;
1372+
originElement.style.left = `${-OVERLAY_WIDTH / 2}px`;
1373+
1374+
positionStrategy
1375+
.withViewportMarginX(15)
1376+
.withViewportMarginY(30)
1377+
.withPositions([
1378+
{
1379+
originX: 'start',
1380+
originY: 'bottom',
1381+
overlayX: 'start',
1382+
overlayY: 'top',
1383+
},
1384+
]);
1385+
1386+
attachOverlay({positionStrategy});
1387+
1388+
const overlayRect = overlayRef.overlayElement.getBoundingClientRect();
1389+
expect(Math.floor(overlayRect.left)).toBe(15);
1390+
expect(Math.floor(overlayRect.top)).toBe(30);
1391+
});
1392+
1393+
it('should only set the vertical margin if no marginY was provided whne pushing the overlay into the viewport from both axes', () => {
1394+
originElement.style.top = `${-OVERLAY_HEIGHT / 2}px`;
1395+
originElement.style.left = `${-OVERLAY_WIDTH / 2}px`;
1396+
1397+
positionStrategy.withViewportMarginY(30).withPositions([
1398+
{
1399+
originX: 'start',
1400+
originY: 'bottom',
1401+
overlayX: 'start',
1402+
overlayY: 'top',
1403+
},
1404+
]);
1405+
1406+
attachOverlay({positionStrategy});
1407+
1408+
const overlayRect = overlayRef.overlayElement.getBoundingClientRect();
1409+
expect(Math.floor(overlayRect.top)).toBe(30);
1410+
expect(Math.floor(overlayRect.left)).toBe(0);
1411+
});
1412+
1413+
it('should only set the horizontal margin if no marginX was provided whne pushing the overlay into the viewport from both axes', () => {
1414+
originElement.style.top = `${-OVERLAY_HEIGHT}px`;
1415+
originElement.style.left = `${-OVERLAY_WIDTH / 2}px`;
1416+
1417+
positionStrategy.withViewportMarginX(30).withPositions([
1418+
{
1419+
originX: 'start',
1420+
originY: 'bottom',
1421+
overlayX: 'start',
1422+
overlayY: 'top',
1423+
},
1424+
]);
1425+
1426+
attachOverlay({positionStrategy});
1427+
1428+
const overlayRect = overlayRef.overlayElement.getBoundingClientRect();
1429+
expect(Math.floor(overlayRect.top)).toBe(0);
1430+
expect(Math.floor(overlayRect.left)).toBe(30);
1431+
});
1432+
13701433
it('should not mess with the left offset when pushing from the top', () => {
13711434
originElement.style.top = `${-OVERLAY_HEIGHT * 2}px`;
13721435
originElement.style.left = '200px';

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

+39-17
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,11 @@ 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 vertical space that must be maintained between the overlay and the edge of the viewport. */
92+
private _viewportMarginX = 0;
93+
94+
/** Amount of horizontal space that must be maintained between the overlay and the edge of the viewport. */
95+
private _viewportMarginY = 0;
9396

9497
/** The Scrollable containers used to check scrollable view properties on position change. */
9598
private _scrollables: CdkScrollable[] = [];
@@ -411,11 +414,30 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
411414
}
412415

413416
/**
414-
* Sets a minimum distance the overlay may be positioned to the edge of the viewport.
417+
* Sets a minimum horizontal distance the overlay may be positioned to the edge of the viewport.
415418
* @param margin Required margin between the overlay and the viewport edge in pixels.
416419
*/
420+
withViewportMarginX(margin: number): this {
421+
this._viewportMarginX = margin;
422+
return this;
423+
}
424+
425+
/**
426+
* Sets a minimum vertical distance the overlay may be positioned to the edge of the viewport.
427+
* @param margin Required vertical margin between the overlay and the viewport edge in pixels.
428+
*/
429+
withViewportMarginY(margin: number): this {
430+
this._viewportMarginY = margin;
431+
return this;
432+
}
433+
434+
/**
435+
* Sets a minimum distance the overlay may be positioned to the vertical and horizontal edge of the viewport.
436+
* @param margin Required vertical and horizontal margin between the overlay and the viewport edge in pixels.
437+
*/
417438
withViewportMargin(margin: number): this {
418-
this._viewportMargin = margin;
439+
this._viewportMarginY = margin;
440+
this._viewportMarginX = margin;
419441
return this;
420442
}
421443

@@ -682,13 +704,13 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
682704
if (overlay.width <= viewport.width) {
683705
pushX = overflowLeft || -overflowRight;
684706
} else {
685-
pushX = start.x < this._viewportMargin ? viewport.left - scrollPosition.left - start.x : 0;
707+
pushX = start.x < this._viewportMarginX ? viewport.left - scrollPosition.left - start.x : 0;
686708
}
687709

688710
if (overlay.height <= viewport.height) {
689711
pushY = overflowTop || -overflowBottom;
690712
} else {
691-
pushY = start.y < this._viewportMargin ? viewport.top - scrollPosition.top - start.y : 0;
713+
pushY = start.y < this._viewportMarginY ? viewport.top - scrollPosition.top - start.y : 0;
692714
}
693715

694716
this._previousPushAmount = {x: pushX, y: pushY};
@@ -777,13 +799,13 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
777799
if (position.overlayY === 'top') {
778800
// Overlay is opening "downward" and thus is bound by the bottom viewport edge.
779801
top = origin.y;
780-
height = viewport.height - top + this._viewportMargin;
802+
height = viewport.height - top + this._viewportMarginY;
781803
} else if (position.overlayY === 'bottom') {
782804
// Overlay is opening "upward" and thus is bound by the top viewport edge. We need to add
783805
// the viewport margin back in, because the viewport rect is narrowed down to remove the
784806
// 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;
807+
bottom = viewport.height - origin.y + this._viewportMarginY * 2;
808+
height = viewport.height - bottom + this._viewportMarginY;
787809
} else {
788810
// If neither top nor bottom, it means that the overlay is vertically centered on the
789811
// origin point. Note that we want the position relative to the viewport, rather than
@@ -815,8 +837,8 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
815837
let width: number, left: number, right: number;
816838

817839
if (isBoundedByLeftViewportEdge) {
818-
right = viewport.width - origin.x + this._viewportMargin * 2;
819-
width = origin.x - this._viewportMargin;
840+
right = viewport.width - origin.x + this._viewportMarginX * 2;
841+
width = origin.x - this._viewportMarginX;
820842
} else if (isBoundedByRightViewportEdge) {
821843
left = origin.x;
822844
width = viewport.right - origin.x;
@@ -1098,12 +1120,12 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
10981120
const scrollPosition = this._viewportRuler.getViewportScrollPosition();
10991121

11001122
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,
1123+
top: scrollPosition.top + this._viewportMarginY,
1124+
left: scrollPosition.left + this._viewportMarginX,
1125+
right: scrollPosition.left + width - this._viewportMarginX,
1126+
bottom: scrollPosition.top + height - this._viewportMarginY,
1127+
width: width - 2 * this._viewportMarginX,
1128+
height: height - 2 * this._viewportMarginY,
11071129
};
11081130
}
11091131

0 commit comments

Comments
 (0)