Skip to content

Commit

Permalink
feat(overlay): support custom offsets
Browse files Browse the repository at this point in the history
  • Loading branch information
kara committed Oct 31, 2016
1 parent ea6c817 commit 9180afc
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 9 deletions.
35 changes: 34 additions & 1 deletion src/lib/core/overlay/overlay-directives.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {Component, ViewChild} from '@angular/core';
import {By} from '@angular/platform-browser';
import {ConnectedOverlayDirective, OverlayModule} from './overlay-directives';
import {OverlayContainer} from './overlay-container';
import {ConnectedPositionStrategy} from './position/connected-position-strategy';
Expand Down Expand Up @@ -121,6 +122,36 @@ describe('Overlay directives', () => {
expect(fixture.componentInstance.backdropClicked).toBe(true);
});

it('should set the offsetX', () => {
const trigger = fixture.debugElement.query(By.css('button')).nativeElement;
const startX = trigger.getBoundingClientRect().left;

fixture.componentInstance.offsetX = 5;
fixture.componentInstance.isOpen = true;
fixture.detectChanges();

// expected x value is the starting x + offset x
const expectedX = startX + 5;
const pane = overlayContainerElement.children[0] as HTMLElement;
expect(pane.style.transform).toContain(`translateX(${expectedX}px)`);
});

it('should set the offsetY', () => {
const trigger = fixture.debugElement.query(By.css('button')).nativeElement;
trigger.style.position = 'absolute';
trigger.style.top = '30px';
trigger.style.height = '20px';

fixture.componentInstance.offsetY = 45;
fixture.componentInstance.isOpen = true;
fixture.detectChanges();

// expected y value is the starting y + trigger height + offset y
// 30 + 20 + 45 = 95px
const pane = overlayContainerElement.children[0] as HTMLElement;
expect(pane.style.transform).toContain(`translateY(95px)`);
});

});

});
Expand All @@ -131,14 +162,16 @@ describe('Overlay directives', () => {
<button overlay-origin #trigger="overlayOrigin">Toggle menu</button>
<template connected-overlay [origin]="trigger" [open]="isOpen" [width]="width" [height]="height"
[hasBackdrop]="hasBackdrop" backdropClass="md-test-class"
(backdropClick)="backdropClicked=true">
(backdropClick)="backdropClicked=true" [offsetX]="offsetX" [offsetY]="offsetY">
<p>Menu content</p>
</template>`,
})
class ConnectedOverlayDirectiveTest {
isOpen = false;
width: number | string;
height: number | string;
offsetX: number = 0;
offsetY: number = 0;
hasBackdrop: boolean;
backdropClicked = false;

Expand Down
19 changes: 16 additions & 3 deletions src/lib/core/overlay/overlay-directives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ export class ConnectedOverlayDirective implements OnDestroy {
@Input() origin: OverlayOrigin;
@Input() positions: ConnectionPositionPair[];

/** The offset in pixels for the overlay connection point on the x-axis */
@Input() offsetX: number = 0;

/** The offset in pixels for the overlay connection point on the y-axis */
@Input() offsetY: number = 0;

/** The width of the overlay panel. */
@Input() width: number | string;

Expand Down Expand Up @@ -150,20 +156,27 @@ export class ConnectedOverlayDirective implements OnDestroy {
overlayConfig.backdropClass = this.backdropClass;
}

overlayConfig.positionStrategy = this._getPosition();
overlayConfig.positionStrategy = this._setUpPosition();

overlayConfig.direction = this.dir;

return overlayConfig;
}

/** Sets up the position strategy with its initial configuration. */
private _setUpPosition(): ConnectedPositionStrategy {
return this._getPosition()
.withDirection(this.dir)
.withOffsetX(this.offsetX)
.withOffsetY(this.offsetY);
}

/** Returns the position of the overlay to be set on the overlay config */
private _getPosition(): ConnectedPositionStrategy {
return this._overlay.position().connectedTo(
this.origin.elementRef,
{originX: this.positions[0].overlayX, originY: this.positions[0].originY},
{overlayX: this.positions[0].overlayX, overlayY: this.positions[0].overlayY})
.setDirection(this.dir);
{overlayX: this.positions[0].overlayX, overlayY: this.positions[0].overlayY});
}

/** Attaches the overlay and subscribes to backdrop clicks if backdrop exists */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,14 +215,45 @@ describe('ConnectedPositionStrategy', () => {
fakeElementRef,
{originX: 'start', originY: 'bottom'},
{overlayX: 'start', overlayY: 'top'})
.setDirection('rtl');
.withDirection('rtl');

strategy.apply(overlayElement);

let overlayRect = overlayElement.getBoundingClientRect();
expect(overlayRect.top).toBe(originRect.bottom);
expect(overlayRect.right).toBe(originRect.right);
});

it('should position a panel with the x offset provided', () => {
originRect = originElement.getBoundingClientRect();
strategy = positionBuilder.connectedTo(
fakeElementRef,
{originX: 'start', originY: 'top'},
{overlayX: 'start', overlayY: 'top'});

strategy.withOffsetX(10);
strategy.apply(overlayElement);

let overlayRect = overlayElement.getBoundingClientRect();
expect(overlayRect.top).toBe(originRect.top);
expect(overlayRect.left).toBe(originRect.left + 10);
});

it('should position a panel with the y offset provided', () => {
originRect = originElement.getBoundingClientRect();
strategy = positionBuilder.connectedTo(
fakeElementRef,
{originX: 'start', originY: 'top'},
{overlayX: 'start', overlayY: 'top'});

strategy.withOffsetY(50);
strategy.apply(overlayElement);

let overlayRect = overlayElement.getBoundingClientRect();
expect(overlayRect.top).toBe(originRect.top + 50);
expect(overlayRect.left).toBe(originRect.left);
});

});


Expand Down
26 changes: 22 additions & 4 deletions src/lib/core/overlay/position/connected-position-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,20 @@ import {

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

/** The offset in pixels for the overlay connection point on the x-axis */
private _offsetX: number = 0;

/** The offset in pixels for the overlay connection point on the y-axis */
private _offsetY: number = 0;

/** Whether the we're dealing with an RTL context */
get _isRtl() {
return this._dir === 'rtl';
Expand Down Expand Up @@ -89,11 +95,23 @@ export class ConnectedPositionStrategy implements PositionStrategy {
}

/** Sets the layout direction so the overlay's position can be adjusted to match. */
setDirection(dir: 'ltr' | 'rtl') {
withDirection(dir: 'ltr' | 'rtl'): ConnectedPositionStrategy {
this._dir = dir;
return this;
}

/** Sets an offset for the overlay's connection point on the x-axis */
withOffsetX(offset: number): ConnectedPositionStrategy {
this._offsetX = offset;
return this;
}

/** Sets an offset for the overlay's connection point on the y-axis */
withOffsetY(offset: number): ConnectedPositionStrategy {
this._offsetY = offset;
return this;
}

/**
* Gets the horizontal (x) "start" dimension based on whether the overlay is in an RTL context.
* @param rect
Expand Down Expand Up @@ -168,8 +186,8 @@ export class ConnectedPositionStrategy implements PositionStrategy {
}

return {
x: originPoint.x + overlayStartX,
y: originPoint.y + overlayStartY
x: originPoint.x + overlayStartX + this._offsetX,
y: originPoint.y + overlayStartY + this._offsetY
};
}

Expand Down

0 comments on commit 9180afc

Please sign in to comment.