Skip to content

Commit

Permalink
feat(overlay): allow for connected overlay to be positioned relative …
Browse files Browse the repository at this point in the history
…to a point (angular#14616)

Allows for the connected overlay's origin to be set to a point on the page, rather than a DOM element. This allows people to easily implement right click context menus.

Relates to angular#5007.
  • Loading branch information
crisbeto authored and s2-abdo committed Jan 18, 2019
1 parent 6e56533 commit d16a242
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,53 @@ describe('FlexibleConnectedPositionStrategy', () => {

});

describe('with origin set to a point', () => {
it('should be able to render at the primary position', () => {
positionStrategy
.setOrigin({x: 50, y: 100})
.withPositions([{
originX: 'start',
originY: 'bottom',
overlayX: 'start',
overlayY: 'top'
}]);

attachOverlay({positionStrategy});

const overlayRect = overlayRef.overlayElement.getBoundingClientRect();
expect(Math.floor(overlayRect.top)).toBe(100);
expect(Math.floor(overlayRect.left)).toBe(50);
});

it('should be able to render at a fallback position', () => {
const viewportHeight = viewport.getViewportRect().height;

positionStrategy
.setOrigin({x: 50, y: viewportHeight})
.withPositions([
{
originX: 'start',
originY: 'bottom',
overlayX: 'start',
overlayY: 'top'
},
{
originX: 'start',
originY: 'top',
overlayX: 'start',
overlayY: 'bottom'
}
]);

attachOverlay({positionStrategy});

const overlayRect = overlayRef.overlayElement.getBoundingClientRect();
expect(Math.floor(overlayRect.bottom)).toBe(viewportHeight);
expect(Math.floor(overlayRect.left)).toBe(50);
});

});

it('should account for the `offsetX` pushing the overlay out of the screen', () => {
// Position the element so it would have enough space to fit.
originElement.style.top = '200px';
Expand Down
49 changes: 39 additions & 10 deletions src/cdk/overlay/position/flexible-connected-position-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
import {Observable, Subscription, Subject, Observer} from 'rxjs';
import {OverlayReference} from '../overlay-reference';
import {isElementScrolledOutsideView, isElementClippedByScrolling} from './scroll-clip';
import {coerceCssPixelValue, coerceArray, coerceElement} from '@angular/cdk/coercion';
import {coerceCssPixelValue, coerceArray} from '@angular/cdk/coercion';
import {Platform} from '@angular/cdk/platform';
import {OverlayContainer} from '../overlay-container';

Expand All @@ -29,6 +29,9 @@ import {OverlayContainer} from '../overlay-container';
/** Class to be added to the overlay bounding box. */
const boundingBoxClass = 'cdk-overlay-connected-position-bounding-box';

/** Possible values that can be set as the origin of a FlexibleConnectedPositionStrategy. */
export type FlexibleConnectedPositionStrategyOrigin = ElementRef | HTMLElement | Point;

/**
* A strategy for positioning overlays. Using this strategy, an overlay is given an
* implicit position relative some origin element. The relative position is defined in terms of
Expand Down Expand Up @@ -80,7 +83,7 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
_preferredPositions: ConnectionPositionPair[] = [];

/** The origin element against which the overlay will be positioned. */
private _origin: HTMLElement;
private _origin: FlexibleConnectedPositionStrategyOrigin;

/** The overlay pane element. */
private _pane: HTMLElement;
Expand Down Expand Up @@ -139,7 +142,7 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
}

constructor(
connectedTo: ElementRef | HTMLElement,
connectedTo: FlexibleConnectedPositionStrategyOrigin,
private _viewportRuler: ViewportRuler,
private _document: Document,
// @breaking-change 8.0.0 `_platform` and `_overlayContainer` parameters to be made required.
Expand Down Expand Up @@ -211,7 +214,7 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
// the overlay relative to the origin.
// We use the viewport rect to determine whether a position would go off-screen.
this._viewportRect = this._getNarrowedViewportRect();
this._originRect = this._origin.getBoundingClientRect();
this._originRect = this._getOriginRect();
this._overlayRect = this._pane.getBoundingClientRect();

const originRect = this._originRect;
Expand Down Expand Up @@ -350,7 +353,7 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
*/
reapplyLastPosition(): void {
if (!this._isDisposed && (!this._platform || this._platform.isBrowser)) {
this._originRect = this._origin.getBoundingClientRect();
this._originRect = this._getOriginRect();
this._overlayRect = this._pane.getBoundingClientRect();
this._viewportRect = this._getNarrowedViewportRect();

Expand Down Expand Up @@ -427,11 +430,14 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
}

/**
* Sets the origin element, relative to which to position the overlay.
* @param origin Reference to the new origin element.
* Sets the origin, relative to which to position the overlay.
* Using an element origin is useful for building components that need to be positioned
* relatively to a trigger (e.g. dropdown menus or tooltips), whereas using a point can be
* used for cases like contextual menus which open relative to the user's pointer.
* @param origin Reference to the new origin.
*/
setOrigin(origin: ElementRef | HTMLElement): this {
this._origin = coerceElement(origin);
setOrigin(origin: FlexibleConnectedPositionStrategyOrigin): this {
this._origin = origin;
return this;
}

Expand Down Expand Up @@ -988,7 +994,7 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
*/
private _getScrollVisibility(): ScrollingVisibility {
// Note: needs fresh rects since the position could've changed.
const originBounds = this._origin.getBoundingClientRect();
const originBounds = this._getOriginRect();
const overlayBounds = this._pane.getBoundingClientRect();

// TODO(jelbourn): instead of needing all of the client rects for these scrolling containers
Expand Down Expand Up @@ -1090,6 +1096,29 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
this._appliedPanelClasses = [];
}
}

/** Returns the ClientRect of the current origin. */
private _getOriginRect(): ClientRect {
const origin = this._origin;

if (origin instanceof ElementRef) {
return origin.nativeElement.getBoundingClientRect();
}

if (origin instanceof HTMLElement) {
return origin.getBoundingClientRect();
}

// If the origin is a point, return a client rect as if it was a 0x0 element at the point.
return {
top: origin.y,
bottom: origin.y,
left: origin.x,
right: origin.x,
height: 0,
width: 0
};
}
}

/** A simple (x, y) coordinate. */
Expand Down
12 changes: 8 additions & 4 deletions src/cdk/overlay/position/overlay-position-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import {DOCUMENT} from '@angular/common';
import {ElementRef, Inject, Injectable, Optional} from '@angular/core';
import {OriginConnectionPosition, OverlayConnectionPosition} from './connected-position';
import {ConnectedPositionStrategy} from './connected-position-strategy';
import {FlexibleConnectedPositionStrategy} from './flexible-connected-position-strategy';
import {
FlexibleConnectedPositionStrategy,
FlexibleConnectedPositionStrategyOrigin,
} from './flexible-connected-position-strategy';
import {GlobalPositionStrategy} from './global-position-strategy';
import {Platform} from '@angular/cdk/platform';
import {OverlayContainer} from '../overlay-container';
Expand Down Expand Up @@ -53,10 +56,11 @@ export class OverlayPositionBuilder {

/**
* Creates a flexible position strategy.
* @param elementRef
* @param origin Origin relative to which to position the overlay.
*/
flexibleConnectedTo(elementRef: ElementRef | HTMLElement): FlexibleConnectedPositionStrategy {
return new FlexibleConnectedPositionStrategy(elementRef, this._viewportRuler, this._document,
flexibleConnectedTo(origin: FlexibleConnectedPositionStrategyOrigin):
FlexibleConnectedPositionStrategy {
return new FlexibleConnectedPositionStrategy(origin, this._viewportRuler, this._document,
this._platform, this._overlayContainer);
}

Expand Down
6 changes: 3 additions & 3 deletions tools/public_api_guard/cdk/overlay.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,13 @@ export declare class FlexibleConnectedPositionStrategy implements PositionStrate
_preferredPositions: ConnectionPositionPair[];
positionChanges: Observable<ConnectedOverlayPositionChange>;
readonly positions: ConnectionPositionPair[];
constructor(connectedTo: ElementRef | HTMLElement, _viewportRuler: ViewportRuler, _document: Document, _platform?: Platform | undefined, _overlayContainer?: OverlayContainer | undefined);
constructor(connectedTo: FlexibleConnectedPositionStrategyOrigin, _viewportRuler: ViewportRuler, _document: Document, _platform?: Platform | undefined, _overlayContainer?: OverlayContainer | undefined);
apply(): void;
attach(overlayRef: OverlayReference): void;
detach(): void;
dispose(): void;
reapplyLastPosition(): void;
setOrigin(origin: ElementRef | HTMLElement): this;
setOrigin(origin: FlexibleConnectedPositionStrategyOrigin): this;
withDefaultOffsetX(offset: number): this;
withDefaultOffsetY(offset: number): this;
withFlexibleDimensions(flexibleDimensions?: boolean): this;
Expand Down Expand Up @@ -216,7 +216,7 @@ export declare class OverlayModule {
export declare class OverlayPositionBuilder {
constructor(_viewportRuler: ViewportRuler, _document: any, _platform?: Platform | undefined, _overlayContainer?: OverlayContainer | undefined);
connectedTo(elementRef: ElementRef, originPos: OriginConnectionPosition, overlayPos: OverlayConnectionPosition): ConnectedPositionStrategy;
flexibleConnectedTo(elementRef: ElementRef | HTMLElement): FlexibleConnectedPositionStrategy;
flexibleConnectedTo(origin: FlexibleConnectedPositionStrategyOrigin): FlexibleConnectedPositionStrategy;
global(): GlobalPositionStrategy;
}

Expand Down

0 comments on commit d16a242

Please sign in to comment.