mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(overlay): support custom offsets (#1660)
This commit is contained in:
@@ -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';
|
||||
@@ -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)`);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -131,7 +162,7 @@ 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>`,
|
||||
})
|
||||
@@ -139,6 +170,8 @@ class ConnectedOverlayDirectiveTest {
|
||||
isOpen = false;
|
||||
width: number | string;
|
||||
height: number | string;
|
||||
offsetX: number = 0;
|
||||
offsetY: number = 0;
|
||||
hasBackdrop: boolean;
|
||||
backdropClicked = false;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -150,20 +156,24 @@ export class ConnectedOverlayDirective implements OnDestroy {
|
||||
overlayConfig.backdropClass = this.backdropClass;
|
||||
}
|
||||
|
||||
overlayConfig.positionStrategy = this._getPosition();
|
||||
overlayConfig.positionStrategy = this._createPositionStrategy();
|
||||
|
||||
overlayConfig.direction = this.dir;
|
||||
|
||||
return overlayConfig;
|
||||
}
|
||||
|
||||
/** 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);
|
||||
/** Returns the position strategy of the overlay to be set on the overlay config */
|
||||
private _createPositionStrategy(): ConnectedPositionStrategy {
|
||||
const pos = this.positions[0];
|
||||
const originPoint = {originX: pos.originX, originY: pos.originY};
|
||||
const overlayPoint = {overlayX: pos.overlayX, overlayY: pos.overlayY};
|
||||
|
||||
return this._overlay.position()
|
||||
.connectedTo(this.origin.elementRef, originPoint, overlayPoint)
|
||||
.withDirection(this.dir)
|
||||
.withOffsetX(this.offsetX)
|
||||
.withOffsetY(this.offsetY);
|
||||
}
|
||||
|
||||
/** Attaches the overlay and subscribes to backdrop clicks if backdrop exists */
|
||||
|
||||
@@ -215,7 +215,7 @@ describe('ConnectedPositionStrategy', () => {
|
||||
fakeElementRef,
|
||||
{originX: 'start', originY: 'bottom'},
|
||||
{overlayX: 'start', overlayY: 'top'})
|
||||
.setDirection('rtl');
|
||||
.withDirection('rtl');
|
||||
|
||||
strategy.apply(overlayElement);
|
||||
|
||||
@@ -223,6 +223,37 @@ describe('ConnectedPositionStrategy', () => {
|
||||
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);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ 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.
|
||||
@@ -19,6 +19,12 @@ import {
|
||||
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';
|
||||
@@ -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'): this {
|
||||
this._dir = dir;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets an offset for the overlay's connection point on the x-axis */
|
||||
withOffsetX(offset: number): this {
|
||||
this._offsetX = offset;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets an offset for the overlay's connection point on the y-axis */
|
||||
withOffsetY(offset: number): this {
|
||||
this._offsetY = offset;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the horizontal (x) "start" dimension based on whether the overlay is in an RTL context.
|
||||
* @param rect
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user