416 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			HTML
		
	
	
	
			
		
		
	
	
			416 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			HTML
		
	
	
	
<!--
 | 
						|
@license
 | 
						|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
 | 
						|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
 | 
						|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
 | 
						|
The complete set of contributors may be found at http://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 http://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-icon/iron-icon.html">
 | 
						|
<link rel="import" href="../iron-validatable-behavior/iron-validatable-behavior.html">
 | 
						|
<link rel="import" href="../paper-input/paper-input.html">
 | 
						|
<link rel="import" href="../paper-menu-button/paper-menu-button.html">
 | 
						|
<link rel="import" href="../paper-ripple/paper-ripple.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](https://www.google.com/design/spec/components/buttons.html#buttons-dropdown-buttons)
 | 
						|
 | 
						|
`paper-dropdown-menu` is similar to a native browser select element.
 | 
						|
`paper-dropdown-menu` 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 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>
 | 
						|
 | 
						|
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-input` | A mixin that is applied to the internal paper input | `{}`
 | 
						|
`--paper-dropdown-menu-icon` | A mixin that is applied to the internal icon | `{}`
 | 
						|
 | 
						|
You can also use any of the `paper-input-container` and `paper-menu-button`
 | 
						|
style mixins and custom properties to style the internal input and menu button
 | 
						|
respectively.
 | 
						|
 | 
						|
@group Paper Elements
 | 
						|
@element paper-dropdown-menu
 | 
						|
@hero hero.svg
 | 
						|
@demo demo/index.html
 | 
						|
-->
 | 
						|
 | 
						|
<dom-module id="paper-dropdown-menu">
 | 
						|
  <template>
 | 
						|
    <style include="paper-dropdown-menu-shared-styles"></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]]"
 | 
						|
      dynamic-align="[[dynamicAlign]]"
 | 
						|
      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]]"
 | 
						|
      restore-focus-on-close="[[restoreFocusOnClose]]">
 | 
						|
      <div class="dropdown-trigger">
 | 
						|
        <paper-ripple></paper-ripple>
 | 
						|
        <!-- paper-input has type="text" for a11y, do not remove -->
 | 
						|
        <paper-input
 | 
						|
          type="text"
 | 
						|
          invalid="[[invalid]]"
 | 
						|
          readonly
 | 
						|
          disabled="[[disabled]]"
 | 
						|
          value="[[selectedItemLabel]]"
 | 
						|
          placeholder="[[placeholder]]"
 | 
						|
          error-message="[[errorMessage]]"
 | 
						|
          always-float-label="[[alwaysFloatLabel]]"
 | 
						|
          no-label-float="[[noLabelFloat]]"
 | 
						|
          label="[[label]]">
 | 
						|
          <iron-icon icon="paper-dropdown-menu:arrow-drop-down" suffix></iron-icon>
 | 
						|
        </paper-input>
 | 
						|
      </div>
 | 
						|
      <content id="content" select=".dropdown-content"></content>
 | 
						|
    </paper-menu-button>
 | 
						|
  </template>
 | 
						|
 | 
						|
  <script>
 | 
						|
    (function() {
 | 
						|
      'use strict';
 | 
						|
 | 
						|
      Polymer({
 | 
						|
        is: 'paper-dropdown-menu',
 | 
						|
 | 
						|
        behaviors: [
 | 
						|
          Polymer.IronButtonState,
 | 
						|
          Polymer.IronControlState,
 | 
						|
          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
 | 
						|
          },
 | 
						|
 | 
						|
          /**
 | 
						|
           * The label for the dropdown.
 | 
						|
           */
 | 
						|
          label: {
 | 
						|
            type: String
 | 
						|
          },
 | 
						|
 | 
						|
          /**
 | 
						|
           * The placeholder for the dropdown.
 | 
						|
           */
 | 
						|
          placeholder: {
 | 
						|
            type: String
 | 
						|
          },
 | 
						|
 | 
						|
          /**
 | 
						|
           * The error message to display when invalid.
 | 
						|
           */
 | 
						|
          errorMessage: {
 | 
						|
              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'
 | 
						|
          },
 | 
						|
 | 
						|
          /**
 | 
						|
           * If true, the `horizontalAlign` and `verticalAlign` properties will
 | 
						|
           * be considered preferences instead of strict requirements when
 | 
						|
           * positioning the dropdown and may be changed if doing so reduces
 | 
						|
           * the area of the dropdown falling outside of `fitInto`.
 | 
						|
           */
 | 
						|
          dynamicAlign: {
 | 
						|
            type: Boolean
 | 
						|
          },
 | 
						|
            
 | 
						|
          /**
 | 
						|
           * Whether focus should be restored to the dropdown when the menu closes.
 | 
						|
           */
 | 
						|
          restoreFocusOnClose: {
 | 
						|
            type: Boolean,
 | 
						|
            value: true
 | 
						|
          },
 | 
						|
        },
 | 
						|
 | 
						|
        listeners: {
 | 
						|
          'tap': '_onTap'
 | 
						|
        },
 | 
						|
 | 
						|
        keyBindings: {
 | 
						|
          'up down': 'open',
 | 
						|
          'esc': 'close'
 | 
						|
        },
 | 
						|
 | 
						|
        hostAttributes: {
 | 
						|
          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);
 | 
						|
          }
 | 
						|
        }
 | 
						|
      });
 | 
						|
    })();
 | 
						|
  </script>
 | 
						|
</dom-module>
 |