Skip to content

feat(overlay): add option to re-use last preferred position when re-applying to open connected overlay #7805

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/cdk/overlay/position/connected-position-strategy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,29 @@ describe('ConnectedPositionStrategy', () => {
expect(Math.floor(overlayRect.left)).toBe(Math.floor(originRect.left));
});

it('should re-use the preferred position when re-applying while locked in', () => {
positionBuilder = new OverlayPositionBuilder(viewportRuler);
strategy = positionBuilder.connectedTo(
fakeElementRef,
{originX: 'end', originY: 'center'},
{overlayX: 'start', overlayY: 'center'})
.withLockedPosition(true)
.withFallbackPosition(
{originX: 'start', originY: 'bottom'},
{overlayX: 'end', overlayY: 'top'});

const recalcSpy = spyOn(strategy, 'recalculateLastPosition');

strategy.attach(fakeOverlayRef(overlayElement));
strategy.apply();

expect(recalcSpy).not.toHaveBeenCalled();

strategy.apply();

expect(recalcSpy).toHaveBeenCalled();
});

/**
* Run all tests for connecting the overlay to the origin such that first preferred
* position does not go off-screen. We do this because there are several cases where we
Expand Down
34 changes: 30 additions & 4 deletions src/cdk/overlay/position/connected-position-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,13 @@ export class ConnectedPositionStrategy implements PositionStrategy {
/** The last position to have been calculated as the best fit position. */
private _lastConnectedPosition: ConnectionPositionPair;

_onPositionChange:
Subject<ConnectedOverlayPositionChange> = new Subject<ConnectedOverlayPositionChange>();
/** Whether the position strategy is applied currently. */
private _applied = false;

/** Whether the overlay position is locked. */
private _positionLocked = false;

private _onPositionChange = new Subject<ConnectedOverlayPositionChange>();

/** Emits an event when the connection point changes. */
get onPositionChange(): Observable<ConnectedOverlayPositionChange> {
Expand Down Expand Up @@ -100,22 +105,32 @@ export class ConnectedPositionStrategy implements PositionStrategy {

/** Disposes all resources used by the position strategy. */
dispose() {
this._applied = false;
this._resizeSubscription.unsubscribe();
}

/** @docs-private */
detach() {
this._applied = false;
this._resizeSubscription.unsubscribe();
}

/**
* Updates the position of the overlay element, using whichever preferred position relative
* to the origin fits on-screen.
* @docs-private
*
* @returns Resolves when the styles have been applied.
*/
apply(): void {
// If the position has been applied already (e.g. when the overlay was opened) and the
// consumer opted into locking in the position, re-use the old position, in order to
// prevent the overlay from jumping around.
if (this._applied && this._positionLocked && this._lastConnectedPosition) {
this.recalculateLastPosition();
return;
}

this._applied = true;

// 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;
Expand Down Expand Up @@ -229,6 +244,17 @@ export class ConnectedPositionStrategy implements PositionStrategy {
return this;
}

/**
* Sets whether the overlay's position should be locked in after it is positioned
* initially. When an overlay is locked in, it won't attempt to reposition itself
* when the position is re-applied (e.g. when the user scrolls away).
* @param isLocked Whether the overlay should locked in.
*/
withLockedPosition(isLocked: boolean): this {
this._positionLocked = isLocked;
return this;
}

/**
* Gets the horizontal (x) "start" dimension based on whether the overlay is in an RTL context.
* @param rect
Expand Down