<!– @license Copyright © 2016 The Polymer Project Authors. All rights reserved. This code may only be used under the BSD style license found at polymer.github.io/LICENSE.txt The complete set of authors may be found at polymer.github.io/AUTHORS.txt The complete set of contributors may be found at polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as part of the polymer project is also subject to an additional IP rights grant found at polymer.github.io/PATENTS.txt –>

<link rel=“import” href=“../polymer/polymer.html”> <link rel=“import” href=“../iron-a11y-keys-behavior/iron-a11y-keys-behavior.html”> <link rel=“import” href=“../iron-behaviors/iron-button-state.html”> <link rel=“import” href=“../iron-behaviors/iron-control-state.html”> <link rel=“import” href=“../iron-form-element-behavior/iron-form-element-behavior.html”> <link rel=“import” href=“../iron-validatable-behavior/iron-validatable-behavior.html”> <link rel=“import” href=“../paper-menu-button/paper-menu-button.html”> <link rel=“import” href=“../paper-behaviors/paper-ripple-behavior.html”> <link rel=“import” href=“../paper-styles/default-theme.html”>

<link rel=“import” href=“paper-dropdown-menu-icons.html”> <link rel=“import” href=“paper-dropdown-menu-shared-styles.html”>

<!– Material design: [Dropdown menus](www.google.com/design/spec/components/buttons.html#buttons-dropdown-buttons)

This is a faster, lighter version of `paper-dropdown-menu`, that does not use a `<paper-input>` internally. Use this element if you're concerned about the performance of this element, i.e., if you plan on using many dropdowns on the same page. Note that this element has a slightly different styling API than `paper-dropdown-menu`.

`paper-dropdown-menu-light` is similar to a native browser select element. `paper-dropdown-menu-light` works with selectable content. The currently selected item is displayed in the control. If no item is selected, the `label` is displayed instead.

Example:

<paper-dropdown-menu-light label="Your favourite pastry">
  <paper-listbox class="dropdown-content">
    <paper-item>Croissant</paper-item>
    <paper-item>Donut</paper-item>
    <paper-item>Financier</paper-item>
    <paper-item>Madeleine</paper-item>
  </paper-listbox>
</paper-dropdown-menu-light>

This example renders a dropdown menu with 4 options.

The child element with the class `dropdown-content` is used as the dropdown menu. This can be a [`paper-listbox`](paper-listbox), or any other or element that acts like an [`iron-selector`](iron-selector).

Specifically, the menu child must fire an [`iron-select`](iron-selector#event-iron-select) event when one of its children is selected, and an [`iron-deselect`](iron-selector#event-iron-deselect) event when a child is deselected. The selected or deselected item must be passed as the event's `detail.item` property.

Applications can listen for the `iron-select` and `iron-deselect` events to react when options are selected and deselected.

### Styling

The following custom properties and mixins are also available for styling:

Custom property | Description | Default —————-|————-|———- `–paper-dropdown-menu` | A mixin that is applied to the element host | `{}` `–paper-dropdown-menu-disabled` | A mixin that is applied to the element host when disabled | `{}` `–paper-dropdown-menu-ripple` | A mixin that is applied to the internal ripple | `{}` `–paper-dropdown-menu-button` | A mixin that is applied to the internal menu button | `{}` `–paper-dropdown-menu-icon` | A mixin that is applied to the internal icon | `{}` `–paper-dropdown-menu-disabled-opacity` | The opacity of the dropdown when disabled | `0.33` `–paper-dropdown-menu-color` | The color of the input/label/underline when the dropdown is unfocused | `–primary-text-color` `–paper-dropdown-menu-focus-color` | The color of the label/underline when the dropdown is focused | `–primary-color` `–paper-dropdown-error-color` | The color of the label/underline when the dropdown is invalid | `–error-color` `–paper-dropdown-menu-label` | Mixin applied to the label | `{}` `–paper-dropdown-menu-input` | Mixin appled to the input | `{}`

Note that in this element, the underline is just the bottom border of the “input”. To style it:

<style is=custom-style>
  paper-dropdown-menu-light.custom {
    --paper-dropdown-menu-input: {
      border-bottom: 2px dashed lavender;
    };
</style>

@group Paper Elements @element paper-dropdown-menu-light @hero hero.svg @demo demo/index.html –>

<dom-module id=“paper-dropdown-menu-light”>

<template>
  <style include="paper-dropdown-menu-shared-styles">
    :host(:focus) {
      outline: none;
    }

    :host {
      width: 200px;  /* Default size of an <input> */
    }

    /**
     * All of these styles below are for styling the fake-input display
     */
    .dropdown-trigger {
      box-sizing: border-box;
      position: relative;
      width: 100%;
      padding: 16px 0 8px 0;
    }

    :host([disabled]) .dropdown-trigger {
      pointer-events: none;
      opacity: var(--paper-dropdown-menu-disabled-opacity, 0.33);
    }

    :host([no-label-float]) .dropdown-trigger {
      padding-top: 8px;   /* If there's no label, we need less space up top. */
    }

    #input {
      @apply(--paper-font-subhead);
      @apply(--paper-font-common-nowrap);
      line-height: 1.5;
      border-bottom: 1px solid var(--paper-dropdown-menu-color, --secondary-text-color);
      color: var(--paper-dropdown-menu-color, --primary-text-color);
      width: 100%;
      box-sizing: border-box;
      padding: 12px 20px 0 0;   /* Right padding so that text doesn't overlap the icon */
      outline: none;
      @apply(--paper-dropdown-menu-input);
    }

    :host-context([dir="rtl"]) #input {
      padding-right: 0px;
      padding-left: 20px;
    }

    :host([disabled]) #input {
      border-bottom: 1px dashed var(--paper-dropdown-menu-color, --secondary-text-color);
    }

    :host([invalid]) #input {
      border-bottom: 2px solid var(--paper-dropdown-error-color, --error-color);
    }

    :host([no-label-float]) #input {
      padding-top: 0;   /* If there's no label, we need less space up top. */
    }

    label {
      @apply(--paper-font-subhead);
      @apply(--paper-font-common-nowrap);
      display: block;
      position: absolute;
      bottom: 0;
      left: 0;
      right: 0;
      /**
       * The container has a 16px top padding, and there's 12px of padding
       * between the input and the label (from the input's padding-top)
       */
      top: 28px;
      box-sizing: border-box;
      width: 100%;
      padding-right: 20px;    /* Right padding so that text doesn't overlap the icon */
      text-align: left;
      transition-duration: .2s;
      transition-timing-function: cubic-bezier(.4,0,.2,1);
      color: var(--paper-dropdown-menu-color, --secondary-text-color);
      @apply(--paper-dropdown-menu-label);
    }

    :host-context([dir="rtl"]) label {
      padding-right: 0px;
      padding-left: 20px;
    }

    :host([no-label-float]) label {
      top: 8px;
      /* Since the label doesn't need to float, remove the animation duration
      which slows down visibility changes (i.e. when a selection is made) */
      transition-duration: 0s;
    }

    label.label-is-floating {
      font-size: 12px;
      top: 8px;
    }

    label.label-is-hidden {
      visibility: hidden;
    }

    :host([focused]) label.label-is-floating {
      color: var(--paper-dropdown-menu-focus-color, --primary-color);
    }

    :host([invalid]) label.label-is-floating {
      color: var(--paper-dropdown-error-color, --error-color);
    }

    /**
     * Sets up the focused underline. It's initially hidden, and becomes
     * visible when it's focused.
     */
    label:after {
      background-color: var(--paper-dropdown-menu-focus-color, --primary-color);
      bottom: 7px;    /* The container has an 8px bottom padding */
      content: '';
      height: 2px;
      left: 45%;
      position: absolute;
      transition-duration: .2s;
      transition-timing-function: cubic-bezier(.4,0,.2,1);
      visibility: hidden;
      width: 8px;
      z-index: 10;
    }

    :host([invalid]) label:after {
      background-color: var(--paper-dropdown-error-color, --error-color);
    }

    :host([no-label-float]) label:after {
      bottom: 7px;    /* The container has a 8px bottom padding */
    }

    :host([focused]:not([disabled])) label:after {
      left: 0;
      visibility: visible;
      width: 100%;
    }

    iron-icon {
      position: absolute;
      right: 0px;
      bottom: 8px;    /* The container has an 8px bottom padding */
      @apply(--paper-font-subhead);
      color: var(--disabled-text-color);
      @apply(--paper-dropdown-menu-icon);
    }

    :host-context([dir="rtl"]) iron-icon {
      left: 0;
      right: auto;
    }

    :host([no-label-float]) iron-icon {
      margin-top: 0px;
    }

    .error {
      display: inline-block;
      visibility: hidden;
      color: var(--paper-dropdown-error-color, --error-color);
      @apply(--paper-font-caption);
      position: absolute;
      left:0;
      right:0;
      bottom: -12px;
    }

    :host([invalid]) .error {
      visibility: visible;
    }
  </style>

  <!-- this div fulfills an a11y requirement for combobox, do not remove -->
  <span role="button"></span>
  <paper-menu-button
    id="menuButton"
    vertical-align="[[verticalAlign]]"
    horizontal-align="[[horizontalAlign]]"
    vertical-offset="[[_computeMenuVerticalOffset(noLabelFloat)]]"
    disabled="[[disabled]]"
    no-animations="[[noAnimations]]"
    on-iron-select="_onIronSelect"
    on-iron-deselect="_onIronDeselect"
    opened="{{opened}}"
    close-on-activate
    allow-outside-scroll="[[allowOutsideScroll]]">
    <div class="dropdown-trigger">
      <label class$="[[_computeLabelClass(noLabelFloat,alwaysFloatLabel,hasContent)]]">
        [[label]]
      </label>
      <div id="input" tabindex="-1">&nbsp;</div>
      <iron-icon icon="paper-dropdown-menu:arrow-drop-down"></iron-icon>
      <span class="error">[[errorMessage]]</span>
    </div>
    <content id="content" select=".dropdown-content"></content>
  </paper-menu-button>
