1 <!-- 2 @license 3 Copyright (c) 2015 The Polymer Project Authors. All rights reserved. 4 This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 Code distributed by Google as part of the polymer project is also 8 subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 --> 10 11 <link rel="import" href="../polymer/polymer.html"> 12 <link rel="import" href="../iron-resizable-behavior/iron-resizable-behavior.html"> 13 14 <!-- 15 `iron-collapse` creates a collapsible block of content. By default, the content 16 will be collapsed. Use `opened` or `toggle()` to show/hide the content. 17 18 <button on-click="toggle">toggle collapse</button> 19 20 <iron-collapse id="collapse"> 21 <div>Content goes here...</div> 22 </iron-collapse> 23 24 ... 25 26 toggle: function() { 27 this.$.collapse.toggle(); 28 } 29 30 `iron-collapse` adjusts the max-height/max-width of the collapsible element to show/hide 31 the content. So avoid putting padding/margin/border on the collapsible directly, 32 and instead put a div inside and style that. 33 34 <style> 35 .collapse-content { 36 padding: 15px; 37 border: 1px solid #dedede; 38 } 39 </style> 40 41 <iron-collapse> 42 <div class="collapse-content"> 43 <div>Content goes here...</div> 44 </div> 45 </iron-collapse> 46 47 @group Iron Elements 48 @hero hero.svg 49 @demo demo/index.html 50 @element iron-collapse 51 --> 52 53 <dom-module id="iron-collapse"> 54 55 <template> 56 57 <style> 58 :host { 59 display: block; 60 transition-duration: 300ms; 61 overflow: visible; 62 } 63 64 :host(.iron-collapse-closed) { 65 display: none; 66 } 67 68 :host(:not(.iron-collapse-opened)) { 69 overflow: hidden; 70 } 71 </style> 72 73 <content></content> 74 75 </template> 76 77 </dom-module> 78 79 <script> 80 81 Polymer({ 82 83 is: 'iron-collapse', 84 85 behaviors: [ 86 Polymer.IronResizableBehavior 87 ], 88 89 properties: { 90 91 /** 92 * If true, the orientation is horizontal; otherwise is vertical. 93 * 94 * @attribute horizontal 95 */ 96 horizontal: { 97 type: Boolean, 98 value: false, 99 observer: '_horizontalChanged' 100 }, 101 102 /** 103 * Set opened to true to show the collapse element and to false to hide it. 104 * 105 * @attribute opened 106 */ 107 opened: { 108 type: Boolean, 109 value: false, 110 notify: true, 111 observer: '_openedChanged' 112 }, 113 114 /** 115 * Set noAnimation to true to disable animations 116 * 117 * @attribute noAnimation 118 */ 119 noAnimation: { 120 type: Boolean 121 }, 122 123 }, 124 125 get dimension() { 126 return this.horizontal ? 'width' : 'height'; 127 }, 128 129 /** 130 * `maxWidth` or `maxHeight`. 131 * @private 132 */ 133 get _dimensionMax() { 134 return this.horizontal ? 'maxWidth' : 'maxHeight'; 135 }, 136 137 /** 138 * `max-width` or `max-height`. 139 * @private 140 */ 141 get _dimensionMaxCss() { 142 return this.horizontal ? 'max-width' : 'max-height'; 143 }, 144 145 hostAttributes: { 146 role: 'group', 147 'aria-hidden': 'true', 148 'aria-expanded': 'false' 149 }, 150 151 listeners: { 152 transitionend: '_transitionEnd' 153 }, 154 155 attached: function() { 156 // It will take care of setting correct classes and styles. 157 this._transitionEnd(); 158 }, 159 160 /** 161 * Toggle the opened state. 162 * 163 * @method toggle 164 */ 165 toggle: function() { 166 this.opened = !this.opened; 167 }, 168 169 show: function() { 170 this.opened = true; 171 }, 172 173 hide: function() { 174 this.opened = false; 175 }, 176 177 /** 178 * Updates the size of the element. 179 * @param {!String} size The new value for `maxWidth`/`maxHeight` as css property value, usually `auto` or `0px`. 180 * @param {boolean=} animated if `true` updates the size with an animation, otherwise without. 181 */ 182 updateSize: function(size, animated) { 183 // No change! 184 var curSize = this.style[this._dimensionMax]; 185 if (curSize === size || (size === 'auto' && !curSize)) { 186 return; 187 } 188 189 this._updateTransition(false); 190 // If we can animate, must do some prep work. 191 if (animated && !this.noAnimation && this._isDisplayed) { 192 // Animation will start at the current size. 193 var startSize = this._calcSize(); 194 // For `auto` we must calculate what is the final size for the animation. 195 // After the transition is done, _transitionEnd will set the size back to `auto`. 196 if (size === 'auto') { 197 this.style[this._dimensionMax] = ''; 198 size = this._calcSize(); 199 } 200 // Go to startSize without animation. 201 this.style[this._dimensionMax] = startSize; 202 // Force layout to ensure transition will go. Set offsetHeight to itself 203 // so that compilers won't remove it. 204 this.offsetHeight = this.offsetHeight; 205 // Enable animation. 206 this._updateTransition(true); 207 } 208 // Set the final size. 209 if (size === 'auto') { 210 this.style[this._dimensionMax] = ''; 211 } else { 212 this.style[this._dimensionMax] = size; 213 } 214 }, 215 216 /** 217 * enableTransition() is deprecated, but left over so it doesn't break existing code. 218 * Please use `noAnimation` property instead. 219 * 220 * @method enableTransition 221 * @deprecated since version 1.0.4 222 */ 223 enableTransition: function(enabled) { 224 Polymer.Base._warn('`enableTransition()` is deprecated, use `noAnimation` instead.'); 225 this.noAnimation = !enabled; 226 }, 227 228 _updateTransition: function(enabled) { 229 this.style.transitionDuration = (enabled && !this.noAnimation) ? '' : '0s'; 230 }, 231 232 _horizontalChanged: function() { 233 this.style.transitionProperty = this._dimensionMaxCss; 234 var otherDimension = this._dimensionMax === 'maxWidth' ? 'maxHeight' : 'maxWidth'; 235 this.style[otherDimension] = ''; 236 this.updateSize(this.opened ? 'auto' : '0px', false); 237 }, 238 239 _openedChanged: function() { 240 this.setAttribute('aria-expanded', this.opened); 241 this.setAttribute('aria-hidden', !this.opened); 242 243 this.toggleClass('iron-collapse-closed', false); 244 this.toggleClass('iron-collapse-opened', false); 245 this.updateSize(this.opened ? 'auto' : '0px', true); 246 247 // Focus the current collapse. 248 if (this.opened) { 249 this.focus(); 250 } 251 if (this.noAnimation) { 252 this._transitionEnd(); 253 } 254 }, 255 256 _transitionEnd: function() { 257 if (this.opened) { 258 this.style[this._dimensionMax] = ''; 259 } 260 this.toggleClass('iron-collapse-closed', !this.opened); 261 this.toggleClass('iron-collapse-opened', this.opened); 262 this._updateTransition(false); 263 this.notifyResize(); 264 }, 265 266 /** 267 * Simplistic heuristic to detect if element has a parent with display: none 268 * 269 * @private 270 */ 271 get _isDisplayed() { 272 var rect = this.getBoundingClientRect(); 273 for (var prop in rect) { 274 if (rect[prop] !== 0) return true; 275 } 276 return false; 277 }, 278 279 _calcSize: function() { 280 return this.getBoundingClientRect()[this.dimension] + 'px'; 281 } 282 283 }); 284 285 </script> 286