| 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 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 70 type: Number, | 70 type: Number, |
| 71 value: 0, | 71 value: 0, |
| 72 notify: true | 72 notify: true |
| 73 }, | 73 }, |
| 74 | 74 |
| 75 /** | 75 /** |
| 76 * The element that should be used to position the dropdown when | 76 * The element that should be used to position the dropdown when |
| 77 * it is opened. | 77 * it is opened. |
| 78 */ | 78 */ |
| 79 positionTarget: { | 79 positionTarget: { |
| 80 type: Object, | 80 type: Object |
| 81 observer: '_positionTargetChanged' | |
| 82 }, | 81 }, |
| 83 | 82 |
| 84 /** | 83 /** |
| 85 * An animation config. If provided, this will be used to animate the | 84 * An animation config. If provided, this will be used to animate the |
| 86 * opening of the dropdown. | 85 * opening of the dropdown. |
| 87 */ | 86 */ |
| 88 openAnimationConfig: { | 87 openAnimationConfig: { |
| 89 type: Object | 88 type: Object |
| 90 }, | 89 }, |
| 91 | 90 |
| (...skipping 24 matching lines...) Expand all Loading... |
| 116 | 115 |
| 117 /** | 116 /** |
| 118 * By default, the dropdown will constrain scrolling on the page | 117 * By default, the dropdown will constrain scrolling on the page |
| 119 * to itself when opened. | 118 * to itself when opened. |
| 120 * Set to true in order to prevent scroll from being constrained | 119 * Set to true in order to prevent scroll from being constrained |
| 121 * to the dropdown when it opens. | 120 * to the dropdown when it opens. |
| 122 */ | 121 */ |
| 123 allowOutsideScroll: { | 122 allowOutsideScroll: { |
| 124 type: Boolean, | 123 type: Boolean, |
| 125 value: false | 124 value: false |
| 126 }, | |
| 127 | |
| 128 /** | |
| 129 * We memoize the positionTarget bounding rectangle so that we can | |
| 130 * limit the number of times it is queried per resize / relayout. | |
| 131 * @type {?Object} | |
| 132 */ | |
| 133 _positionRectMemo: { | |
| 134 type: Object | |
| 135 } | 125 } |
| 136 }, | 126 }, |
| 137 | 127 |
| 138 listeners: { | 128 listeners: { |
| 139 'neon-animation-finish': '_onNeonAnimationFinish' | 129 'neon-animation-finish': '_onNeonAnimationFinish' |
| 140 }, | 130 }, |
| 141 | 131 |
| 142 observers: [ | 132 observers: [ |
| 143 '_updateOverlayPosition(verticalAlign, horizontalAlign, verticalOffset
, horizontalOffset)' | 133 '_updateOverlayPosition(positionTarget, verticalAlign, horizontalAlign
, verticalOffset, horizontalOffset)' |
| 144 ], | 134 ], |
| 145 | 135 |
| 146 attached: function() { | 136 attached: function() { |
| 147 if (this.positionTarget === undefined) { | 137 this.positionTarget = this.positionTarget || this._defaultPositionTarg
et; |
| 148 this.positionTarget = this._defaultPositionTarget; | 138 // Memoize this to avoid expensive calculations & relayouts. |
| 149 } | 139 this._isRTL = window.getComputedStyle(this).direction == 'rtl'; |
| 150 }, | 140 }, |
| 151 | 141 |
| 152 /** | 142 /** |
| 153 * The element that is contained by the dropdown, if any. | 143 * The element that is contained by the dropdown, if any. |
| 154 */ | 144 */ |
| 155 get containedElement() { | 145 get containedElement() { |
| 156 return Polymer.dom(this.$.content).getDistributedNodes()[0]; | 146 return Polymer.dom(this.$.content).getDistributedNodes()[0]; |
| 157 }, | 147 }, |
| 158 | 148 |
| 159 /** | 149 /** |
| 160 * The element that should be focused when the dropdown opens. | 150 * The element that should be focused when the dropdown opens. |
| 161 * @deprecated | 151 * @deprecated |
| 162 */ | 152 */ |
| 163 get _focusTarget() { | 153 get _focusTarget() { |
| 164 return this.focusTarget || this.containedElement; | 154 return this.focusTarget || this.containedElement; |
| 165 }, | 155 }, |
| 166 | 156 |
| 167 /** | 157 /** |
| 168 * Whether the text direction is RTL | |
| 169 */ | |
| 170 _isRTL: function() { | |
| 171 return window.getComputedStyle(this).direction == 'rtl'; | |
| 172 }, | |
| 173 | |
| 174 /** | |
| 175 * The element that should be used to position the dropdown when | 158 * The element that should be used to position the dropdown when |
| 176 * it opens, if no position target is configured. | 159 * it opens, if no position target is configured. |
| 177 */ | 160 */ |
| 178 get _defaultPositionTarget() { | 161 get _defaultPositionTarget() { |
| 179 var parent = Polymer.dom(this).parentNode; | 162 var parent = Polymer.dom(this).parentNode; |
| 180 | 163 |
| 181 if (parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { | 164 if (parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { |
| 182 parent = parent.host; | 165 parent = parent.host; |
| 183 } | 166 } |
| 184 | 167 |
| 185 return parent; | 168 return parent; |
| 186 }, | 169 }, |
| 187 | 170 |
| 188 /** | 171 /** |
| 189 * The bounding rect of the position target. | 172 * The horizontal align value, accounting for the RTL/LTR text direction
. |
| 190 */ | 173 */ |
| 191 get _positionRect() { | 174 get _localeHorizontalAlign() { |
| 192 if (!this._positionRectMemo && this.positionTarget) { | 175 // In RTL, "left" becomes "right". |
| 193 this._positionRectMemo = this.positionTarget.getBoundingClientRect()
; | 176 if (this._isRTL) { |
| 177 return this.horizontalAlign === 'right' ? 'left' : 'right'; |
| 178 } else { |
| 179 return this.horizontalAlign; |
| 194 } | 180 } |
| 195 | |
| 196 return this._positionRectMemo; | |
| 197 }, | 181 }, |
| 198 | 182 |
| 199 /** | 183 /** |
| 200 * The horizontal offset value used to position the dropdown. | 184 * The horizontal offset value used to position the dropdown. |
| 185 * @param {ClientRect} dropdownRect |
| 186 * @param {ClientRect} positionRect |
| 187 * @param {boolean=false} fromRight |
| 188 * @return {number} pixels |
| 189 * @private |
| 201 */ | 190 */ |
| 202 get _horizontalAlignTargetValue() { | 191 _horizontalAlignTargetValue: function(dropdownRect, positionRect, fromRi
ght) { |
| 203 var target; | 192 var target; |
| 204 | 193 if (fromRight) { |
| 205 // In RTL, the direction flips, so what is "right" in LTR becomes "lef
t". | 194 target = document.documentElement.clientWidth - positionRect.right -
(this._fitWidth - dropdownRect.right); |
| 206 var isRTL = this._isRTL(); | |
| 207 if ((!isRTL && this.horizontalAlign === 'right') || | |
| 208 (isRTL && this.horizontalAlign === 'left')) { | |
| 209 target = document.documentElement.clientWidth - this._positionRect.r
ight; | |
| 210 } else { | 195 } else { |
| 211 target = this._positionRect.left; | 196 target = positionRect.left - dropdownRect.left; |
| 212 } | 197 } |
| 213 | |
| 214 target += this.horizontalOffset; | 198 target += this.horizontalOffset; |
| 215 | 199 |
| 216 return Math.max(target, 0); | 200 return Math.max(target, 0); |
| 217 }, | 201 }, |
| 218 | 202 |
| 219 /** | 203 /** |
| 220 * The vertical offset value used to position the dropdown. | 204 * The vertical offset value used to position the dropdown. |
| 205 * @param {ClientRect} dropdownRect |
| 206 * @param {ClientRect} positionRect |
| 207 * @param {boolean=false} fromBottom |
| 208 * @return {number} pixels |
| 209 * @private |
| 221 */ | 210 */ |
| 222 get _verticalAlignTargetValue() { | 211 _verticalAlignTargetValue: function(dropdownRect, positionRect, fromBott
om) { |
| 223 var target; | 212 var target; |
| 224 | 213 if (fromBottom) { |
| 225 if (this.verticalAlign === 'bottom') { | 214 target = document.documentElement.clientHeight - positionRect.bottom
- (this._fitHeight - dropdownRect.bottom); |
| 226 target = document.documentElement.clientHeight - this._positionRect.
bottom; | |
| 227 } else { | 215 } else { |
| 228 target = this._positionRect.top; | 216 target = positionRect.top - dropdownRect.top; |
| 229 } | 217 } |
| 230 | |
| 231 target += this.verticalOffset; | 218 target += this.verticalOffset; |
| 232 | 219 |
| 233 return Math.max(target, 0); | 220 return Math.max(target, 0); |
| 234 }, | 221 }, |
| 235 | 222 |
| 236 /** | 223 /** |
| 237 * The horizontal align value, accounting for the RTL/LTR text direction
. | |
| 238 */ | |
| 239 get _localeHorizontalAlign() { | |
| 240 // In RTL, "left" becomes "right". | |
| 241 if (this._isRTL()) { | |
| 242 return this.horizontalAlign === 'right' ? 'left' : 'right'; | |
| 243 } else { | |
| 244 return this.horizontalAlign; | |
| 245 } | |
| 246 }, | |
| 247 | |
| 248 /** | |
| 249 * Called when the value of `opened` changes. | 224 * Called when the value of `opened` changes. |
| 250 * | 225 * |
| 251 * @param {boolean} opened True if the dropdown is opened. | 226 * @param {boolean} opened True if the dropdown is opened. |
| 252 */ | 227 */ |
| 253 _openedChanged: function(opened) { | 228 _openedChanged: function(opened) { |
| 254 if (opened && this.disabled) { | 229 if (opened && this.disabled) { |
| 255 this.cancel(); | 230 this.cancel(); |
| 256 } else { | 231 } else { |
| 257 this.cancelAnimation(); | 232 this.cancelAnimation(); |
| 258 this._prepareDropdown(); | 233 this.sizingTarget = this.containedElement || this.sizingTarget; |
| 234 this._updateAnimationConfig(); |
| 259 Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this, arguments
); | 235 Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this, arguments
); |
| 260 } | 236 } |
| 261 }, | 237 }, |
| 262 | 238 |
| 263 /** | 239 /** |
| 264 * Overridden from `IronOverlayBehavior`. | 240 * Overridden from `IronOverlayBehavior`. |
| 265 */ | 241 */ |
| 266 _renderOpened: function() { | 242 _renderOpened: function() { |
| 267 if (!this.allowOutsideScroll) { | 243 if (!this.allowOutsideScroll) { |
| 268 Polymer.IronDropdownScrollManager.pushScrollLock(this); | 244 Polymer.IronDropdownScrollManager.pushScrollLock(this); |
| 269 } | 245 } |
| 270 | 246 |
| 271 if (!this.noAnimations && this.animationConfig && this.animationConfig
.open) { | 247 if (!this.noAnimations && this.animationConfig && this.animationConfig
.open) { |
| 248 if (this.withBackdrop) { |
| 249 this.backdropElement.open(); |
| 250 } |
| 272 this.$.contentWrapper.classList.add('animating'); | 251 this.$.contentWrapper.classList.add('animating'); |
| 273 this.playAnimation('open'); | 252 this.playAnimation('open'); |
| 274 } else { | 253 } else { |
| 275 Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this, arguments)
; | 254 Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this, arguments)
; |
| 276 } | 255 } |
| 277 }, | 256 }, |
| 278 | 257 |
| 279 /** | 258 /** |
| 280 * Overridden from `IronOverlayBehavior`. | 259 * Overridden from `IronOverlayBehavior`. |
| 281 */ | 260 */ |
| 282 _renderClosed: function() { | 261 _renderClosed: function() { |
| 283 Polymer.IronDropdownScrollManager.removeScrollLock(this); | 262 Polymer.IronDropdownScrollManager.removeScrollLock(this); |
| 284 if (!this.noAnimations && this.animationConfig && this.animationConfig
.close) { | 263 if (!this.noAnimations && this.animationConfig && this.animationConfig
.close) { |
| 264 if (this.withBackdrop) { |
| 265 this.backdropElement.close(); |
| 266 } |
| 285 this.$.contentWrapper.classList.add('animating'); | 267 this.$.contentWrapper.classList.add('animating'); |
| 286 this.playAnimation('close'); | 268 this.playAnimation('close'); |
| 287 } else { | 269 } else { |
| 288 Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this, arguments)
; | 270 Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this, arguments)
; |
| 289 } | 271 } |
| 290 }, | 272 }, |
| 291 | 273 |
| 292 /** | 274 /** |
| 293 * Called when animation finishes on the dropdown (when opening or | 275 * Called when animation finishes on the dropdown (when opening or |
| 294 * closing). Responsible for "completing" the process of opening or | 276 * closing). Responsible for "completing" the process of opening or |
| 295 * closing the dropdown by positioning it or setting its display to | 277 * closing the dropdown by positioning it or setting its display to |
| 296 * none. | 278 * none. |
| 297 */ | 279 */ |
| 298 _onNeonAnimationFinish: function() { | 280 _onNeonAnimationFinish: function() { |
| 299 this.$.contentWrapper.classList.remove('animating'); | 281 this.$.contentWrapper.classList.remove('animating'); |
| 300 if (this.opened) { | 282 if (this.opened) { |
| 301 Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this); | 283 Polymer.IronOverlayBehaviorImpl._finishRenderOpened.apply(this); |
| 302 } else { | 284 } else { |
| 303 Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this); | 285 Polymer.IronOverlayBehaviorImpl._finishRenderClosed.apply(this); |
| 304 } | 286 } |
| 305 }, | 287 }, |
| 306 | 288 |
| 307 /** | 289 /** |
| 308 * Called when an `iron-resize` event fires. | |
| 309 */ | |
| 310 _onIronResize: function() { | |
| 311 var containedElement = this.containedElement; | |
| 312 var scrollTop; | |
| 313 var scrollLeft; | |
| 314 | |
| 315 if (this.opened && containedElement) { | |
| 316 scrollTop = containedElement.scrollTop; | |
| 317 scrollLeft = containedElement.scrollLeft; | |
| 318 } | |
| 319 | |
| 320 if (this.opened) { | |
| 321 this._updateOverlayPosition(); | |
| 322 } | |
| 323 | |
| 324 Polymer.IronOverlayBehaviorImpl._onIronResize.apply(this, arguments); | |
| 325 | |
| 326 if (this.opened && containedElement) { | |
| 327 containedElement.scrollTop = scrollTop; | |
| 328 containedElement.scrollLeft = scrollLeft; | |
| 329 } | |
| 330 }, | |
| 331 | |
| 332 /** | |
| 333 * Called when the `positionTarget` property changes. | |
| 334 */ | |
| 335 _positionTargetChanged: function() { | |
| 336 this._updateOverlayPosition(); | |
| 337 }, | |
| 338 | |
| 339 /** | |
| 340 * Constructs the final animation config from different properties used | 290 * Constructs the final animation config from different properties used |
| 341 * to configure specific parts of the opening and closing animations. | 291 * to configure specific parts of the opening and closing animations. |
| 342 */ | 292 */ |
| 343 _updateAnimationConfig: function() { | 293 _updateAnimationConfig: function() { |
| 344 var animationConfig = {}; | 294 var animationConfig = {}; |
| 345 var animations = []; | 295 var animations = []; |
| 346 | 296 |
| 347 if (this.openAnimationConfig) { | 297 if (this.openAnimationConfig) { |
| 348 // NOTE(cdata): When making `display:none` elements visible in Safar
i, | 298 // NOTE(cdata): When making `display:none` elements visible in Safar
i, |
| 349 // the element will paint once in a fully visible state, causing the | 299 // the element will paint once in a fully visible state, causing the |
| (...skipping 11 matching lines...) Expand all Loading... |
| 361 } | 311 } |
| 362 | 312 |
| 363 animations.forEach(function(animation) { | 313 animations.forEach(function(animation) { |
| 364 animation.node = this.containedElement; | 314 animation.node = this.containedElement; |
| 365 }, this); | 315 }, this); |
| 366 | 316 |
| 367 this.animationConfig = animationConfig; | 317 this.animationConfig = animationConfig; |
| 368 }, | 318 }, |
| 369 | 319 |
| 370 /** | 320 /** |
| 371 * Prepares the dropdown for opening by updating measured layout | |
| 372 * values. | |
| 373 */ | |
| 374 _prepareDropdown: function() { | |
| 375 this.sizingTarget = this.containedElement || this.sizingTarget; | |
| 376 this._updateAnimationConfig(); | |
| 377 this._updateOverlayPosition(); | |
| 378 }, | |
| 379 | |
| 380 /** | |
| 381 * Updates the overlay position based on configured horizontal | 321 * Updates the overlay position based on configured horizontal |
| 382 * and vertical alignment, and re-memoizes these values for the sake | 322 * and vertical alignment. |
| 383 * of behavior in `IronFitBehavior`. | |
| 384 */ | 323 */ |
| 385 _updateOverlayPosition: function() { | 324 _updateOverlayPosition: function() { |
| 386 this._positionRectMemo = null; | 325 if (this.isAttached) { |
| 387 | 326 // This triggers iron-resize, and iron-overlay-behavior will call re
fit if needed. |
| 388 if (!this.positionTarget) { | 327 this.notifyResize(); |
| 389 return; | |
| 390 } | |
| 391 | |
| 392 this.style[this._localeHorizontalAlign] = | |
| 393 this._horizontalAlignTargetValue + 'px'; | |
| 394 | |
| 395 this.style[this.verticalAlign] = | |
| 396 this._verticalAlignTargetValue + 'px'; | |
| 397 | |
| 398 // NOTE(cdata): We re-memoize inline styles here, otherwise | |
| 399 // calling `refit` from `IronFitBehavior` will reset inline styles | |
| 400 // to whatever they were when the dropdown first opened. | |
| 401 if (this._fitInfo) { | |
| 402 this._fitInfo.inlineStyle[this.horizontalAlign] = | |
| 403 this.style[this.horizontalAlign]; | |
| 404 | |
| 405 this._fitInfo.inlineStyle[this.verticalAlign] = | |
| 406 this.style[this.verticalAlign]; | |
| 407 } | 328 } |
| 408 }, | 329 }, |
| 409 | 330 |
| 410 /** | 331 /** |
| 332 * Useful to call this after the element, the window, or the `fitInfo` |
| 333 * element has been resized. Will maintain the scroll position. |
| 334 */ |
| 335 refit: function () { |
| 336 if (!this.opened) { |
| 337 return |
| 338 } |
| 339 var containedElement = this.containedElement; |
| 340 var scrollTop; |
| 341 var scrollLeft; |
| 342 |
| 343 if (containedElement) { |
| 344 scrollTop = containedElement.scrollTop; |
| 345 scrollLeft = containedElement.scrollLeft; |
| 346 } |
| 347 Polymer.IronFitBehavior.refit.apply(this, arguments); |
| 348 |
| 349 if (containedElement) { |
| 350 containedElement.scrollTop = scrollTop; |
| 351 containedElement.scrollLeft = scrollLeft; |
| 352 } |
| 353 }, |
| 354 |
| 355 /** |
| 356 * Resets the target element's position and size constraints, and clear |
| 357 * the memoized data. |
| 358 */ |
| 359 resetFit: function() { |
| 360 Polymer.IronFitBehavior.resetFit.apply(this, arguments); |
| 361 |
| 362 var hAlign = this._localeHorizontalAlign; |
| 363 var vAlign = this.verticalAlign; |
| 364 // Set to 0, 0 in order to discover any offset caused by parent stacki
ng contexts. |
| 365 this.style[hAlign] = this.style[vAlign] = '0px'; |
| 366 |
| 367 var dropdownRect = this.getBoundingClientRect(); |
| 368 var positionRect = this.positionTarget.getBoundingClientRect(); |
| 369 var horizontalValue = this._horizontalAlignTargetValue(dropdownRect, p
ositionRect, hAlign === 'right'); |
| 370 var verticalValue = this._verticalAlignTargetValue(dropdownRect, posit
ionRect, vAlign === 'bottom'); |
| 371 |
| 372 this.style[hAlign] = horizontalValue + 'px'; |
| 373 this.style[vAlign] = verticalValue + 'px'; |
| 374 }, |
| 375 |
| 376 /** |
| 377 * Overridden from `IronFitBehavior`. |
| 378 * Ensure positionedBy has correct values for horizontally & vertically. |
| 379 */ |
| 380 _discoverInfo: function() { |
| 381 Polymer.IronFitBehavior._discoverInfo.apply(this, arguments); |
| 382 // Note(valdrin): in Firefox, an element with style `position: fixed;
bottom: 90vh; height: 20vh` |
| 383 // would have `getComputedStyle(element).top < 0` (instead of being `a
uto`) http://jsbin.com/cofired/3/edit?html,output |
| 384 // This would cause IronFitBehavior's `constrain` to wrongly calculate
sizes |
| 385 // (it would use `top` instead of `bottom`), so we ensure we give the
correct values. |
| 386 this._fitInfo.positionedBy.horizontally = this._localeHorizontalAlign; |
| 387 this._fitInfo.positionedBy.vertically = this.verticalAlign; |
| 388 }, |
| 389 |
| 390 /** |
| 411 * Apply focus to focusTarget or containedElement | 391 * Apply focus to focusTarget or containedElement |
| 412 */ | 392 */ |
| 413 _applyFocus: function () { | 393 _applyFocus: function () { |
| 414 var focusTarget = this.focusTarget || this.containedElement; | 394 var focusTarget = this.focusTarget || this.containedElement; |
| 415 if (focusTarget && this.opened && !this.noAutoFocus) { | 395 if (focusTarget && this.opened && !this.noAutoFocus) { |
| 416 focusTarget.focus(); | 396 focusTarget.focus(); |
| 417 } else { | 397 } else { |
| 418 Polymer.IronOverlayBehaviorImpl._applyFocus.apply(this, arguments); | 398 Polymer.IronOverlayBehaviorImpl._applyFocus.apply(this, arguments); |
| 419 } | 399 } |
| 420 } | 400 } |
| 421 }); | 401 }); |
| 422 })(); | 402 })(); |
| OLD | NEW |