Update Polymer and pull in iron-list
[chromium-blink-merge.git] / third_party / polymer / v1_0 / components-chromium / iron-fit-behavior / iron-fit-behavior-extracted.js
blob8a390077efceeed98adf5e7be3d56ac169a88dcd
3 /**
4 Polymer.IronFitBehavior fits an element in another element using `max-height` and `max-width`, and
5 optionally centers it in the window or another element.
7 The element will only be sized and/or positioned if it has not already been sized and/or positioned
8 by CSS.
10 CSS properties               | Action
11 -----------------------------|-------------------------------------------
12 `position` set               | Element is not centered horizontally or vertically
13 `top` or `bottom` set        | Element is not vertically centered
14 `left` or `right` set        | Element is not horizontally centered
15 `max-height` or `height` set | Element respects `max-height` or `height`
16 `max-width` or `width` set   | Element respects `max-width` or `width`
18 @demo demo/index.html
19 @polymerBehavior
22   Polymer.IronFitBehavior = {
24     properties: {
26       /**
27        * The element that will receive a `max-height`/`width`. By default it is the same as `this`,
28        * but it can be set to a child element. This is useful, for example, for implementing a
29        * scrolling region inside the element.
30        * @type {!Element}
31        */
32       sizingTarget: {
33         type: Object,
34         value: function() {
35           return this;
36         }
37       },
39       /**
40        * The element to fit `this` into.
41        */
42       fitInto: {
43         type: Object,
44         value: window
45       },
47       /**
48        * Set to true to auto-fit on attach.
49        */
50       autoFitOnAttach: {
51         type: Boolean,
52         value: false
53       },
55       /** @type {?Object} */
56       _fitInfo: {
57         type: Object
58       }
60     },
62     get _fitWidth() {
63       var fitWidth;
64       if (this.fitInto === window) {
65         fitWidth = this.fitInto.innerWidth;
66       } else {
67         fitWidth = this.fitInto.getBoundingClientRect().width;
68       }
69       return fitWidth;
70     },
72     get _fitHeight() {
73       var fitHeight;
74       if (this.fitInto === window) {
75         fitHeight = this.fitInto.innerHeight;
76       } else {
77         fitHeight = this.fitInto.getBoundingClientRect().height;
78       }
79       return fitHeight;
80     },
82     get _fitLeft() {
83       var fitLeft;
84       if (this.fitInto === window) {
85         fitLeft = 0;
86       } else {
87         fitLeft = this.fitInto.getBoundingClientRect().left;
88       }
89       return fitLeft;
90     },
92     get _fitTop() {
93       var fitTop;
94       if (this.fitInto === window) {
95         fitTop = 0;
96       } else {
97         fitTop = this.fitInto.getBoundingClientRect().top;
98       }
99       return fitTop;
100     },
102     attached: function() {
103       if (this.autoFitOnAttach) {
104         if (window.getComputedStyle(this).display === 'none') {
105           setTimeout(function() {
106             this.fit();
107           }.bind(this));
108         } else {
109           this.fit();
110         }
111       }
112     },
114     /**
115      * Fits and optionally centers the element into the window, or `fitInfo` if specified.
116      */
117     fit: function() {
118       this._discoverInfo();
119       this.constrain();
120       this.center();
121     },
123     /**
124      * Memoize information needed to position and size the target element.
125      */
126     _discoverInfo: function() {
127       if (this._fitInfo) {
128         return;
129       }
130       var target = window.getComputedStyle(this);
131       var sizer = window.getComputedStyle(this.sizingTarget);
132       this._fitInfo = {
133         inlineStyle: {
134           top: this.style.top || '',
135           left: this.style.left || ''
136         },
137         positionedBy: {
138           vertically: target.top !== 'auto' ? 'top' : (target.bottom !== 'auto' ?
139             'bottom' : null),
140           horizontally: target.left !== 'auto' ? 'left' : (target.right !== 'auto' ?
141             'right' : null),
142           css: target.position
143         },
144         sizedBy: {
145           height: sizer.maxHeight !== 'none',
146           width: sizer.maxWidth !== 'none'
147         },
148         margin: {
149           top: parseInt(target.marginTop, 10) || 0,
150           right: parseInt(target.marginRight, 10) || 0,
151           bottom: parseInt(target.marginBottom, 10) || 0,
152           left: parseInt(target.marginLeft, 10) || 0
153         }
154       };
155     },
157     /**
158      * Resets the target element's position and size constraints, and clear
159      * the memoized data.
160      */
161     resetFit: function() {
162       if (!this._fitInfo || !this._fitInfo.sizedBy.height) {
163         this.sizingTarget.style.maxHeight = '';
164         this.style.top = this._fitInfo ? this._fitInfo.inlineStyle.top : '';
165       }
166       if (!this._fitInfo || !this._fitInfo.sizedBy.width) {
167         this.sizingTarget.style.maxWidth = '';
168         this.style.left = this._fitInfo ? this._fitInfo.inlineStyle.left : '';
169       }
170       if (this._fitInfo) {
171         this.style.position = this._fitInfo.positionedBy.css;
172       }
173       this._fitInfo = null;
174     },
176     /**
177      * Equivalent to calling `resetFit()` and `fit()`. Useful to call this after the element,
178      * the window, or the `fitInfo` element has been resized.
179      */
180     refit: function() {
181       this.resetFit();
182       this.fit();
183     },
185     /**
186      * Constrains the size of the element to the window or `fitInfo` by setting `max-height`
187      * and/or `max-width`.
188      */
189     constrain: function() {
190       var info = this._fitInfo;
191       // position at (0px, 0px) if not already positioned, so we can measure the natural size.
192       if (!this._fitInfo.positionedBy.vertically) {
193         this.style.top = '0px';
194       }
195       if (!this._fitInfo.positionedBy.horizontally) {
196         this.style.left = '0px';
197       }
198       // need border-box for margin/padding
199       this.sizingTarget.style.boxSizing = 'border-box';
200       // constrain the width and height if not already set
201       var rect = this.getBoundingClientRect();
202       if (!info.sizedBy.height) {
203         this._sizeDimension(rect, info.positionedBy.vertically, 'top', 'bottom', 'Height');
204       }
205       if (!info.sizedBy.width) {
206         this._sizeDimension(rect, info.positionedBy.horizontally, 'left', 'right', 'Width');
207       }
208     },
210     _sizeDimension: function(rect, positionedBy, start, end, extent) {
211       var info = this._fitInfo;
212       var max = extent === 'Width' ? this._fitWidth : this._fitHeight;
213       var flip = (positionedBy === end);
214       var offset = flip ? max - rect[end] : rect[start];
215       var margin = info.margin[flip ? start : end];
216       var offsetExtent = 'offset' + extent;
217       var sizingOffset = this[offsetExtent] - this.sizingTarget[offsetExtent];
218       this.sizingTarget.style['max' + extent] = (max - margin - offset - sizingOffset) + 'px';
219     },
221     /**
222      * Centers horizontally and vertically if not already positioned. This also sets
223      * `position:fixed`.
224      */
225     center: function() {
226       if (!this._fitInfo.positionedBy.vertically || !this._fitInfo.positionedBy.horizontally) {
227         // need position:fixed to center
228         this.style.position = 'fixed';
229       }
230       if (!this._fitInfo.positionedBy.vertically) {
231         var top = (this._fitHeight - this.offsetHeight) / 2 + this._fitTop;
232         top -= this._fitInfo.margin.top;
233         this.style.top = top + 'px';
234       }
235       if (!this._fitInfo.positionedBy.horizontally) {
236         var left = (this._fitWidth - this.offsetWidth) / 2 + this._fitLeft;
237         left -= this._fitInfo.margin.left;
238         this.style.left = left + 'px';
239       }
240     }
242   };