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 |