Home | History | Annotate | Download | only in paper-menu-button
      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