• 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<link rel="import" href="../iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
14<link rel="import" href="../iron-behaviors/iron-control-state.html">
15<link rel="import" href="../iron-overlay-behavior/iron-overlay-behavior.html">
16<link rel="import" href="../neon-animation/neon-animation-runner-behavior.html">
17<link rel="import" href="../neon-animation/animations/opaque-animation.html">
18<link rel="import" href="iron-dropdown-scroll-manager.html">
19
20<!--
21`<iron-dropdown>` is a generalized element that is useful when you have
22hidden content (`.dropdown-content`) that is revealed due to some change in
23state that should cause it to do so.
24
25Note that this is a low-level element intended to be used as part of other
26composite elements that cause dropdowns to be revealed.
27
28Examples of elements that might be implemented using an `iron-dropdown`
29include comboboxes, menubuttons, selects. The list goes on.
30
31The `<iron-dropdown>` element exposes attributes that allow the position
32of the `.dropdown-content` relative to the `.dropdown-trigger` to be
33configured.
34
35    <iron-dropdown horizontal-align="right" vertical-align="top">
36      <div class="dropdown-content">Hello!</div>
37    </iron-dropdown>
38
39In the above example, the `<div>` with class `.dropdown-content` will be
40hidden until the dropdown element has `opened` set to true, or when the `open`
41method is called on the element.
42
43@demo demo/index.html
44-->
45
46<dom-module id="iron-dropdown">
47  <style>
48    :host {
49      position: fixed;
50    }
51
52    #contentWrapper ::content > * {
53      overflow: auto;
54    }
55
56    #contentWrapper.animating ::content > * {
57      overflow: hidden;
58    }
59  </style>
60  <template>
61    <div id="contentWrapper">
62      <content id="content" select=".dropdown-content"></content>
63    </div>
64  </template>
65
66  <script>
67    (function() {
68      'use strict';
69
70      Polymer({
71        is: 'iron-dropdown',
72
73        behaviors: [
74          Polymer.IronControlState,
75          Polymer.IronA11yKeysBehavior,
76          Polymer.IronOverlayBehavior,
77          Polymer.NeonAnimationRunnerBehavior
78        ],
79
80        properties: {
81          /**
82           * The orientation against which to align the dropdown content
83           * horizontally relative to the dropdown trigger.
84           * Overridden from `Polymer.IronFitBehavior`.
85           */
86          horizontalAlign: {
87            type: String,
88            value: 'left',
89            reflectToAttribute: true
90          },
91
92          /**
93           * The orientation against which to align the dropdown content
94           * vertically relative to the dropdown trigger.
95           * Overridden from `Polymer.IronFitBehavior`.
96           */
97          verticalAlign: {
98            type: String,
99            value: 'top',
100            reflectToAttribute: true
101          },
102
103          /**
104           * An animation config. If provided, this will be used to animate the
105           * opening of the dropdown.
106           */
107          openAnimationConfig: {
108            type: Object
109          },
110
111          /**
112           * An animation config. If provided, this will be used to animate the
113           * closing of the dropdown.
114           */
115          closeAnimationConfig: {
116            type: Object
117          },
118
119          /**
120           * If provided, this will be the element that will be focused when
121           * the dropdown opens.
122           */
123          focusTarget: {
124            type: Object
125          },
126
127          /**
128           * Set to true to disable animations when opening and closing the
129           * dropdown.
130           */
131          noAnimations: {
132            type: Boolean,
133            value: false
134          },
135
136          /**
137           * By default, the dropdown will constrain scrolling on the page
138           * to itself when opened.
139           * Set to true in order to prevent scroll from being constrained
140           * to the dropdown when it opens.
141           */
142          allowOutsideScroll: {
143            type: Boolean,
144            value: false
145          }
146        },
147
148        listeners: {
149          'neon-animation-finish': '_onNeonAnimationFinish'
150        },
151
152        observers: [
153          '_updateOverlayPosition(positionTarget, verticalAlign, horizontalAlign, verticalOffset, horizontalOffset)'
154        ],
155
156        /**
157         * The element that is contained by the dropdown, if any.
158         */
159        get containedElement() {
160          return Polymer.dom(this.$.content).getDistributedNodes()[0];
161        },
162
163        /**
164         * The element that should be focused when the dropdown opens.
165         * @deprecated
166         */
167        get _focusTarget() {
168          return this.focusTarget || this.containedElement;
169        },
170
171        detached: function() {
172          this.cancelAnimation();
173          Polymer.IronDropdownScrollManager.removeScrollLock(this);
174        },
175
176        /**
177         * Called when the value of `opened` changes.
178         * Overridden from `IronOverlayBehavior`
179         */
180        _openedChanged: function() {
181          if (this.opened && this.disabled) {
182            this.cancel();
183          } else {
184            this.cancelAnimation();
185            this.sizingTarget = this.containedElement || this.sizingTarget;
186            this._updateAnimationConfig();
187            if (this.opened && !this.allowOutsideScroll) {
188              Polymer.IronDropdownScrollManager.pushScrollLock(this);
189            } else {
190              Polymer.IronDropdownScrollManager.removeScrollLock(this);
191            }
192            Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this, arguments);
193          }
194        },
195
196        /**
197         * Overridden from `IronOverlayBehavior`.
198         */
199        _renderOpened: function() {
200          if (!this.noAnimations && this.animationConfig.open) {
201            this.$.contentWrapper.classList.add('animating');
202            this.playAnimation('open');
203          } else {
204            Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this, arguments);
205          }
206        },
207
208        /**
209         * Overridden from `IronOverlayBehavior`.
210         */
211        _renderClosed: function() {
212          if (!this.noAnimations && this.animationConfig.close) {
213            this.$.contentWrapper.classList.add('animating');
214            this.playAnimation('close');
215          } else {
216            Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this, arguments);
217          }
218        },
219
220        /**
221         * Called when animation finishes on the dropdown (when opening or
222         * closing). Responsible for "completing" the process of opening or
223         * closing the dropdown by positioning it or setting its display to
224         * none.
225         */
226        _onNeonAnimationFinish: function() {
227          this.$.contentWrapper.classList.remove('animating');
228          if (this.opened) {
229            this._finishRenderOpened();
230          } else {
231            this._finishRenderClosed();
232          }
233        },
234
235        /**
236         * Constructs the final animation config from different properties used
237         * to configure specific parts of the opening and closing animations.
238         */
239        _updateAnimationConfig: function() {
240          var animations = (this.openAnimationConfig || []).concat(this.closeAnimationConfig || []);
241          for (var i = 0; i < animations.length; i++) {
242            animations[i].node = this.containedElement;
243          }
244          this.animationConfig = {
245            open: this.openAnimationConfig,
246            close: this.closeAnimationConfig
247          };
248        },
249
250        /**
251         * Updates the overlay position based on configured horizontal
252         * and vertical alignment.
253         */
254        _updateOverlayPosition: function() {
255          if (this.isAttached) {
256            // This triggers iron-resize, and iron-overlay-behavior will call refit if needed.
257            this.notifyResize();
258          }
259        },
260
261        /**
262         * Apply focus to focusTarget or containedElement
263         */
264        _applyFocus: function () {
265          var focusTarget = this.focusTarget || this.containedElement;
266          if (focusTarget && this.opened && !this.noAutoFocus) {
267            focusTarget.focus();
268          } else {
269            Polymer.IronOverlayBehaviorImpl._applyFocus.apply(this, arguments);
270          }
271        }
272      });
273    })();
274  </script>
275</dom-module>
276