| OLD | NEW |
| 1 (function() { | 1 (function() { |
| 2 'use strict'; | 2 'use strict'; |
| 3 | 3 |
| 4 Polymer({ | 4 Polymer({ |
| 5 is: 'iron-dropdown', | 5 is: 'iron-dropdown', |
| 6 | 6 |
| 7 behaviors: [ | 7 behaviors: [ |
| 8 Polymer.IronControlState, | 8 Polymer.IronControlState, |
| 9 Polymer.IronA11yKeysBehavior, | 9 Polymer.IronA11yKeysBehavior, |
| 10 Polymer.IronOverlayBehavior, | 10 Polymer.IronOverlayBehavior, |
| (...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 73 | 73 |
| 74 /** | 74 /** |
| 75 * An animation config. If provided, this will be used to animate the | 75 * An animation config. If provided, this will be used to animate the |
| 76 * closing of the dropdown. | 76 * closing of the dropdown. |
| 77 */ | 77 */ |
| 78 closeAnimationConfig: { | 78 closeAnimationConfig: { |
| 79 type: Object | 79 type: Object |
| 80 }, | 80 }, |
| 81 | 81 |
| 82 /** | 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 }, |
| 89 |
| 90 /** |
| 83 * Set to true to disable animations when opening and closing the | 91 * Set to true to disable animations when opening and closing the |
| 84 * dropdown. | 92 * dropdown. |
| 85 */ | 93 */ |
| 86 noAnimations: { | 94 noAnimations: { |
| 87 type: Boolean, | 95 type: Boolean, |
| 88 value: false | 96 value: false |
| 89 }, | 97 }, |
| 90 | 98 |
| 91 /** | 99 /** |
| 92 * We memoize the positionTarget bounding rectangle so that we can | 100 * We memoize the positionTarget bounding rectangle so that we can |
| (...skipping 19 matching lines...) Expand all Loading... |
| 112 } | 120 } |
| 113 }, | 121 }, |
| 114 | 122 |
| 115 /** | 123 /** |
| 116 * The element that is contained by the dropdown, if any. | 124 * The element that is contained by the dropdown, if any. |
| 117 */ | 125 */ |
| 118 get containedElement() { | 126 get containedElement() { |
| 119 return Polymer.dom(this.$.content).getDistributedNodes()[0]; | 127 return Polymer.dom(this.$.content).getDistributedNodes()[0]; |
| 120 }, | 128 }, |
| 121 | 129 |
| 130 /** |
| 131 * The element that should be focused when the dropdown opens. |
| 132 */ |
| 133 get _focusTarget() { |
| 134 return this.focusTarget || this.containedElement; |
| 135 }, |
| 136 |
| 137 /** |
| 138 * The element that should be used to position the dropdown when |
| 139 * it opens, if no position target is configured. |
| 140 */ |
| 122 get _defaultPositionTarget() { | 141 get _defaultPositionTarget() { |
| 123 var parent = Polymer.dom(this).parentNode; | 142 var parent = Polymer.dom(this).parentNode; |
| 124 | 143 |
| 125 if (parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { | 144 if (parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { |
| 126 parent = parent.host; | 145 parent = parent.host; |
| 127 } | 146 } |
| 128 | 147 |
| 129 return parent; | 148 return parent; |
| 130 }, | 149 }, |
| 131 | 150 |
| 151 /** |
| 152 * The bounding rect of the position target. |
| 153 */ |
| 132 get _positionRect() { | 154 get _positionRect() { |
| 133 if (!this._positionRectMemo && this.positionTarget) { | 155 if (!this._positionRectMemo && this.positionTarget) { |
| 134 this._positionRectMemo = this.positionTarget.getBoundingClientRect()
; | 156 this._positionRectMemo = this.positionTarget.getBoundingClientRect()
; |
| 135 } | 157 } |
| 136 | 158 |
| 137 return this._positionRectMemo; | 159 return this._positionRectMemo; |
| 138 }, | 160 }, |
| 139 | 161 |
| 162 /** |
| 163 * The horizontal offset value used to position the dropdown. |
| 164 */ |
| 140 get _horizontalAlignTargetValue() { | 165 get _horizontalAlignTargetValue() { |
| 141 var target; | 166 var target; |
| 142 | 167 |
| 143 if (this.horizontalAlign === 'right') { | 168 if (this.horizontalAlign === 'right') { |
| 144 target = document.documentElement.clientWidth - this._positionRect.r
ight; | 169 target = document.documentElement.clientWidth - this._positionRect.r
ight; |
| 145 } else { | 170 } else { |
| 146 target = this._positionRect.left; | 171 target = this._positionRect.left; |
| 147 } | 172 } |
| 148 | 173 |
| 149 target += this.horizontalOffset; | 174 target += this.horizontalOffset; |
| 150 | 175 |
| 151 return Math.max(target, 0); | 176 return Math.max(target, 0); |
| 152 }, | 177 }, |
| 153 | 178 |
| 179 /** |
| 180 * The vertical offset value used to position the dropdown. |
| 181 */ |
| 154 get _verticalAlignTargetValue() { | 182 get _verticalAlignTargetValue() { |
| 155 var target; | 183 var target; |
| 156 | 184 |
| 157 if (this.verticalAlign === 'bottom') { | 185 if (this.verticalAlign === 'bottom') { |
| 158 target = document.documentElement.clientHeight - this._positionRect.
bottom; | 186 target = document.documentElement.clientHeight - this._positionRect.
bottom; |
| 159 } else { | 187 } else { |
| 160 target = this._positionRect.top; | 188 target = this._positionRect.top; |
| 161 } | 189 } |
| 162 | 190 |
| 163 target += this.verticalOffset; | 191 target += this.verticalOffset; |
| 164 | 192 |
| 165 return Math.max(target, 0); | 193 return Math.max(target, 0); |
| 166 }, | 194 }, |
| 167 | 195 |
| 196 /** |
| 197 * Called when the value of `opened` changes. |
| 198 * |
| 199 * @param {boolean} opened True if the dropdown is opened. |
| 200 */ |
| 168 _openedChanged: function(opened) { | 201 _openedChanged: function(opened) { |
| 169 if (opened && this.disabled) { | 202 if (opened && this.disabled) { |
| 170 this.cancel(); | 203 this.cancel(); |
| 171 } else { | 204 } else { |
| 172 this._cancelAnimations(); | 205 this.cancelAnimation(); |
| 173 this._prepareDropdown(); | 206 this._prepareDropdown(); |
| 174 Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this, arguments
); | 207 Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this, arguments
); |
| 175 } | 208 } |
| 209 |
| 210 if (this.opened) { |
| 211 this._focusContent(); |
| 212 } |
| 176 }, | 213 }, |
| 177 | 214 |
| 215 /** |
| 216 * Overridden from `IronOverlayBehavior`. |
| 217 */ |
| 178 _renderOpened: function() { | 218 _renderOpened: function() { |
| 179 Polymer.IronDropdownScrollManager.pushScrollLock(this); | 219 Polymer.IronDropdownScrollManager.pushScrollLock(this); |
| 180 if (!this.noAnimations && this.animationConfig && this.animationConfig
.open) { | 220 if (!this.noAnimations && this.animationConfig && this.animationConfig
.open) { |
| 181 this.$.contentWrapper.classList.add('animating'); | 221 this.$.contentWrapper.classList.add('animating'); |
| 182 this.playAnimation('open'); | 222 this.playAnimation('open'); |
| 183 } else { | 223 } else { |
| 184 this._focusContent(); | |
| 185 Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this, arguments)
; | 224 Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this, arguments)
; |
| 186 } | 225 } |
| 187 }, | 226 }, |
| 188 | 227 |
| 228 /** |
| 229 * Overridden from `IronOverlayBehavior`. |
| 230 */ |
| 189 _renderClosed: function() { | 231 _renderClosed: function() { |
| 190 Polymer.IronDropdownScrollManager.removeScrollLock(this); | 232 Polymer.IronDropdownScrollManager.removeScrollLock(this); |
| 191 if (!this.noAnimations && this.animationConfig && this.animationConfig
.close) { | 233 if (!this.noAnimations && this.animationConfig && this.animationConfig
.close) { |
| 192 this.$.contentWrapper.classList.add('animating'); | 234 this.$.contentWrapper.classList.add('animating'); |
| 193 this.playAnimation('close'); | 235 this.playAnimation('close'); |
| 194 } else { | 236 } else { |
| 195 Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this, arguments)
; | 237 Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this, arguments)
; |
| 196 } | 238 } |
| 197 }, | 239 }, |
| 198 | 240 |
| 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 */ |
| 199 _onNeonAnimationFinish: function() { | 247 _onNeonAnimationFinish: function() { |
| 200 this.$.contentWrapper.classList.remove('animating'); | 248 this.$.contentWrapper.classList.remove('animating'); |
| 201 if (this.opened) { | 249 if (this.opened) { |
| 202 Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this); | 250 Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this); |
| 203 } else { | 251 } else { |
| 204 Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this); | 252 Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this); |
| 205 } | 253 } |
| 206 }, | 254 }, |
| 207 | 255 |
| 256 /** |
| 257 * Called when an `iron-resize` event fires. |
| 258 */ |
| 208 _onIronResize: function() { | 259 _onIronResize: function() { |
| 209 var containedElement = this.containedElement; | 260 var containedElement = this.containedElement; |
| 210 var scrollTop; | 261 var scrollTop; |
| 211 var scrollLeft; | 262 var scrollLeft; |
| 212 | 263 |
| 213 if (containedElement) { | 264 if (containedElement) { |
| 214 scrollTop = containedElement.scrollTop; | 265 scrollTop = containedElement.scrollTop; |
| 215 scrollLeft = containedElement.scrollLeft; | 266 scrollLeft = containedElement.scrollLeft; |
| 216 } | 267 } |
| 217 | 268 |
| 218 if (this.opened) { | 269 if (this.opened) { |
| 219 this._updateOverlayPosition(); | 270 this._updateOverlayPosition(); |
| 220 } | 271 } |
| 221 | 272 |
| 222 Polymer.IronOverlayBehaviorImpl._onIronResize.apply(this, arguments); | 273 Polymer.IronOverlayBehaviorImpl._onIronResize.apply(this, arguments); |
| 223 | 274 |
| 224 if (containedElement) { | 275 if (containedElement) { |
| 225 containedElement.scrollTop = scrollTop; | 276 containedElement.scrollTop = scrollTop; |
| 226 containedElement.scrollLeft = scrollLeft; | 277 containedElement.scrollLeft = scrollLeft; |
| 227 } | 278 } |
| 228 }, | 279 }, |
| 229 | 280 |
| 281 /** |
| 282 * Called when the `positionTarget` property changes. |
| 283 */ |
| 230 _positionTargetChanged: function() { | 284 _positionTargetChanged: function() { |
| 231 this._updateOverlayPosition(); | 285 this._updateOverlayPosition(); |
| 232 }, | 286 }, |
| 233 | 287 |
| 234 _cancelAnimations: function() { | 288 /** |
| 235 this.cancelAnimation(); | 289 * Constructs the final animation config from different properties used |
| 236 }, | 290 * to configure specific parts of the opening and closing animations. |
| 237 | 291 */ |
| 238 _updateAnimationConfig: function() { | 292 _updateAnimationConfig: function() { |
| 239 var animationConfig = {}; | 293 var animationConfig = {}; |
| 240 var animations = []; | 294 var animations = []; |
| 241 | 295 |
| 242 if (this.openAnimationConfig) { | 296 if (this.openAnimationConfig) { |
| 243 // NOTE(cdata): When making `display:none` elements visible in Safar
i, | 297 // NOTE(cdata): When making `display:none` elements visible in Safar
i, |
| 244 // the element will paint once in a fully visible state, causing the | 298 // the element will paint once in a fully visible state, causing the |
| 245 // dropdown to flash before it fades in. We prepend an | 299 // dropdown to flash before it fades in. We prepend an |
| 246 // `opaque-animation` to fix this problem: | 300 // `opaque-animation` to fix this problem: |
| 247 animationConfig.open = [{ | 301 animationConfig.open = [{ |
| 248 name: 'opaque-animation', | 302 name: 'opaque-animation', |
| 249 }].concat(this.openAnimationConfig); | 303 }].concat(this.openAnimationConfig); |
| 250 animations = animations.concat(animationConfig.open); | 304 animations = animations.concat(animationConfig.open); |
| 251 } | 305 } |
| 252 | 306 |
| 253 if (this.closeAnimationConfig) { | 307 if (this.closeAnimationConfig) { |
| 254 animationConfig.close = this.closeAnimationConfig; | 308 animationConfig.close = this.closeAnimationConfig; |
| 255 animations = animations.concat(animationConfig.close); | 309 animations = animations.concat(animationConfig.close); |
| 256 } | 310 } |
| 257 | 311 |
| 258 animations.forEach(function(animation) { | 312 animations.forEach(function(animation) { |
| 259 animation.node = this.containedElement; | 313 animation.node = this.containedElement; |
| 260 }, this); | 314 }, this); |
| 261 | 315 |
| 262 this.animationConfig = animationConfig; | 316 this.animationConfig = animationConfig; |
| 263 }, | 317 }, |
| 264 | 318 |
| 319 /** |
| 320 * Prepares the dropdown for opening by updating measured layout |
| 321 * values. |
| 322 */ |
| 265 _prepareDropdown: function() { | 323 _prepareDropdown: function() { |
| 266 this.sizingTarget = this.containedElement || this.sizingTarget; | 324 this.sizingTarget = this.containedElement || this.sizingTarget; |
| 267 this._updateAnimationConfig(); | 325 this._updateAnimationConfig(); |
| 268 this._updateOverlayPosition(); | 326 this._updateOverlayPosition(); |
| 269 }, | 327 }, |
| 270 | 328 |
| 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 */ |
| 271 _updateOverlayPosition: function() { | 334 _updateOverlayPosition: function() { |
| 272 this._positionRectMemo = null; | 335 this._positionRectMemo = null; |
| 273 | 336 |
| 274 if (!this.positionTarget) { | 337 if (!this.positionTarget) { |
| 275 return; | 338 return; |
| 276 } | 339 } |
| 277 | 340 |
| 278 this.style[this.horizontalAlign] = | 341 this.style[this.horizontalAlign] = |
| 279 this._horizontalAlignTargetValue + 'px'; | 342 this._horizontalAlignTargetValue + 'px'; |
| 280 | 343 |
| 281 this.style[this.verticalAlign] = | 344 this.style[this.verticalAlign] = |
| 282 this._verticalAlignTargetValue + 'px'; | 345 this._verticalAlignTargetValue + 'px'; |
| 283 | 346 |
| 284 // NOTE(cdata): We re-memoize inline styles here, otherwise | 347 // NOTE(cdata): We re-memoize inline styles here, otherwise |
| 285 // calling `refit` from `IronFitBehavior` will reset inline styles | 348 // calling `refit` from `IronFitBehavior` will reset inline styles |
| 286 // to whatever they were when the dropdown first opened. | 349 // to whatever they were when the dropdown first opened. |
| 287 if (this._fitInfo) { | 350 if (this._fitInfo) { |
| 288 this._fitInfo.inlineStyle[this.horizontalAlign] = | 351 this._fitInfo.inlineStyle[this.horizontalAlign] = |
| 289 this.style[this.horizontalAlign]; | 352 this.style[this.horizontalAlign]; |
| 290 | 353 |
| 291 this._fitInfo.inlineStyle[this.verticalAlign] = | 354 this._fitInfo.inlineStyle[this.verticalAlign] = |
| 292 this.style[this.verticalAlign]; | 355 this.style[this.verticalAlign]; |
| 293 } | 356 } |
| 294 }, | 357 }, |
| 295 | 358 |
| 359 /** |
| 360 * Focuses the configured focus target. |
| 361 */ |
| 296 _focusContent: function() { | 362 _focusContent: function() { |
| 297 if (this.containedElement) { | 363 // NOTE(cdata): This is async so that it can attempt the focus after |
| 298 this.containedElement.focus(); | 364 // `display: none` is removed from the element. |
| 299 } | 365 this.async(function() { |
| 366 if (this._focusTarget) { |
| 367 this._focusTarget.focus(); |
| 368 } |
| 369 }); |
| 300 } | 370 } |
| 301 }); | 371 }); |
| 302 })(); | 372 })(); |
| OLD | NEW |