| Index: third_party/polymer/components/iron-fit-behavior/iron-fit-behavior.html
|
| diff --git a/third_party/polymer/components/iron-fit-behavior/iron-fit-behavior.html b/third_party/polymer/components/iron-fit-behavior/iron-fit-behavior.html
|
| index 1705f31a20edf1e1685917ec88aa79a974e8f2bb..d51d87dfe44f135dce3cdb01d95536709d68923f 100644
|
| --- a/third_party/polymer/components/iron-fit-behavior/iron-fit-behavior.html
|
| +++ b/third_party/polymer/components/iron-fit-behavior/iron-fit-behavior.html
|
| @@ -11,9 +11,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
| <link rel="import" href="../polymer/polymer.html">
|
|
|
| <script>
|
| -
|
| /**
|
| -Polymer.IronFitBehavior fits an element in another element using `max-height` and `max-width`, and
|
| +`Polymer.IronFitBehavior` fits an element in another element using `max-height` and `max-width`, and
|
| optionally centers it in the window or another element.
|
|
|
| The element will only be sized and/or positioned if it has not already been sized and/or positioned
|
| @@ -24,8 +23,25 @@ CSS properties | Action
|
| `position` set | Element is not centered horizontally or vertically
|
| `top` or `bottom` set | Element is not vertically centered
|
| `left` or `right` set | Element is not horizontally centered
|
| -`max-height` or `height` set | Element respects `max-height` or `height`
|
| -`max-width` or `width` set | Element respects `max-width` or `width`
|
| +`max-height` set | Element respects `max-height`
|
| +`max-width` set | Element respects `max-width`
|
| +
|
| +`Polymer.IronFitBehavior` can position an element into another element using
|
| +`verticalAlign` and `horizontalAlign`. This will override the element's css position.
|
| +
|
| + <div class="container">
|
| + <iron-fit-impl vertical-align="top" horizontal-align="auto">
|
| + Positioned into the container
|
| + </iron-fit-impl>
|
| + </div>
|
| +
|
| +Use `noOverlap` to position the element around another element without overlapping it.
|
| +
|
| + <div class="container">
|
| + <iron-fit-impl no-overlap vertical-align="auto" horizontal-align="auto">
|
| + Positioned around the container
|
| + </iron-fit-impl>
|
| + </div>
|
|
|
| @demo demo/index.html
|
| @polymerBehavior
|
| @@ -57,6 +73,66 @@ CSS properties | Action
|
| },
|
|
|
| /**
|
| + * Will position the element around the positionTarget without overlapping it.
|
| + */
|
| + noOverlap: {
|
| + type: Boolean
|
| + },
|
| +
|
| + /**
|
| + * The element that should be used to position the element. If not set, it will
|
| + * default to the parent node.
|
| + * @type {!Element}
|
| + */
|
| + positionTarget: {
|
| + type: Element
|
| + },
|
| +
|
| + /**
|
| + * The orientation against which to align the element horizontally
|
| + * relative to the `positionTarget`. Possible values are "left", "right", "auto".
|
| + */
|
| + horizontalAlign: {
|
| + type: String
|
| + },
|
| +
|
| + /**
|
| + * The orientation against which to align the element vertically
|
| + * relative to the `positionTarget`. Possible values are "top", "bottom", "auto".
|
| + */
|
| + verticalAlign: {
|
| + type: String
|
| + },
|
| +
|
| + /**
|
| + * If true, it will use `horizontalAlign` and `verticalAlign` values as preferred alignment
|
| + * and if there's not enough space, it will pick the values which minimize the cropping.
|
| + */
|
| + dynamicAlign: {
|
| + type: Boolean
|
| + },
|
| +
|
| + /**
|
| + * The same as setting margin-left and margin-right css properties.
|
| + * @deprecated
|
| + */
|
| + horizontalOffset: {
|
| + type: Number,
|
| + value: 0,
|
| + notify: true
|
| + },
|
| +
|
| + /**
|
| + * The same as setting margin-top and margin-bottom css properties.
|
| + * @deprecated
|
| + */
|
| + verticalOffset: {
|
| + type: Number,
|
| + value: 0,
|
| + notify: true
|
| + },
|
| +
|
| + /**
|
| * Set to true to auto-fit on attach.
|
| */
|
| autoFitOnAttach: {
|
| @@ -68,7 +144,6 @@ CSS properties | Action
|
| _fitInfo: {
|
| type: Object
|
| }
|
| -
|
| },
|
|
|
| get _fitWidth() {
|
| @@ -111,7 +186,40 @@ CSS properties | Action
|
| return fitTop;
|
| },
|
|
|
| + /**
|
| + * The element that should be used to position the element,
|
| + * if no position target is configured.
|
| + */
|
| + get _defaultPositionTarget() {
|
| + var parent = Polymer.dom(this).parentNode;
|
| +
|
| + if (parent && parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
| + parent = parent.host;
|
| + }
|
| +
|
| + return parent;
|
| + },
|
| +
|
| + /**
|
| + * The horizontal align value, accounting for the RTL/LTR text direction.
|
| + */
|
| + get _localeHorizontalAlign() {
|
| + if (this._isRTL) {
|
| + // In RTL, "left" becomes "right".
|
| + if (this.horizontalAlign === 'right') {
|
| + return 'left';
|
| + }
|
| + if (this.horizontalAlign === 'left') {
|
| + return 'right';
|
| + }
|
| + }
|
| + return this.horizontalAlign;
|
| + },
|
| +
|
| attached: function() {
|
| + // Memoize this to avoid expensive calculations & relayouts.
|
| + this._isRTL = window.getComputedStyle(this).direction == 'rtl';
|
| + this.positionTarget = this.positionTarget || this._defaultPositionTarget;
|
| if (this.autoFitOnAttach) {
|
| if (window.getComputedStyle(this).display === 'none') {
|
| setTimeout(function() {
|
| @@ -124,16 +232,18 @@ CSS properties | Action
|
| },
|
|
|
| /**
|
| - * Fits and optionally centers the element into the window, or `fitInfo` if specified.
|
| + * Positions and fits the element into the `fitInto` element.
|
| */
|
| fit: function() {
|
| this._discoverInfo();
|
| + this.position();
|
| this.constrain();
|
| this.center();
|
| },
|
|
|
| /**
|
| * Memoize information needed to position and size the target element.
|
| + * @suppress {deprecated}
|
| */
|
| _discoverInfo: function() {
|
| if (this._fitInfo) {
|
| @@ -141,21 +251,29 @@ CSS properties | Action
|
| }
|
| var target = window.getComputedStyle(this);
|
| var sizer = window.getComputedStyle(this.sizingTarget);
|
| +
|
| this._fitInfo = {
|
| inlineStyle: {
|
| top: this.style.top || '',
|
| - left: this.style.left || ''
|
| + left: this.style.left || '',
|
| + position: this.style.position || ''
|
| + },
|
| + sizerInlineStyle: {
|
| + maxWidth: this.sizingTarget.style.maxWidth || '',
|
| + maxHeight: this.sizingTarget.style.maxHeight || '',
|
| + boxSizing: this.sizingTarget.style.boxSizing || ''
|
| },
|
| positionedBy: {
|
| vertically: target.top !== 'auto' ? 'top' : (target.bottom !== 'auto' ?
|
| 'bottom' : null),
|
| horizontally: target.left !== 'auto' ? 'left' : (target.right !== 'auto' ?
|
| - 'right' : null),
|
| - css: target.position
|
| + 'right' : null)
|
| },
|
| sizedBy: {
|
| height: sizer.maxHeight !== 'none',
|
| - width: sizer.maxWidth !== 'none'
|
| + width: sizer.maxWidth !== 'none',
|
| + minWidth: parseInt(sizer.minWidth, 10) || 0,
|
| + minHeight: parseInt(sizer.minHeight, 10) || 0
|
| },
|
| margin: {
|
| top: parseInt(target.marginTop, 10) || 0,
|
| @@ -164,6 +282,20 @@ CSS properties | Action
|
| left: parseInt(target.marginLeft, 10) || 0
|
| }
|
| };
|
| +
|
| + // Support these properties until they are removed.
|
| + if (this.verticalOffset) {
|
| + this._fitInfo.margin.top = this._fitInfo.margin.bottom = this.verticalOffset;
|
| + this._fitInfo.inlineStyle.marginTop = this.style.marginTop || '';
|
| + this._fitInfo.inlineStyle.marginBottom = this.style.marginBottom || '';
|
| + this.style.marginTop = this.style.marginBottom = this.verticalOffset + 'px';
|
| + }
|
| + if (this.horizontalOffset) {
|
| + this._fitInfo.margin.left = this._fitInfo.margin.right = this.horizontalOffset;
|
| + this._fitInfo.inlineStyle.marginLeft = this.style.marginLeft || '';
|
| + this._fitInfo.inlineStyle.marginRight = this.style.marginRight || '';
|
| + this.style.marginLeft = this.style.marginRight = this.horizontalOffset + 'px';
|
| + }
|
| },
|
|
|
| /**
|
| @@ -171,61 +303,138 @@ CSS properties | Action
|
| * the memoized data.
|
| */
|
| resetFit: function() {
|
| - if (!this._fitInfo || !this._fitInfo.sizedBy.width) {
|
| - this.sizingTarget.style.maxWidth = '';
|
| - }
|
| - if (!this._fitInfo || !this._fitInfo.sizedBy.height) {
|
| - this.sizingTarget.style.maxHeight = '';
|
| + var info = this._fitInfo || {};
|
| + for (var property in info.sizerInlineStyle) {
|
| + this.sizingTarget.style[property] = info.sizerInlineStyle[property];
|
| }
|
| - this.style.top = this._fitInfo ? this._fitInfo.inlineStyle.top : '';
|
| - this.style.left = this._fitInfo ? this._fitInfo.inlineStyle.left : '';
|
| - if (this._fitInfo) {
|
| - this.style.position = this._fitInfo.positionedBy.css;
|
| + for (var property in info.inlineStyle) {
|
| + this.style[property] = info.inlineStyle[property];
|
| }
|
| +
|
| this._fitInfo = null;
|
| },
|
|
|
| /**
|
| - * Equivalent to calling `resetFit()` and `fit()`. Useful to call this after the element,
|
| - * the window, or the `fitInfo` element has been resized.
|
| + * Equivalent to calling `resetFit()` and `fit()`. Useful to call this after
|
| + * the element or the `fitInto` element has been resized, or if any of the
|
| + * positioning properties (e.g. `horizontalAlign, verticalAlign`) is updated.
|
| + * It preserves the scroll position of the sizingTarget.
|
| */
|
| refit: function() {
|
| + var scrollLeft = this.sizingTarget.scrollLeft;
|
| + var scrollTop = this.sizingTarget.scrollTop;
|
| this.resetFit();
|
| this.fit();
|
| + this.sizingTarget.scrollLeft = scrollLeft;
|
| + this.sizingTarget.scrollTop = scrollTop;
|
| + },
|
| +
|
| + /**
|
| + * Positions the element according to `horizontalAlign, verticalAlign`.
|
| + */
|
| + position: function() {
|
| + if (!this.horizontalAlign && !this.verticalAlign) {
|
| + // needs to be centered, and it is done after constrain.
|
| + return;
|
| + }
|
| +
|
| + this.style.position = 'fixed';
|
| + // Need border-box for margin/padding.
|
| + this.sizingTarget.style.boxSizing = 'border-box';
|
| + // Set to 0, 0 in order to discover any offset caused by parent stacking contexts.
|
| + this.style.left = '0px';
|
| + this.style.top = '0px';
|
| +
|
| + var rect = this.getBoundingClientRect();
|
| + var positionRect = this.__getNormalizedRect(this.positionTarget);
|
| + var fitRect = this.__getNormalizedRect(this.fitInto);
|
| +
|
| + var margin = this._fitInfo.margin;
|
| +
|
| + // Consider the margin as part of the size for position calculations.
|
| + var size = {
|
| + width: rect.width + margin.left + margin.right,
|
| + height: rect.height + margin.top + margin.bottom
|
| + };
|
| +
|
| + var position = this.__getPosition(this._localeHorizontalAlign, this.verticalAlign, size, positionRect, fitRect);
|
| +
|
| + var left = position.left + margin.left;
|
| + var top = position.top + margin.top;
|
| +
|
| + // Use original size (without margin).
|
| + var right = Math.min(fitRect.right - margin.right, left + rect.width);
|
| + var bottom = Math.min(fitRect.bottom - margin.bottom, top + rect.height);
|
| +
|
| + var minWidth = this._fitInfo.sizedBy.minWidth;
|
| + var minHeight = this._fitInfo.sizedBy.minHeight;
|
| + if (left < margin.left) {
|
| + left = margin.left;
|
| + if (right - left < minWidth) {
|
| + left = right - minWidth;
|
| + }
|
| + }
|
| + if (top < margin.top) {
|
| + top = margin.top;
|
| + if (bottom - top < minHeight) {
|
| + top = bottom - minHeight;
|
| + }
|
| + }
|
| +
|
| + this.sizingTarget.style.maxWidth = (right - left) + 'px';
|
| + this.sizingTarget.style.maxHeight = (bottom - top) + 'px';
|
| +
|
| + // Remove the offset caused by any stacking context.
|
| + this.style.left = (left - rect.left) + 'px';
|
| + this.style.top = (top - rect.top) + 'px';
|
| },
|
|
|
| /**
|
| - * Constrains the size of the element to the window or `fitInfo` by setting `max-height`
|
| + * Constrains the size of the element to `fitInto` by setting `max-height`
|
| * and/or `max-width`.
|
| */
|
| constrain: function() {
|
| + if (this.horizontalAlign || this.verticalAlign) {
|
| + return;
|
| + }
|
| var info = this._fitInfo;
|
| // position at (0px, 0px) if not already positioned, so we can measure the natural size.
|
| - if (!this._fitInfo.positionedBy.vertically) {
|
| + if (!info.positionedBy.vertically) {
|
| + this.style.position = 'fixed';
|
| this.style.top = '0px';
|
| }
|
| - if (!this._fitInfo.positionedBy.horizontally) {
|
| - this.style.left = '0px';
|
| - }
|
| - if (!this._fitInfo.positionedBy.vertically || !this._fitInfo.positionedBy.horizontally) {
|
| - // need position:fixed to properly size the element
|
| + if (!info.positionedBy.horizontally) {
|
| this.style.position = 'fixed';
|
| + this.style.left = '0px';
|
| }
|
| +
|
| // need border-box for margin/padding
|
| this.sizingTarget.style.boxSizing = 'border-box';
|
| // constrain the width and height if not already set
|
| var rect = this.getBoundingClientRect();
|
| if (!info.sizedBy.height) {
|
| - this._sizeDimension(rect, info.positionedBy.vertically, 'top', 'bottom', 'Height');
|
| + this.__sizeDimension(rect, info.positionedBy.vertically, 'top', 'bottom', 'Height');
|
| }
|
| if (!info.sizedBy.width) {
|
| - this._sizeDimension(rect, info.positionedBy.horizontally, 'left', 'right', 'Width');
|
| + this.__sizeDimension(rect, info.positionedBy.horizontally, 'left', 'right', 'Width');
|
| }
|
| },
|
|
|
| + /**
|
| + * @protected
|
| + * @deprecated
|
| + */
|
| _sizeDimension: function(rect, positionedBy, start, end, extent) {
|
| + this.__sizeDimension(rect, positionedBy, start, end, extent);
|
| + },
|
| +
|
| + /**
|
| + * @private
|
| + */
|
| + __sizeDimension: function(rect, positionedBy, start, end, extent) {
|
| var info = this._fitInfo;
|
| - var max = extent === 'Width' ? this._fitWidth : this._fitHeight;
|
| + var fitRect = this.__getNormalizedRect(this.fitInto);
|
| + var max = extent === 'Width' ? fitRect.width : fitRect.height;
|
| var flip = (positionedBy === end);
|
| var offset = flip ? max - rect[end] : rect[start];
|
| var margin = info.margin[flip ? start : end];
|
| @@ -239,6 +448,9 @@ CSS properties | Action
|
| * `position:fixed`.
|
| */
|
| center: function() {
|
| + if (this.horizontalAlign || this.verticalAlign) {
|
| + return;
|
| + }
|
| var positionedBy = this._fitInfo.positionedBy;
|
| if (positionedBy.vertically && positionedBy.horizontally) {
|
| // Already positioned.
|
| @@ -257,16 +469,126 @@ CSS properties | Action
|
| }
|
| // It will take in consideration margins and transforms
|
| var rect = this.getBoundingClientRect();
|
| + var fitRect = this.__getNormalizedRect(this.fitInto);
|
| if (!positionedBy.vertically) {
|
| - var top = this._fitTop - rect.top + (this._fitHeight - rect.height) / 2;
|
| + var top = fitRect.top - rect.top + (fitRect.height - rect.height) / 2;
|
| this.style.top = top + 'px';
|
| }
|
| if (!positionedBy.horizontally) {
|
| - var left = this._fitLeft - rect.left + (this._fitWidth - rect.width) / 2;
|
| + var left = fitRect.left - rect.left + (fitRect.width - rect.width) / 2;
|
| this.style.left = left + 'px';
|
| }
|
| + },
|
| +
|
| + __getNormalizedRect: function(target) {
|
| + if (target === document.documentElement || target === window) {
|
| + return {
|
| + top: 0,
|
| + left: 0,
|
| + width: window.innerWidth,
|
| + height: window.innerHeight,
|
| + right: window.innerWidth,
|
| + bottom: window.innerHeight
|
| + };
|
| + }
|
| + return target.getBoundingClientRect();
|
| + },
|
| +
|
| + __getCroppedArea: function(position, size, fitRect) {
|
| + var verticalCrop = Math.min(0, position.top) + Math.min(0, fitRect.bottom - (position.top + size.height));
|
| + var horizontalCrop = Math.min(0, position.left) + Math.min(0, fitRect.right - (position.left + size.width));
|
| + return Math.abs(verticalCrop) * size.width + Math.abs(horizontalCrop) * size.height;
|
| + },
|
| +
|
| +
|
| + __getPosition: function(hAlign, vAlign, size, positionRect, fitRect) {
|
| + // All the possible configurations.
|
| + // Ordered as top-left, top-right, bottom-left, bottom-right.
|
| + var positions = [{
|
| + verticalAlign: 'top',
|
| + horizontalAlign: 'left',
|
| + top: positionRect.top,
|
| + left: positionRect.left
|
| + }, {
|
| + verticalAlign: 'top',
|
| + horizontalAlign: 'right',
|
| + top: positionRect.top,
|
| + left: positionRect.right - size.width
|
| + }, {
|
| + verticalAlign: 'bottom',
|
| + horizontalAlign: 'left',
|
| + top: positionRect.bottom - size.height,
|
| + left: positionRect.left
|
| + }, {
|
| + verticalAlign: 'bottom',
|
| + horizontalAlign: 'right',
|
| + top: positionRect.bottom - size.height,
|
| + left: positionRect.right - size.width
|
| + }];
|
| +
|
| + if (this.noOverlap) {
|
| + // Duplicate.
|
| + for (var i = 0, l = positions.length; i < l; i++) {
|
| + var copy = {};
|
| + for (var key in positions[i]) {
|
| + copy[key] = positions[i][key];
|
| + }
|
| + positions.push(copy);
|
| + }
|
| + // Horizontal overlap only.
|
| + positions[0].top = positions[1].top += positionRect.height;
|
| + positions[2].top = positions[3].top -= positionRect.height;
|
| + // Vertical overlap only.
|
| + positions[4].left = positions[6].left += positionRect.width;
|
| + positions[5].left = positions[7].left -= positionRect.width;
|
| + }
|
| +
|
| + // Consider auto as null for coding convenience.
|
| + vAlign = vAlign === 'auto' ? null : vAlign;
|
| + hAlign = hAlign === 'auto' ? null : hAlign;
|
| +
|
| + var position;
|
| + for (var i = 0; i < positions.length; i++) {
|
| + var pos = positions[i];
|
| +
|
| + // If both vAlign and hAlign are defined, return exact match.
|
| + // For dynamicAlign and noOverlap we'll have more than one candidate, so
|
| + // we'll have to check the croppedArea to make the best choice.
|
| + if (!this.dynamicAlign && !this.noOverlap &&
|
| + pos.verticalAlign === vAlign && pos.horizontalAlign === hAlign) {
|
| + position = pos;
|
| + break;
|
| + }
|
| +
|
| + // Align is ok if alignment preferences are respected. If no preferences,
|
| + // it is considered ok.
|
| + var alignOk = (!vAlign || pos.verticalAlign === vAlign) &&
|
| + (!hAlign || pos.horizontalAlign === hAlign);
|
| +
|
| + // Filter out elements that don't match the alignment (if defined).
|
| + // With dynamicAlign, we need to consider all the positions to find the
|
| + // one that minimizes the cropped area.
|
| + if (!this.dynamicAlign && !alignOk) {
|
| + continue;
|
| + }
|
| +
|
| + position = position || pos;
|
| + pos.croppedArea = this.__getCroppedArea(pos, size, fitRect);
|
| + var diff = pos.croppedArea - position.croppedArea;
|
| + // Check which crops less. If it crops equally, check if align is ok.
|
| + if (diff < 0 || (diff === 0 && alignOk)) {
|
| + position = pos;
|
| + }
|
| + // If not cropped and respects the align requirements, keep it.
|
| + // This allows to prefer positions overlapping horizontally over the
|
| + // ones overlapping vertically.
|
| + if (position.croppedArea === 0 && alignOk) {
|
| + break;
|
| + }
|
| + }
|
| +
|
| + return position;
|
| }
|
|
|
| };
|
| -
|
| </script>
|
|
|