354 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			HTML
		
	
	
	
			
		
		
	
	
			354 lines
		
	
	
		
			12 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-resizable-behavior/iron-resizable-behavior.html">
 | 
						|
<link rel="import" href="../iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
 | 
						|
<link rel="import" href="../iron-behaviors/iron-control-state.html">
 | 
						|
<link rel="import" href="../iron-overlay-behavior/iron-overlay-behavior.html">
 | 
						|
<link rel="import" href="../neon-animation/neon-animation-runner-behavior.html">
 | 
						|
<link rel="import" href="../neon-animation/animations/opaque-animation.html">
 | 
						|
<link rel="import" href="iron-dropdown-scroll-manager.html">
 | 
						|
 | 
						|
<!--
 | 
						|
`<iron-dropdown>` is a generalized element that is useful when you have
 | 
						|
hidden content (`.dropdown-content`) that is revealed due to some change in
 | 
						|
state that should cause it to do so.
 | 
						|
 | 
						|
Note that this is a low-level element intended to be used as part of other
 | 
						|
composite elements that cause dropdowns to be revealed.
 | 
						|
 | 
						|
Examples of elements that might be implemented using an `iron-dropdown`
 | 
						|
include comboboxes, menubuttons, selects. The list goes on.
 | 
						|
 | 
						|
The `<iron-dropdown>` element exposes attributes that allow the position
 | 
						|
of the `.dropdown-content` relative to the `.dropdown-trigger` to be
 | 
						|
configured.
 | 
						|
 | 
						|
    <iron-dropdown horizontal-align="right" vertical-align="top">
 | 
						|
      <div class="dropdown-content">Hello!</div>
 | 
						|
    </iron-dropdown>
 | 
						|
 | 
						|
In the above example, the `<div>` with class `.dropdown-content` will be
 | 
						|
hidden until the dropdown element has `opened` set to true, or when the `open`
 | 
						|
method is called on the element.
 | 
						|
 | 
						|
@demo demo/index.html
 | 
						|
-->
 | 
						|
 | 
						|
