mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
chore(overlay): improve connected pos strategy api (#464)
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import {it, describe, expect, beforeEach} from '@angular/core/testing';
|
||||
import {ElementRef} from '@angular/core';
|
||||
import {ConnectedPositionStrategy, RelativePosition} from './connected-position-strategy';
|
||||
import {ConnectedPositionStrategy} from './connected-position-strategy';
|
||||
import {ViewportRuler} from './viewport-ruler';
|
||||
import {OverlayPositionBuilder} from './overlay-position-builder';
|
||||
|
||||
|
||||
// Default width and height of the overlay and origin panels throughout these tests.
|
||||
@@ -25,6 +26,7 @@ export function main() {
|
||||
let strategy: ConnectedPositionStrategy;
|
||||
let fakeElementRef: ElementRef;
|
||||
let fakeViewportRuler: FakeViewportRuler;
|
||||
let positionBuilder: OverlayPositionBuilder;
|
||||
|
||||
let originRect: ClientRect;
|
||||
let originCenterX: number;
|
||||
@@ -40,8 +42,7 @@ export function main() {
|
||||
document.body.appendChild(overlayElement);
|
||||
|
||||
fakeElementRef = new FakeElementRef(originElement);
|
||||
|
||||
strategy = new ConnectedPositionStrategy(fakeElementRef, new ViewportRuler());
|
||||
positionBuilder = new OverlayPositionBuilder(new ViewportRuler());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -118,22 +119,13 @@ export function main() {
|
||||
originElement.style.left = '200px';
|
||||
originRect = originElement.getBoundingClientRect();
|
||||
|
||||
// Above, right aligned.
|
||||
let offscreenPos = new RelativePosition();
|
||||
offscreenPos.originX = 'end';
|
||||
offscreenPos.originY = 'start';
|
||||
offscreenPos.overlayX = 'end';
|
||||
offscreenPos.overlayY = 'end';
|
||||
|
||||
// Below, left aligned.
|
||||
let fallbackPosition = new RelativePosition();
|
||||
fallbackPosition.originX = 'start';
|
||||
fallbackPosition.originY = 'end';
|
||||
fallbackPosition.overlayX = 'start';
|
||||
fallbackPosition.overlayY = 'start';
|
||||
|
||||
strategy.addPreferredPosition(offscreenPos);
|
||||
strategy.addPreferredPosition(fallbackPosition);
|
||||
strategy = positionBuilder.connectedTo(
|
||||
fakeElementRef,
|
||||
{originX: 'end', originY: 'top'},
|
||||
{overlayX: 'end', overlayY: 'bottom'})
|
||||
.withFallbackPosition(
|
||||
{originX: 'start', originY: 'bottom'},
|
||||
{overlayX: 'start', overlayY: 'top'});
|
||||
|
||||
strategy.apply(overlayElement);
|
||||
|
||||
@@ -151,22 +143,13 @@ export function main() {
|
||||
originRect = originElement.getBoundingClientRect();
|
||||
originCenterY = originRect.top + (ORIGIN_HEIGHT / 2);
|
||||
|
||||
// To the left, below.
|
||||
let offscreenPos = new RelativePosition();
|
||||
offscreenPos.originX = 'start';
|
||||
offscreenPos.originY = 'end';
|
||||
offscreenPos.overlayX = 'end';
|
||||
offscreenPos.overlayY = 'start';
|
||||
|
||||
// To the right, centered.
|
||||
let fallbackPos = new RelativePosition();
|
||||
fallbackPos.originX = 'end';
|
||||
fallbackPos.originY = 'center';
|
||||
fallbackPos.overlayX = 'start';
|
||||
fallbackPos.overlayY = 'center';
|
||||
|
||||
strategy.addPreferredPosition(offscreenPos);
|
||||
strategy.addPreferredPosition(fallbackPos);
|
||||
strategy = positionBuilder.connectedTo(
|
||||
fakeElementRef,
|
||||
{originX: 'start', originY: 'bottom'},
|
||||
{overlayX: 'end', overlayY: 'top'})
|
||||
.withFallbackPosition(
|
||||
{originX: 'end', originY: 'center'},
|
||||
{overlayX: 'start', overlayY: 'center'});
|
||||
|
||||
strategy.apply(overlayElement);
|
||||
|
||||
@@ -180,28 +163,19 @@ export function main() {
|
||||
fakeViewportRuler.fakeRect = {
|
||||
top: 0, left: 0, width: 500, height: 500, right: 500, bottom: 500
|
||||
};
|
||||
strategy = new ConnectedPositionStrategy(fakeElementRef, fakeViewportRuler);
|
||||
positionBuilder = new OverlayPositionBuilder(fakeViewportRuler);
|
||||
|
||||
originElement.style.top = '475px';
|
||||
originElement.style.left = '200px';
|
||||
originRect = originElement.getBoundingClientRect();
|
||||
|
||||
// Below, left aligned.
|
||||
let offscreenPos = new RelativePosition();
|
||||
offscreenPos.originX = 'start';
|
||||
offscreenPos.originY = 'end';
|
||||
offscreenPos.overlayX = 'start';
|
||||
offscreenPos.overlayY = 'start';
|
||||
|
||||
// Above, right aligned.
|
||||
let fallbackPos = new RelativePosition();
|
||||
fallbackPos.originX = 'end';
|
||||
fallbackPos.originY = 'start';
|
||||
fallbackPos.overlayX = 'end';
|
||||
fallbackPos.overlayY = 'end';
|
||||
|
||||
strategy.addPreferredPosition(offscreenPos);
|
||||
strategy.addPreferredPosition(fallbackPos);
|
||||
strategy = positionBuilder.connectedTo(
|
||||
fakeElementRef,
|
||||
{originX: 'start', originY: 'bottom'},
|
||||
{overlayX: 'start', overlayY: 'top'})
|
||||
.withFallbackPosition(
|
||||
{originX: 'end', originY: 'top'},
|
||||
{overlayX: 'end', overlayY: 'bottom'});
|
||||
|
||||
strategy.apply(overlayElement);
|
||||
|
||||
@@ -215,28 +189,19 @@ export function main() {
|
||||
fakeViewportRuler.fakeRect = {
|
||||
top: 0, left: 0, width: 500, height: 500, right: 500, bottom: 500
|
||||
};
|
||||
strategy = new ConnectedPositionStrategy(fakeElementRef, fakeViewportRuler);
|
||||
positionBuilder = new OverlayPositionBuilder(fakeViewportRuler);
|
||||
|
||||
originElement.style.top = '200px';
|
||||
originElement.style.left = '475px';
|
||||
originRect = originElement.getBoundingClientRect();
|
||||
|
||||
// To the right, centered.
|
||||
let offscreenPos = new RelativePosition();
|
||||
offscreenPos.originX = 'end';
|
||||
offscreenPos.originY = 'center';
|
||||
offscreenPos.overlayX = 'start';
|
||||
offscreenPos.overlayY = 'center';
|
||||
|
||||
// To the left, below.
|
||||
let fallbackPos = new RelativePosition();
|
||||
fallbackPos.originX = 'start';
|
||||
fallbackPos.originY = 'end';
|
||||
fallbackPos.overlayX = 'end';
|
||||
fallbackPos.overlayY = 'start';
|
||||
|
||||
strategy.addPreferredPosition(offscreenPos);
|
||||
strategy.addPreferredPosition(fallbackPos);
|
||||
strategy = positionBuilder.connectedTo(
|
||||
fakeElementRef,
|
||||
{originX: 'end', originY: 'center'},
|
||||
{overlayX: 'start', overlayY: 'center'})
|
||||
.withFallbackPosition(
|
||||
{originX: 'start', originY: 'bottom'},
|
||||
{overlayX: 'end', overlayY: 'top'});
|
||||
|
||||
strategy.apply(overlayElement);
|
||||
|
||||
@@ -255,12 +220,10 @@ export function main() {
|
||||
*/
|
||||
function runSimplePositionTests() {
|
||||
it('should position a panel below, left-aligned', () => {
|
||||
var pos = new RelativePosition();
|
||||
pos.originX = 'start';
|
||||
pos.originY = 'end';
|
||||
pos.overlayX = 'start';
|
||||
pos.overlayY = 'start';
|
||||
strategy.addPreferredPosition(pos);
|
||||
strategy = positionBuilder.connectedTo(
|
||||
fakeElementRef,
|
||||
{originX: 'start', originY: 'bottom'},
|
||||
{overlayX: 'start', overlayY: 'top'});
|
||||
|
||||
strategy.apply(overlayElement);
|
||||
|
||||
@@ -270,12 +233,10 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should position to the right, center aligned vertically', () => {
|
||||
var pos = new RelativePosition();
|
||||
pos.originX = 'end';
|
||||
pos.originY = 'center';
|
||||
pos.overlayX = 'start';
|
||||
pos.overlayY = 'center';
|
||||
strategy.addPreferredPosition(pos);
|
||||
strategy = positionBuilder.connectedTo(
|
||||
fakeElementRef,
|
||||
{originX: 'end', originY: 'center'},
|
||||
{overlayX: 'start', overlayY: 'center'});
|
||||
|
||||
strategy.apply(overlayElement);
|
||||
|
||||
@@ -285,12 +246,10 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should position to the left, below', () => {
|
||||
var pos = new RelativePosition();
|
||||
pos.originX = 'start';
|
||||
pos.originY = 'end';
|
||||
pos.overlayX = 'end';
|
||||
pos.overlayY = 'start';
|
||||
strategy.addPreferredPosition(pos);
|
||||
strategy = positionBuilder.connectedTo(
|
||||
fakeElementRef,
|
||||
{originX: 'start', originY: 'bottom'},
|
||||
{overlayX: 'end', overlayY: 'top'});
|
||||
|
||||
strategy.apply(overlayElement);
|
||||
|
||||
@@ -300,12 +259,10 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should position above, right aligned', () => {
|
||||
var pos = new RelativePosition();
|
||||
pos.originX = 'end';
|
||||
pos.originY = 'start';
|
||||
pos.overlayX = 'end';
|
||||
pos.overlayY = 'end';
|
||||
strategy.addPreferredPosition(pos);
|
||||
strategy = positionBuilder.connectedTo(
|
||||
fakeElementRef,
|
||||
{originX: 'end', originY: 'top'},
|
||||
{overlayX: 'end', overlayY: 'bottom'});
|
||||
|
||||
strategy.apply(overlayElement);
|
||||
|
||||
@@ -315,12 +272,10 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should position below, centered', () => {
|
||||
var pos = new RelativePosition();
|
||||
pos.originX = 'center';
|
||||
pos.originY = 'end';
|
||||
pos.overlayX = 'center';
|
||||
pos.overlayY = 'start';
|
||||
strategy.addPreferredPosition(pos);
|
||||
strategy = positionBuilder.connectedTo(
|
||||
fakeElementRef,
|
||||
{originX: 'center', originY: 'bottom'},
|
||||
{overlayX: 'center', overlayY: 'top'});
|
||||
|
||||
strategy.apply(overlayElement);
|
||||
|
||||
@@ -330,12 +285,10 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should center the overlay on the origin', () => {
|
||||
var pos = new RelativePosition();
|
||||
pos.originX = 'center';
|
||||
pos.originY = 'center';
|
||||
pos.overlayX = 'center';
|
||||
pos.overlayY = 'center';
|
||||
strategy.addPreferredPosition(pos);
|
||||
strategy = positionBuilder.connectedTo(
|
||||
fakeElementRef,
|
||||
{originX: 'center', originY: 'center'},
|
||||
{overlayX: 'center', overlayY: 'center'});
|
||||
|
||||
strategy.apply(overlayElement);
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import {PositionStrategy} from './position-strategy';
|
||||
import {ElementRef} from '@angular/core';
|
||||
import {applyCssTransform} from '@angular2-material/core/style/apply-transform';
|
||||
import {ViewportRuler} from './viewport-ruler';
|
||||
import {applyCssTransform} from '@angular2-material/core/style/apply-transform';
|
||||
import {ConnectionPair, OriginPos, OverlayPos} from './connected-position';
|
||||
|
||||
|
||||
|
||||
@@ -18,20 +19,26 @@ export class ConnectedPositionStrategy implements PositionStrategy {
|
||||
_isRtl: boolean = false;
|
||||
|
||||
/** Ordered list of preferred positions, from most to least desirable. */
|
||||
_preferredPositions: RelativePosition[] = [];
|
||||
_preferredPositions: ConnectionPair[] = [];
|
||||
|
||||
/** The origin element against which the overlay will be positioned. */
|
||||
private _origin: HTMLElement;
|
||||
|
||||
|
||||
constructor(private _connectedTo: ElementRef, private _viewportRuler: ViewportRuler) {
|
||||
constructor(
|
||||
private _connectedTo: ElementRef,
|
||||
private _originPos: OriginPos,
|
||||
private _overlayPos: OverlayPos,
|
||||
private _viewportRuler: ViewportRuler) {
|
||||
this._origin = this._connectedTo.nativeElement;
|
||||
this.withFallbackPosition(_originPos, _overlayPos);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the position of the overlay element, using whichever preferred position relative
|
||||
* to the origin fits on-screen.
|
||||
* @internal
|
||||
*/
|
||||
apply(element: HTMLElement): Promise<void> {
|
||||
// We need the bounding rects for the origin and the overlay to determine how to position
|
||||
@@ -67,16 +74,21 @@ export class ConnectedPositionStrategy implements PositionStrategy {
|
||||
|
||||
|
||||
/** Adds a preferred position to the end of the ordered preferred position list. */
|
||||
addPreferredPosition(pos: RelativePosition): void {
|
||||
addPreferredPosition(pos: ConnectionPair): void {
|
||||
this._preferredPositions.push(pos);
|
||||
}
|
||||
|
||||
withFallbackPosition(originPos: OriginPos, overlayPos: OverlayPos): this {
|
||||
this._preferredPositions.push(new ConnectionPair(originPos, overlayPos));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the horizontal (x) "start" dimension based on whether the overlay is in an RTL context.
|
||||
* @param rect
|
||||
*/
|
||||
_getStartX(rect: ClientRect): number {
|
||||
private _getStartX(rect: ClientRect): number {
|
||||
return this._isRtl ? rect.right : rect.left;
|
||||
}
|
||||
|
||||
@@ -84,7 +96,7 @@ export class ConnectedPositionStrategy implements PositionStrategy {
|
||||
* Gets the horizontal (x) "end" dimension based on whether the overlay is in an RTL context.
|
||||
* @param rect
|
||||
*/
|
||||
_getEndX(rect: ClientRect): number {
|
||||
private _getEndX(rect: ClientRect): number {
|
||||
return this._isRtl ? rect.left : rect.right;
|
||||
}
|
||||
|
||||
@@ -94,7 +106,7 @@ export class ConnectedPositionStrategy implements PositionStrategy {
|
||||
* @param originRect
|
||||
* @param pos
|
||||
*/
|
||||
private _getOriginConnectionPoint(originRect: ClientRect, pos: RelativePosition): Point {
|
||||
private _getOriginConnectionPoint(originRect: ClientRect, pos: ConnectionPair): Point {
|
||||
const originStartX = this._getStartX(originRect);
|
||||
const originEndX = this._getEndX(originRect);
|
||||
|
||||
@@ -109,7 +121,7 @@ export class ConnectedPositionStrategy implements PositionStrategy {
|
||||
if (pos.originY == 'center') {
|
||||
y = originRect.top + (originRect.height / 2);
|
||||
} else {
|
||||
y = pos.originY == 'start' ? originRect.top : originRect.bottom;
|
||||
y = pos.originY == 'top' ? originRect.top : originRect.bottom;
|
||||
}
|
||||
|
||||
return {x, y};
|
||||
@@ -126,7 +138,7 @@ export class ConnectedPositionStrategy implements PositionStrategy {
|
||||
private _getOverlayPoint(
|
||||
originPoint: Point,
|
||||
overlayRect: ClientRect,
|
||||
pos: RelativePosition): Point {
|
||||
pos: ConnectionPair): Point {
|
||||
// Calculate the (overlayStartX, overlayStartY), the start of the potential overlay position
|
||||
// relative to the origin point.
|
||||
let overlayStartX: number;
|
||||
@@ -140,7 +152,7 @@ export class ConnectedPositionStrategy implements PositionStrategy {
|
||||
if (pos.overlayY == 'center') {
|
||||
overlayStartY = -overlayRect.height / 2;
|
||||
} else {
|
||||
overlayStartY = pos.overlayY == 'start' ? 0 : -overlayRect.height;
|
||||
overlayStartY = pos.overlayY == 'top' ? 0 : -overlayRect.height;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -191,14 +203,3 @@ export class ConnectedPositionStrategy implements PositionStrategy {
|
||||
type Point = {x: number, y: number};
|
||||
|
||||
|
||||
/** One dimension of a connection point on the perimeter of the origin or overlay element. */
|
||||
export type ConnectionPoint = 'start' | 'center' | 'end';
|
||||
|
||||
|
||||
/** The points of the origin element and the overlay element to connect. */
|
||||
export class RelativePosition {
|
||||
originX: ConnectionPoint;
|
||||
originY: ConnectionPoint;
|
||||
overlayX: ConnectionPoint;
|
||||
overlayY: ConnectionPoint;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/** Horizontal dimension of a connection point on the perimeter of the origin or overlay element. */
|
||||
export type HorizontalConnectionPos = 'start' | 'center' | 'end';
|
||||
|
||||
/** Vertical dimension of a connection point on the perimeter of the origin or overlay element. */
|
||||
export type VerticalConnectionPos = 'top' | 'center' | 'bottom';
|
||||
|
||||
|
||||
/** A connection point on the origin element. */
|
||||
export interface OriginPos {
|
||||
originX: HorizontalConnectionPos;
|
||||
originY: VerticalConnectionPos;
|
||||
}
|
||||
|
||||
/** A connection point on the overlay element. */
|
||||
export interface OverlayPos {
|
||||
overlayX: HorizontalConnectionPos;
|
||||
overlayY: VerticalConnectionPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* The points of the origin element and the overlay element to connect.
|
||||
* @internal
|
||||
*/
|
||||
export class ConnectionPair {
|
||||
originX: HorizontalConnectionPos;
|
||||
originY: VerticalConnectionPos;
|
||||
overlayX: HorizontalConnectionPos;
|
||||
overlayY: VerticalConnectionPos;
|
||||
|
||||
constructor(origin: OriginPos, overlay: OverlayPos) {
|
||||
this.originX = origin.originX;
|
||||
this.originY = origin.originY;
|
||||
this.overlayX = overlay.overlayX;
|
||||
this.overlayY = overlay.overlayY;
|
||||
}
|
||||
}
|
||||
@@ -85,7 +85,10 @@ export class GlobalPositionStrategy implements PositionStrategy {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Apply the position to the element. */
|
||||
/**
|
||||
* Apply the position to the element.
|
||||
* @internal
|
||||
*/
|
||||
apply(element: HTMLElement): Promise<void> {
|
||||
element.style.position = this._cssPosition;
|
||||
element.style.top = this._top;
|
||||
|
||||
@@ -2,6 +2,7 @@ import {ViewportRuler} from './viewport-ruler';
|
||||
import {ConnectedPositionStrategy} from './connected-position-strategy';
|
||||
import {ElementRef, Injectable} from '@angular/core';
|
||||
import {GlobalPositionStrategy} from './global-position-strategy';
|
||||
import {OverlayPos, OriginPos} from './connected-position';
|
||||
|
||||
|
||||
|
||||
@@ -16,8 +17,8 @@ export class OverlayPositionBuilder {
|
||||
}
|
||||
|
||||
/** Creates a relative position strategy. */
|
||||
connectedTo(elementRef: ElementRef) {
|
||||
return new ConnectedPositionStrategy(elementRef, this._viewportRuler);
|
||||
connectedTo(elementRef: ElementRef, originPos: OriginPos, overlayPos: OverlayPos) {
|
||||
return new ConnectedPositionStrategy(elementRef, originPos, overlayPos, this._viewportRuler);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@ import {
|
||||
import {Overlay, OverlayState, OVERLAY_PROVIDERS} from '@angular2-material/core/overlay/overlay';
|
||||
import {ComponentPortal, Portal} from '@angular2-material/core/portal/portal';
|
||||
import {TemplatePortalDirective} from '@angular2-material/core/portal/portal-directives';
|
||||
import {RelativePosition} from
|
||||
'@angular2-material/core/overlay/position/connected-position-strategy';
|
||||
|
||||
|
||||
|
||||
@@ -70,15 +68,12 @@ export class OverlayDemo {
|
||||
}
|
||||
|
||||
openSpaghettiPanel() {
|
||||
// TODO(jelbourn): make a nice API for this.
|
||||
// TODO(jelbourn): separate overlay demo for connected positioning.
|
||||
let strategy = this.overlay.position().connectedTo(this._overlayOrigin.elementRef);
|
||||
var pos = new RelativePosition();
|
||||
pos.originX = 'start';
|
||||
pos.originY = 'end';
|
||||
pos.overlayX = 'start';
|
||||
pos.overlayY = 'start';
|
||||
strategy.addPreferredPosition(pos);
|
||||
let strategy = this.overlay.position()
|
||||
.connectedTo(
|
||||
this._overlayOrigin.elementRef,
|
||||
{originX: 'start', originY: 'bottom'},
|
||||
{overlayX: 'start', overlayY: 'top'} );
|
||||
|
||||
let config = new OverlayState();
|
||||
config.positionStrategy = strategy;
|
||||
|
||||
Reference in New Issue
Block a user