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() { |
(...skipping 10 matching lines...) Expand all Loading... | |
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 /** | 29 /** |
34 * @type {string} Selector to get the sections. Derived elements | 30 * @type {string} Selector to get the sections. Derived elements |
35 * must override. | 31 * must override. |
36 */ | 32 */ |
37 sectionSelector: '', | 33 sectionSelector: '', |
38 | 34 |
39 /** @type {?Element} The scrolling container. */ | |
40 scroller: null, | |
41 | |
42 /** @override */ | 35 /** @override */ |
43 attached: function() { | 36 attached: function() { |
37 /** @type {!HTMLElement} The scrolling container. */ | |
44 this.scroller = this.domHost && this.domHost.parentNode.$.mainContainer; | 38 this.scroller = this.domHost && this.domHost.parentNode.$.mainContainer; |
45 }, | 39 }, |
46 | 40 |
47 /** | 41 /** |
48 * Hides or unhides the sections not being expanded. | 42 * Hides or unhides the sections not being expanded. |
49 * @param {string} sectionName The section to keep visible. | 43 * @param {string} sectionName The section to keep visible. |
50 * @param {boolean} hidden Whether the sections should be hidden. | 44 * @param {boolean} hidden Whether the sections should be hidden. |
51 * @private | 45 * @private |
52 */ | 46 */ |
53 toggleOtherSectionsHidden_: function(sectionName, hidden) { | 47 toggleOtherSectionsHidden_: function(sectionName, hidden) { |
54 var sections = Polymer.dom(this.root).querySelectorAll( | 48 var sections = Polymer.dom(this.root).querySelectorAll( |
55 this.sectionSelector + ':not([section=' + sectionName + '])'); | 49 this.sectionSelector + ':not([section=' + sectionName + '])'); |
56 for (var section of sections) | 50 for (var section of sections) |
57 section.hidden = hidden; | 51 section.hidden = hidden; |
58 }, | 52 }, |
59 | 53 |
60 /** | 54 /** |
61 * Animates the card in |section|, expanding it to fill the page. | 55 * Animates the card in |section|, expanding it to fill the page. |
62 * @param {!SettingsSectionElement} section | 56 * @param {!SettingsSectionElement} section |
63 */ | 57 */ |
64 expandSection: function(section) { | 58 expandSection: function(section) { |
65 // If another section's card is expanding, cancel that animation first. | 59 // TODO(michaelpg): Manage transition lifetime better. |
66 var expanding = this.$$('.expanding'); | 60 // crbug.com/624145 |
67 if (expanding) { | 61 if (this.openSectionTransition_) { |
68 if (expanding == section) | 62 if (this.openSectionTransition_.section == section) |
69 return; | 63 return; |
70 | 64 |
71 if (this.animations['section']) { | 65 // Cancel the other section expanding. |
72 // Cancel the animation, then call startExpandSection_. | 66 this.openSectionTransition_.cancel(); |
73 this.cancelAnimation('section', function() { | 67 // After everything, schedule this function again to open the section. |
74 this.startExpandSection_(section); | 68 this.openSectionTransition_.finished |
75 }.bind(this)); | 69 .then(function() { |
76 } else { | 70 // The animation already finished, so close that section. |
77 // The animation must have finished but its promise hasn't resolved yet. | 71 this.collapseSection(this.openSectionTransition_.section); |
78 // When it resolves, collapse that section's card before expanding | 72 }).catch(function() {}) |
79 // this one. | 73 .then(this.expandSection.bind(this, section)); |
80 setTimeout(function() { | 74 return; |
81 this.collapseSection( | 75 } |
82 /** @type {!SettingsSectionElement} */(expanding)); | 76 if (this.closeSectionTransition_) { |
83 this.finishAnimation('section', function() { | 77 // Finish collapsing the section, then expand this section. |
84 this.startExpandSection_(section); | 78 this.closeSectionTransition_.finished |
85 }.bind(this)); | 79 .then(this.expandSection.bind(this, section)); |
Dan Beam
2016/07/12 02:01:54
i don't see lines like this as a big improvement w
michaelpg
2016/07/20 22:10:57
Acknowledged. Breaking here after .then( instead.
| |
86 }.bind(this)); | |
87 } | |
88 | |
89 return; | 80 return; |
90 } | 81 } |
91 | 82 |
92 if (this.$$('.collapsing') && this.animations['section']) { | 83 this.openSectionTransition_ = |
93 // Finish the collapse animation before expanding. | 84 new settings.OpenSectionTransition(section, this.scroller); |
94 this.finishAnimation('section', function() { | |
95 this.startExpandSection_(section); | |
96 }.bind(this)); | |
97 return; | |
98 } | |
99 | |
100 this.startExpandSection_(section); | |
101 }, | |
102 | |
103 /** | |
104 * Helper function to set up and start the expand animation. | |
105 * @param {!SettingsSectionElement} section | |
106 */ | |
107 startExpandSection_: function(section) { | |
108 if (section.classList.contains('expanded')) | |
109 return; | |
110 | 85 |
111 // Freeze the scroller and save its position. | 86 // Freeze the scroller and save its position. |
112 this.listScrollTop_ = this.scroller.scrollTop; | 87 this.scroller.listScrollTop_ = this.scroller.scrollTop; |
113 | |
114 var scrollerWidth = this.scroller.clientWidth; | 88 var scrollerWidth = this.scroller.clientWidth; |
115 this.scroller.style.overflow = 'hidden'; | 89 this.scroller.style.overflow = 'hidden'; |
90 | |
116 // Adjust width to compensate for scroller. | 91 // Adjust width to compensate for scroller. |
117 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth; | 92 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth; |
118 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)'; | 93 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)'; |
119 | 94 |
120 // Freezes the section's height so its card can be removed from the flow. | 95 this.openSectionTransition_.play() |
121 this.freezeSection_(section); | 96 .then(function() { |
122 | 97 this.toggleOtherSectionsHidden_(section.section, true); |
123 // Expand the section's card to fill the parent. | 98 this.scroller.scrollTop = 0; |
124 var animationPromise = this.playExpandSection_(section); | 99 this.classList.add('showing-subpage'); |
125 | 100 this.fire('subpage-expand'); |
126 animationPromise.then(function() { | 101 }.bind(this)) |
127 this.scroller.scrollTop = 0; | 102 .catch(function() { |
128 this.toggleOtherSectionsHidden_(section.section, true); | 103 this.scroller.scrollTop = this.scroller.listScrollTop_; |
129 }.bind(this), function() { | 104 }.bind(this)) |
130 // Animation was canceled; restore the section. | 105 .then(function() { |
131 this.unfreezeSection_(section); | 106 this.scroller.style.width = ''; |
132 }.bind(this)).then(function() { | 107 this.scroller.style.overflow = ''; |
133 this.scroller.style.overflow = ''; | 108 this.openSectionTransition_ = null; |
134 this.scroller.style.width = ''; | 109 }.bind(this)); |
135 }.bind(this)); | |
136 }, | 110 }, |
137 | 111 |
138 /** | 112 /** |
139 * Animates the card in |section|, collapsing it back into its section. | 113 * Animates the card in |section|, collapsing it back into its section. |
140 * @param {!SettingsSectionElement} section | 114 * @param {!SettingsSectionElement} section |
141 */ | 115 */ |
142 collapseSection: function(section) { | 116 collapseSection: function(section) { |
143 // If the section's card is still expanding, cancel the expand animation. | 117 if (this.closeSectionTransition_) { |
144 if (section.classList.contains('expanding')) { | 118 assert(this.closeSectionTransition_.section == section); |
145 if (this.animations['section']) { | 119 this.closeSectionTransition_.cancel(); |
146 this.cancelAnimation('section'); | |
147 } else { | |
148 // The animation must have finished but its promise hasn't finished | |
149 // resolving; try again asynchronously. | |
150 this.async(function() { | |
151 this.collapseSection(section); | |
152 }); | |
153 } | |
154 return; | 120 return; |
155 } | 121 } |
156 | 122 |
157 if (!section.classList.contains('expanded')) | 123 if (this.openSectionTransition_) { |
124 assert(this.openSectionTransition_.section == section); | |
125 this.openSectionTransition_.cancel(); | |
158 return; | 126 return; |
127 } | |
159 | 128 |
160 this.toggleOtherSectionsHidden_(section.section, false); | 129 assert(section.classList.contains('expanded')); |
161 | 130 |
162 var scrollerWidth = this.scroller.clientWidth; | 131 var scrollerWidth = this.scroller.clientWidth; |
163 this.scroller.style.overflow = 'hidden'; | 132 this.scroller.style.overflow = 'hidden'; |
133 | |
164 // Adjust width to compensate for scroller. | 134 // Adjust width to compensate for scroller. |
135 // TODO(michaelpg): Minimize horizontal motion when scrollbar changes for | |
136 // the common cases. | |
165 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth; | 137 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth; |
166 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)'; | 138 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)'; |
167 | 139 |
168 this.playCollapseSection_(section).then(function() { | 140 // Allow time for the dom-ifs in settings-main to re-render. |
169 this.unfreezeSection_(section); | 141 // TODO(michaelpg): Use a readiness signal (e.g., from the router) rather |
170 this.scroller.style.overflow = ''; | 142 // than firing events for settings-main and running this function async. |
171 this.scroller.style.width = ''; | 143 this.fire('subpage-collapsing'); |
172 section.classList.remove('collapsing'); | 144 this.async(function() { |
173 }.bind(this)); | 145 // Set up the close transition first to take the section out of the flow |
174 }, | 146 // before showing everything. |
147 this.closeSectionTransition_ = | |
148 new settings.CloseSectionTransition(section, this.scroller); | |
149 this.closeSectionTransition_.setUp(); | |
175 | 150 |
176 /** | 151 this.toggleOtherSectionsHidden_(section.section, false); |
177 * Freezes a section's height so its card can be removed from the flow without | 152 this.classList.remove('showing-subpage'); |
178 * affecting the layout of the surrounding sections. | 153 this.scroller.scrollTop = this.scroller.listScrollTop_; |
179 * @param {!SettingsSectionElement} section | |
180 * @private | |
181 */ | |
182 freezeSection_: function(section) { | |
183 var card = section.$.card; | |
184 section.style.height = section.clientHeight + 'px'; | |
185 | 154 |
186 var cardHeight = card.offsetHeight; | 155 this.closeSectionTransition_.play() |
187 var cardWidth = card.offsetWidth; | 156 .catch(function() { |
188 // If the section is not displayed yet (e.g., navigated directly to a | 157 this.fire('subpage-expand'); |
189 // sub-page), cardHeight and cardWidth are 0, so do not set the height or | 158 }.bind(this)) |
190 // width explicitly. | 159 .then(function() { |
191 // TODO(michaelpg): Improve this logic when refactoring | 160 this.scroller.style.overflow = ''; |
192 // settings-animated-pages. | 161 this.scroller.style.width = ''; |
193 if (cardHeight && cardWidth) { | 162 this.closeSectionTransition_ = null; |
194 // TODO(michaelpg): Temporary hack to store the height the section should | 163 }.bind(this)); |
195 // collapse to when it closes. | |
196 card.origHeight_ = cardHeight; | |
197 | |
198 card.style.height = cardHeight + 'px'; | |
199 card.style.width = cardWidth + 'px'; | |
200 } else { | |
201 // Set an invalid value so we don't try to use it later. | |
202 card.origHeight_ = NaN; | |
203 } | |
204 | |
205 // Place the section's card at its current position but removed from the | |
206 // flow. | |
207 card.style.top = card.getBoundingClientRect().top + 'px'; | |
208 section.classList.add('frozen'); | |
209 }, | |
210 | |
211 /** | |
212 * After freezeSection_, restores the section to its normal height. | |
213 * @param {!SettingsSectionElement} section | |
214 * @private | |
215 */ | |
216 unfreezeSection_: function(section) { | |
217 if (!section.classList.contains('frozen')) | |
218 return; | |
219 var card = section.$.card; | |
220 section.classList.remove('frozen'); | |
221 card.style.top = ''; | |
222 card.style.height = ''; | |
223 card.style.width = ''; | |
224 section.style.height = ''; | |
225 }, | |
226 | |
227 /** | |
228 * Expands the card in |section| to fill the page. | |
229 * @param {!SettingsSectionElement} section | |
230 * @return {!Promise} | |
231 * @private | |
232 */ | |
233 playExpandSection_: function(section) { | |
234 var card = section.$.card; | |
235 | |
236 // The card should start at the top of the page. | |
237 var targetTop = this.scroller.getBoundingClientRect().top; | |
238 | |
239 section.classList.add('expanding'); | |
240 | |
241 // Expand the card, using minHeight. (The card must span the container's | |
242 // client height, so it must be at least 100% in case the card is too short. | |
243 // If the card is already taller than the container's client height, we | |
244 // don't want to shrink the card to 100% or the content will overflow, so | |
245 // we can't use height, and animating height wouldn't look right anyway.) | |
246 var keyframes = [{ | |
247 top: card.style.top, | |
248 minHeight: card.style.height, | |
249 easing: EASING_FUNCTION, | |
250 }, { | |
251 top: targetTop + 'px', | |
252 minHeight: 'calc(100% - ' + targetTop + 'px)', | |
253 }]; | |
254 var options = /** @type {!KeyframeEffectOptions} */({ | |
255 duration: EXPAND_DURATION | |
256 }); | 164 }); |
257 // TODO(michaelpg): Change elevation of sections. | |
258 var promise; | |
259 if (keyframes[0].top && keyframes[0].minHeight) | |
260 promise = this.animateElement('section', card, keyframes, options); | |
261 else | |
262 promise = Promise.resolve(); | |
263 | |
264 promise.then(function() { | |
265 section.classList.add('expanded'); | |
266 card.style.top = ''; | |
267 this.style.margin = 'auto'; | |
268 section.$.header.hidden = true; | |
269 section.style.height = ''; | |
270 }.bind(this), function() { | |
271 // The animation was canceled; catch the error and continue. | |
272 }).then(function() { | |
273 // Whether finished or canceled, clean up the animation. | |
274 section.classList.remove('expanding'); | |
275 card.style.height = ''; | |
276 card.style.width = ''; | |
277 }); | |
278 | |
279 return promise; | |
280 }, | |
281 | |
282 /** | |
283 * Collapses the card in |section| back to its normal position. | |
284 * @param {!SettingsSectionElement} section | |
285 * @return {!Promise} | |
286 * @private | |
287 */ | |
288 playCollapseSection_: function(section) { | |
289 var card = section.$.card; | |
290 | |
291 this.style.margin = ''; | |
292 section.$.header.hidden = false; | |
293 | |
294 var startingTop = this.scroller.getBoundingClientRect().top; | |
295 | |
296 var cardHeightStart = card.clientHeight; | |
297 var cardWidthStart = card.clientWidth; | |
298 | |
299 section.classList.add('collapsing'); | |
300 section.classList.remove('expanding', 'expanded'); | |
301 | |
302 // If we navigated here directly, we don't know the original height of the | |
303 // section, so we skip the animation. | |
304 // TODO(michaelpg): remove this condition once sliding is implemented. | |
305 if (isNaN(card.origHeight_)) | |
306 return Promise.resolve(); | |
307 | |
308 // Restore the section to its proper height to make room for the card. | |
309 section.style.height = section.clientHeight + card.origHeight_ + 'px'; | |
310 | |
311 // TODO(michaelpg): this should be in collapseSection(), but we need to wait | |
312 // until the full page height is available (setting the section height). | |
313 this.scroller.scrollTop = this.listScrollTop_; | |
314 | |
315 // The card is unpositioned, so use its position as the ending state, | |
316 // but account for scroll. | |
317 var targetTop = card.getBoundingClientRect().top - this.scroller.scrollTop; | |
318 | |
319 // Account for the section header. | |
320 var headerStyle = getComputedStyle(section.$.header); | |
321 targetTop += section.$.header.offsetHeight + | |
322 parseInt(headerStyle.marginBottom, 10) + | |
323 parseInt(headerStyle.marginTop, 10); | |
324 | |
325 var keyframes = [{ | |
326 top: startingTop + 'px', | |
327 minHeight: cardHeightStart + 'px', | |
328 easing: EASING_FUNCTION, | |
329 }, { | |
330 top: targetTop + 'px', | |
331 minHeight: card.origHeight_ + 'px', | |
332 }]; | |
333 var options = /** @type {!KeyframeEffectOptions} */({ | |
334 duration: EXPAND_DURATION | |
335 }); | |
336 | |
337 card.style.width = cardWidthStart + 'px'; | |
338 var promise = this.animateElement('section', card, keyframes, options); | |
339 promise.then(function() { | |
340 card.style.width = ''; | |
341 }); | |
342 return promise; | |
343 }, | 165 }, |
344 }; | 166 }; |
345 | 167 |
346 | 168 |
347 /** @polymerBehavior */ | 169 /** @polymerBehavior */ |
348 var MainPageBehavior = [ | 170 var MainPageBehavior = [ |
349 TransitionBehavior, | |
350 MainPageBehaviorImpl | 171 MainPageBehaviorImpl |
351 ]; | 172 ]; |
352 | 173 |
353 | 174 |
354 /** | 175 /** |
355 * TODO(michaelpg): integrate slide animations. | 176 * TODO(michaelpg): integrate slide animations. |
356 * @polymerBehavior RoutableBehavior | 177 * @polymerBehavior RoutableBehavior |
357 */ | 178 */ |
358 var RoutableBehaviorImpl = { | 179 var RoutableBehaviorImpl = { |
359 properties: { | 180 properties: { |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
420 this.$$('[section=' + section + ']')); | 241 this.$$('[section=' + section + ']')); |
421 }, | 242 }, |
422 }; | 243 }; |
423 | 244 |
424 | 245 |
425 /** @polymerBehavior */ | 246 /** @polymerBehavior */ |
426 var RoutableBehavior = [ | 247 var RoutableBehavior = [ |
427 MainPageBehavior, | 248 MainPageBehavior, |
428 RoutableBehaviorImpl | 249 RoutableBehaviorImpl |
429 ]; | 250 ]; |
OLD | NEW |