| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 // Fast out, slow in. | |
| 6 var EASING_FUNCTION = 'cubic-bezier(0.4, 0, 0.2, 1)'; | |
| 7 var EXPAND_DURATION = 350; | |
| 8 | |
| 9 /** | 5 /** |
| 10 * Calls |readyTest| repeatedly until it returns true, then calls | 6 * Calls |readyTest| repeatedly until it returns true, then calls |
| 11 * |readyCallback|. | 7 * |readyCallback|. |
| 12 * @param {function():boolean} readyTest | 8 * @param {function():boolean} readyTest |
| 13 * @param {!Function} readyCallback | 9 * @param {!Function} readyCallback |
| 14 */ | 10 */ |
| 15 function doWhenReady(readyTest, readyCallback) { | 11 function doWhenReady(readyTest, readyCallback) { |
| 16 // TODO(dschuyler): Determine whether this hack can be removed. | 12 // TODO(dschuyler): Determine whether this hack can be removed. |
| 17 // See also: https://github.com/Polymer/polymer/issues/3629 | 13 // See also: https://github.com/Polymer/polymer/issues/3629 |
| 18 var intervalId = setInterval(function() { | 14 var intervalId = setInterval(function() { |
| 19 if (readyTest()) { | 15 if (readyTest()) { |
| 20 clearInterval(intervalId); | 16 clearInterval(intervalId); |
| 21 readyCallback(); | 17 readyCallback(); |
| 22 } | 18 } |
| 23 }, 10); | 19 }, 10); |
| 24 } | 20 } |
| 25 | 21 |
| 26 /** | 22 /** |
| 27 * Provides animations to expand and collapse individual sections in a page. | 23 * Provides animations to expand and collapse individual sections in a page. |
| 28 * Expanded sections take up the full height of the container. At most one | 24 * Expanded sections take up the full height of the container. At most one |
| 29 * section should be expanded at any given time. | 25 * section should be expanded at any given time. |
| 30 * @polymerBehavior Polymer.MainPageBehavior | 26 * @polymerBehavior Polymer.MainPageBehavior |
| 31 */ | 27 */ |
| 32 var MainPageBehaviorImpl = { | 28 var MainPageBehaviorImpl = { |
| 33 /** @type {?Element} The scrolling container. */ | 29 /** @type {?HTMLElement} The scrolling container. */ |
| 34 scroller: null, | 30 scroller: null, |
| 35 | 31 |
| 36 /** @override */ | 32 /** @override */ |
| 37 attached: function() { | 33 attached: function() { |
| 38 if (this.domHost && this.domHost.parentNode.tagName == 'PAPER-HEADER-PANEL') | 34 if (this.domHost && this.domHost.parentNode.tagName == 'PAPER-HEADER-PANEL') |
| 39 this.scroller = this.domHost.parentNode.scroller; | 35 this.scroller = this.domHost.parentNode.scroller; |
| 40 else | 36 else |
| 41 this.scroller = document.body; // Used in unit tests. | 37 this.scroller = document.body; // Used in unit tests. |
| 42 }, | 38 }, |
| 43 | 39 |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 95 } | 91 } |
| 96 | 92 |
| 97 this.startExpandSection_(section); | 93 this.startExpandSection_(section); |
| 98 }, | 94 }, |
| 99 | 95 |
| 100 /** | 96 /** |
| 101 * Helper function to set up and start the expand animation. | 97 * Helper function to set up and start the expand animation. |
| 102 * @param {!SettingsSectionElement} section | 98 * @param {!SettingsSectionElement} section |
| 103 */ | 99 */ |
| 104 startExpandSection_: function(section) { | 100 startExpandSection_: function(section) { |
| 105 if (section.classList.contains('expanded')) | 101 if (!section.canAnimateExpand()) |
| 106 return; | 102 return; |
| 107 | 103 |
| 108 // Freeze the scroller and save its position. | 104 // Freeze the scroller and save its position. |
| 109 this.listScrollTop_ = this.scroller.scrollTop; | 105 this.listScrollTop_ = this.scroller.scrollTop; |
| 110 | 106 |
| 111 var scrollerWidth = this.scroller.clientWidth; | 107 var scrollerWidth = this.scroller.clientWidth; |
| 112 this.scroller.style.overflow = 'hidden'; | 108 this.scroller.style.overflow = 'hidden'; |
| 113 // Adjust width to compensate for scroller. | 109 // Adjust width to compensate for scroller. |
| 114 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth; | 110 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth; |
| 115 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)'; | 111 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)'; |
| 116 | 112 |
| 117 // Freezes the section's height so its card can be removed from the flow. | 113 // Freezes the section's height so its card can be removed from the flow. |
| 118 this.freezeSection_(section); | 114 section.setFrozen(true); |
| 119 | 115 |
| 120 // Expand the section's card to fill the parent. | 116 // Expand the section's card to fill the parent. |
| 121 var animationPromise = this.playExpandSection_(section); | 117 var animationPromise = this.playExpandSection_(section); |
| 122 | 118 |
| 123 animationPromise.then(function() { | 119 animationPromise.then(function() { |
| 124 this.scroller.scrollTop = 0; | 120 this.scroller.scrollTop = 0; |
| 125 this.toggleOtherSectionsHidden_(section.section, true); | 121 this.toggleOtherSectionsHidden_(section.section, true); |
| 126 }.bind(this), function() { | 122 }.bind(this), function() { |
| 127 // Animation was canceled; restore the section. | 123 // Animation was canceled; restore the section. |
| 128 this.unfreezeSection_(section); | 124 section.setFrozen(false); |
| 129 }.bind(this)).then(function() { | 125 }.bind(this)).then(function() { |
| 130 this.scroller.style.overflow = ''; | 126 this.scroller.style.overflow = ''; |
| 131 this.scroller.style.width = ''; | 127 this.scroller.style.width = ''; |
| 132 }.bind(this)); | 128 }.bind(this)); |
| 133 }, | 129 }, |
| 134 | 130 |
| 135 /** | 131 /** |
| 136 * Animates the card in |section|, collapsing it back into its section. | 132 * Animates the card in |section|, collapsing it back into its section. |
| 137 * @param {!SettingsSectionElement} section | 133 * @param {!SettingsSectionElement} section |
| 138 */ | 134 */ |
| 139 collapseSection: function(section) { | 135 collapseSection: function(section) { |
| 140 // If the section's card is still expanding, cancel the expand animation. | 136 // If the section's card is still expanding, cancel the expand animation. |
| 141 if (section.classList.contains('expanding')) { | 137 if (section.classList.contains('expanding')) { |
| 142 if (this.animations['section']) { | 138 if (this.animations['section']) { |
| 143 this.cancelAnimation('section'); | 139 this.cancelAnimation('section'); |
| 144 } else { | 140 } else { |
| 145 // The animation must have finished but its promise hasn't finished | 141 // The animation must have finished but its promise hasn't finished |
| 146 // resolving; try again asynchronously. | 142 // resolving; try again asynchronously. |
| 147 this.async(function() { | 143 this.async(function() { |
| 148 this.collapseSection(section); | 144 this.collapseSection(section); |
| 149 }); | 145 }); |
| 150 } | 146 } |
| 151 return; | 147 return; |
| 152 } | 148 } |
| 153 | 149 |
| 154 if (!section.classList.contains('expanded')) | 150 if (!section.canAnimateCollapse()) |
| 155 return; | 151 return; |
| 156 | 152 |
| 157 this.toggleOtherSectionsHidden_(section.section, false); | 153 this.toggleOtherSectionsHidden_(section.section, false); |
| 158 | 154 |
| 159 var scrollerWidth = this.scroller.clientWidth; | 155 var scrollerWidth = this.scroller.clientWidth; |
| 160 this.scroller.style.overflow = 'hidden'; | 156 this.scroller.style.overflow = 'hidden'; |
| 161 // Adjust width to compensate for scroller. | 157 // Adjust width to compensate for scroller. |
| 162 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth; | 158 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth; |
| 163 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)'; | 159 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)'; |
| 164 | 160 |
| 165 this.playCollapseSection_(section).then(function() { | 161 this.playCollapseSection_(section).then(function() { |
| 166 this.unfreezeSection_(section); | 162 section.setFrozen(false); |
| 167 this.scroller.style.overflow = ''; | 163 this.scroller.style.overflow = ''; |
| 168 this.scroller.style.width = ''; | 164 this.scroller.style.width = ''; |
| 169 section.classList.remove('collapsing'); | 165 section.classList.remove('collapsing'); |
| 170 }.bind(this)); | 166 }.bind(this)); |
| 171 }, | 167 }, |
| 172 | 168 |
| 173 /** | 169 /** |
| 174 * Freezes a section's height so its card can be removed from the flow without | |
| 175 * affecting the layout of the surrounding sections. | |
| 176 * @param {!SettingsSectionElement} section | |
| 177 * @private | |
| 178 */ | |
| 179 freezeSection_: function(section) { | |
| 180 var card = section.$.card; | |
| 181 section.style.height = section.clientHeight + 'px'; | |
| 182 | |
| 183 var cardHeight = card.offsetHeight; | |
| 184 var cardWidth = card.offsetWidth; | |
| 185 // If the section is not displayed yet (e.g., navigated directly to a | |
| 186 // sub-page), cardHeight and cardWidth are 0, so do not set the height or | |
| 187 // width explicitly. | |
| 188 // TODO(michaelpg): Improve this logic when refactoring | |
| 189 // settings-animated-pages. | |
| 190 if (cardHeight && cardWidth) { | |
| 191 // TODO(michaelpg): Temporary hack to store the height the section should | |
| 192 // collapse to when it closes. | |
| 193 card.origHeight_ = cardHeight; | |
| 194 | |
| 195 card.style.height = cardHeight + 'px'; | |
| 196 card.style.width = cardWidth + 'px'; | |
| 197 } else { | |
| 198 // Set an invalid value so we don't try to use it later. | |
| 199 card.origHeight_ = NaN; | |
| 200 } | |
| 201 | |
| 202 // Place the section's card at its current position but removed from the | |
| 203 // flow. | |
| 204 card.style.top = card.getBoundingClientRect().top + 'px'; | |
| 205 section.classList.add('frozen'); | |
| 206 }, | |
| 207 | |
| 208 /** | |
| 209 * After freezeSection_, restores the section to its normal height. | |
| 210 * @param {!SettingsSectionElement} section | |
| 211 * @private | |
| 212 */ | |
| 213 unfreezeSection_: function(section) { | |
| 214 if (!section.classList.contains('frozen')) | |
| 215 return; | |
| 216 var card = section.$.card; | |
| 217 section.classList.remove('frozen'); | |
| 218 card.style.top = ''; | |
| 219 card.style.height = ''; | |
| 220 card.style.width = ''; | |
| 221 section.style.height = ''; | |
| 222 }, | |
| 223 | |
| 224 /** | |
| 225 * Expands the card in |section| to fill the page. | 170 * Expands the card in |section| to fill the page. |
| 226 * @param {!SettingsSectionElement} section | 171 * @param {!SettingsSectionElement} section |
| 227 * @return {!Promise} | 172 * @return {!Promise} |
| 228 * @private | 173 * @private |
| 229 */ | 174 */ |
| 230 playExpandSection_: function(section) { | 175 playExpandSection_: function(section) { |
| 231 var card = section.$.card; | 176 // We must be attached. |
| 177 assert(this.scroller); |
| 232 | 178 |
| 233 // The card should start at the top of the page. | 179 var promise; |
| 234 var targetTop = this.scroller.getBoundingClientRect().top; | 180 var animationConfig = section.animateExpand(this.scroller); |
| 181 if (animationConfig) { |
| 182 promise = this.animateElement('section', animationConfig.card, |
| 183 animationConfig.keyframes, animationConfig.options); |
| 184 } else { |
| 185 promise = Promise.resolve(); |
| 186 } |
| 235 | 187 |
| 236 section.classList.add('expanding'); | 188 var finished; |
| 237 | |
| 238 // Expand the card, using minHeight. (The card must span the container's | |
| 239 // client height, so it must be at least 100% in case the card is too short. | |
| 240 // If the card is already taller than the container's client height, we | |
| 241 // don't want to shrink the card to 100% or the content will overflow, so | |
| 242 // we can't use height, and animating height wouldn't look right anyway.) | |
| 243 var keyframes = [{ | |
| 244 top: card.style.top, | |
| 245 minHeight: card.style.height, | |
| 246 easing: EASING_FUNCTION, | |
| 247 }, { | |
| 248 top: targetTop + 'px', | |
| 249 minHeight: 'calc(100% - ' + targetTop + 'px)', | |
| 250 }]; | |
| 251 var options = /** @type {!KeyframeEffectOptions} */({ | |
| 252 duration: EXPAND_DURATION | |
| 253 }); | |
| 254 // TODO(michaelpg): Change elevation of sections. | |
| 255 var promise; | |
| 256 if (keyframes[0].top && keyframes[0].minHeight) | |
| 257 promise = this.animateElement('section', card, keyframes, options); | |
| 258 else | |
| 259 promise = Promise.resolve(); | |
| 260 | |
| 261 promise.then(function() { | 189 promise.then(function() { |
| 262 section.classList.add('expanded'); | 190 finished = true; |
| 263 card.style.top = ''; | |
| 264 this.style.margin = 'auto'; | 191 this.style.margin = 'auto'; |
| 265 section.$.header.hidden = true; | |
| 266 section.style.height = ''; | |
| 267 }.bind(this), function() { | 192 }.bind(this), function() { |
| 268 // The animation was canceled; catch the error and continue. | 193 // The animation was canceled; catch the error and continue. |
| 194 finished = false; |
| 269 }).then(function() { | 195 }).then(function() { |
| 270 // Whether finished or canceled, clean up the animation. | 196 section.cleanUpAnimateExpand(finished); |
| 271 section.classList.remove('expanding'); | |
| 272 card.style.height = ''; | |
| 273 card.style.width = ''; | |
| 274 }); | 197 }); |
| 275 | 198 |
| 276 return promise; | 199 return promise; |
| 277 }, | 200 }, |
| 278 | 201 |
| 279 /** | 202 /** |
| 280 * Collapses the card in |section| back to its normal position. | 203 * Collapses the card in |section| back to its normal position. |
| 281 * @param {!SettingsSectionElement} section | 204 * @param {!SettingsSectionElement} section |
| 282 * @return {!Promise} | 205 * @return {!Promise} |
| 283 * @private | 206 * @private |
| 284 */ | 207 */ |
| 285 playCollapseSection_: function(section) { | 208 playCollapseSection_: function(section) { |
| 286 var card = section.$.card; | 209 // We must be attached. |
| 210 assert(this.scroller); |
| 287 | 211 |
| 288 this.style.margin = ''; | 212 this.style.margin = ''; |
| 289 section.$.header.hidden = false; | |
| 290 | 213 |
| 291 var startingTop = this.scroller.getBoundingClientRect().top; | 214 var promise; |
| 215 var animationConfig = |
| 216 section.animateCollapse(this.scroller, this.listScrollTop_); |
| 217 if (animationConfig) { |
| 218 promise = this.animateElement('section', animationConfig.card, |
| 219 animationConfig.keyframes, animationConfig.options); |
| 220 } else { |
| 221 promise = Promise.resolve(); |
| 222 } |
| 292 | 223 |
| 293 var cardHeightStart = card.clientHeight; | |
| 294 var cardWidthStart = card.clientWidth; | |
| 295 | |
| 296 section.classList.add('collapsing'); | |
| 297 section.classList.remove('expanding', 'expanded'); | |
| 298 | |
| 299 // If we navigated here directly, we don't know the original height of the | |
| 300 // section, so we skip the animation. | |
| 301 // TODO(michaelpg): remove this condition once sliding is implemented. | |
| 302 if (isNaN(card.origHeight_)) | |
| 303 return Promise.resolve(); | |
| 304 | |
| 305 // Restore the section to its proper height to make room for the card. | |
| 306 section.style.height = section.clientHeight + card.origHeight_ + 'px'; | |
| 307 | |
| 308 // TODO(michaelpg): this should be in collapseSection(), but we need to wait | |
| 309 // until the full page height is available (setting the section height). | |
| 310 this.scroller.scrollTop = this.listScrollTop_; | |
| 311 | |
| 312 // The card is unpositioned, so use its position as the ending state, | |
| 313 // but account for scroll. | |
| 314 var targetTop = card.getBoundingClientRect().top - this.scroller.scrollTop; | |
| 315 | |
| 316 // Account for the section header. | |
| 317 var headerStyle = getComputedStyle(section.$.header); | |
| 318 targetTop += section.$.header.offsetHeight + | |
| 319 parseInt(headerStyle.marginBottom, 10) + | |
| 320 parseInt(headerStyle.marginTop, 10); | |
| 321 | |
| 322 var keyframes = [{ | |
| 323 top: startingTop + 'px', | |
| 324 minHeight: cardHeightStart + 'px', | |
| 325 easing: EASING_FUNCTION, | |
| 326 }, { | |
| 327 top: targetTop + 'px', | |
| 328 minHeight: card.origHeight_ + 'px', | |
| 329 }]; | |
| 330 var options = /** @type {!KeyframeEffectOptions} */({ | |
| 331 duration: EXPAND_DURATION | |
| 332 }); | |
| 333 | |
| 334 card.style.width = cardWidthStart + 'px'; | |
| 335 var promise = this.animateElement('section', card, keyframes, options); | |
| 336 promise.then(function() { | 224 promise.then(function() { |
| 337 card.style.width = ''; | 225 section.cleanUpAnimateCollapse(true); |
| 226 }, function() { |
| 227 section.cleanUpAnimateCollapse(false); |
| 338 }); | 228 }); |
| 339 return promise; | 229 return promise; |
| 340 }, | 230 }, |
| 341 }; | 231 }; |
| 342 | 232 |
| 343 | 233 |
| 344 /** @polymerBehavior */ | 234 /** @polymerBehavior */ |
| 345 var MainPageBehavior = [ | 235 var MainPageBehavior = [ |
| 346 TransitionBehavior, | 236 TransitionBehavior, |
| 347 MainPageBehaviorImpl | 237 MainPageBehaviorImpl |
| (...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 421 this.$$('[section=' + section + ']')); | 311 this.$$('[section=' + section + ']')); |
| 422 }, | 312 }, |
| 423 }; | 313 }; |
| 424 | 314 |
| 425 | 315 |
| 426 /** @polymerBehavior */ | 316 /** @polymerBehavior */ |
| 427 var RoutableBehavior = [ | 317 var RoutableBehavior = [ |
| 428 MainPageBehavior, | 318 MainPageBehavior, |
| 429 RoutableBehaviorImpl | 319 RoutableBehaviorImpl |
| 430 ]; | 320 ]; |
| OLD | NEW |