Skip to content

Commit 3253ff3

Browse files
committed
feat(overlay): support custom offsets
1 parent 93f8e04 commit 3253ff3

File tree

4 files changed

+106
-14
lines changed

4 files changed

+106
-14
lines changed

src/lib/core/overlay/overlay-directives.spec.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {ComponentFixture, TestBed} from '@angular/core/testing';
22
import {Component, ViewChild} from '@angular/core';
3+
import {By} from '@angular/platform-browser';
34
import {ConnectedOverlayDirective, OverlayModule} from './overlay-directives';
45
import {OverlayContainer} from './overlay-container';
56
import {ConnectedPositionStrategy} from './position/connected-position-strategy';
@@ -121,6 +122,36 @@ describe('Overlay directives', () => {
121122
expect(fixture.componentInstance.backdropClicked).toBe(true);
122123
});
123124

125+
it('should set the offsetX', () => {
126+
const trigger = fixture.debugElement.query(By.css('button')).nativeElement;
127+
const startX = trigger.getBoundingClientRect().left;
128+
129+
fixture.componentInstance.offsetX = 5;
130+
fixture.componentInstance.isOpen = true;
131+
fixture.detectChanges();
132+
133+
// expected x value is the starting x + offset x
134+
const expectedX = startX + 5;
135+
const pane = overlayContainerElement.children[0] as HTMLElement;
136+
expect(pane.style.transform).toContain(`translateX(${expectedX}px)`);
137+
});
138+
139+
it('should set the offsetY', () => {
140+
const trigger = fixture.debugElement.query(By.css('button')).nativeElement;
141+
trigger.style.position = 'absolute';
142+
trigger.style.top = '30px';
143+
trigger.style.height = '20px';
144+
145+
fixture.componentInstance.offsetY = 45;
146+
fixture.componentInstance.isOpen = true;
147+
fixture.detectChanges();
148+
149+
// expected y value is the starting y + trigger height + offset y
150+
// 30 + 20 + 45 = 95px
151+
const pane = overlayContainerElement.children[0] as HTMLElement;
152+
expect(pane.style.transform).toContain(`translateY(95px)`);
153+
});
154+
124155
});
125156

