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-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 <!-- 22 Material 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 25 another element that represents "content", to create a dropdown menu that 26 displays the "content" when the "trigger" is clicked. 27 28 The 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 30 used as the "content" element. 31 32 The `paper-menu-button` is sensitive to its content's `iron-select` events. If 33 the "content" element triggers an `iron-select` event, the `paper-menu-button` 34 will close automatically. 35 36 Example: 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 49 The following custom properties and mixins are also available for styling: 50 51 Custom 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