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
JavaScript
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