chore(overlay): add attach method to PositionStrategy interface (#6505)

This commit is contained in:
Jeremy Elbourn
2017-08-22 10:13:23 -07:00
committed by Kara
parent 215ff3c77c
commit 0db3635f01
7 changed files with 122 additions and 67 deletions
+5 -1
View File
@@ -45,6 +45,10 @@ export class OverlayRef implements PortalHost {
attach(portal: Portal<any>): any {
let attachResult = this._portalHost.attach(portal);
if (this._state.positionStrategy) {
this._state.positionStrategy.attach(this);
}
// Update the pane element with the given state configuration.
this._updateStackingOrder();
this.updateSize();
@@ -146,7 +150,7 @@ export class OverlayRef implements PortalHost {
/** Updates the position of the overlay based on the position strategy. */
updatePosition() {
if (this._state.positionStrategy) {
this._state.positionStrategy.apply(this._pane);
this._state.positionStrategy.apply();
}
}
+8 -3
View File
@@ -532,9 +532,14 @@ class OverlayTestModule { }
class OverlayContainerThemingTestModule { }
class FakePositionStrategy implements PositionStrategy {
apply(element: Element): Promise<null> {
element.classList.add('fake-positioned');
return Promise.resolve(null);
element: HTMLElement;
apply(): void {
this.element.classList.add('fake-positioned');
}
attach(overlayRef: OverlayRef) {
this.element = overlayRef.overlayElement;
}
dispose() {}
@@ -7,6 +7,7 @@ import {ConnectedOverlayPositionChange} from './connected-position';
import {Scrollable} from '../scroll/scrollable';
import {Subscription} from 'rxjs/Subscription';
import {ScrollDispatchModule} from '../scroll/index';
import {OverlayRef} from '../overlay-ref';
// Default width and height of the overlay and origin panels throughout these tests.
@@ -142,7 +143,8 @@ describe('ConnectedPositionStrategy', () => {
{originX: 'start', originY: 'bottom'},
{overlayX: 'start', overlayY: 'top'});
strategy.apply(overlayElement);
strategy.attach(fakeOverlayRef(overlayElement));
strategy.apply();
let overlayRect = overlayElement.getBoundingClientRect();
expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect.bottom));
@@ -166,7 +168,8 @@ describe('ConnectedPositionStrategy', () => {
{originX: 'end', originY: 'center'},
{overlayX: 'start', overlayY: 'center'});
strategy.apply(overlayElement);
strategy.attach(fakeOverlayRef(overlayElement));
strategy.apply();
let overlayRect = overlayElement.getBoundingClientRect();
expect(Math.floor(overlayRect.top)).toBe(Math.floor(originCenterY - (OVERLAY_HEIGHT / 2)));
@@ -188,7 +191,8 @@ describe('ConnectedPositionStrategy', () => {
{originX: 'end', originY: 'top'},
{overlayX: 'end', overlayY: 'bottom'});
strategy.apply(overlayElement);
strategy.attach(fakeOverlayRef(overlayElement));
strategy.apply();
let overlayRect = overlayElement.getBoundingClientRect();
expect(Math.floor(overlayRect.bottom)).toBe(Math.floor(originRect.top));
@@ -210,7 +214,8 @@ describe('ConnectedPositionStrategy', () => {
{originX: 'start', originY: 'bottom'},
{overlayX: 'end', overlayY: 'top'});
strategy.apply(overlayElement);
strategy.attach(fakeOverlayRef(overlayElement));
strategy.apply();
let overlayRect = overlayElement.getBoundingClientRect();
@@ -234,7 +239,8 @@ describe('ConnectedPositionStrategy', () => {
{overlayX: 'start', overlayY: 'bottom'});
// This should apply the fallback position, as the original position won't fit.
strategy.apply(overlayElement);
strategy.attach(fakeOverlayRef(overlayElement));
strategy.apply();
// Now make the overlay small enough to fit in the first preferred position.
overlayElement.style.height = '15px';
@@ -260,7 +266,8 @@ describe('ConnectedPositionStrategy', () => {
{originX: 'start', originY: 'top'},
{overlayX: 'start', overlayY: 'bottom'});
strategy.apply(overlayElement);
strategy.attach(fakeOverlayRef(overlayElement));
strategy.apply();
strategy.recalculateLastPosition();
let overlayRect = overlayElement.getBoundingClientRect();
@@ -279,7 +286,8 @@ describe('ConnectedPositionStrategy', () => {
{overlayX: 'start', overlayY: 'top'})
.withDirection('rtl');
strategy.apply(overlayElement);
strategy.attach(fakeOverlayRef(overlayElement));
strategy.apply();
let overlayRect = overlayElement.getBoundingClientRect();
expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect.bottom));
@@ -294,7 +302,8 @@ describe('ConnectedPositionStrategy', () => {
{overlayX: 'start', overlayY: 'top'});
strategy.withOffsetX(10);
strategy.apply(overlayElement);
strategy.attach(fakeOverlayRef(overlayElement));
strategy.apply();
let overlayRect = overlayElement.getBoundingClientRect();
expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect.top));
@@ -309,7 +318,8 @@ describe('ConnectedPositionStrategy', () => {
{overlayX: 'start', overlayY: 'top'});
strategy.withOffsetY(50);
strategy.apply(overlayElement);
strategy.attach(fakeOverlayRef(overlayElement));
strategy.apply();
let overlayRect = overlayElement.getBoundingClientRect();
expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect.top + 50));
@@ -334,7 +344,8 @@ describe('ConnectedPositionStrategy', () => {
const positionChangeHandler = jasmine.createSpy('positionChangeHandler');
const subscription = strategy.onPositionChange.subscribe(positionChangeHandler);
strategy.apply(overlayElement);
strategy.attach(fakeOverlayRef(overlayElement));
strategy.apply();
const latestCall = positionChangeHandler.calls.mostRecent();
@@ -346,7 +357,8 @@ describe('ConnectedPositionStrategy', () => {
// the position change event should be emitted again.
originElement.style.top = '200px';
originElement.style.left = '200px';
strategy.apply(overlayElement);
strategy.attach(fakeOverlayRef(overlayElement));
strategy.apply();
expect(positionChangeHandler).toHaveBeenCalledTimes(2);
@@ -369,7 +381,8 @@ describe('ConnectedPositionStrategy', () => {
const positionChangeHandler = jasmine.createSpy('positionChangeHandler');
const subscription = strategy.onPositionChange.subscribe(positionChangeHandler);
strategy.apply(overlayElement);
strategy.attach(fakeOverlayRef(overlayElement));
strategy.apply();
expect(positionChangeHandler).toHaveBeenCalled();
@@ -394,7 +407,8 @@ describe('ConnectedPositionStrategy', () => {
{originX: 'end', originY: 'top'},
{overlayX: 'end', overlayY: 'top'});
strategy.apply(overlayElement);
strategy.attach(fakeOverlayRef(overlayElement));
strategy.apply();
let overlayRect = overlayElement.getBoundingClientRect();
@@ -415,7 +429,8 @@ describe('ConnectedPositionStrategy', () => {
{originX: 'start', originY: 'bottom'},
{overlayX: 'start', overlayY: 'top'});
strategy.apply(overlayElement);
strategy.attach(fakeOverlayRef(overlayElement));
strategy.apply();
let overlayRect = overlayElement.getBoundingClientRect();
expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect!.bottom));
@@ -428,7 +443,8 @@ describe('ConnectedPositionStrategy', () => {
{originX: 'end', originY: 'center'},
{overlayX: 'start', overlayY: 'center'});
strategy.apply(overlayElement);
strategy.attach(fakeOverlayRef(overlayElement));
strategy.apply();
let overlayRect = overlayElement.getBoundingClientRect();
expect(Math.floor(overlayRect.top)).toBe(Math.floor(originCenterY! - (OVERLAY_HEIGHT / 2)));
@@ -441,7 +457,8 @@ describe('ConnectedPositionStrategy', () => {
{originX: 'start', originY: 'bottom'},
{overlayX: 'end', overlayY: 'top'});
strategy.apply(overlayElement);
strategy.attach(fakeOverlayRef(overlayElement));
strategy.apply();
let overlayRect = overlayElement.getBoundingClientRect();
@@ -455,7 +472,8 @@ describe('ConnectedPositionStrategy', () => {
{originX: 'end', originY: 'top'},
{overlayX: 'end', overlayY: 'bottom'});
strategy.apply(overlayElement);
strategy.attach(fakeOverlayRef(overlayElement));
strategy.apply();
let overlayRect = overlayElement.getBoundingClientRect();
expect(Math.round(overlayRect.bottom)).toBe(Math.round(originRect!.top));
@@ -468,7 +486,8 @@ describe('ConnectedPositionStrategy', () => {
{originX: 'center', originY: 'bottom'},
{overlayX: 'center', overlayY: 'top'});
strategy.apply(overlayElement);
strategy.attach(fakeOverlayRef(overlayElement));
strategy.apply();
let overlayRect = overlayElement.getBoundingClientRect();
expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect!.bottom));
@@ -481,7 +500,8 @@ describe('ConnectedPositionStrategy', () => {
{originX: 'center', originY: 'center'},
{overlayX: 'center', overlayY: 'center'});
strategy.apply(overlayElement);
strategy.attach(fakeOverlayRef(overlayElement));
strategy.apply();
let overlayRect = overlayElement.getBoundingClientRect();
expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect!.top));
@@ -526,7 +546,7 @@ describe('ConnectedPositionStrategy', () => {
strategy.withScrollableContainers([
new Scrollable(new FakeElementRef(scrollable), null!, null!, null!)]);
strategy.attach(fakeOverlayRef(overlayElement));
positionChangeHandler = jasmine.createSpy('positionChangeHandler');
onPositionChangeSubscription = strategy.onPositionChange.subscribe(positionChangeHandler);
});
@@ -538,7 +558,7 @@ describe('ConnectedPositionStrategy', () => {
});
it('should not have origin or overlay clipped or out of view without scroll', () => {
strategy.apply(overlayElement);
strategy.apply();
expect(positionChangeHandler).toHaveBeenCalled();
positionChange = positionChangeHandler.calls.mostRecent().args[0];
@@ -552,7 +572,7 @@ describe('ConnectedPositionStrategy', () => {
it('should evaluate if origin is clipped if scrolled slightly down', () => {
scrollable.scrollTop = 10; // Clip the origin by 10 pixels
strategy.apply(overlayElement);
strategy.apply();
expect(positionChangeHandler).toHaveBeenCalled();
positionChange = positionChangeHandler.calls.mostRecent().args[0];
@@ -566,7 +586,7 @@ describe('ConnectedPositionStrategy', () => {
it('should evaluate if origin is out of view and overlay is clipped if scrolled enough', () => {
scrollable.scrollTop = 31; // Origin is 30 pixels, move out of view and clip the overlay 1px
strategy.apply(overlayElement);
strategy.apply();
expect(positionChangeHandler).toHaveBeenCalled();
positionChange = positionChangeHandler.calls.mostRecent().args[0];
@@ -580,7 +600,7 @@ describe('ConnectedPositionStrategy', () => {
it('should evaluate the overlay and origin are both out of the view', () => {
scrollable.scrollTop = 61; // Scroll by overlay height + origin height + 1px buffer
strategy.apply(overlayElement);
strategy.apply();
expect(positionChangeHandler).toHaveBeenCalled();
positionChange = positionChangeHandler.calls.mostRecent().args[0];
@@ -626,7 +646,8 @@ describe('ConnectedPositionStrategy', () => {
{originX: 'start', originY: 'top'},
{overlayX: 'start', overlayY: 'top'});
strategy.apply(overlayElement);
strategy.attach(fakeOverlayRef(overlayElement));
strategy.apply();
expect(overlayElement.style.left).toBeTruthy();
expect(overlayElement.style.right).toBeFalsy();
});
@@ -637,7 +658,8 @@ describe('ConnectedPositionStrategy', () => {
{originX: 'end', originY: 'top'},
{overlayX: 'end', overlayY: 'top'});
strategy.apply(overlayElement);
strategy.attach(fakeOverlayRef(overlayElement));
strategy.apply();
expect(overlayElement.style.right).toBeTruthy();
expect(overlayElement.style.left).toBeFalsy();
});
@@ -653,7 +675,8 @@ describe('ConnectedPositionStrategy', () => {
)
.withDirection('rtl');
strategy.apply(overlayElement);
strategy.attach(fakeOverlayRef(overlayElement));
strategy.apply();
expect(overlayElement.style.right).toBeTruthy();
expect(overlayElement.style.left).toBeFalsy();
});
@@ -665,7 +688,8 @@ describe('ConnectedPositionStrategy', () => {
{overlayX: 'end', overlayY: 'top'}
).withDirection('rtl');
strategy.apply(overlayElement);
strategy.attach(fakeOverlayRef(overlayElement));
strategy.apply();
expect(overlayElement.style.left).toBeTruthy();
expect(overlayElement.style.right).toBeFalsy();
});
@@ -679,7 +703,8 @@ describe('ConnectedPositionStrategy', () => {
{overlayX: 'start', overlayY: 'top'}
);
strategy.apply(overlayElement);
strategy.attach(fakeOverlayRef(overlayElement));
strategy.apply();
expect(overlayElement.style.top).toBeTruthy();
expect(overlayElement.style.bottom).toBeFalsy();
});
@@ -691,7 +716,8 @@ describe('ConnectedPositionStrategy', () => {
{overlayX: 'start', overlayY: 'bottom'}
);
strategy.apply(overlayElement);
strategy.attach(fakeOverlayRef(overlayElement));
strategy.apply();
expect(overlayElement.style.bottom).toBeTruthy();
expect(overlayElement.style.top).toBeFalsy();
});
@@ -741,3 +767,7 @@ function createOverflowContainerElement() {
class FakeElementRef implements ElementRef {
constructor(public nativeElement: HTMLElement) { }
}
function fakeOverlayRef(overlayElement: HTMLElement) {
return {overlayElement} as OverlayRef;
}
@@ -20,6 +20,8 @@ import {Subject} from 'rxjs/Subject';
import {Observable} from 'rxjs/Observable';
import {Scrollable} from '../scroll/scrollable';
import {isElementScrolledOutsideView, isElementClippedByScrolling} from './scroll-clip';
import {OverlayRef} from '../overlay-ref';
/**
@@ -30,6 +32,9 @@ import {isElementScrolledOutsideView, isElementClippedByScrolling} from './scrol
* of the overlay.
*/
export class ConnectedPositionStrategy implements PositionStrategy {
/** The overlay to which this strategy is attached. */
private _overlayRef: OverlayRef;
private _dir = 'ltr';
/** The offset in pixels for the overlay connection point on the x-axis */
@@ -80,9 +85,12 @@ export class ConnectedPositionStrategy implements PositionStrategy {
return this._preferredPositions;
}
/**
* To be used to for any cleanup after the element gets destroyed.
*/
attach(overlayRef: OverlayRef): void {
this._overlayRef = overlayRef;
this._pane = overlayRef.overlayElement;
}
/** Performs any cleanup after the element is destroyed. */
dispose() { }
/**
@@ -90,15 +98,12 @@ export class ConnectedPositionStrategy implements PositionStrategy {
* to the origin fits on-screen.
* @docs-private
*
* @param element Element to which to apply the CSS styles.
* @returns Resolves when the styles have been applied.
*/
apply(element: HTMLElement): void {
// Cache the overlay pane element in case re-calculating position is necessary
this._pane = element;
apply(): void {
// We need the bounding rects for the origin and the overlay to determine how to position
// the overlay relative to the origin.
const element = this._pane;
const originRect = this._origin.getBoundingClientRect();
const overlayRect = element.getBoundingClientRect();
@@ -1,9 +1,6 @@
import {
inject,
fakeAsync,
flushMicrotasks,
} from '@angular/core/testing';
import {fakeAsync, flushMicrotasks, inject} from '@angular/core/testing';
import {GlobalPositionStrategy} from './global-position-strategy';
import {OverlayRef} from '../overlay-ref';
describe('GlobalPositonStrategy', () => {
@@ -14,6 +11,7 @@ describe('GlobalPositonStrategy', () => {
element = document.createElement('div');
strategy = new GlobalPositionStrategy();
document.body.appendChild(element);
strategy.attach({overlayElement: element} as OverlayRef);
});
afterEach(() => {
@@ -22,7 +20,7 @@ describe('GlobalPositonStrategy', () => {
});
it('should position the element to the (top, left) with an offset', fakeAsyncTest(() => {
strategy.top('10px').left('40px').apply(element);
strategy.top('10px').left('40px').apply();
flushMicrotasks();
@@ -39,7 +37,7 @@ describe('GlobalPositonStrategy', () => {
}));
it('should position the element to the (bottom, right) with an offset', fakeAsyncTest(() => {
strategy.bottom('70px').right('15em').apply(element);
strategy.bottom('70px').right('15em').apply();
flushMicrotasks();
@@ -56,10 +54,10 @@ describe('GlobalPositonStrategy', () => {
}));
it('should overwrite previously applied positioning', fakeAsyncTest(() => {
strategy.centerHorizontally().centerVertically().apply(element);
strategy.centerHorizontally().centerVertically().apply();
flushMicrotasks();
strategy.top('10px').left('40%').apply(element);
strategy.top('10px').left('40%').apply();
flushMicrotasks();
let elementStyle = element.style;
@@ -73,7 +71,7 @@ describe('GlobalPositonStrategy', () => {
expect(parentStyle.justifyContent).toBe('flex-start');
expect(parentStyle.alignItems).toBe('flex-start');
strategy.bottom('70px').right('15em').apply(element);
strategy.bottom('70px').right('15em').apply();
flushMicrotasks();
@@ -87,7 +85,7 @@ describe('GlobalPositonStrategy', () => {
}));
it('should center the element', fakeAsyncTest(() => {
strategy.centerHorizontally().centerVertically().apply(element);
strategy.centerHorizontally().centerVertically().apply();
flushMicrotasks();
@@ -98,7 +96,7 @@ describe('GlobalPositonStrategy', () => {
}));
it('should center the element with an offset', fakeAsyncTest(() => {
strategy.centerHorizontally('10px').centerVertically('15px').apply(element);
strategy.centerHorizontally('10px').centerVertically('15px').apply();
flushMicrotasks();
@@ -113,7 +111,7 @@ describe('GlobalPositonStrategy', () => {
}));
it('should make the element position: static', fakeAsyncTest(() => {
strategy.apply(element);
strategy.apply();
flushMicrotasks();
@@ -121,7 +119,7 @@ describe('GlobalPositonStrategy', () => {
}));
it('should wrap the element in a `cdk-global-overlay-wrapper`', fakeAsyncTest(() => {
strategy.apply(element);
strategy.apply();
flushMicrotasks();
@@ -132,7 +130,7 @@ describe('GlobalPositonStrategy', () => {
it('should remove the parent wrapper from the DOM', fakeAsync(() => {
strategy.apply(element);
strategy.apply();
flushMicrotasks();
@@ -144,7 +142,7 @@ describe('GlobalPositonStrategy', () => {
}));
it('should set the element width', fakeAsync(() => {
strategy.width('100px').apply(element);
strategy.width('100px').apply();
flushMicrotasks();
@@ -152,7 +150,7 @@ describe('GlobalPositonStrategy', () => {
}));
it('should set the element height', fakeAsync(() => {
strategy.height('100px').apply(element);
strategy.height('100px').apply();
flushMicrotasks();
@@ -160,7 +158,7 @@ describe('GlobalPositonStrategy', () => {
}));
it('should reset the horizontal position and offset when the width is 100%', fakeAsync(() => {
strategy.centerHorizontally().width('100%').apply(element);
strategy.centerHorizontally().width('100%').apply();
flushMicrotasks();
@@ -169,7 +167,7 @@ describe('GlobalPositonStrategy', () => {
}));
it('should reset the vertical position and offset when the height is 100%', fakeAsync(() => {
strategy.centerVertically().height('100%').apply(element);
strategy.centerVertically().height('100%').apply();
flushMicrotasks();
@@ -7,6 +7,7 @@
*/
import {PositionStrategy} from './position-strategy';
import {OverlayRef} from '../overlay-ref';
/**
@@ -16,6 +17,9 @@ import {PositionStrategy} from './position-strategy';
* element to become blurry.
*/
export class GlobalPositionStrategy implements PositionStrategy {
/** The overlay to which this strategy is attached. */
private _overlayRef: OverlayRef;
private _cssPosition: string = 'static';
private _topOffset: string = '';
private _bottomOffset: string = '';
@@ -29,6 +33,10 @@ export class GlobalPositionStrategy implements PositionStrategy {
/* A lazily-created wrapper for the overlay element that is used as a flex container. */
private _wrapper: HTMLElement | null = null;
attach(overlayRef: OverlayRef): void {
this._overlayRef = overlayRef;
}
/**
* Sets the top position of the overlay. Clears any previously set vertical position.
* @param value New top offset.
@@ -133,10 +141,11 @@ export class GlobalPositionStrategy implements PositionStrategy {
* Apply the position to the element.
* @docs-private
*
* @param element Element to which to apply the CSS.
* @returns Resolved when the styles have been applied.
*/
apply(element: HTMLElement): void {
apply(): void {
const element = this._overlayRef.overlayElement;
if (!this._wrapper && element.parentNode) {
this._wrapper = document.createElement('div');
this._wrapper.classList.add('cdk-global-overlay-wrapper');
@@ -159,9 +168,7 @@ export class GlobalPositionStrategy implements PositionStrategy {
parentStyles.alignItems = this._alignItems;
}
/**
* Removes the wrapper element from the DOM.
*/
/** Removes the wrapper element from the DOM. */
dispose(): void {
if (this._wrapper && this._wrapper.parentNode) {
this._wrapper.parentNode.removeChild(this._wrapper);
@@ -6,11 +6,17 @@
* found in the LICENSE file at https://angular.io/license
*/
import {OverlayRef} from '../overlay-ref';
/** Strategy for setting the position on an overlay. */
export interface PositionStrategy {
/** Attaches this position strategy to an overlay. */
attach(overlay: OverlayRef): void;
/** Updates the position of the overlay element. */
apply(element: Element): void;
apply(): void;
/** Cleans up any DOM modifications made by the position strategy, if necessary. */
dispose(): void;