126157
});
@@ -131,14 +162,16 @@ describe('Overlay directives', () => {
131162
<button overlay-origin #trigger="overlayOrigin">Toggle menu</button>
132163
<template connected-overlay [origin]="trigger" [open]="isOpen" [width]="width" [height]="height"
133164
[hasBackdrop]="hasBackdrop" backdropClass="md-test-class"
134-
(backdropClick)="backdropClicked=true">
165+
(backdropClick)="backdropClicked=true" [offsetX]="offsetX" [offsetY]="offsetY">
135166
<p>Menu content</p>
136167
</template>`,
137168
})
138169
class ConnectedOverlayDirectiveTest {
139170
isOpen = false;
140171
width: number | string;
141172
height: number | string;
173+
offsetX: number = 0;
174+
offsetY: number = 0;
142175
hasBackdrop: boolean;
143176
backdropClicked = false;
144177

src/lib/core/overlay/overlay-directives.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ export class ConnectedOverlayDirective implements OnDestroy {
6767
@Input() origin: OverlayOrigin;
6868
@Input() positions: ConnectionPositionPair[];
6969

70+
/** The offset in pixels for the overlay connection point on the x-axis */
71+
@Input() offsetX: number = 0;
72+
73+
/** The offset in pixels for the overlay connection point on the y-axis */
74+
@Input() offsetY: number = 0;
75+
7076
/** The width of the overlay panel. */
7177
@Input() width: number | string;
7278

@@ -150,20 +156,24 @@ export class ConnectedOverlayDirective implements OnDestroy {
150156
overlayConfig.backdropClass = this.backdropClass;
151157
}
152158

153-
overlayConfig.positionStrategy = this._getPosition();
159+
overlayConfig.positionStrategy = this._createPositionStrategy();
154160

155161
overlayConfig.direction = this.dir;
156162

157163
return overlayConfig;
158164
}
159165

160-
/** Returns the position of the overlay to be set on the overlay config */
161-
private _getPosition(): ConnectedPositionStrategy {
162-
return this._overlay.position().connectedTo(
163-
this.origin.elementRef,
164-
{originX: this.positions[0].overlayX, originY: this.positions[0].originY},
165-
{overlayX: this.positions[0].overlayX, overlayY: this.positions[0].overlayY})
166-
.setDirection(this.dir);
166+
/** Returns the position strategy of the overlay to be set on the overlay config */
167+
private _createPositionStrategy(): ConnectedPositionStrategy {
168+
const pos = this.positions[0];
169+
const originPoint = {originX: pos.originX, originY: pos.originY};
170+
const overlayPoint = {overlayX: pos.overlayX, overlayY: pos.overlayY};
171+
172+
return this._overlay.position()
173+
.connectedTo(this.origin.elementRef, originPoint, overlayPoint)
174+
.withDirection(this.dir)
175+
.withOffsetX(this.offsetX)
176+
.withOffsetY(this.offsetY);
167177
}
168178

169179
/** Attaches the overlay and subscribes to backdrop clicks if backdrop exists */

src/lib/core/overlay/position/connected-position-strategy.spec.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,14 +215,45 @@ describe('ConnectedPositionStrategy', () => {
215215
fakeElementRef,
216216
{originX: 'start', originY: 'bottom'},
217217
{overlayX: 'start', overlayY: 'top'})
218-
.setDirection('rtl');
218+
.withDirection('rtl');
219219

220220
strategy.apply(overlayElement);
221221

222222
let overlayRect = overlayElement.getBoundingClientRect();
223223
expect(overlayRect.top).toBe(originRect.bottom);
224224
expect(overlayRect.right).toBe(originRect.right);
225225
});
226+
227+
it('should position a panel with the x offset provided', () => {
228+
originRect = originElement.getBoundingClientRect();
229+
strategy = positionBuilder.connectedTo(
230+
fakeElementRef,
231+
{originX: 'start', originY: 'top'},
232+
{overlayX: 'start', overlayY: 'top'});
233+
234+
strategy.withOffsetX(10);
235+
strategy.apply(overlayElement);
236+
237+
let overlayRect = overlayElement.getBoundingClientRect();
238+
expect(overlayRect.top).toBe(originRect.top);
239+
expect(overlayRect.left).toBe(originRect.left + 10);
240+
});
241+
242+
it('should position a panel with the y offset provided', () => {
243+
originRect = originElement.getBoundingClientRect();
244+
strategy = positionBuilder.connectedTo(
245+
fakeElementRef,
246+
{originX: 'start', originY: 'top'},
247+
{overlayX: 'start', overlayY: 'top'});
248+
249+
strategy.withOffsetY(50);
250+
strategy.apply(overlayElement);
251+
252+
let overlayRect = overlayElement.getBoundingClientRect();
253+
expect(overlayRect.top).toBe(originRect.top + 50);
254+
expect(overlayRect.left).toBe(originRect.left);
255+
});
256+
226257
});
227258

228259

src/lib/core/overlay/position/connected-position-strategy.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,20 @@ import {
1111

1212
/**
1313
* A strategy for positioning overlays. Using this strategy, an overlay is given an
14-
* implict position relative some origin element. The relative position is defined in terms of
14+
* implicit position relative some origin element. The relative position is defined in terms of
1515
* a point on the origin element that is connected to a point on the overlay element. For example,
1616
* a basic dropdown is connecting the bottom-left corner of the origin to the top-left corner
1717
* of the overlay.
1818
*/
1919
export class ConnectedPositionStrategy implements PositionStrategy {
2020
private _dir = 'ltr';
2121

22+
/** The offset in pixels for the overlay connection point on the x-axis */
23+
private _offsetX: number = 0;
24+
25+
/** The offset in pixels for the overlay connection point on the y-axis */
26+
private _offsetY: number = 0;
27+
2228
/** Whether the we're dealing with an RTL context */
2329
get _isRtl() {
2430
return this._dir === 'rtl';
@@ -89,11 +95,23 @@ export class ConnectedPositionStrategy implements PositionStrategy {
8995
}
9096

9197
/** Sets the layout direction so the overlay's position can be adjusted to match. */
92-
setDirection(dir: 'ltr' | 'rtl') {
98+
withDirection(dir: 'ltr' | 'rtl'): this {
9399
this._dir = dir;
94100
return this;
95101
}
96102

103+
/** Sets an offset for the overlay's connection point on the x-axis */
104+
withOffsetX(offset: number): this {
105+
this._offsetX = offset;
106+
return this;
107+
}
108+
109+
/** Sets an offset for the overlay's connection point on the y-axis */
110+
withOffsetY(offset: number): this {
111+
this._offsetY = offset;
112+
return this;
113+
}
114+
97115
/**
98116
* Gets the horizontal (x) "start" dimension based on whether the overlay is in an RTL context.
99117
* @param rect
@@ -168,8 +186,8 @@ export class ConnectedPositionStrategy implements PositionStrategy {
168186
}
169187

170188
return {
171-
x: originPoint.x + overlayStartX,
172-
y: originPoint.y + overlayStartY
189+
x: originPoint.x + overlayStartX + this._offsetX,
190+
y: originPoint.y + overlayStartY + this._offsetY
173191
};
174192
}
175193

0 commit comments

Comments
 (0)