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