</template>

<script>
  (function() {
    'use strict';

    Polymer({
      is: 'paper-dropdown-menu-light',

      behaviors: [
        Polymer.IronButtonState,
        Polymer.IronControlState,
        Polymer.PaperRippleBehavior,
        Polymer.IronFormElementBehavior,
        Polymer.IronValidatableBehavior
      ],

      properties: {
        /**
         * The derived "label" of the currently selected item. This value
         * is the `label` property on the selected item if set, or else the
         * trimmed text content of the selected item.
         */
        selectedItemLabel: {
          type: String,
          notify: true,
          readOnly: true
        },

        /**
         * The last selected item. An item is selected if the dropdown menu has
         * a child with class `dropdown-content`, and that child triggers an
         * `iron-select` event with the selected `item` in the `detail`.
         *
         * @type {?Object}
         */
        selectedItem: {
          type: Object,
          notify: true,
          readOnly: true
        },

        /**
         * The value for this element that will be used when submitting in
         * a form. It is read only, and will always have the same value
         * as `selectedItemLabel`.
         */
        value: {
          type: String,
          notify: true,
          readOnly: true,
          observer: '_valueChanged',
        },

        /**
         * The label for the dropdown.
         */
        label: {
          type: String
        },

        /**
         * The placeholder for the dropdown.
         */
        placeholder: {
          type: String
        },

        /**
         * True if the dropdown is open. Otherwise, false.
         */
        opened: {
          type: Boolean,
          notify: true,
          value: false,
          observer: '_openedChanged'
        },

        /**
         * By default, the dropdown will constrain scrolling on the page
         * to itself when opened.
         * Set to true in order to prevent scroll from being constrained
         * to the dropdown when it opens.
         */
        allowOutsideScroll: {
          type: Boolean,
          value: false
        },

        /**
         * Set to true to disable the floating label. Bind this to the
         * `<paper-input-container>`'s `noLabelFloat` property.
         */
        noLabelFloat: {
            type: Boolean,
            value: false,
            reflectToAttribute: true
        },

        /**
         * Set to true to always float the label. Bind this to the
         * `<paper-input-container>`'s `alwaysFloatLabel` property.
         */
        alwaysFloatLabel: {
          type: Boolean,
          value: false
        },

        /**
         * Set to true to disable animations when opening and closing the
         * dropdown.
         */
        noAnimations: {
          type: Boolean,
          value: false
        },

        /**
         * The orientation against which to align the menu dropdown
         * horizontally relative to the dropdown trigger.
         */
        horizontalAlign: {
          type: String,
          value: 'right'
        },

        /**
         * The orientation against which to align the menu dropdown
         * vertically relative to the dropdown trigger.
         */
        verticalAlign: {
          type: String,
          value: 'top'
        },

        hasContent: {
          type: Boolean,
          readOnly: true
        }
      },

      listeners: {
        'tap': '_onTap'
      },

      keyBindings: {
        'up down': 'open',
        'esc': 'close'
      },

      hostAttributes: {
        tabindex: 0,
        role: 'combobox',
        'aria-autocomplete': 'none',
        'aria-haspopup': 'true'
      },

      observers: [
        '_selectedItemChanged(selectedItem)'
      ],

      attached: function() {
        // NOTE(cdata): Due to timing, a preselected value in a `IronSelectable`
        // child will cause an `iron-select` event to fire while the element is
        // still in a `DocumentFragment`. This has the effect of causing
        // handlers not to fire. So, we double check this value on attached:
        var contentElement = this.contentElement;
        if (contentElement && contentElement.selectedItem) {
          this._setSelectedItem(contentElement.selectedItem);
        }
      },

      /**
       * The content element that is contained by the dropdown menu, if any.
       */
      get contentElement() {
        return Polymer.dom(this.$.content).getDistributedNodes()[0];
      },

      /**
       * Show the dropdown content.
       */
      open: function() {
        this.$.menuButton.open();
      },

      /**
       * Hide the dropdown content.
       */
      close: function() {
        this.$.menuButton.close();
      },

      /**
       * A handler that is called when `iron-select` is fired.
       *
       * @param {CustomEvent} event An `iron-select` event.
       */
      _onIronSelect: function(event) {
        this._setSelectedItem(event.detail.item);
      },

      /**
       * A handler that is called when `iron-deselect` is fired.
       *
       * @param {CustomEvent} event An `iron-deselect` event.
       */
      _onIronDeselect: function(event) {
        this._setSelectedItem(null);
      },

      /**
       * A handler that is called when the dropdown is tapped.
       *
       * @param {CustomEvent} event A tap event.
       */
      _onTap: function(event) {
        if (Polymer.Gestures.findOriginalTarget(event) === this) {
          this.open();
        }
      },

      /**
       * Compute the label for the dropdown given a selected item.
       *
       * @param {Element} selectedItem A selected Element item, with an
       * optional `label` property.
       */
      _selectedItemChanged: function(selectedItem) {
        var value = '';
        if (!selectedItem) {
          value = '';
        } else {
          value = selectedItem.label || selectedItem.getAttribute('label') || selectedItem.textContent.trim();
        }

        this._setValue(value);
        this._setSelectedItemLabel(value);
      },

      /**
       * Compute the vertical offset of the menu based on the value of
       * `noLabelFloat`.
       *
       * @param {boolean} noLabelFloat True if the label should not float
       * above the input, otherwise false.
       */
      _computeMenuVerticalOffset: function(noLabelFloat) {
        // NOTE(cdata): These numbers are somewhat magical because they are
        // derived from the metrics of elements internal to `paper-input`'s
        // template. The metrics will change depending on whether or not the
        // input has a floating label.
        return noLabelFloat ? -4 : 8;
      },

      /**
       * Returns false if the element is required and does not have a selection,
       * and true otherwise.
       * @param {*=} _value Ignored.
       * @return {boolean} true if `required` is false, or if `required` is true
       * and the element has a valid selection.
       */
      _getValidity: function(_value) {
        return this.disabled || !this.required || (this.required && !!this.value);
      },

      _openedChanged: function() {
        var openState = this.opened ? 'true' : 'false';
        var e = this.contentElement;
        if (e) {
          e.setAttribute('aria-expanded', openState);
        }
      },

      _computeLabelClass: function(noLabelFloat, alwaysFloatLabel, hasContent) {
        var cls = '';
        if (noLabelFloat === true) {
          return hasContent ? 'label-is-hidden' : '';
        }

        if (hasContent || alwaysFloatLabel === true) {
          cls += ' label-is-floating';
        }
        return cls;
      },

      _valueChanged: function() {
        // Only update if it's actually different.
        if (this.$.input && this.$.input.textContent !== this.value) {
          this.$.input.textContent = this.value;
        }
        this._setHasContent(!!this.value);
      },
    });
  })();
</script>

</dom-module>