UNPKG

md2

Version:

Angular2 based Material Design components, directives and services are Accordion, Autocomplete, Chips(Tags), Collapse, Colorpicker, Data Table, Datepicker, Dialog(Modal), Menu, Multiselect, Select, Tabs, Tags(Chips), Toast and Tooltip.

335 lines 16.9 kB
import { ConnectionPositionPair, ConnectedOverlayPositionChange } from './connected-position'; import { Subject } from 'rxjs/Subject'; /** * 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. */ var ConnectedPositionStrategy = (function () { function ConnectedPositionStrategy(_connectedTo, _originPos, _overlayPos, _viewportRuler) { this._connectedTo = _connectedTo; this._originPos = _originPos; this._overlayPos = _overlayPos; this._viewportRuler = _viewportRuler; this._dir = 'ltr'; /** The offset in pixels for the overlay connection point on the x-axis */ this._offsetX = 0; /** The offset in pixels for the overlay connection point on the y-axis */ this._offsetY = 0; /** The Scrollable containers used to check scrollable view properties on position change. */ this.scrollables = []; /** Ordered list of preferred positions, from most to least desirable. */ this._preferredPositions = []; this._onPositionChange = new Subject(); this._origin = this._connectedTo.nativeElement; this.withFallbackPosition(_originPos, _overlayPos); } Object.defineProperty(ConnectedPositionStrategy.prototype, "_isRtl", { /** Whether the we're dealing with an RTL context */ get: function () { return this._dir === 'rtl'; }, enumerable: true, configurable: true }); Object.defineProperty(ConnectedPositionStrategy.prototype, "onPositionChange", { /** Emits an event when the connection point changes. */ get: function () { return this._onPositionChange.asObservable(); }, enumerable: true, configurable: true }); Object.defineProperty(ConnectedPositionStrategy.prototype, "positions", { /** Ordered list of preferred positions, from most to least desirable. */ get: function () { return this._preferredPositions; }, enumerable: true, configurable: true }); /** * To be used to for any cleanup after the element gets destroyed. */ ConnectedPositionStrategy.prototype.dispose = function () { }; /** * 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. */ ConnectedPositionStrategy.prototype.apply = function (element) { // 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. var originRect = this._origin.getBoundingClientRect(); var overlayRect = element.getBoundingClientRect(); // We use the viewport rect to determine whether a position would go off-screen. var viewportRect = this._viewportRuler.getViewportRect(); // Fallback point if none of the fallbacks fit into the viewport. var fallbackPoint = null; var fallbackPosition = null; // We want to place the overlay in the first of the preferred positions such that the // overlay fits on-screen. for (var _i = 0, _a = this._preferredPositions; _i < _a.length; _i++) { var pos = _a[_i]; // 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`. var originPoint = this._getOriginConnectionPoint(originRect, pos); var 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. var scrollableViewProperties = this.getScrollableViewProperties(element); var positionChange = new ConnectedOverlayPositionChange(pos, scrollableViewProperties); this._onPositionChange.next(positionChange); return Promise.resolve(null); } 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); return Promise.resolve(null); }; /** * 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. */ ConnectedPositionStrategy.prototype.recalculateLastPosition = function () { var originRect = this._origin.getBoundingClientRect(); var overlayRect = this._pane.getBoundingClientRect(); var viewportRect = this._viewportRuler.getViewportRect(); var lastPosition = this._lastConnectedPosition || this._preferredPositions[0]; var originPoint = this._getOriginConnectionPoint(originRect, lastPosition); var 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. */ ConnectedPositionStrategy.prototype.withScrollableContainers = function (scrollables) { this.scrollables = scrollables; }; /** * Adds a new preferred fallback position. * @param originPos * @param overlayPos */ ConnectedPositionStrategy.prototype.withFallbackPosition = function (originPos, overlayPos) { 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. */ ConnectedPositionStrategy.prototype.withDirection = function (dir) { 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. */ ConnectedPositionStrategy.prototype.withOffsetX = function (offset) { 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. */ ConnectedPositionStrategy.prototype.withOffsetY = function (offset) { this._offsetY = offset; return this; }; /** * Gets the horizontal (x) "start" dimension based on whether the overlay is in an RTL context. * @param rect */ ConnectedPositionStrategy.prototype._getStartX = function (rect) { 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 */ ConnectedPositionStrategy.prototype._getEndX = function (rect) { 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 */ ConnectedPositionStrategy.prototype._getOriginConnectionPoint = function (originRect, pos) { var originStartX = this._getStartX(originRect); var originEndX = this._getEndX(originRect); var x; if (pos.originX == 'center') { x = originStartX + (originRect.width / 2); } else { x = pos.originX == 'start' ? originStartX : originEndX; } var y; if (pos.originY == 'center') { y = originRect.top + (originRect.height / 2); } else { y = pos.originY == 'top' ? originRect.top : originRect.bottom; } return { x: x, y: 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. */ ConnectedPositionStrategy.prototype._getOverlayPoint = function (originPoint, overlayRect, viewportRect, pos) { // Calculate the (overlayStartX, overlayStartY), the start of the potential overlay position // relative to the origin point. var overlayStartX; 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; } var overlayStartY; if (pos.overlayY == 'center') { overlayStartY = -overlayRect.height / 2; } else { overlayStartY = pos.overlayY == 'top' ? 0 : -overlayRect.height; } // The (x, y) coordinates of the overlay. var x = originPoint.x + overlayStartX + this._offsetX; var y = originPoint.y + overlayStartY + this._offsetY; // How much the overlay would overflow at this position, on each side. var leftOverflow = 0 - x; var rightOverflow = (x + overlayRect.width) - viewportRect.width; var topOverflow = 0 - y; var bottomOverflow = (y + overlayRect.height) - viewportRect.height; // Visible parts of the element on each axis. var visibleWidth = this._subtractOverflows(overlayRect.width, leftOverflow, rightOverflow); var visibleHeight = this._subtractOverflows(overlayRect.height, topOverflow, bottomOverflow); // The area of the element that's within the viewport. var visibleArea = visibleWidth * visibleHeight; var fitsInViewport = (overlayRect.width * overlayRect.height) === visibleArea; return { x: x, y: y, fitsInViewport: fitsInViewport, visibleArea: 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. */ ConnectedPositionStrategy.prototype.getScrollableViewProperties = function (overlay) { var _this = this; var originBounds = this._getElementBounds(this._origin); var overlayBounds = this._getElementBounds(overlay); var scrollContainerBounds = this.scrollables.map(function (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. */ ConnectedPositionStrategy.prototype.isElementOutsideView = function (elementBounds, containersBounds) { return containersBounds.some(function (containerBounds) { var outsideAbove = elementBounds.bottom < containerBounds.top; var outsideBelow = elementBounds.top > containerBounds.bottom; var outsideLeft = elementBounds.right < containerBounds.left; var outsideRight = elementBounds.left > containerBounds.right; return outsideAbove || outsideBelow || outsideLeft || outsideRight; }); }; /** Whether the element is clipped by any of the containers. */ ConnectedPositionStrategy.prototype.isElementClipped = function (elementBounds, containersBounds) { return containersBounds.some(function (containerBounds) { var clippedAbove = elementBounds.top < containerBounds.top; var clippedBelow = elementBounds.bottom > containerBounds.bottom; var clippedLeft = elementBounds.left < containerBounds.left; var clippedRight = elementBounds.right > containerBounds.right; return clippedAbove || clippedBelow || clippedLeft || clippedRight; }); }; /** Physically positions the overlay element to the given coordinate. */ ConnectedPositionStrategy.prototype._setElementPosition = function (element, overlayRect, overlayPoint, pos) { // 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. var 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. var 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. var horizontalStyleProperty; 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. var 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(function (p) { return 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. */ ConnectedPositionStrategy.prototype._getElementBounds = function (element) { var 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. */ ConnectedPositionStrategy.prototype._subtractOverflows = function (length) { var overflows = []; for (var _i = 1; _i < arguments.length; _i++) { overflows[_i - 1] = arguments[_i]; } return overflows.reduce(function (currentValue, currentOverflow) { return currentValue - Math.max(currentOverflow, 0); }, length); }; return ConnectedPositionStrategy; }()); export { ConnectedPositionStrategy }; //# sourceMappingURL=connected-position-strategy.js.map