Fix OOP <webview> resize and autosize.
[chromium-blink-merge.git] / third_party / polymer / v1_0 / components-chromium / iron-dropdown / iron-dropdown-extracted.js
blob2d39dfeb8bd3a16ffe12dcd37b8184de4c3493fe
1 (function() {
2       'use strict';
4       Polymer({
5         is: 'iron-dropdown',
7         behaviors: [
8           Polymer.IronControlState,
9           Polymer.IronA11yKeysBehavior,
10           Polymer.IronOverlayBehavior,
11           Polymer.NeonAnimationRunnerBehavior
12         ],
14         properties: {
15           /**
16            * The orientation against which to align the dropdown content
17            * horizontally relative to the dropdown trigger.
18            */
19           horizontalAlign: {
20             type: String,
21             value: 'left',
22             reflectToAttribute: true
23           },
25           /**
26            * The orientation against which to align the dropdown content
27            * vertically relative to the dropdown trigger.
28            */
29           verticalAlign: {
30             type: String,
31             value: 'top',
32             reflectToAttribute: true
33           },
35           /**
36            * A pixel value that will be added to the position calculated for the
37            * given `horizontalAlign`. Use a negative value to offset to the
38            * left, or a positive value to offset to the right.
39            */
40           horizontalOffset: {
41             type: Number,
42             value: 0,
43             notify: true
44           },
46           /**
47            * A pixel value that will be added to the position calculated for the
48            * given `verticalAlign`. Use a negative value to offset towards the
49            * top, or a positive value to offset towards the bottom.
50            */
51           verticalOffset: {
52             type: Number,
53             value: 0,
54             notify: true
55           },
57           /**
58            * The element that should be used to position the dropdown when
59            * it is opened.
60            */
61           positionTarget: {
62             type: Object,
63             observer: '_positionTargetChanged'
64           },
66           /**
67            * An animation config. If provided, this will be used to animate the
68            * opening of the dropdown.
69            */
70           openAnimationConfig: {
71             type: Object
72           },
74           /**
75            * An animation config. If provided, this will be used to animate the
76            * closing of the dropdown.
77            */
78           closeAnimationConfig: {
79             type: Object
80           },
82           /**
83            * If provided, this will be the element that will be focused when
84            * the dropdown opens.
85            */
86           focusTarget: {
87             type: Object
88           },
90           /**
91            * Set to true to disable animations when opening and closing the
92            * dropdown.
93            */
94           noAnimations: {
95             type: Boolean,
96             value: false
97           },
99           /**
100            * We memoize the positionTarget bounding rectangle so that we can
101            * limit the number of times it is queried per resize / relayout.
102            * @type {?Object}
103            */
104           _positionRectMemo: {
105             type: Object
106           }
107         },
109         listeners: {
110           'neon-animation-finish': '_onNeonAnimationFinish'
111         },
113         observers: [
114           '_updateOverlayPosition(verticalAlign, horizontalAlign, verticalOffset, horizontalOffset)'
115         ],
117         attached: function() {
118           if (this.positionTarget === undefined) {
119             this.positionTarget = this._defaultPositionTarget;
120           }
121         },
123         /**
124          * The element that is contained by the dropdown, if any.
125          */
126         get containedElement() {
127           return Polymer.dom(this.$.content).getDistributedNodes()[0];
128         },
130         /**
131          * The element that should be focused when the dropdown opens.
132          */
133         get _focusTarget() {
134           return this.focusTarget || this.containedElement;
135         },
137         /**
138          * The element that should be used to position the dropdown when
139          * it opens, if no position target is configured.
140          */
141         get _defaultPositionTarget() {
142           var parent = Polymer.dom(this).parentNode;
144           if (parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
145             parent = parent.host;
146           }
148           return parent;
149         },
151         /**
152          * The bounding rect of the position target.
153          */
154         get _positionRect() {
155           if (!this._positionRectMemo && this.positionTarget) {
156             this._positionRectMemo = this.positionTarget.getBoundingClientRect();
157           }
159           return this._positionRectMemo;
160         },
162         /**
163          * The horizontal offset value used to position the dropdown.
164          */
165         get _horizontalAlignTargetValue() {
166           var target;
168           if (this.horizontalAlign === 'right') {
169             target = document.documentElement.clientWidth - this._positionRect.right;
170           } else {
171             target = this._positionRect.left;
172           }
174           target += this.horizontalOffset;
176           return Math.max(target, 0);
177         },
179         /**
180          * The vertical offset value used to position the dropdown.
181          */
182         get _verticalAlignTargetValue() {
183           var target;
185           if (this.verticalAlign === 'bottom') {
186             target = document.documentElement.clientHeight - this._positionRect.bottom;
187           } else {
188             target = this._positionRect.top;
189           }
191           target += this.verticalOffset;
193           return Math.max(target, 0);
194         },
196         /**
197          * Called when the value of `opened` changes.
198          *
199          * @param {boolean} opened True if the dropdown is opened.
200          */
201         _openedChanged: function(opened) {
202           if (opened && this.disabled) {
203             this.cancel();
204           } else {
205             this.cancelAnimation();
206             this._prepareDropdown();
207             Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this, arguments);
208           }
210           if (this.opened) {
211             this._focusContent();
212           }
213         },
215         /**
216          * Overridden from `IronOverlayBehavior`.
217          */
218         _renderOpened: function() {
219           Polymer.IronDropdownScrollManager.pushScrollLock(this);
220           if (!this.noAnimations && this.animationConfig && this.animationConfig.open) {
221             this.$.contentWrapper.classList.add('animating');
222             this.playAnimation('open');
223           } else {
224             Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this, arguments);
225           }
226         },
228         /**
229          * Overridden from `IronOverlayBehavior`.
230          */
231         _renderClosed: function() {
232           Polymer.IronDropdownScrollManager.removeScrollLock(this);
233           if (!this.noAnimations && this.animationConfig && this.animationConfig.close) {
234             this.$.contentWrapper.classList.add('animating');
235             this.playAnimation('close');
236           } else {
237             Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this, arguments);
238           }
239         },
241         /**
242          * Called when animation finishes on the dropdown (when opening or
243          * closing). Responsible for "completing" the process of opening or
244          * closing the dropdown by positioning it or setting its display to
245          * none.
246          */
247         _onNeonAnimationFinish: function() {
248           this.$.contentWrapper.classList.remove('animating');
249           if (this.opened) {
250             Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this);
251           } else {
252             Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this);
253           }
254         },
256         /**
257          * Called when an `iron-resize` event fires.
258          */
259         _onIronResize: function() {
260           var containedElement = this.containedElement;
261           var scrollTop;
262           var scrollLeft;
264           if (containedElement) {
265             scrollTop = containedElement.scrollTop;
266             scrollLeft = containedElement.scrollLeft;
267           }
269           if (this.opened) {
270             this._updateOverlayPosition();
271           }
273           Polymer.IronOverlayBehaviorImpl._onIronResize.apply(this, arguments);
275           if (containedElement) {
276             containedElement.scrollTop = scrollTop;
277             containedElement.scrollLeft = scrollLeft;
278           }
279         },
281         /**
282          * Called when the `positionTarget` property changes.
283          */
284         _positionTargetChanged: function() {
285           this._updateOverlayPosition();
286         },
288         /**
289          * Constructs the final animation config from different properties used
290          * to configure specific parts of the opening and closing animations.
291          */
292         _updateAnimationConfig: function() {
293           var animationConfig = {};
294           var animations = [];
296           if (this.openAnimationConfig) {
297             // NOTE(cdata): When making `display:none` elements visible in Safari,
298             // the element will paint once in a fully visible state, causing the
299             // dropdown to flash before it fades in. We prepend an
300             // `opaque-animation` to fix this problem:
301             animationConfig.open = [{
302               name: 'opaque-animation',
303             }].concat(this.openAnimationConfig);
304             animations = animations.concat(animationConfig.open);
305           }
307           if (this.closeAnimationConfig) {
308             animationConfig.close = this.closeAnimationConfig;
309             animations = animations.concat(animationConfig.close);
310           }
312           animations.forEach(function(animation) {
313             animation.node = this.containedElement;
314           }, this);
316           this.animationConfig = animationConfig;
317         },
319         /**
320          * Prepares the dropdown for opening by updating measured layout
321          * values.
322          */
323         _prepareDropdown: function() {
324           this.sizingTarget = this.containedElement || this.sizingTarget;
325           this._updateAnimationConfig();
326           this._updateOverlayPosition();
327         },
329         /**
330          * Updates the overlay position based on configured horizontal
331          * and vertical alignment, and re-memoizes these values for the sake
332          * of behavior in `IronFitBehavior`.
333          */
334         _updateOverlayPosition: function() {
335           this._positionRectMemo = null;
337           if (!this.positionTarget) {
338             return;
339           }
341           this.style[this.horizontalAlign] =
342             this._horizontalAlignTargetValue + 'px';
344           this.style[this.verticalAlign] =
345             this._verticalAlignTargetValue + 'px';
347           // NOTE(cdata): We re-memoize inline styles here, otherwise
348           // calling `refit` from `IronFitBehavior` will reset inline styles
349           // to whatever they were when the dropdown first opened.
350           if (this._fitInfo) {
351             this._fitInfo.inlineStyle[this.horizontalAlign] =
352               this.style[this.horizontalAlign];
354             this._fitInfo.inlineStyle[this.verticalAlign] =
355               this.style[this.verticalAlign];
356           }
357         },
359         /**
360          * Focuses the configured focus target.
361          */
362         _focusContent: function() {
363           // NOTE(cdata): This is async so that it can attempt the focus after
364           // `display: none` is removed from the element.
365           this.async(function() {
366             if (this._focusTarget) {
367               this._focusTarget.focus();
368             }
369           });
370         }
371       });
372     })();