• 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-a11y-keys-behavior/iron-a11y-keys-behavior.html">
13<link rel="import" href="../iron-behaviors/iron-control-state.html">
14<link rel="import" href="../iron-dropdown/iron-dropdown.html">
15<link rel="import" href="../neon-animation/animations/fade-in-animation.html">
16<link rel="import" href="../neon-animation/animations/fade-out-animation.html">
17<link rel="import" href="../paper-styles/default-theme.html">
18<link rel="import" href="../paper-styles/shadow.html">
19<link rel="import" href="paper-menu-button-animations.html">
20
21<!--
22Material design: [Dropdown buttons](https://www.google.com/design/spec/components/buttons.html#buttons-dropdown-buttons)
23
24`paper-menu-button` allows one to compose a designated "trigger" element with
25another element that represents "content", to create a dropdown menu that
26displays the "content" when the "trigger" is clicked.
27
28The child element with the class `dropdown-trigger` will be used as the
29"trigger" element. The child element with the class `dropdown-content` will be
30used as the "content" element.
31
32The `paper-menu-button` is sensitive to its content's `iron-select` events. If
33the "content" element triggers an `iron-select` event, the `paper-menu-button`
34will close automatically.
35
36Example:
37
38    <paper-menu-button>
39      <paper-icon-button icon="menu" class="dropdown-trigger"></paper-icon-button>
40      <paper-menu class="dropdown-content">
41        <paper-item>Share</paper-item>
42        <paper-item>Settings</paper-item>
43        <paper-item>Help</paper-item>
44      </paper-menu>
45    </paper-menu-button>
46
47### Styling
48
49The following custom properties and mixins are also available for styling:
50
51Custom property | Description | Default
52----------------|-------------|----------
53`--paper-menu-button-dropdown-background` | Background color of the paper-menu-button dropdown | `--primary-background-color`
54`--paper-menu-button` | Mixin applied to the paper-menu-button | `{}`
55`--paper-menu-button-disabled` | Mixin applied to the paper-menu-button when disabled | `{}`
56`--paper-menu-button-dropdown` | Mixin applied to the paper-menu-button dropdown | `{}`
57`--paper-menu-button-content` | Mixin applied to the paper-menu-button content | `{}`
58
59@hero hero.svg
60@demo demo/index.html
61-->
62
63<dom-module id="paper-menu-button">
64  <template>
65    <style>
66      :host {
67        display: inline-block;
68        position: relative;
69        padding: 8px;
70        outline: none;
71
72        @apply(--paper-menu-button);
73      }
74
75      :host([disabled]) {
76        cursor: auto;
77        color: var(--disabled-text-color);
78
79        @apply(--paper-menu-button-disabled);
80      }
81
82      iron-dropdown {
83        @apply(--paper-menu-button-dropdown);
84      }
85
86      .dropdown-content {
87        @apply(--shadow-elevation-2dp);
88
89        position: relative;
90        border-radius: 2px;
91        background-color: var(--paper-menu-button-dropdown-background, --primary-background-color);
92
93        @apply(--paper-menu-button-content);
94      }
95
96      :host([vertical-align="top"]) .dropdown-content {
97        margin-bottom: 20px;
98        margin-top: -10px;
99        top: 10px;
100      }
101
102      :host([vertical-align="bottom"]) .dropdown-content {
103        bottom: 10px;
104        margin-bottom: -10px;
105        margin-top: 20px;
106      }
107    </style>
108
109    <div id="trigger" on-tap="toggle">
110      <content select=".dropdown-trigger"></content>
111    </div>
112
113    <iron-dropdown
114      id="dropdown"
115      opened="{{opened}}"
116      horizontal-align="[[horizontalAlign]]"
117      vertical-align="[[verticalAlign]]"
118      horizontal-offset="[[horizontalOffset]]"
119      vertical-offset="[[verticalOffset]]"
120      open-animation-config="[[openAnimationConfig]]"
121      close-animation-config="[[closeAnimationConfig]]"
122      no-animations="[[noAnimations]]"
123      focus-target="[[_dropdownContent]]"
124      restore-focus-on-close
125      on-iron-overlay-canceled="__onIronOverlayCanceled">
126      <div class="dropdown-content">
127        <content id="content" select=".dropdown-content"></content>
128      </div>
129    </iron-dropdown>
130  </template>
131
132  <script>
133    (function() {
134      'use strict';
135
136      var PaperMenuButton = Polymer({
137        is: 'paper-menu-button',
138
139        /**
140         * Fired when the dropdown opens.
141         *
142         * @event paper-dropdown-open
143         */
144
145        /**
146         * Fired when the dropdown closes.
147         *
148         * @event paper-dropdown-close
149         */
150
151        behaviors: [
152          Polymer.IronA11yKeysBehavior,
153          Polymer.IronControlState
154        ],
155
156        properties: {
157          /**
158           * True if the content is currently displayed.
159           */
160          opened: {
161            type: Boolean,
162            value: false,
163            notify: true,
164            observer: '_openedChanged'
165          },
166
167          /**
168           * The orientation against which to align the menu dropdown
169           * horizontally relative to the dropdown trigger.
170           */
171          horizontalAlign: {
172            type: String,
173            value: 'left',
174            reflectToAttribute: true
175          },
176
177          /**
178           * The orientation against which to align the menu dropdown
179           * vertically relative to the dropdown trigger.
180           */
181          verticalAlign: {
182            type: String,
183            value: 'top',
184            reflectToAttribute: true
185          },
186
187          /**
188           * A pixel value that will be added to the position calculated for the
189           * given `horizontalAlign`. Use a negative value to offset to the
190           * left, or a positive value to offset to the right.
191           */
192          horizontalOffset: {
193            type: Number,
194            value: 0,
195            notify: true
196          },
197
198          /**
199           * A pixel value that will be added to the position calculated for the
200           * given `verticalAlign`. Use a negative value to offset towards the
201           * top, or a positive value to offset towards the bottom.
202           */
203          verticalOffset: {
204            type: Number,
205            value: 0,
206            notify: true
207          },
208
209          /**
210           * Set to true to disable animations when opening and closing the
211           * dropdown.
212           */
213          noAnimations: {
214            type: Boolean,
215            value: false
216          },
217
218          /**
219           * Set to true to disable automatically closing the dropdown after
220           * a selection has been made.
221           */
222          ignoreSelect: {
223            type: Boolean,
224            value: false
225          },
226
227          /**
228           * An animation config. If provided, this will be used to animate the
229           * opening of the dropdown.
230           */
231          openAnimationConfig: {
232            type: Object,
233            value: function() {
234              return [{
235                name: 'fade-in-animation',
236                timing: {
237                  delay: 100,
238                  duration: 200
239                }
240              }, {
241                name: 'paper-menu-grow-width-animation',
242                timing: {
243                  delay: 100,
244                  duration: 150,
245                  easing: PaperMenuButton.ANIMATION_CUBIC_BEZIER
246                }
247              }, {
248                name: 'paper-menu-grow-height-animation',
249                timing: {
250                  delay: 100,
251                  duration: 275,
252                  easing: PaperMenuButton.ANIMATION_CUBIC_BEZIER
253                }
254              }];
255            }
256          },
257
258          /**
259           * An animation config. If provided, this will be used to animate the
260           * closing of the dropdown.
261           */
262          closeAnimationConfig: {
263            type: Object,
264            value: function() {
265              return [{
266                name: 'fade-out-animation',
267                timing: {
268                  duration: 150
269                }
270              }, {
271                name: 'paper-menu-shrink-width-animation',
272                timing: {
273                  delay: 100,
274                  duration: 50,
275                  easing: PaperMenuButton.ANIMATION_CUBIC_BEZIER
276                }
277              }, {
278                name: 'paper-menu-shrink-height-animation',
279                timing: {
280                  duration: 200,
281                  easing: 'ease-in'
282                }
283              }];
284            }
285          },
286
287          /**
288           * This is the element intended to be bound as the focus target
289           * for the `iron-dropdown` contained by `paper-menu-button`.
290           */
291          _dropdownContent: {
292            type: Object
293          }
294        },
295
296        hostAttributes: {
297          role: 'group',
298          'aria-haspopup': 'true'
299        },
300
301        listeners: {
302          'iron-select': '_onIronSelect'
303        },
304
305        /**
306         * The content element that is contained by the menu button, if any.
307         */
308        get contentElement() {
309          return Polymer.dom(this.$.content).getDistributedNodes()[0];
310        },
311
312        /**
313         * Toggles the drowpdown content between opened and closed.
314         */
315        toggle: function() {
316          if (this.opened) {
317            this.close();
318          } else {
319            this.open();
320          }
321        },
322
323        /**
324         * Make the dropdown content appear as an overlay positioned relative
325         * to the dropdown trigger.
326         */
327        open: function() {
328          if (this.disabled) {
329            return;
330          }
331
332          this.$.dropdown.open();
333        },
334
335        /**
336         * Hide the dropdown content.
337         */
338        close: function() {
339          this.$.dropdown.close();
340        },
341
342        /**
343         * When an `iron-select` event is received, the dropdown should
344         * automatically close on the assumption that a value has been chosen.
345         *
346         * @param {CustomEvent} event A CustomEvent instance with type
347         * set to `"iron-select"`.
348         */
349        _onIronSelect: function(event) {
350          if (!this.ignoreSelect) {
351            this.close();
352          }
353        },
354
355        /**
356         * When the dropdown opens, the `paper-menu-button` fires `paper-open`.
357         * When the dropdown closes, the `paper-menu-button` fires `paper-close`.
358         *
359         * @param {boolean} opened True if the dropdown is opened, otherwise false.
360         * @param {boolean} oldOpened The previous value of `opened`.
361         */
362        _openedChanged: function(opened, oldOpened) {
363          if (opened) {
364            // TODO(cdata): Update this when we can measure changes in distributed
365            // children in an idiomatic way.
366            // We poke this property in case the element has changed. This will
367            // cause the focus target for the `iron-dropdown` to be updated as
368            // necessary:
369            this._dropdownContent = this.contentElement;
370            this.fire('paper-dropdown-open');
371          } else if (oldOpened != null) {
372            this.fire('paper-dropdown-close');
373          }
374        },
375
376        /**
377         * If the dropdown is open when disabled becomes true, close the
378         * dropdown.
379         *
380         * @param {boolean} disabled True if disabled, otherwise false.
381         */
382        _disabledChanged: function(disabled) {
383          Polymer.IronControlState._disabledChanged.apply(this, arguments);
384          if (disabled && this.opened) {
385            this.close();
386          }
387        },
388
389        __onIronOverlayCanceled: function(event) {
390          var uiEvent = event.detail;
391          var target = Polymer.dom(uiEvent).rootTarget;
392          var trigger = this.$.trigger;
393          var path = Polymer.dom(uiEvent).path;
394
395          if (path.indexOf(trigger) > -1) {
396            event.preventDefault();
397          }
398        }
399      });
400
401      PaperMenuButton.ANIMATION_CUBIC_BEZIER = 'cubic-bezier(.3,.95,.5,1)';
402      PaperMenuButton.MAX_ANIMATION_TIME_MS = 400;
403
404      Polymer.PaperMenuButton = PaperMenuButton;
405    })();
406  </script>
407</dom-module>
408