diff options
author | vempo <vitaliy.emporopulo@amdocs.com> | 2018-07-24 17:34:04 +0300 |
---|---|---|
committer | vempo <vitaliy.emporopulo@amdocs.com> | 2018-07-25 11:39:10 +0300 |
commit | a52d50e788792a63e97a9176ab319d53db7a2853 (patch) | |
tree | b1c2222cacf4b8192aea16d1e0315b1f005c5347 /deprecated-workflow-designer/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position | |
parent | 3c2665debb400aef7f0ed9e235698d2ff9f859db (diff) |
Replaced old implementation at root
Old project files and directories has been moved
under 'deprecated-workflow-designer'. The old project
is not built by the CI anymore, but can be still built manually.
New modules/directories have been moved up and integrated with
the CI system.
Change-Id: I1528c792bcbcce9e50bfc294a1328a20e72c91cf
Issue-ID: SDC-1559
Signed-off-by: vempo <vitaliy.emporopulo@amdocs.com>
Diffstat (limited to 'deprecated-workflow-designer/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position')
8 files changed, 1029 insertions, 0 deletions
diff --git a/deprecated-workflow-designer/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/connected-position-strategy.ts b/deprecated-workflow-designer/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/connected-position-strategy.ts new file mode 100644 index 00000000..d144c81f --- /dev/null +++ b/deprecated-workflow-designer/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/connected-position-strategy.ts @@ -0,0 +1,478 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +/* tslint:disable:array-type member-access variable-name typedef + only-arrow-functions directive-class-suffix component-class-suffix + component-selector no-unnecessary-type-assertion arrow-parens + no-unused-variable*/ +import {ElementRef} from '@angular/core'; +import {Observable} from 'rxjs/Observable'; +import {Subject} from 'rxjs/Subject'; + +import {Scrollable} from '../scroll/scrollable'; + +import {ConnectedOverlayPositionChange, ConnectionPositionPair, OriginConnectionPosition, OverlayConnectionPosition, ScrollableViewProperties} from './connected-position'; +import {PositionStrategy} from './position-strategy'; +import {ViewportRuler} from './viewport-ruler'; + +/** + * Container to hold the bounding positions of a particular element with respect + * to the viewport, where top and bottom are the y-axis coordinates of the + * bounding rectangle and left and right are the x-axis coordinates. + */ +interface ElementBoundingPositions { + top: number; + right: number; + bottom: number; + left: number; +} + +/** + * 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 a point on the origin element that is connected to a + * point on the overlay element. For example, a basic dropdown is connecting the + * bottom-left corner of the origin to the top-left corner of the overlay. + */ +export class ConnectedPositionStrategy implements PositionStrategy { + private _dir = 'ltr'; + + /** The offset in pixels for the overlay connection point on the x-axis */ + private _offsetX = 0; + + /** The offset in pixels for the overlay connection point on the y-axis */ + private _offsetY = 0; + + /** The Scrollable containers used to check scrollable view properties on + * position change. */ + private scrollables: Scrollable[] = []; + + /** Whether the we're dealing with an RTL context */ + get _isRtl() { + return this._dir === 'rtl'; + } + + /** Ordered list of preferred positions, from most to least desirable. */ + _preferredPositions: ConnectionPositionPair[] = []; + + /** The origin element against which the overlay will be positioned. */ + private _origin: HTMLElement; + + /** The overlay pane element. */ + private _pane: HTMLElement; + + /** The last position to have been calculated as the best fit position. */ + private _lastConnectedPosition: ConnectionPositionPair; + + _onPositionChange: Subject<ConnectedOverlayPositionChange> = + new Subject<ConnectedOverlayPositionChange>(); + + /** Emits an event when the connection point changes. */ + get onPositionChange(): Observable<ConnectedOverlayPositionChange> { + return this._onPositionChange.asObservable(); + } + + constructor( + private _connectedTo: ElementRef, + private _originPos: OriginConnectionPosition, + private _overlayPos: OverlayConnectionPosition, + private _viewportRuler: ViewportRuler) { + this._origin = this._connectedTo.nativeElement; + this.withFallbackPosition(this._originPos, this._overlayPos); + } + + /** Ordered list of preferred positions, from most to least desirable. */ + get positions() { + return this._preferredPositions; + } + + /** + * To be used to for any cleanup after the element gets destroyed. + */ + dispose() { + // + } + + /** + * Updates the position of the overlay element, using whichever preferred + * position relative 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; + + // We need the bounding rects for the origin and the overlay to determine + // how to position the overlay relative to the origin. + const originRect = this._origin.getBoundingClientRect(); + const overlayRect = element.getBoundingClientRect(); + + // We use the viewport rect to determine whether a position would go + // off-screen. + const viewportRect = this._viewportRuler.getViewportRect(); + + // Fallback point if none of the fallbacks fit into the viewport. + let fallbackPoint: OverlayPoint|undefined; + let fallbackPosition: ConnectionPositionPair|undefined; + + // We want to place the overlay in the first of the preferred positions such + // that the overlay fits on-screen. + for (const pos of this._preferredPositions) { + // Get the (x, y) point of connection on the origin, and then use that to + // get the (top, left) coordinate for the overlay at `pos`. + const originPoint = this._getOriginConnectionPoint(originRect, pos); + const overlayPoint = + this._getOverlayPoint(originPoint, overlayRect, viewportRect, pos); + + // If the overlay in the calculated position fits on-screen, put it there + // and we're done. + if (overlayPoint.fitsInViewport) { + this._setElementPosition(element, overlayRect, overlayPoint, pos); + + // Save the last connected position in case the position needs to be + // re-calculated. + this._lastConnectedPosition = pos; + + // Notify that the position has been changed along with its change + // properties. + const scrollableViewProperties = + this.getScrollableViewProperties(element); + const positionChange = + new ConnectedOverlayPositionChange(pos, scrollableViewProperties); + this._onPositionChange.next(positionChange); + + return; + } else if ( + !fallbackPoint || + fallbackPoint.visibleArea < overlayPoint.visibleArea) { + fallbackPoint = overlayPoint; + fallbackPosition = pos; + } + } + + // If none of the preferred positions were in the viewport, take the one + // with the largest visible area. + this._setElementPosition( + element, overlayRect, fallbackPoint, fallbackPosition); + } + + /** + * This re-aligns the overlay element with the trigger in its last calculated + * position, even if a position higher in the "preferred positions" list would + * now fit. This allows one to re-align the panel without changing the + * orientation of the panel. + */ + recalculateLastPosition(): void { + const originRect = this._origin.getBoundingClientRect(); + const overlayRect = this._pane.getBoundingClientRect(); + const viewportRect = this._viewportRuler.getViewportRect(); + const lastPosition = + this._lastConnectedPosition || this._preferredPositions[0]; + + const originPoint = + this._getOriginConnectionPoint(originRect, lastPosition); + const overlayPoint = this._getOverlayPoint( + originPoint, overlayRect, viewportRect, lastPosition); + this._setElementPosition( + this._pane, overlayRect, overlayPoint, lastPosition); + } + + /** + * Sets the list of Scrollable containers that host the origin element so that + * on reposition we can evaluate if it or the overlay has been clipped or + * outside view. Every Scrollable must be an ancestor element of the + * strategy's origin element. + */ + withScrollableContainers(scrollables: Scrollable[]) { + this.scrollables = scrollables; + } + + /** + * Adds a new preferred fallback position. + * @param originPos + * @param overlayPos + */ + withFallbackPosition( + originPos: OriginConnectionPosition, + overlayPos: OverlayConnectionPosition): this { + this._preferredPositions.push( + new ConnectionPositionPair(originPos, overlayPos)); + return this; + } + + /** + * Sets the layout direction so the overlay's position can be adjusted to + * match. + * @param dir New layout direction. + */ + withDirection(dir: 'ltr'|'rtl'): this { + this._dir = dir; + return this; + } + + /** + * Sets an offset for the overlay's connection point on the x-axis + * @param offset New offset in the X axis. + */ + withOffsetX(offset: number): this { + this._offsetX = offset; + return this; + } + + /** + * Sets an offset for the overlay's connection point on the y-axis + * @param offset New offset in the Y axis. + */ + withOffsetY(offset: number): this { + this._offsetY = offset; + return this; + } + + /** + * Gets the horizontal (x) "start" dimension based on whether the overlay is + * in an RTL context. + * @param rect + */ + private _getStartX(rect: ClientRect): number { + return this._isRtl ? rect.right : rect.left; + } + + /** + * Gets the horizontal (x) "end" dimension based on whether the overlay is in + * an RTL context. + * @param rect + */ + private _getEndX(rect: ClientRect): number { + return this._isRtl ? rect.left : rect.right; + } + + + /** + * Gets the (x, y) coordinate of a connection point on the origin based on a + * relative position. + * @param originRect + * @param pos + */ + private _getOriginConnectionPoint( + originRect: ClientRect, pos: ConnectionPositionPair): Point { + const originStartX = this._getStartX(originRect); + const originEndX = this._getEndX(originRect); + + let x: number; + if (pos.originX === 'center') { + x = originStartX + (originRect.width / 2); + } else { + x = pos.originX === 'start' ? originStartX : originEndX; + } + + let y: number; + if (pos.originY === 'center') { + y = originRect.top + (originRect.height / 2); + } else { + y = pos.originY === 'top' ? originRect.top : originRect.bottom; + } + + return {x, y}; + } + + + /** + * Gets the (x, y) coordinate of the top-left corner of the overlay given a + * given position and origin point to which the overlay should be connected, + * as well as how much of the element would be inside the viewport at that + * position. + */ + private _getOverlayPoint( + originPoint: Point, overlayRect: ClientRect, viewportRect: ClientRect, + pos: ConnectionPositionPair): OverlayPoint { + // Calculate the (overlayStartX, overlayStartY), the start of the potential + // overlay position relative to the origin point. + let overlayStartX: number; + if (pos.overlayX === 'center') { + overlayStartX = -overlayRect.width / 2; + } else if (pos.overlayX === 'start') { + overlayStartX = this._isRtl ? -overlayRect.width : 0; + } else { + overlayStartX = this._isRtl ? 0 : -overlayRect.width; + } + + let overlayStartY: number; + if (pos.overlayY === 'center') { + overlayStartY = -overlayRect.height / 2; + } else { + overlayStartY = pos.overlayY === 'top' ? 0 : -overlayRect.height; + } + + // The (x, y) coordinates of the overlay. + const x = originPoint.x + overlayStartX + this._offsetX; + const y = originPoint.y + overlayStartY + this._offsetY; + + // How much the overlay would overflow at this position, on each side. + const leftOverflow = 0 - x; + const rightOverflow = (x + overlayRect.width) - viewportRect.width; + const topOverflow = 0 - y; + const bottomOverflow = (y + overlayRect.height) - viewportRect.height; + + // Visible parts of the element on each axis. + const visibleWidth = + this._subtractOverflows(overlayRect.width, leftOverflow, rightOverflow); + const visibleHeight = this._subtractOverflows( + overlayRect.height, topOverflow, bottomOverflow); + + // The area of the element that's within the viewport. + const visibleArea = visibleWidth * visibleHeight; + const fitsInViewport = + (overlayRect.width * overlayRect.height) === visibleArea; + + return {x, y, fitsInViewport, visibleArea}; + } + + /** + * Gets the view properties of the trigger and overlay, including whether they + * are clipped or completely outside the view of any of the strategy's + * scrollables. + */ + private getScrollableViewProperties(overlay: HTMLElement): + ScrollableViewProperties { + const originBounds = this._getElementBounds(this._origin); + const overlayBounds = this._getElementBounds(overlay); + const scrollContainerBounds = + this.scrollables.map((scrollable: Scrollable) => { + return this._getElementBounds( + scrollable.getElementRef().nativeElement); + }); + + return { + isOriginClipped: + this.isElementClipped(originBounds, scrollContainerBounds), + isOriginOutsideView: + this.isElementOutsideView(originBounds, scrollContainerBounds), + isOverlayClipped: + this.isElementClipped(overlayBounds, scrollContainerBounds), + isOverlayOutsideView: + this.isElementOutsideView(overlayBounds, scrollContainerBounds), + }; + } + + /** Whether the element is completely out of the view of any of the + * containers. */ + private isElementOutsideView( + elementBounds: ElementBoundingPositions, + containersBounds: ElementBoundingPositions[]): boolean { + return containersBounds.some( + (containerBounds: ElementBoundingPositions) => { + const outsideAbove = elementBounds.bottom < containerBounds.top; + const outsideBelow = elementBounds.top > containerBounds.bottom; + const outsideLeft = elementBounds.right < containerBounds.left; + const outsideRight = elementBounds.left > containerBounds.right; + + return outsideAbove || outsideBelow || outsideLeft || outsideRight; + }); + } + + /** Whether the element is clipped by any of the containers. */ + private isElementClipped( + elementBounds: ElementBoundingPositions, + containersBounds: ElementBoundingPositions[]): boolean { + return containersBounds.some( + (containerBounds: ElementBoundingPositions) => { + const clippedAbove = elementBounds.top < containerBounds.top; + const clippedBelow = elementBounds.bottom > containerBounds.bottom; + const clippedLeft = elementBounds.left < containerBounds.left; + const clippedRight = elementBounds.right > containerBounds.right; + + return clippedAbove || clippedBelow || clippedLeft || clippedRight; + }); + } + + /** Physically positions the overlay element to the given coordinate. */ + private _setElementPosition( + element: HTMLElement, overlayRect: ClientRect, overlayPoint: Point, + pos: ConnectionPositionPair) { + // We want to set either `top` or `bottom` based on whether the overlay + // wants to appear above or below the origin and the direction in which the + // element will expand. + const verticalStyleProperty = pos.overlayY === 'bottom' ? 'bottom' : 'top'; + + // When using `bottom`, we adjust the y position such that it is the + // distance from the bottom of the viewport rather than the top. + const y = verticalStyleProperty === 'top' ? + overlayPoint.y : + document.documentElement.clientHeight - + (overlayPoint.y + overlayRect.height); + + // We want to set either `left` or `right` based on whether the overlay + // wants to appear "before" or "after" the origin, which determines the + // direction in which the element will expand. For the horizontal axis, the + // meaning of "before" and "after" change based on whether the page is in + // RTL or LTR. + let horizontalStyleProperty: any; + if (this._dir === 'rtl') { + horizontalStyleProperty = pos.overlayX === 'end' ? 'left' : 'right'; + } else { + horizontalStyleProperty = pos.overlayX === 'end' ? 'right' : 'left'; + } + + // When we're setting `right`, we adjust the x position such that it is the + // distance from the right edge of the viewport rather than the left edge. + const x = horizontalStyleProperty === 'left' ? + overlayPoint.x : + document.documentElement.clientWidth - + (overlayPoint.x + overlayRect.width); + + + // Reset any existing styles. This is necessary in case the preferred + // position has changed since the last `apply`. + ['top', 'bottom', 'left', 'right'].forEach( + (p: any) => element.style[p] = null); + + element.style[verticalStyleProperty] = `${y}px`; + element.style[horizontalStyleProperty] = `${x}px`; + } + + /** Returns the bounding positions of the provided element with respect to the + * viewport. */ + private _getElementBounds(element: HTMLElement): ElementBoundingPositions { + const boundingClientRect = element.getBoundingClientRect(); + return { + top: boundingClientRect.top, + right: boundingClientRect.left + boundingClientRect.width, + bottom: boundingClientRect.top + boundingClientRect.height, + left: boundingClientRect.left + }; + } + + /** + * Subtracts the amount that an element is overflowing on an axis from it's + * length. + */ + private _subtractOverflows(length: number, ...overflows: number[]): number { + return overflows.reduce((currentValue: number, currentOverflow: number) => { + return currentValue - Math.max(currentOverflow, 0); + }, length); + } +} + +/** A simple (x, y) coordinate. */ +interface Point { + x: number; + y: number; +} + +/** + * Expands the simple (x, y) coordinate by adding info about whether the + * element would fit inside the viewport at that position, as well as + * how much of the element would be visible. + */ +interface OverlayPoint extends Point { + visibleArea: number; + fitsInViewport: boolean; +} diff --git a/deprecated-workflow-designer/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/connected-position.ts b/deprecated-workflow-designer/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/connected-position.ts new file mode 100644 index 00000000..dad3f04e --- /dev/null +++ b/deprecated-workflow-designer/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/connected-position.ts @@ -0,0 +1,87 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +/* tslint:disable:array-type member-access variable-name typedef + only-arrow-functions directive-class-suffix component-class-suffix + component-selector no-unnecessary-type-assertion arrow-parens*/ +/** Horizontal dimension of a connection point on the perimeter of the origin or + * overlay element. */ +import {Optional} from '@angular/core'; +export type HorizontalConnectionPos = 'start' | 'center' | 'end'; + +/** Vertical dimension of a connection point on the perimeter of the origin or + * overlay element. */ +export type VerticalConnectionPos = 'top' | 'center' | 'bottom'; + + +/** A connection point on the origin element. */ +export interface OriginConnectionPosition { + originX: HorizontalConnectionPos; + originY: VerticalConnectionPos; +} + +/** A connection point on the overlay element. */ +export interface OverlayConnectionPosition { + overlayX: HorizontalConnectionPos; + overlayY: VerticalConnectionPos; +} + +/** The points of the origin element and the overlay element to connect. */ +export class ConnectionPositionPair { + originX: HorizontalConnectionPos; + originY: VerticalConnectionPos; + overlayX: HorizontalConnectionPos; + overlayY: VerticalConnectionPos; + + constructor( + origin: OriginConnectionPosition, overlay: OverlayConnectionPosition) { + this.originX = origin.originX; + this.originY = origin.originY; + this.overlayX = overlay.overlayX; + this.overlayY = overlay.overlayY; + } +} + +/** + * Set of properties regarding the position of the origin and overlay relative + * to the viewport with respect to the containing Scrollable elements. + * + * The overlay and origin are clipped if any part of their bounding client + * rectangle exceeds the bounds of any one of the strategy's Scrollable's + * bounding client rectangle. + * + * The overlay and origin are outside view if there is no overlap between their + * bounding client rectangle and any one of the strategy's Scrollable's bounding + * client rectangle. + * + * ----------- ----------- + * | outside | | clipped | + * | view | -------------------------- + * | | | | | | + * ---------- | ----------- | + * -------------------------- | | + * | | | Scrollable | + * | | | | + * | | -------------------------- + * | Scrollable | + * | | + * -------------------------- + */ +export class ScrollableViewProperties { + isOriginClipped: boolean; + isOriginOutsideView: boolean; + isOverlayClipped: boolean; + isOverlayOutsideView: boolean; +} + +/** The change event emitted by the strategy when a fallback position is used. + */ +export class ConnectedOverlayPositionChange { + constructor( + public connectionPair: ConnectionPositionPair, + @Optional() public scrollableViewProperties: ScrollableViewProperties) {} +} diff --git a/deprecated-workflow-designer/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/fake-viewport-ruler.ts b/deprecated-workflow-designer/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/fake-viewport-ruler.ts new file mode 100644 index 00000000..698aed1d --- /dev/null +++ b/deprecated-workflow-designer/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/fake-viewport-ruler.ts @@ -0,0 +1,25 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +/** @docs-private */ +export class FakeViewportRuler { + getViewportRect() { + return { + left: 0, + top: 0, + width: 1014, + height: 686, + bottom: 686, + right: 1014 + }; + } + + getViewportScrollPosition() { + return {top: 0, left: 0}; + } +} diff --git a/deprecated-workflow-designer/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/free-position-strategy.ts b/deprecated-workflow-designer/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/free-position-strategy.ts new file mode 100644 index 00000000..bd33e404 --- /dev/null +++ b/deprecated-workflow-designer/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/free-position-strategy.ts @@ -0,0 +1,83 @@ +/* tslint:disable:array-type member-access variable-name typedef + only-arrow-functions directive-class-suffix component-class-suffix + component-selector no-unnecessary-type-assertion arrow-parens*/ +import {PositionStrategy} from './position-strategy'; + +/** + * Free position strategy for overlay without origin + * @author lingyi.zcs + */ +export class FreePositionStrategy implements PositionStrategy { + private _wrapper: HTMLElement; + // private _cssPosition: string = ''; + // private _top: string = ''; + // private _left: string = ''; + // private _width: string = ''; + // private _height: string = ''; + + // cssPosition(value: string) { + // this._cssPosition = value; + // return this; + // } + + // top(value: number | string): this { + // this._top = this._toCssValue(value); + // return this; + // } + + // left(value: number | string): this { + // this._left = this._toCssValue(value); + // return this; + // } + + // width(value: number | string): this { + // this._width = this._toCssValue(value); + // return this; + // } + + // height(value: number | string): this { + // this._height = this._toCssValue(value); + // return this; + // } + + /** + * Apply the position to the element. (NOTE: normally will triggered by + * scrolling) + * @docs-private + * + * @param element Element to which to apply the CSS. + * @returns Resolved when the styles have been applied. + */ + apply(element: HTMLElement): void { + if (!this._wrapper) { + this._wrapper = document.createElement('div'); + this._wrapper.classList.add('cdk-free-overlay-wrapper'); + element.parentNode.insertBefore(this._wrapper, element); + this._wrapper.appendChild(element); + + // // Initialized style once + // const style = element.style; + // style.position = this._cssPosition; + // style.top = this._top; + // style.left = this._left; + // style.width = this._width; + // style.height = this._height; + } + + // TODO: do somethings while triggered (eg. by scrolling) + } + + /** + * Removes the wrapper element from the DOM. + */ + dispose(): void { + if (this._wrapper && this._wrapper.parentNode) { + this._wrapper.parentNode.removeChild(this._wrapper); + this._wrapper = null; + } + } + + // private _toCssValue(value: number | string) { + // return typeof value === 'number' ? value + 'px' : value; + // } +} diff --git a/deprecated-workflow-designer/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/global-position-strategy.ts b/deprecated-workflow-designer/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/global-position-strategy.ts new file mode 100644 index 00000000..8e3204a9 --- /dev/null +++ b/deprecated-workflow-designer/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/global-position-strategy.ts @@ -0,0 +1,178 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +/* tslint:disable:array-type member-access variable-name typedef + only-arrow-functions directive-class-suffix component-class-suffix + component-selector no-unnecessary-type-assertion arrow-parens*/ +import {PositionStrategy} from './position-strategy'; + + +/** + * A strategy for positioning overlays. Using this strategy, an overlay is given + * an explicit position relative to the browser's viewport. We use flexbox, + * instead of transforms, in order to avoid issues with subpixel rendering which + * can cause the element to become blurry. + */ +export class GlobalPositionStrategy implements PositionStrategy { + private _cssPosition = 'static'; + private _topOffset = ''; + private _bottomOffset = ''; + private _leftOffset = ''; + private _rightOffset = ''; + private _alignItems = ''; + private _justifyContent = ''; + private _width = ''; + private _height = ''; + + /* A lazily-created wrapper for the overlay element that is used as a flex + * container. */ + private _wrapper: HTMLElement|null = null; + + /** + * Sets the top position of the overlay. Clears any previously set vertical + * position. + * @param value New top offset. + */ + top(value = ''): this { + this._bottomOffset = ''; + this._topOffset = value; + this._alignItems = 'flex-start'; + return this; + } + + /** + * Sets the left position of the overlay. Clears any previously set horizontal + * position. + * @param value New left offset. + */ + left(value = ''): this { + this._rightOffset = ''; + this._leftOffset = value; + this._justifyContent = 'flex-start'; + return this; + } + + /** + * Sets the bottom position of the overlay. Clears any previously set vertical + * position. + * @param value New bottom offset. + */ + bottom(value = ''): this { + this._topOffset = ''; + this._bottomOffset = value; + this._alignItems = 'flex-end'; + return this; + } + + /** + * Sets the right position of the overlay. Clears any previously set + * horizontal position. + * @param value New right offset. + */ + right(value = ''): this { + this._leftOffset = ''; + this._rightOffset = value; + this._justifyContent = 'flex-end'; + return this; + } + + /** + * Sets the overlay width and clears any previously set width. + * @param value New width for the overlay + */ + width(value = ''): this { + this._width = value; + + // When the width is 100%, we should reset the `left` and the offset, + // in order to ensure that the element is flush against the viewport edge. + if (value === '100%') { + this.left('0px'); + } + + return this; + } + + /** + * Sets the overlay height and clears any previously set height. + * @param value New height for the overlay + */ + height(value = ''): this { + this._height = value; + + // When the height is 100%, we should reset the `top` and the offset, + // in order to ensure that the element is flush against the viewport edge. + if (value === '100%') { + this.top('0px'); + } + + return this; + } + + /** + * Centers the overlay horizontally with an optional offset. + * Clears any previously set horizontal position. + * + * @param offset Overlay offset from the horizontal center. + */ + centerHorizontally(offset = ''): this { + this.left(offset); + this._justifyContent = 'center'; + return this; + } + + /** + * Centers the overlay vertically with an optional offset. + * Clears any previously set vertical position. + * + * @param offset Overlay offset from the vertical center. + */ + centerVertically(offset = ''): this { + this.top(offset); + this._alignItems = 'center'; + return this; + } + + /** + * 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 { + if (!this._wrapper && element.parentNode) { + this._wrapper = document.createElement('div'); + this._wrapper.classList.add('cdk-global-overlay-wrapper'); + element.parentNode.insertBefore(this._wrapper, element); + this._wrapper.appendChild(element); + } + + const styles = element.style; + const parentStyles = (element.parentNode as HTMLElement).style; + + styles.position = this._cssPosition; + styles.marginTop = this._topOffset; + styles.marginLeft = this._leftOffset; + styles.marginBottom = this._bottomOffset; + styles.marginRight = this._rightOffset; + styles.width = this._width; + styles.height = this._height; + + parentStyles.justifyContent = this._justifyContent; + parentStyles.alignItems = this._alignItems; + } + + /** + * Removes the wrapper element from the DOM. + */ + dispose(): void { + if (this._wrapper && this._wrapper.parentNode) { + this._wrapper.parentNode.removeChild(this._wrapper); + this._wrapper = null; + } + } +} diff --git a/deprecated-workflow-designer/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/overlay-position-builder.ts b/deprecated-workflow-designer/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/overlay-position-builder.ts new file mode 100644 index 00000000..0f6735eb --- /dev/null +++ b/deprecated-workflow-designer/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/overlay-position-builder.ts @@ -0,0 +1,51 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +/* tslint:disable:array-type member-access variable-name typedef + only-arrow-functions directive-class-suffix component-class-suffix + component-selector no-unnecessary-type-assertion arrow-parens*/ +import {ElementRef, Injectable} from '@angular/core'; + +import {OriginConnectionPosition, OverlayConnectionPosition} from './connected-position'; +import {ConnectedPositionStrategy} from './connected-position-strategy'; +import {FreePositionStrategy} from './free-position-strategy'; +import {GlobalPositionStrategy} from './global-position-strategy'; +import {ViewportRuler} from './viewport-ruler'; + + +/** Builder for overlay position strategy. */ +@Injectable() +export class OverlayPositionBuilder { + constructor(private _viewportRuler: ViewportRuler) {} + + /** + * Creates a free position strategy + */ + free(): FreePositionStrategy { + return new FreePositionStrategy(); + } + + /** + * Creates a global position strategy. + */ + global(): GlobalPositionStrategy { + return new GlobalPositionStrategy(); + } + + /** + * Creates a relative position strategy. + * @param elementRef + * @param originPos + * @param overlayPos + */ + connectedTo( + elementRef: ElementRef, originPos: OriginConnectionPosition, + overlayPos: OverlayConnectionPosition): ConnectedPositionStrategy { + return new ConnectedPositionStrategy( + elementRef, originPos, overlayPos, this._viewportRuler); + } +} diff --git a/deprecated-workflow-designer/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/position-strategy.ts b/deprecated-workflow-designer/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/position-strategy.ts new file mode 100644 index 00000000..21bd0fa0 --- /dev/null +++ b/deprecated-workflow-designer/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/position-strategy.ts @@ -0,0 +1,17 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +/** Strategy for setting the position on an overlay. */ +export interface PositionStrategy { + /** Updates the position of the overlay element. */ + apply(element: Element): void; + + /** Cleans up any DOM modifications made by the position strategy, if + * necessary. */ + dispose(): void; +} diff --git a/deprecated-workflow-designer/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/viewport-ruler.ts b/deprecated-workflow-designer/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/viewport-ruler.ts new file mode 100644 index 00000000..298cd642 --- /dev/null +++ b/deprecated-workflow-designer/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/viewport-ruler.ts @@ -0,0 +1,110 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +/* tslint:disable:array-type member-access variable-name typedef + only-arrow-functions directive-class-suffix component-class-suffix + component-selector no-unnecessary-type-assertion arrow-parens*/ +import {Injectable, Optional, SkipSelf} from '@angular/core'; +import {ScrollDispatcher} from '../scroll/scroll-dispatcher'; + + +/** + * Simple utility for getting the bounds of the browser viewport. + * @docs-private + */ +@Injectable() +export class ViewportRuler { + /** Cached document client rectangle. */ + private _documentRect?: ClientRect; + + constructor(scrollDispatcher: ScrollDispatcher) { + // Subscribe to scroll and resize events and update the document rectangle + // on changes. + scrollDispatcher.scrolled(0, () => this._cacheViewportGeometry()); + } + + /** Gets a ClientRect for the viewport's bounds. */ + getViewportRect(documentRect = this._documentRect): ClientRect { + // Cache the document bounding rect so that we don't recompute it for + // multiple calls. + if (!documentRect) { + this._cacheViewportGeometry(); + documentRect = this._documentRect; + } + + // Use the document element's bounding rect rather than the window scroll + // properties (e.g. pageYOffset, scrollY) due to in issue in Chrome and IE + // where window scroll properties and client coordinates + // (boundingClientRect, clientX/Y, etc.) are in different conceptual + // viewports. Under most circumstances these viewports are equivalent, but + // they can disagree when the page is pinch-zoomed (on devices that support + // touch). See + // https://bugs.chromium.org/p/chromium/issues/detail?id=489206#c4 We use + // the documentElement instead of the body because, by default (without a + // css reset) browsers typically give the document body an 8px margin, which + // is not included in getBoundingClientRect(). + const scrollPosition = this.getViewportScrollPosition(documentRect); + const height = window.innerHeight; + const width = window.innerWidth; + + return { + top: scrollPosition.top, + left: scrollPosition.left, + bottom: scrollPosition.top + height, + right: scrollPosition.left + width, + height, + width, + }; + } + + + /** + * Gets the (top, left) scroll position of the viewport. + * @param documentRect + */ + getViewportScrollPosition(documentRect = this._documentRect) { + // Cache the document bounding rect so that we don't recompute it for + // multiple calls. + if (!documentRect) { + this._cacheViewportGeometry(); + documentRect = this._documentRect; + } + + // The top-left-corner of the viewport is determined by the scroll position + // of the document body, normally just (scrollLeft, scrollTop). However, + // Chrome and Firefox disagree about whether `document.body` or + // `document.documentElement` is the scrolled element, so reading + // `scrollTop` and `scrollLeft` is inconsistent. However, using the bounding + // rect of `document.documentElement` works consistently, where the `top` + // and `left` values will equal negative the scroll position. + const top = -documentRect.top || document.body.scrollTop || + window.scrollY || document.documentElement.scrollTop || 0; + + const left = -documentRect.left || document.body.scrollLeft || + window.scrollX || document.documentElement.scrollLeft || 0; + + return {top, left}; + } + + /** Caches the latest client rectangle of the document element. */ + _cacheViewportGeometry() { + this._documentRect = document.documentElement.getBoundingClientRect(); + } +} + +export function VIEWPORT_RULER_PROVIDER_FACTORY( + parentRuler: ViewportRuler, scrollDispatcher: ScrollDispatcher) { + return parentRuler || new ViewportRuler(scrollDispatcher); +} + +export const VIEWPORT_RULER_PROVIDER = { + // If there is already a ViewportRuler available, use that. Otherwise, provide + // a new one. + provide: ViewportRuler, + deps: [[new Optional(), new SkipSelf(), ViewportRuler], ScrollDispatcher], + useFactory: VIEWPORT_RULER_PROVIDER_FACTORY +}; |