From 571500da4c20c73dae102a2690464d9bcf0beade Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Mon, 14 Nov 2016 11:41:57 -0800 Subject: [PATCH] feat(overlay): emit position change event --- .../connected-position-strategy.spec.ts | 28 +++++++++++++++++++ .../position/connected-position-strategy.ts | 17 +++++++++-- .../overlay/position/connected-position.ts | 5 ++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/lib/core/overlay/position/connected-position-strategy.spec.ts b/src/lib/core/overlay/position/connected-position-strategy.spec.ts index 1bf84a26a1f4..c070344bd91b 100644 --- a/src/lib/core/overlay/position/connected-position-strategy.spec.ts +++ b/src/lib/core/overlay/position/connected-position-strategy.spec.ts @@ -2,6 +2,7 @@ import {ElementRef} from '@angular/core'; import {ConnectedPositionStrategy} from './connected-position-strategy'; import {ViewportRuler} from './viewport-ruler'; import {OverlayPositionBuilder} from './overlay-position-builder'; +import {ConnectedOverlayPositionChange} from './connected-position'; // Default width and height of the overlay and origin panels throughout these tests. @@ -259,6 +260,33 @@ describe('ConnectedPositionStrategy', () => { }); + it('should emit onPositionChange event when position changes', () => { + // force the overlay to open in a fallback position + fakeViewportRuler.fakeRect = { + top: 0, left: 0, width: 500, height: 500, right: 500, bottom: 500 + }; + positionBuilder = new OverlayPositionBuilder(fakeViewportRuler); + originElement.style.top = '200px'; + originElement.style.left = '475px'; + + strategy = positionBuilder.connectedTo( + fakeElementRef, + {originX: 'end', originY: 'center'}, + {overlayX: 'start', overlayY: 'center'}) + .withFallbackPosition( + {originX: 'start', originY: 'bottom'}, + {overlayX: 'end', overlayY: 'top'}); + + const positionChangeHandler = jasmine.createSpy('positionChangeHandler'); + strategy.onPositionChange.first().subscribe(positionChangeHandler); + + strategy.apply(overlayElement); + expect(positionChangeHandler).toHaveBeenCalled(); + expect(positionChangeHandler.calls.mostRecent().args[0]) + .toEqual(jasmine.any(ConnectedOverlayPositionChange), + `Expected strategy to emit an instance of ConnectedOverlayPositionChange.`); + }); + /** * Run all tests for connecting the overlay to the origin such that first preferred diff --git a/src/lib/core/overlay/position/connected-position-strategy.ts b/src/lib/core/overlay/position/connected-position-strategy.ts index 7e3da99318f6..c04b066c9e66 100644 --- a/src/lib/core/overlay/position/connected-position-strategy.ts +++ b/src/lib/core/overlay/position/connected-position-strategy.ts @@ -5,9 +5,11 @@ import {applyCssTransform} from '../../style/apply-transform'; import { ConnectionPositionPair, OriginConnectionPosition, - OverlayConnectionPosition + OverlayConnectionPosition, + ConnectedOverlayPositionChange } from './connected-position'; - +import {Subject} from 'rxjs/Subject'; +import {Observable} from 'rxjs/Observable'; /** * A strategy for positioning overlays. Using this strategy, an overlay is given an @@ -36,6 +38,12 @@ export class ConnectedPositionStrategy implements PositionStrategy { /** The origin element against which the overlay will be positioned. */ private _origin: HTMLElement; + private _onPositionChange: Subject = new Subject(); + + /** Emits an event when the connection point changes. */ + get onPositionChange(): Observable { + return this._onPositionChange.asObservable(); + } constructor( private _connectedTo: ElementRef, @@ -64,6 +72,7 @@ export class ConnectedPositionStrategy implements PositionStrategy { // We use the viewport rect to determine whether a position would go off-screen. const viewportRect = this._viewportRuler.getViewportRect(); let firstOverlayPoint: Point = null; + let isFirstPosition = true; // We want to place the overlay in the first of the preferred positions such that the // overlay fits on-screen. @@ -77,8 +86,12 @@ export class ConnectedPositionStrategy implements PositionStrategy { // If the overlay in the calculated position fits on-screen, put it there and we're done. if (this._willOverlayFitWithinViewport(overlayPoint, overlayRect, viewportRect)) { this._setElementPosition(element, overlayPoint); + if (!isFirstPosition) { + this._onPositionChange.next(new ConnectedOverlayPositionChange(pos)); + } return Promise.resolve(null); } + isFirstPosition = false; } // TODO(jelbourn): fallback behavior for when none of the preferred positions fit on-screen. diff --git a/src/lib/core/overlay/position/connected-position.ts b/src/lib/core/overlay/position/connected-position.ts index 92fab48398cc..b2e5b9b4a000 100644 --- a/src/lib/core/overlay/position/connected-position.ts +++ b/src/lib/core/overlay/position/connected-position.ts @@ -31,3 +31,8 @@ export class ConnectionPositionPair { this.overlayY = overlay.overlayY; } } + +/** The change event emitted by the strategy when a fallback position is used. */ +export class ConnectedOverlayPositionChange { + constructor(public connectionPair: ConnectionPositionPair) {} +}