1 <!-- 2 @license 3 Copyright (c) 2015 The Polymer Project Authors. All rights reserved. 4 This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 Code distributed by Google as part of the polymer project is also 8 subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 --> 10 11 <link rel="import" href="../polymer/polymer.html"> 12 <link rel="import" href="../iron-resizable-behavior/iron-resizable-behavior.html"> 13 <link rel="import" href="../iron-a11y-keys-behavior/iron-a11y-keys-behavior.html"> 14 <link rel="import" href="../iron-behaviors/iron-control-state.html"> 15 <link rel="import" href="../iron-overlay-behavior/iron-overlay-behavior.html"> 16 <link rel="import" href="../neon-animation/neon-animation-runner-behavior.html"> 17 <link rel="import" href="../neon-animation/animations/opaque-animation.html"> 18 <link rel="import" href="iron-dropdown-scroll-manager.html"> 19 20 <!-- 21 `<iron-dropdown>` is a generalized element that is useful when you have 22 hidden content (`.dropdown-content`) that is revealed due to some change in 23 state that should cause it to do so. 24 25 Note that this is a low-level element intended to be used as part of other 26 composite elements that cause dropdowns to be revealed. 27 28 Examples of elements that might be implemented using an `iron-dropdown` 29 include comboboxes, menubuttons, selects. The list goes on. 30 31 The `<iron-dropdown>` element exposes attributes that allow the position 32 of the `.dropdown-content` relative to the `.dropdown-trigger` to be 33 configured. 34 35 <iron-dropdown horizontal-align="right" vertical-align="top"> 36 <div class="dropdown-content">Hello!</div> 37 </iron-dropdown> 38 39 In the above example, the `<div>` with class `.dropdown-content` will be 40 hidden until the dropdown element has `opened` set to true, or when the `open` 41 method is called on the element. 42 43 @demo demo/index.html 44 --> 45 46 <dom-module id="iron-dropdown"> 47 <style> 48 :host { 49 position: fixed; 50 } 51 52 #contentWrapper ::content > * { 53 overflow: auto; 54 } 55 56 #contentWrapper.animating ::content > * { 57 overflow: hidden; 58 } 59 </style> 60 <template> 61 <div id="contentWrapper"> 62 <content id="content" select=".dropdown-content"></content> 63 </div> 64 </template> 65 66 <script> 67 (function() { 68 'use strict'; 69 70 Polymer({ 71 is: 'iron-dropdown', 72 73 behaviors: [ 74 Polymer.IronControlState, 75 Polymer.IronA11yKeysBehavior, 76 Polymer.IronOverlayBehavior, 77 Polymer.NeonAnimationRunnerBehavior 78 ], 79 80 properties: { 81 /** 82 * The orientation against which to align the dropdown content 83 * horizontally relative to the dropdown trigger. 84 * Overridden from `Polymer.IronFitBehavior`. 85 */ 86 horizontalAlign: { 87 type: String, 88 value: 'left', 89 reflectToAttribute: true 90 }, 91 92 /** 93 * The orientation against which to align the dropdown content 94 * vertically relative to the dropdown trigger. 95 * Overridden from `Polymer.IronFitBehavior`. 96 */ 97 verticalAlign: { 98 type: String, 99 value: 'top', 100 reflectToAttribute: true 101 }, 102 103 /** 104 * An animation config. If provided, this will be used to animate the 105 * opening of the dropdown. 106 */ 107 openAnimationConfig: { 108 type: Object 109 }, 110 111 /** 112 * An animation config. If provided, this will be used to animate the 113 * closing of the dropdown. 114 */ 115 closeAnimationConfig: { 116 type: Object 117 }, 118 119 /** 120 * If provided, this will be the element that will be focused when 121 * the dropdown opens. 122 */ 123 focusTarget: { 124 type: Object 125 }, 126 127 /** 128 * Set to true to disable animations when opening and closing the 129 * dropdown. 130 */ 131 noAnimations: { 132 type: Boolean, 133 value: false 134 }, 135 136 /** 137 * By default, the dropdown will constrain scrolling on the page 138 * to itself when opened. 139 * Set to true in order to prevent scroll from being constrained 140 * to the dropdown when it opens. 141 */ 142 allowOutsideScroll: { 143 type: Boolean, 144 value: false 145 } 146 }, 147 148 listeners: { 149 'neon-animation-finish': '_onNeonAnimationFinish' 150 }, 151 152 observers: [ 153 '_updateOverlayPosition(positionTarget, verticalAlign, horizontalAlign, verticalOffset, horizontalOffset)' 154 ], 155 156 /** 157 * The element that is contained by the dropdown, if any. 158 */ 159 get containedElement() { 160 return Polymer.dom(this.$.content).getDistributedNodes()[0]; 161 }, 162 163 /** 164 * The element that should be focused when the dropdown opens. 165 * @deprecated 166 */ 167 get _focusTarget() { 168 return this.focusTarget || this.containedElement; 169 }, 170 171 detached: function() { 172 this.cancelAnimation(); 173 Polymer.IronDropdownScrollManager.removeScrollLock(this); 174 }, 175 176 /** 177 * Called when the value of `opened` changes. 178 * Overridden from `IronOverlayBehavior` 179 */ 180 _openedChanged: function() { 181 if (this.opened && this.disabled) { 182 this.cancel(); 183 } else { 184 this.cancelAnimation(); 185 this.sizingTarget = this.containedElement || this.sizingTarget; 186 this._updateAnimationConfig(); 187 if (this.opened && !this.allowOutsideScroll) { 188 Polymer.IronDropdownScrollManager.pushScrollLock(this); 189 } else { 190 Polymer.IronDropdownScrollManager.removeScrollLock(this); 191 } 192 Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this, arguments); 193 } 194 }, 195 196 /** 197 * Overridden from `IronOverlayBehavior`. 198 */ 199 _renderOpened: function() { 200 if (!this.noAnimations && this.animationConfig.open) { 201 this.$.contentWrapper.classList.add('animating'); 202 this.playAnimation('open'); 203 } else { 204 Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this, arguments); 205 } 206 }, 207 208 /** 209 * Overridden from `IronOverlayBehavior`. 210 */ 211 _renderClosed: function() { 212 if (!this.noAnimations && this.animationConfig.close) { 213 this.$.contentWrapper.classList.add('animating'); 214 this.playAnimation('close'); 215 } else { 216 Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this, arguments); 217 } 218 }, 219 220 /** 221 * Called when animation finishes on the dropdown (when opening or 222 * closing). Responsible for "completing" the process of opening or 223 * closing the dropdown by positioning it or setting its display to 224 * none. 225 */ 226 _onNeonAnimationFinish: function() { 227 this.$.contentWrapper.classList.remove('animating'); 228 if (this.opened) { 229 this._finishRenderOpened(); 230 } else { 231 this._finishRenderClosed(); 232 } 233 }, 234 235 /** 236 * Constructs the final animation config from different properties used 237 * to configure specific parts of the opening and closing animations. 238 */ 239 _updateAnimationConfig: function() { 240 var animations = (this.openAnimationConfig || []).concat(this.closeAnimationConfig || []); 241 for (var i = 0; i < animations.length; i++) { 242 animations[i].node = this.containedElement; 243 } 244 this.animationConfig = { 245 open: this.openAnimationConfig, 246 close: this.closeAnimationConfig 247 }; 248 }, 249 250 /** 251 * Updates the overlay position based on configured horizontal 252 * and vertical alignment. 253 */ 254 _updateOverlayPosition: function() { 255 if (this.isAttached) { 256 // This triggers iron-resize, and iron-overlay-behavior will call refit if needed. 257 this.notifyResize(); 258 } 259 }, 260 261 /** 262 * Apply focus to focusTarget or containedElement 263 */ 264 _applyFocus: function () { 265 var focusTarget = this.focusTarget || this.containedElement; 266 if (focusTarget && this.opened && !this.noAutoFocus) { 267 focusTarget.focus(); 268 } else { 269 Polymer.IronOverlayBehaviorImpl._applyFocus.apply(this, arguments); 270 } 271 } 272 }); 273 })(); 274 </script> 275 </dom-module> 276