<!-- @license Copyright (c) 2015 The Polymer Project Authors. All rights reserved. This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as part of the polymer project is also subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt --> <link rel="import" href="../polymer/polymer.html"> <link rel="import" href="../iron-resizable-behavior/iron-resizable-behavior.html"> <!-- `iron-collapse` creates a collapsible block of content. By default, the content will be collapsed. Use `opened` or `toggle()` to show/hide the content. <button on-click="toggle">toggle collapse</button> <iron-collapse id="collapse"> <div>Content goes here...</div> </iron-collapse> ... toggle: function() { this.$.collapse.toggle(); } `iron-collapse` adjusts the max-height/max-width of the collapsible element to show/hide the content. So avoid putting padding/margin/border on the collapsible directly, and instead put a div inside and style that. <style> .collapse-content { padding: 15px; border: 1px solid #dedede; } </style> <iron-collapse> <div class="collapse-content"> <div>Content goes here...</div> </div> </iron-collapse> @group Iron Elements @hero hero.svg @demo demo/index.html @element iron-collapse --> <dom-module id="iron-collapse"> <template> <style> :host { display: block; transition-duration: 300ms; overflow: visible; } :host(.iron-collapse-closed) { display: none; } :host(:not(.iron-collapse-opened)) { overflow: hidden; } </style> <content></content> </template> </dom-module> <script> Polymer({ is: 'iron-collapse', behaviors: [ Polymer.IronResizableBehavior ], properties: { /** * If true, the orientation is horizontal; otherwise is vertical. * * @attribute horizontal */ horizontal: { type: Boolean, value: false, observer: '_horizontalChanged' }, /** * Set opened to true to show the collapse element and to false to hide it. * * @attribute opened */ opened: { type: Boolean, value: false, notify: true, observer: '_openedChanged' }, /** * Set noAnimation to true to disable animations * * @attribute noAnimation */ noAnimation: { type: Boolean }, }, get dimension() { return this.horizontal ? 'width' : 'height'; }, /** * `maxWidth` or `maxHeight`. * @private */ get _dimensionMax() { return this.horizontal ? 'maxWidth' : 'maxHeight'; }, /** * `max-width` or `max-height`. * @private */ get _dimensionMaxCss() { return this.horizontal ? 'max-width' : 'max-height'; }, hostAttributes: { role: 'group', 'aria-hidden': 'true', 'aria-expanded': 'false' }, listeners: { transitionend: '_transitionEnd' }, attached: function() { // It will take care of setting correct classes and styles. this._transitionEnd(); }, /** * Toggle the opened state. * * @method toggle */ toggle: function() { this.opened = !this.opened; }, show: function() { this.opened = true; }, hide: function() { this.opened = false; }, /** * Updates the size of the element. * @param {!String} size The new value for `maxWidth`/`maxHeight` as css property value, usually `auto` or `0px`. * @param {boolean=} animated if `true` updates the size with an animation, otherwise without. */ updateSize: function(size, animated) { // No change! var curSize = this.style[this._dimensionMax]; if (curSize === size || (size === 'auto' && !curSize)) { return; } this._updateTransition(false); // If we can animate, must do some prep work. if (animated && !this.noAnimation && this._isDisplayed) { // Animation will start at the current size. var startSize = this._calcSize(); // For `auto` we must calculate what is the final size for the animation. // After the transition is done, _transitionEnd will set the size back to `auto`. if (size === 'auto') { this.style[this._dimensionMax] = ''; size = this._calcSize(); } // Go to startSize without animation. this.style[this._dimensionMax] = startSize; // Force layout to ensure transition will go. Set offsetHeight to itself // so that compilers won't remove it. this.offsetHeight = this.offsetHeight; // Enable animation. this._updateTransition(true); } // Set the final size. if (size === 'auto') { this.style[this._dimensionMax] = ''; } else { this.style[this._dimensionMax] = size; } }, /** * enableTransition() is deprecated, but left over so it doesn't break existing code. * Please use `noAnimation` property instead. * * @method enableTransition * @deprecated since version 1.0.4 */ enableTransition: function(enabled) { Polymer.Base._warn('`enableTransition()` is deprecated, use `noAnimation` instead.'); this.noAnimation = !enabled; }, _updateTransition: function(enabled) { this.style.transitionDuration = (enabled && !this.noAnimation) ? '' : '0s'; }, _horizontalChanged: function() { this.style.transitionProperty = this._dimensionMaxCss; var otherDimension = this._dimensionMax === 'maxWidth' ? 'maxHeight' : 'maxWidth'; this.style[otherDimension] = ''; this.updateSize(this.opened ? 'auto' : '0px', false); }, _openedChanged: function() { this.setAttribute('aria-expanded', this.opened); this.setAttribute('aria-hidden', !this.opened); this.toggleClass('iron-collapse-closed', false); this.toggleClass('iron-collapse-opened', false); this.updateSize(this.opened ? 'auto' : '0px', true); // Focus the current collapse. if (this.opened) { this.focus(); } if (this.noAnimation) { this._transitionEnd(); } }, _transitionEnd: function() { if (this.opened) { this.style[this._dimensionMax] = ''; } this.toggleClass('iron-collapse-closed', !this.opened); this.toggleClass('iron-collapse-opened', this.opened); this._updateTransition(false); this.notifyResize(); }, /** * Simplistic heuristic to detect if element has a parent with display: none * * @private */ get _isDisplayed() { var rect = this.getBoundingClientRect(); for (var prop in rect) { if (rect[prop] !== 0) return true; } return false; }, _calcSize: function() { return this.getBoundingClientRect()[this.dimension] + 'px'; } }); </script>