• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1<!--
2@license
3Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
4This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
5The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
6The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
7Code distributed by Google as part of the polymer project is also
8subject 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
16will 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
31the content.  So avoid putting padding/margin/border on the collapsible directly,
32and 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