mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(overlay): allow for connected overlay to be positioned relative to a point (#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 #5007.
This commit is contained in:
committed by
vivian-hu
parent
0bd93dd64e
commit
651549f7af
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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. */
|
||||
|
||||
@@ -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';
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user