Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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 | |
| 3 // found in the LICENSE file. | |
| 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 /** | |
| 10 * Provides animations to expand and collapse individual sections in a page. | |
| 11 * Expanded sections take up the full height of the container. At most one | |
| 12 * section should be expanded at any given time. | |
| 13 * @polymerBehavior | |
| 14 */ | |
| 15 var MainPageBehavior = [TransitionBehavior, { | |
|
Dan Beam
2016/02/26 19:18:23
up to you, but Polymer typically does:
var MainPa
michaelpg
2016/03/01 00:18:28
Done.
| |
| 16 /** @type {string} Selector to get the sections. Elements must override. */ | |
|
Dan Beam
2016/02/26 19:18:23
nit: s/Elements/Consumers/? Downstream users? De
michaelpg
2016/03/01 00:18:28
Done.
| |
| 17 sectionSelector: '', | |
| 18 | |
| 19 /** @type {Element} The scrolling container. Elements must set this. */ | |
|
Dan Beam
2016/02/26 19:18:23
nit: ?Element
michaelpg
2016/03/01 00:18:28
Done.
| |
| 20 scroller: null, | |
| 21 | |
| 22 /** | |
| 23 * Hides or unhides the sections not being expanded. | |
| 24 * @param {string} section The section to keep visible. | |
| 25 * @param {boolean} hidden Whether the sections should be hidden. | |
| 26 * @private | |
| 27 */ | |
| 28 toggleOtherSectionsHidden_: function(section, hidden) { | |
| 29 var sections = Polymer.dom(this.root).querySelectorAll( | |
| 30 this.sectionSelector + ':not([section=' + section + '])'); | |
| 31 for (var section of sections) | |
| 32 section.hidden = hidden; | |
| 33 }, | |
| 34 | |
| 35 /** | |
| 36 * Animates the card in |section|, expanding it to fill the page. | |
| 37 * @param {!SettingsSectionElement} section | |
| 38 */ | |
| 39 expandSection: function(section) { | |
| 40 // If another section's card is expanding, cancel that animation first. | |
| 41 var expanding = this.$$('.expanding'); | |
| 42 if (expanding) { | |
| 43 if (expanding == section) | |
| 44 return; | |
| 45 | |
| 46 if (this.animations['section']) { | |
|
Dan Beam
2016/02/26 19:18:23
why ['section'] instead of .section?
michaelpg
2016/03/01 00:18:28
it's the name/key for the animation, not a propert
Dan Beam
2016/03/04 19:24:24
Acknowledged.
| |
| 47 // Cancel the animation, then call startExpandSection_. | |
| 48 this.cancelAnimation('section', function() { | |
| 49 this.startExpandSection_(section); | |
| 50 }.bind(this)); | |
| 51 } else { | |
| 52 // The animation must have finished but its promise hasn't resolved yet. | |
| 53 // When it resolves, collapse that section's card before expanding | |
| 54 // this one. | |
| 55 setTimeout(function() { | |
| 56 this.collapseSection( | |
| 57 /** @type {!SettingsSectionElement} */(expanding)); | |
| 58 this.finishAnimation('section', function() { | |
| 59 this.startExpandSection_(section); | |
| 60 }.bind(this)); | |
| 61 }.bind(this)); | |
| 62 } | |
| 63 | |
| 64 return; | |
| 65 } | |
| 66 | |
| 67 if (this.$$('.collapsing') && this.animations['section']) { | |
| 68 // Finish the collapse animation before expanding. | |
| 69 this.finishAnimation('section', function() { | |
| 70 this.startExpandSection_(section); | |
| 71 }.bind(this)); | |
| 72 return; | |
| 73 } | |
| 74 | |
| 75 this.startExpandSection_(section); | |
| 76 }, | |
| 77 | |
| 78 /** | |
| 79 * Helper function to set up and start the expand animation. | |
| 80 * @param {!SettingsSectionElement} section | |
| 81 */ | |
| 82 startExpandSection_: function(section) { | |
| 83 if (section.classList.contains('expanded')) | |
| 84 return; | |
| 85 | |
| 86 // Freeze the scroller and save its position. | |
| 87 this.listScrollTop_ = this.scroller.scrollTop; | |
| 88 | |
| 89 var scrollerWidth = this.scroller.clientWidth; | |
| 90 this.scroller.style.overflow = 'hidden'; | |
| 91 // Adjust width to compensate for scroller. | |
| 92 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth; | |
| 93 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)'; | |
| 94 | |
| 95 // Freezes the section's height so its card can be removed from the flow. | |
| 96 this.freezeSection_(section); | |
| 97 | |
| 98 // Expand the section's card to fill the parent. | |
| 99 var animationPromise = this.playExpandSection_(section); | |
| 100 | |
| 101 animationPromise.then(function() { | |
| 102 this.scroller.scrollTop = 0; | |
| 103 this.toggleOtherSectionsHidden_(section.section, true); | |
| 104 }.bind(this), function() { | |
| 105 // Animation was cancelled; restore the section. | |
| 106 this.unfreezeSection_(section); | |
| 107 }.bind(this)).then(function() { | |
| 108 this.scroller.style.overflow = ''; | |
| 109 this.scroller.style.width = ''; | |
| 110 }.bind(this)); | |
| 111 }, | |
| 112 | |
| 113 /** | |
| 114 * Animates the card in |section|, collapsing it back into its section. | |
| 115 * @param {!SettingsSectionElement} section | |
| 116 */ | |
| 117 collapseSection: function(section) { | |
| 118 // If the section's card is still expanding, cancel the expand animation. | |
| 119 if (section.classList.contains('expanding')) { | |
| 120 if (this.animations['section']) { | |
| 121 this.cancelAnimation('section'); | |
| 122 } else { | |
| 123 // The animation must have finished but its promise hasn't finished | |
| 124 // resolving; try again asynchronously. | |
| 125 this.async(function() { | |
| 126 this.collapseSection(section); | |
| 127 }); | |
| 128 } | |
| 129 return; | |
| 130 } | |
| 131 | |
| 132 if (!section.classList.contains('expanded')) | |
| 133 return; | |
| 134 | |
| 135 this.toggleOtherSectionsHidden_(section.section, false); | |
| 136 | |
| 137 var scrollerWidth = this.scroller.clientWidth; | |
| 138 this.scroller.style.overflow = 'hidden'; | |
| 139 // Adjust width to compensate for scroller. | |
| 140 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth; | |
| 141 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)'; | |
| 142 | |
| 143 this.playCollapseSection_(section).then(function() { | |
| 144 this.unfreezeSection_(section); | |
| 145 this.scroller.style.overflow = ''; | |
| 146 this.scroller.style.width = ''; | |
| 147 section.classList.remove('collapsing'); | |
| 148 }.bind(this)); | |
| 149 }, | |
| 150 | |
| 151 /** | |
| 152 * Freezes a section's height so its card can be removed from the flow without | |
| 153 * affecting the layout of the surrounding sections. | |
| 154 * @param {!SettingsSectionElement} section | |
| 155 * @private | |
| 156 */ | |
| 157 freezeSection_: function(section) { | |
| 158 var card = section.$.card; | |
| 159 section.style.height = section.clientHeight + 'px'; | |
| 160 | |
| 161 var cardHeight = card.offsetHeight; | |
| 162 var cardWidth = card.offsetWidth; | |
| 163 // If the section is not displayed yet (e.g., navigated directly to a | |
| 164 // sub-page), cardHeight and cardWidth are 0, so do not set the height or | |
| 165 // width explicitly. | |
| 166 // TODO(michaelpg): Improve this logic when refactoring | |
| 167 // settings-animated-pages. | |
| 168 if (cardHeight && cardWidth) { | |
| 169 // TODO(michaelpg): Temporary hack to store the height the section should | |
| 170 // collapse to when it closes. | |
| 171 card.origHeight_ = cardHeight; | |
| 172 | |
| 173 card.style.height = cardHeight + 'px'; | |
| 174 card.style.width = cardWidth + 'px'; | |
| 175 } | |
| 176 | |
| 177 // Place the section's card at its current position but removed from the | |
| 178 // flow. | |
| 179 card.style.top = card.getBoundingClientRect().top + 'px'; | |
| 180 section.classList.add('frozen-section'); | |
|
Dan Beam
2016/02/26 19:18:23
nit: why not just frozen? expanding and expanded
michaelpg
2016/03/01 00:18:28
Done.
| |
| 181 }, | |
| 182 | |
| 183 /** | |
| 184 * After freezeSection_, restores the section to its normal height. | |
| 185 * @param {!SettingsSectionElement} section | |
| 186 * @private | |
| 187 */ | |
| 188 unfreezeSection_: function(section) { | |
| 189 if (!section.classList.contains('frozen-section')) | |
| 190 return; | |
| 191 var card = section.$.card; | |
| 192 section.classList.remove('frozen-section'); | |
| 193 card.style.top = ''; | |
| 194 card.style.height = ''; | |
| 195 card.style.width = ''; | |
| 196 section.style.height = ''; | |
| 197 }, | |
| 198 | |
| 199 /** | |
| 200 * Expands the card in |section| to fill the page. | |
| 201 * @param {!SettingsSectionElement} section | |
| 202 * @return {!Promise} | |
| 203 * @private | |
| 204 */ | |
| 205 playExpandSection_: function(section) { | |
| 206 var card = section.$.card; | |
| 207 | |
| 208 // The card should start at the top of the page. | |
| 209 var targetTop = this.parentElement.getBoundingClientRect().top; | |
| 210 | |
| 211 section.classList.add('expanding'); | |
| 212 | |
| 213 // Expand the card, using minHeight. (The card must span the container's | |
| 214 // client height, so it must be at least 100% in case the card is too short. | |
| 215 // If the card is already taller than the container's client height, we | |
| 216 // don't want to shrink the card to 100% or the content will overflow, so | |
| 217 // we can't use height, and animating height wouldn't look right anyway.) | |
| 218 var keyframes = [{ | |
| 219 top: card.style.top, | |
| 220 minHeight: card.style.height, | |
| 221 easing: EASING_FUNCTION, | |
| 222 }, { | |
| 223 top: targetTop + 'px', | |
| 224 minHeight: 'calc(100% - ' + targetTop + 'px)', | |
| 225 }]; | |
| 226 var options = {duration: EXPAND_DURATION}; | |
| 227 // TODO(michaelpg): Change elevation of sections. | |
| 228 var promise; | |
| 229 if (keyframes[0].top && keyframes[0].minHeight) | |
| 230 promise = this.animateElement('section', card, keyframes, options); | |
| 231 else | |
| 232 var promise = Promise.resolve(); | |
|
Dan Beam
2016/02/26 19:18:23
nit: s/ var//
michaelpg
2016/03/01 00:18:28
Done.
| |
| 233 | |
| 234 promise.then(function() { | |
| 235 section.classList.add('expanded'); | |
| 236 card.style.top = ''; | |
| 237 this.style.margin = 'auto'; | |
| 238 section.$.header.hidden = true; | |
| 239 section.style.height = ''; | |
| 240 }.bind(this), function() { | |
|
Dan Beam
2016/02/26 19:18:23
why is this second function here? isn't it option
michaelpg
2016/03/01 00:18:28
if the animation is cancelled, the promise is reje
| |
| 241 }).then(function() { | |
| 242 // Whether finished or cancelled, clean up the animation. | |
| 243 section.classList.remove('expanding'); | |
| 244 card.style.height = ''; | |
| 245 }); | |
| 246 | |
| 247 return promise; | |
| 248 }, | |
| 249 | |
| 250 /** | |
| 251 * Collapses the cand in |section| back to its normal position. | |
|
Dan Beam
2016/02/26 19:18:23
cand -> card
michaelpg
2016/03/01 00:18:28
Done.
| |
| 252 * @param {!SettingsSectionElement} section | |
| 253 * @return {!Promise} | |
| 254 * @private | |
| 255 */ | |
| 256 playCollapseSection_: function(section) { | |
| 257 var card = section.$.card; | |
| 258 var cardStyle = getComputedStyle(card); | |
| 259 | |
| 260 this.style.margin = ''; | |
| 261 section.$.header.hidden = false; | |
| 262 | |
| 263 var startingTop = this.parentElement.getBoundingClientRect().top; | |
| 264 | |
| 265 var cardHeightStart = card.clientHeight; | |
| 266 | |
| 267 section.classList.add('collapsing'); | |
| 268 section.classList.remove('expanding'); | |
| 269 section.classList.remove('expanded'); | |
|
Dan Beam
2016/02/26 19:18:23
nit: .remove('expanding', 'expanded')
michaelpg
2016/03/01 00:18:28
Done.
| |
| 270 | |
| 271 // If we navigated here directly, we don't know the original height of the | |
| 272 // section, so we skip the animation. | |
| 273 // TODO(michaelpg): remove this condition once sliding is implemented. | |
| 274 if (!card.origHeight_) | |
|
Dan Beam
2016/02/26 19:18:23
i think it might be safer to set this to an invali
michaelpg
2016/03/01 00:18:28
What about... and I don't think I've ever used thi
| |
| 275 return Promise.resolve(); | |
| 276 | |
| 277 // Restore the section to its proper height to make room for the card. | |
| 278 section.style.height = section.clientHeight + card.origHeight_ + 'px'; | |
| 279 | |
| 280 // TODO(michaelpg): this should be in collapseSection(), but we need to wait | |
| 281 // until the full page height is available (setting the section height). | |
| 282 this.scroller.scrollTop = this.listScrollTop_; | |
| 283 | |
| 284 // The card is unpositioned, so use its position as the ending state, | |
| 285 // but account for scroll. | |
| 286 var targetTop = card.getBoundingClientRect().top - this.scroller.scrollTop; | |
| 287 | |
| 288 var keyframes = [{ | |
| 289 top: startingTop + 'px', | |
| 290 minHeight: cardHeightStart + 'px', | |
| 291 easing: EASING_FUNCTION, | |
| 292 }, { | |
| 293 top: targetTop + 'px', | |
| 294 minHeight: card.origHeight_ + 'px', | |
| 295 }]; | |
| 296 var options = {duration: EXPAND_DURATION}; | |
| 297 var promise = this.animateElement('section', card, keyframes, options); | |
| 298 return promise; | |
| 299 }, | |
| 300 }]; | |
| 301 | |
| 302 /** | |
| 303 * TODO(michaelpg): integrate slide animations. | |
| 304 * @polymerBehavior | |
| 305 */ | |
| 306 var RoutableBehavior = [MainPageBehavior, { | |
| 307 properties: { | |
| 308 /** Contains the current route. */ | |
| 309 currentRoute: { | |
| 310 type: Object, | |
| 311 notify: true, | |
| 312 observer: 'currentRouteChanged_', | |
| 313 }, | |
| 314 }, | |
| 315 | |
| 316 /** @private */ | |
| 317 currentRouteChanged_: function(newRoute, oldRoute) { | |
| 318 // route.section is only non-empty when the user is within a subpage. | |
| 319 // When the user is not in a subpage, but on the Basic page, route.section | |
| 320 // is an empty string. | |
| 321 var newRouteIsSubpage = newRoute && newRoute.section; | |
| 322 var oldRouteIsSubpage = oldRoute && oldRoute.section; | |
| 323 | |
| 324 if (!oldRoute && newRouteIsSubpage) { | |
| 325 // Allow the page to load before expanding the section. TODO(michaelpg): | |
| 326 // Time this better when refactoring settings-animated-pages. | |
| 327 setTimeout(function() { | |
| 328 var section = this.$$('*[section=' + newRoute.section + ']'); | |
|
Dan Beam
2016/02/26 19:18:23
why do you need the * before *[section?
michaelpg
2016/03/01 00:18:28
Removed.
| |
| 329 if (!section) | |
| 330 return; | |
| 331 this.expandSection(/** @type {!SettingsSectionElement} */(section)); | |
| 332 }.bind(this)); | |
| 333 return; | |
| 334 } | |
| 335 | |
| 336 if (!newRouteIsSubpage && oldRouteIsSubpage) { | |
| 337 var section = this.$$('*[section=' + oldRoute.section + ']'); | |
| 338 if (section) | |
| 339 this.collapseSection(/** @type {!SettingsSectionElement} */(section)); | |
| 340 } else if (newRouteIsSubpage && | |
| 341 (!oldRouteIsSubpage || newRoute.section != oldRoute.section)) { | |
| 342 var section = this.$$('*[section=' + newRoute.section + ']'); | |
|
Dan Beam
2016/02/26 19:18:23
maybe make this into a getSection_ method? both o
michaelpg
2016/03/01 00:18:28
Done.
| |
| 343 if (section) | |
| 344 this.expandSection(/** @type {!SettingsSectionElement} */(section)); | |
| 345 } | |
| 346 }, | |
| 347 }]; | |
| OLD | NEW |