<dom-module id="iron-dropdown">
 | 
						|
  <template>
 | 
						|
    <style>
 | 
						|
      :host {
 | 
						|
        position: fixed;
 | 
						|
      }
 | 
						|
 | 
						|
      #contentWrapper ::content > * {
 | 
						|
        overflow: auto;
 | 
						|
      }
 | 
						|
 | 
						|
      #contentWrapper.animating ::content > * {
 | 
						|
        overflow: hidden;
 | 
						|
      }
 | 
						|
    </style>
 | 
						|
 | 
						|
    <div id="contentWrapper">
 | 
						|
      <content id="content" select=".dropdown-content"></content>
 | 
						|
    </div>
 | 
						|
  </template>
 | 
						|
 | 
						|
  <script>
 | 
						|
    (function() {
 | 
						|
      'use strict';
 | 
						|
 | 
						|
      Polymer({
 | 
						|
        is: 'iron-dropdown',
 | 
						|
 | 
						|
        behaviors: [
 | 
						|
          Polymer.IronControlState,
 | 
						|
          Polymer.IronA11yKeysBehavior,
 | 
						|
          Polymer.IronOverlayBehavior,
 | 
						|
          Polymer.NeonAnimationRunnerBehavior
 | 
						|
        ],
 | 
						|
 | 
						|
        properties: {
 | 
						|
          /**
 | 
						|
           * The orientation against which to align the dropdown content
 | 
						|
           * horizontally relative to the dropdown trigger.
 | 
						|
           * Overridden from `Polymer.IronFitBehavior`.
 | 
						|
           */
 | 
						|
          horizontalAlign: {
 | 
						|
            type: String,
 | 
						|
            value: 'left',
 | 
						|
            reflectToAttribute: true
 | 
						|
          },
 | 
						|
 | 
						|
          /**
 | 
						|
           * The orientation against which to align the dropdown content
 | 
						|
           * vertically relative to the dropdown trigger.
 | 
						|
           * Overridden from `Polymer.IronFitBehavior`.
 | 
						|
           */
 | 
						|
          verticalAlign: {
 | 
						|
            type: String,
 | 
						|
            value: 'top',
 | 
						|
            reflectToAttribute: true
 | 
						|
          },
 | 
						|
 | 
						|
          /**
 | 
						|
           * An animation config. If provided, this will be used to animate the
 | 
						|
           * opening of the dropdown. Pass an Array for multiple animations.
 | 
						|
           * See `neon-animation` documentation for more animation configuration
 | 
						|
           * details.
 | 
						|
           */
 | 
						|
          openAnimationConfig: {
 | 
						|
            type: Object
 | 
						|
          },
 | 
						|
 | 
						|
          /**
 | 
						|
           * An animation config. If provided, this will be used to animate the
 | 
						|
           * closing of the dropdown. Pass an Array for multiple animations.
 | 
						|
           * See `neon-animation` documentation for more animation configuration
 | 
						|
           * details.
 | 
						|
           */
 | 
						|
          closeAnimationConfig: {
 | 
						|
            type: Object
 | 
						|
          },
 | 
						|
 | 
						|
          /**
 | 
						|
           * If provided, this will be the element that will be focused when
 | 
						|
           * the dropdown opens.
 | 
						|
           */
 | 
						|
          focusTarget: {
 | 
						|
            type: Object
 | 
						|
          },
 | 
						|
 | 
						|
          /**
 | 
						|
           * Set to true to disable animations when opening and closing the
 | 
						|
           * dropdown.
 | 
						|
           */
 | 
						|
          noAnimations: {
 | 
						|
            type: Boolean,
 | 
						|
            value: false
 | 
						|
          },
 | 
						|
 | 
						|
          /**
 | 
						|
           * 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
 | 
						|
          },
 | 
						|
 | 
						|
          /**
 | 
						|
           * Callback for scroll events.
 | 
						|
           * @type {Function}
 | 
						|
           * @private
 | 
						|
           */
 | 
						|
          _boundOnCaptureScroll: {
 | 
						|
            type: Function,
 | 
						|
            value: function() {
 | 
						|
              return this._onCaptureScroll.bind(this);
 | 
						|
            }
 | 
						|
          }
 | 
						|
        },
 | 
						|
 | 
						|
        listeners: {
 | 
						|
          'neon-animation-finish': '_onNeonAnimationFinish'
 | 
						|
        },
 | 
						|
 | 
						|
        observers: [
 | 
						|
          '_updateOverlayPosition(positionTarget, verticalAlign, horizontalAlign, verticalOffset, horizontalOffset)'
 | 
						|
        ],
 | 
						|
 | 
						|
        /**
 | 
						|
         * The element that is contained by the dropdown, if any.
 | 
						|
         */
 | 
						|
        get containedElement() {
 | 
						|
          return Polymer.dom(this.$.content).getDistributedNodes()[0];
 | 
						|
        },
 | 
						|
 | 
						|
        /**
 | 
						|
         * The element that should be focused when the dropdown opens.
 | 
						|
         * @deprecated
 | 
						|
         */
 | 
						|
        get _focusTarget() {
 | 
						|
          return this.focusTarget || this.containedElement;
 | 
						|
        },
 | 
						|
 | 
						|
        ready: function() {
 | 
						|
          // Memoized scrolling position, used to block scrolling outside.
 | 
						|
          this._scrollTop = 0;
 | 
						|
          this._scrollLeft = 0;
 | 
						|
          // Used to perform a non-blocking refit on scroll.
 | 
						|
          this._refitOnScrollRAF = null;
 | 
						|
        },
 | 
						|
 | 
						|
        attached: function () {
 | 
						|
          if (!this.sizingTarget || this.sizingTarget === this) {
 | 
						|
            this.sizingTarget = this.containedElement || this;
 | 
						|
          }
 | 
						|
        },
 | 
						|
 | 
						|
        detached: function() {
 | 
						|
          this.cancelAnimation();
 | 
						|
          document.removeEventListener('scroll', this._boundOnCaptureScroll);
 | 
						|
          Polymer.IronDropdownScrollManager.removeScrollLock(this);
 | 
						|
        },
 | 
						|
 | 
						|
        /**
 | 
						|
         * Called when the value of `opened` changes.
 | 
						|
         * Overridden from `IronOverlayBehavior`
 | 
						|
         */
 | 
						|
        _openedChanged: function() {
 | 
						|
          if (this.opened && this.disabled) {
 | 
						|
            this.cancel();
 | 
						|
          } else {
 | 
						|
            this.cancelAnimation();
 | 
						|
            this._updateAnimationConfig();
 | 
						|
            this._saveScrollPosition();
 | 
						|
            if (this.opened) {
 | 
						|
              document.addEventListener('scroll', this._boundOnCaptureScroll);
 | 
						|
              !this.allowOutsideScroll && Polymer.IronDropdownScrollManager.pushScrollLock(this);
 | 
						|
            } else {
 | 
						|
              document.removeEventListener('scroll', this._boundOnCaptureScroll);
 | 
						|
              Polymer.IronDropdownScrollManager.removeScrollLock(this);
 | 
						|
            }
 | 
						|
            Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this, arguments);
 | 
						|
          }
 | 
						|
        },
 | 
						|
 | 
						|
        /**
 | 
						|
         * Overridden from `IronOverlayBehavior`.
 | 
						|
         */
 | 
						|
        _renderOpened: function() {
 | 
						|
          if (!this.noAnimations && this.animationConfig.open) {
 | 
						|
            this.$.contentWrapper.classList.add('animating');
 | 
						|
            this.playAnimation('open');
 | 
						|
          } else {
 | 
						|
            Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this, arguments);
 | 
						|
          }
 | 
						|
        },
 | 
						|
 | 
						|
        /**
 | 
						|
         * Overridden from `IronOverlayBehavior`.
 | 
						|
         */
 | 
						|
        _renderClosed: function() {
 | 
						|
 | 
						|
          if (!this.noAnimations && this.animationConfig.close) {
 | 
						|
            this.$.contentWrapper.classList.add('animating');
 | 
						|
            this.playAnimation('close');
 | 
						|
          } else {
 | 
						|
            Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this, arguments);
 | 
						|
          }
 | 
						|
        },
 | 
						|
 | 
						|
        /**
 | 
						|
         * Called when animation finishes on the dropdown (when opening or
 | 
						|
         * closing). Responsible for "completing" the process of opening or
 | 
						|
         * closing the dropdown by positioning it or setting its display to
 | 
						|
         * none.
 | 
						|
         */
 | 
						|
        _onNeonAnimationFinish: function() {
 | 
						|
          this.$.contentWrapper.classList.remove('animating');
 | 
						|
          if (this.opened) {
 | 
						|
            this._finishRenderOpened();
 | 
						|
          } else {
 | 
						|
            this._finishRenderClosed();
 | 
						|
          }
 | 
						|
        },
 | 
						|
 | 
						|
        _onCaptureScroll: function() {
 | 
						|
          if (!this.allowOutsideScroll) {
 | 
						|
            this._restoreScrollPosition();
 | 
						|
          } else {
 | 
						|
            this._refitOnScrollRAF && window.cancelAnimationFrame(this._refitOnScrollRAF);
 | 
						|
            this._refitOnScrollRAF = window.requestAnimationFrame(this.refit.bind(this));
 | 
						|
          }
 | 
						|
        },
 | 
						|
 | 
						|
        /**
 | 
						|
         * Memoizes the scroll position of the outside scrolling element.
 | 
						|
         * @private
 | 
						|
         */
 | 
						|
        _saveScrollPosition: function() {
 | 
						|
          if (document.scrollingElement) {
 | 
						|
            this._scrollTop = document.scrollingElement.scrollTop;
 | 
						|
            this._scrollLeft = document.scrollingElement.scrollLeft;
 | 
						|
          } else {
 | 
						|
            // Since we don't know if is the body or html, get max.
 | 
						|
            this._scrollTop = Math.max(document.documentElement.scrollTop, document.body.scrollTop);
 | 
						|
            this._scrollLeft = Math.max(document.documentElement.scrollLeft, document.body.scrollLeft);
 | 
						|
          }
 | 
						|
        },
 | 
						|
 | 
						|
        /**
 | 
						|
         * Resets the scroll position of the outside scrolling element.
 | 
						|
         * @private
 | 
						|
         */
 | 
						|
        _restoreScrollPosition: function() {
 | 
						|
          if (document.scrollingElement) {
 | 
						|
            document.scrollingElement.scrollTop = this._scrollTop;
 | 
						|
            document.scrollingElement.scrollLeft = this._scrollLeft;
 | 
						|
          } else {
 | 
						|
            // Since we don't know if is the body or html, set both.
 | 
						|
            document.documentElement.scrollTop = this._scrollTop;
 | 
						|
            document.documentElement.scrollLeft = this._scrollLeft;
 | 
						|
            document.body.scrollTop = this._scrollTop;
 | 
						|
            document.body.scrollLeft = this._scrollLeft;
 | 
						|
          }
 | 
						|
        },
 | 
						|
 | 
						|
        /**
 | 
						|
         * Constructs the final animation config from different properties used
 | 
						|
         * to configure specific parts of the opening and closing animations.
 | 
						|
         */
 | 
						|
        _updateAnimationConfig: function() {
 | 
						|
          // Update the animation node to be the containedElement.
 | 
						|
          var animationNode = this.containedElement;
 | 
						|
          var animations = [].concat(this.openAnimationConfig || []).concat(this.closeAnimationConfig || []);
 | 
						|
          for (var i = 0; i < animations.length; i++) {
 | 
						|
            animations[i].node = animationNode;
 | 
						|
          }
 | 
						|
          this.animationConfig = {
 | 
						|
            open: this.openAnimationConfig,
 | 
						|
            close: this.closeAnimationConfig
 | 
						|
          };
 | 
						|
        },
 | 
						|
 | 
						|
        /**
 | 
						|
         * Updates the overlay position based on configured horizontal
 | 
						|
         * and vertical alignment.
 | 
						|
         */
 | 
						|
        _updateOverlayPosition: function() {
 | 
						|
          if (this.isAttached) {
 | 
						|
            // This triggers iron-resize, and iron-overlay-behavior will call refit if needed.
 | 
						|
            this.notifyResize();
 | 
						|
          }
 | 
						|
        },
 | 
						|
 | 
						|
        /**
 | 
						|
         * Apply focus to focusTarget or containedElement
 | 
						|
         */
 | 
						|
        _applyFocus: function () {
 | 
						|
          var focusTarget = this.focusTarget || this.containedElement;
 | 
						|
          if (focusTarget && this.opened && !this.noAutoFocus) {
 | 
						|
            focusTarget.focus();
 | 
						|
          } else {
 | 
						|
            Polymer.IronOverlayBehaviorImpl._applyFocus.apply(this, arguments);
 | 
						|
          }
 | 
						|
        }
 | 
						|
      });
 | 
						|
    })();
 | 
						|
  </script>
 | 
						|
</dom-module>
 |