Home | History | Annotate | Download | only in iron-dropdown
      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-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
     22 hidden content (`.dropdown-content`) that is revealed due to some change in
     23 state that should cause it to do so.
     24 
     25 Note that this is a low-level element intended to be used as part of other
     26 composite elements that cause dropdowns to be revealed.
     27 
     28 Examples of elements that might be implemented using an `iron-dropdown`
     29 include comboboxes, menubuttons, selects. The list goes on.
     30 
     31 The `<iron-dropdown>` element exposes attributes that allow the position
     32 of the `.dropdown-content` relative to the `.dropdown-trigger` to be
     33 configured.
     34 
     35     <iron-dropdown horizontal-align="right" vertical-align="top">
     36       <div class="dropdown-content">Hello!</div>
     37     </iron-dropdown>
     38 
     39 In the above example, the `<div>` with class `.dropdown-content` will be
     40 hidden until the dropdown element has `opened` set to true, or when the `open`
     41 method 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