9 Polymer.IronControlState,
10 Polymer.IronA11yKeysBehavior,
11 Polymer.IronOverlayBehavior,
12 Polymer.NeonAnimationRunnerBehavior
17 * The orientation against which to align the dropdown content
18 * horizontally relative to the dropdown trigger.
23 reflectToAttribute: true
27 * The orientation against which to align the dropdown content
28 * vertically relative to the dropdown trigger.
33 reflectToAttribute: true
37 * A pixel value that will be added to the position calculated for the
38 * given `horizontalAlign`. Use a negative value to offset to the
39 * left, or a positive value to offset to the right.
48 * A pixel value that will be added to the position calculated for the
49 * given `verticalAlign`. Use a negative value to offset towards the
50 * top, or a positive value to offset towards the bottom.
59 * The element that should be used to position the dropdown when
64 observer: '_positionTargetChanged'
68 * An animation config. If provided, this will be used to animate the
69 * opening of the dropdown.
71 openAnimationConfig: {
76 * An animation config. If provided, this will be used to animate the
77 * closing of the dropdown.
79 closeAnimationConfig: {
84 * If provided, this will be the element that will be focused when
92 * Set to true to disable animations when opening and closing the
101 * We memoize the positionTarget bounding rectangle so that we can
102 * limit the number of times it is queried per resize / relayout.
111 'neon-animation-finish': '_onNeonAnimationFinish'
115 '_updateOverlayPosition(verticalAlign, horizontalAlign, verticalOffset, horizontalOffset)'
118 attached: function() {
119 if (this.positionTarget === undefined) {
120 this.positionTarget = this._defaultPositionTarget;
125 * The element that is contained by the dropdown, if any.
127 get containedElement() {
128 return Polymer.dom(this.$.content).getDistributedNodes()[0];
132 * The element that should be focused when the dropdown opens.
135 return this.focusTarget || this.containedElement;
139 * The element that should be used to position the dropdown when
140 * it opens, if no position target is configured.
142 get _defaultPositionTarget() {
143 var parent = Polymer.dom(this).parentNode;
145 if (parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
146 parent = parent.host;
153 * The bounding rect of the position target.
155 get _positionRect() {
156 if (!this._positionRectMemo && this.positionTarget) {
157 this._positionRectMemo = this.positionTarget.getBoundingClientRect();
160 return this._positionRectMemo;
164 * The horizontal offset value used to position the dropdown.
166 get _horizontalAlignTargetValue() {
169 if (this.horizontalAlign === 'right') {
170 target = document.documentElement.clientWidth - this._positionRect.right;
172 target = this._positionRect.left;
175 target += this.horizontalOffset;
177 return Math.max(target, 0);
181 * The vertical offset value used to position the dropdown.
183 get _verticalAlignTargetValue() {
186 if (this.verticalAlign === 'bottom') {
187 target = document.documentElement.clientHeight - this._positionRect.bottom;
189 target = this._positionRect.top;
192 target += this.verticalOffset;
194 return Math.max(target, 0);
198 * Called when the value of `opened` changes.
200 * @param {boolean} opened True if the dropdown is opened.
202 _openedChanged: function(opened) {
203 if (opened && this.disabled) {
206 this.cancelAnimation();
207 this._prepareDropdown();
208 Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this, arguments);
212 this._focusContent();
217 * Overridden from `IronOverlayBehavior`.
219 _renderOpened: function() {
220 Polymer.IronDropdownScrollManager.pushScrollLock(this);
221 if (!this.noAnimations && this.animationConfig && this.animationConfig.open) {
222 this.$.contentWrapper.classList.add('animating');
223 this.playAnimation('open');
225 Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this, arguments);
230 * Overridden from `IronOverlayBehavior`.
232 _renderClosed: function() {
233 Polymer.IronDropdownScrollManager.removeScrollLock(this);
234 if (!this.noAnimations && this.animationConfig && this.animationConfig.close) {
235 this.$.contentWrapper.classList.add('animating');
236 this.playAnimation('close');
238 Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this, arguments);
243 * Called when animation finishes on the dropdown (when opening or
244 * closing). Responsible for "completing" the process of opening or
245 * closing the dropdown by positioning it or setting its display to
248 _onNeonAnimationFinish: function() {
249 this.$.contentWrapper.classList.remove('animating');
251 Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this);
253 Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this);
258 * Called when an `iron-resize` event fires.
260 _onIronResize: function() {
261 var containedElement = this.containedElement;
265 if (containedElement) {
266 scrollTop = containedElement.scrollTop;
267 scrollLeft = containedElement.scrollLeft;
271 this._updateOverlayPosition();
274 Polymer.IronOverlayBehaviorImpl._onIronResize.apply(this, arguments);
276 if (containedElement) {
277 containedElement.scrollTop = scrollTop;
278 containedElement.scrollLeft = scrollLeft;
283 * Called when the `positionTarget` property changes.
285 _positionTargetChanged: function() {
286 this._updateOverlayPosition();
290 * Constructs the final animation config from different properties used
291 * to configure specific parts of the opening and closing animations.
293 _updateAnimationConfig: function() {
294 var animationConfig = {};
297 if (this.openAnimationConfig) {
298 // NOTE(cdata): When making `display:none` elements visible in Safari,
299 // the element will paint once in a fully visible state, causing the
300 // dropdown to flash before it fades in. We prepend an
301 // `opaque-animation` to fix this problem:
302 animationConfig.open = [{
303 name: 'opaque-animation',
304 }].concat(this.openAnimationConfig);
305 animations = animations.concat(animationConfig.open);
308 if (this.closeAnimationConfig) {
309 animationConfig.close = this.closeAnimationConfig;
310 animations = animations.concat(animationConfig.close);
313 animations.forEach(function(animation) {
314 animation.node = this.containedElement;
317 this.animationConfig = animationConfig;
321 * Prepares the dropdown for opening by updating measured layout
324 _prepareDropdown: function() {
325 this.sizingTarget = this.containedElement || this.sizingTarget;
326 this._updateAnimationConfig();
327 this._updateOverlayPosition();
331 * Updates the overlay position based on configured horizontal
332 * and vertical alignment, and re-memoizes these values for the sake
333 * of behavior in `IronFitBehavior`.
335 _updateOverlayPosition: function() {
336 this._positionRectMemo = null;
338 if (!this.positionTarget) {
342 this.style[this.horizontalAlign] =
343 this._horizontalAlignTargetValue + 'px';
345 this.style[this.verticalAlign] =
346 this._verticalAlignTargetValue + 'px';
348 // NOTE(cdata): We re-memoize inline styles here, otherwise
349 // calling `refit` from `IronFitBehavior` will reset inline styles
350 // to whatever they were when the dropdown first opened.
352 this._fitInfo.inlineStyle[this.horizontalAlign] =
353 this.style[this.horizontalAlign];
355 this._fitInfo.inlineStyle[this.verticalAlign] =
356 this.style[this.verticalAlign];
361 * Focuses the configured focus target.
363 _focusContent: function() {
364 // NOTE(cdata): This is async so that it can attempt the focus after
365 // `display: none` is removed from the element.
366 this.async(function() {
367 if (this._focusTarget) {
368 this._focusTarget.focus();