OLD | NEW |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 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 /** | 5 /** |
6 * @fileoverview | 6 * @fileoverview |
7 * 'settings-section' shows a paper material themed section with a header | 7 * 'settings-section' shows a paper material themed section with a header |
8 * which shows its page title. | 8 * which shows its page title. |
9 * | 9 * |
| 10 * The section can expand vertically to fill its container's padding edge. |
| 11 * |
10 * Example: | 12 * Example: |
11 * | 13 * |
12 * <settings-section page-title="[[pageTitle]]" section="privacy"> | 14 * <settings-section page-title="[[pageTitle]]" section="privacy"> |
13 * <!-- Insert your section controls here --> | 15 * <!-- Insert your section controls here --> |
14 * </settings-section> | 16 * </settings-section> |
15 */ | 17 */ |
16 Polymer({ | 18 |
| 19 // Fast out, slow in. |
| 20 var EASING_FUNCTION = 'cubic-bezier(0.4, 0, 0.2, 1)'; |
| 21 var EXPAND_DURATION = 350; |
| 22 |
| 23 var SettingsSectionElement = Polymer({ |
17 is: 'settings-section', | 24 is: 'settings-section', |
18 | 25 |
19 properties: { | 26 properties: { |
20 /** | 27 /** |
21 * The current active route. | 28 * The current active route. |
22 */ | 29 */ |
23 currentRoute: Object, | 30 currentRoute: Object, |
24 | 31 |
25 /** | 32 /** |
26 * The section is expanded to a full-page view when this property matches | 33 * The section name should match a name specified in route.js. The |
| 34 * MainPageBehavior will expand this section if this section name matches |
27 * currentRoute.section. | 35 * currentRoute.section. |
28 * | |
29 * The section name must match the name specified in settings_router.js. | |
30 */ | 36 */ |
31 section: { | 37 section: String, |
32 type: String, | 38 |
33 }, | 39 /** Title for the section header. */ |
34 | |
35 /** | |
36 * Title for the page header and navigation menu. | |
37 */ | |
38 pageTitle: String, | 40 pageTitle: String, |
39 }, | 41 }, |
| 42 |
| 43 /** |
| 44 * Freezes the section's height so its card can be removed from the flow |
| 45 * without affecting the layout of the surrounding sections. |
| 46 * @param {boolean} frozen True to freeze, false to unfreeze. |
| 47 * @private |
| 48 */ |
| 49 setFrozen: function(frozen) { |
| 50 var card = this.$.card; |
| 51 if (frozen) { |
| 52 this.style.height = this.clientHeight + 'px'; |
| 53 |
| 54 var cardHeight = card.offsetHeight; |
| 55 var cardWidth = card.offsetWidth; |
| 56 // If the section is not displayed yet (e.g., navigated directly to a |
| 57 // sub-page), cardHeight and cardWidth are 0, so do not set the height or |
| 58 // width explicitly. |
| 59 // TODO(michaelpg): Improve this logic when refactoring |
| 60 // settings-animated-pages. |
| 61 if (cardHeight && cardWidth) { |
| 62 // TODO(michaelpg): Temporary hack to store the height the section |
| 63 // should collapse to when it closes. |
| 64 card.origHeight_ = cardHeight; |
| 65 |
| 66 card.style.height = cardHeight + 'px'; |
| 67 card.style.width = cardWidth + 'px'; |
| 68 } else { |
| 69 // Set an invalid value so we don't try to use it later. |
| 70 card.origHeight_ = NaN; |
| 71 } |
| 72 |
| 73 // Place the section's card at its current position but removed from the |
| 74 // flow. |
| 75 card.style.top = card.getBoundingClientRect().top + 'px'; |
| 76 this.classList.add('frozen'); |
| 77 } else { |
| 78 // Restore the section to its normal height. |
| 79 if (!this.classList.contains('frozen')) |
| 80 return; |
| 81 this.classList.remove('frozen'); |
| 82 this.$.card.style.top = ''; |
| 83 this.$.card.style.height = ''; |
| 84 this.$.card.style.width = ''; |
| 85 this.style.height = ''; |
| 86 } |
| 87 }, |
| 88 |
| 89 /** |
| 90 * @return {boolean} True if the section is currently rendered and not |
| 91 * already expanded or transitioning. |
| 92 */ |
| 93 canAnimateExpand: function() { |
| 94 return !this.classList.contains('expanded'); |
| 95 }, |
| 96 |
| 97 /** |
| 98 * Animates the section expanding to fill the container. The section is fixed |
| 99 * in the viewport during the animation. The section adds the "expanding" |
| 100 * class while the animation plays. |
| 101 * |
| 102 * @param {!HTMLElement} container The scrolling container to fill. |
| 103 * @return {?SettingsSectionElement.AnimationConfig} |
| 104 */ |
| 105 animateExpand: function(container) { |
| 106 var card = this.$.card; |
| 107 |
| 108 // The card should start at the top of the page. |
| 109 var targetTop = container.getBoundingClientRect().top; |
| 110 |
| 111 this.classList.add('expanding'); |
| 112 |
| 113 // Nothing to animate. |
| 114 if (!card.style.top || !card.style.height) |
| 115 return null; |
| 116 |
| 117 // Expand the card, using minHeight. (The card must span the container's |
| 118 // client height, so it must be at least 100% in case the card is too short. |
| 119 // If the card is already taller than the container's client height, we |
| 120 // don't want to shrink the card to 100% or the content will overflow, so |
| 121 // we can't use height, and animating height wouldn't look right anyway.) |
| 122 var keyframes = [{ |
| 123 top: card.style.top, |
| 124 minHeight: card.style.height, |
| 125 easing: EASING_FUNCTION, |
| 126 }, { |
| 127 top: targetTop + 'px', |
| 128 minHeight: 'calc(100% - ' + targetTop + 'px)', |
| 129 }]; |
| 130 var options = /** @type {!KeyframeEffectOptions} */({ |
| 131 duration: EXPAND_DURATION |
| 132 }); |
| 133 // TODO(michaelpg): Change elevation of sections. |
| 134 return {card: card, keyframes: keyframes, options: options}; |
| 135 }, |
| 136 |
| 137 /** |
| 138 * Cleans up after animateExpand(). |
| 139 * @param {boolean} finished Whether the animation finished successfully. |
| 140 */ |
| 141 cleanUpAnimateExpand: function(finished) { |
| 142 if (finished) { |
| 143 this.classList.add('expanded'); |
| 144 this.$.card.style.top = ''; |
| 145 this.$.header.hidden = true; |
| 146 this.style.height = ''; |
| 147 } |
| 148 |
| 149 this.classList.remove('expanding'); |
| 150 this.$.card.style.height = ''; |
| 151 this.$.card.style.width = ''; |
| 152 }, |
| 153 |
| 154 /** @return {boolean} True if the section is currently expanded. */ |
| 155 canAnimateCollapse: function() { |
| 156 return this.classList.contains('expanded'); |
| 157 }, |
| 158 |
| 159 /** |
| 160 * Collapses an expanded section's card back into position in the main page. |
| 161 * Adds the "expanding" class during the animation. |
| 162 * @param {!HTMLElement} container The scrolling container the card fills. |
| 163 * @param {number} prevScrollTop scrollTop of the container before this |
| 164 * section expanded. |
| 165 * @return {?SettingsSectionElement.AnimationConfig} |
| 166 */ |
| 167 animateCollapse: function(container, prevScrollTop) { |
| 168 this.$.header.hidden = false; |
| 169 |
| 170 var startingTop = container.getBoundingClientRect().top; |
| 171 |
| 172 var card = this.$.card; |
| 173 var cardHeightStart = card.clientHeight; |
| 174 var cardWidthStart = card.clientWidth; |
| 175 |
| 176 this.classList.add('collapsing'); |
| 177 this.classList.remove('expanding', 'expanded'); |
| 178 |
| 179 // If we navigated here directly, we don't know the original height of the |
| 180 // section, so we skip the animation. |
| 181 // TODO(michaelpg): remove this condition once sliding is implemented. |
| 182 if (Number.isNaN(card.origHeight_)) |
| 183 return null; |
| 184 |
| 185 // Restore the section to its proper height to make room for the card. |
| 186 this.style.height = (this.clientHeight + card.origHeight_) + 'px'; |
| 187 |
| 188 // TODO(michaelpg): this should be in MainPageBehavior(), but we need to |
| 189 // wait until the full page height is available (setting the section |
| 190 // height). |
| 191 container.scrollTop = prevScrollTop; |
| 192 |
| 193 // The card is unpositioned, so use its position as the ending state, |
| 194 // but account for scroll. |
| 195 var targetTop = card.getBoundingClientRect().top - container.scrollTop; |
| 196 |
| 197 // Account for the section header. |
| 198 var headerStyle = getComputedStyle(this.$.header); |
| 199 targetTop += this.$.header.offsetHeight + |
| 200 parseInt(headerStyle.marginBottom, 10) + |
| 201 parseInt(headerStyle.marginTop, 10); |
| 202 |
| 203 var keyframes = [{ |
| 204 top: startingTop + 'px', |
| 205 minHeight: cardHeightStart + 'px', |
| 206 easing: EASING_FUNCTION, |
| 207 }, { |
| 208 top: targetTop + 'px', |
| 209 minHeight: card.origHeight_ + 'px', |
| 210 }]; |
| 211 var options = /** @type {!KeyframeEffectOptions} */({ |
| 212 duration: EXPAND_DURATION |
| 213 }); |
| 214 |
| 215 card.style.width = cardWidthStart + 'px'; |
| 216 |
| 217 return {card: card, keyframes: keyframes, options: options}; |
| 218 }, |
| 219 |
| 220 /** |
| 221 * Cleans up after animateCollapse(). |
| 222 * @param {boolean} finished Whether the animation finished successfully. |
| 223 */ |
| 224 cleanUpAnimateCollapse: function(finished) { |
| 225 if (finished) |
| 226 this.$.card.style.width = ''; |
| 227 }, |
40 }); | 228 }); |
| 229 |
| 230 /** |
| 231 * Information needed by TransitionBehavior to schedule animations. |
| 232 * @typedef {{ |
| 233 * card: !HTMLElement, |
| 234 * keyframes: !Array<!Object>, |
| 235 * options: !KeyframeEffectOptions |
| 236 * }} |
| 237 */ |
| 238 SettingsSectionElement.AnimationConfig; |
OLD | NEW |