408 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			HTML
		
	
	
	
			
		
		
	
	
			408 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="../neon-animation/neon-animation-runner-behavior.html">
 | 
						|
<link rel="import" href="../neon-animation/animations/fade-in-animation.html">
 | 
						|
<link rel="import" href="../neon-animation/animations/fade-out-animation.html">
 | 
						|
 | 
						|
<!--
 | 
						|
Material design: [Tooltips](https://www.google.com/design/spec/components/tooltips.html)
 | 
						|
 | 
						|
`<paper-tooltip>` is a label that appears on hover and focus when the user
 | 
						|
hovers over an element with the cursor or with the keyboard. It will be centered
 | 
						|
to an anchor element specified in the `for` attribute, or, if that doesn't exist,
 | 
						|
centered to the parent node containing it.
 | 
						|
 | 
						|
Example:
 | 
						|
 | 
						|
    <div style="display:inline-block">
 | 
						|
      <button>Click me!</button>
 | 
						|
      <paper-tooltip>Tooltip text</paper-tooltip>
 | 
						|
    </div>
 | 
						|
 | 
						|
    <div>
 | 
						|
      <button id="btn">Click me!</button>
 | 
						|
      <paper-tooltip for="btn">Tooltip text</paper-tooltip>
 | 
						|
    </div>
 | 
						|
 | 
						|
The tooltip can be positioned on the top|bottom|left|right of the anchor using
 | 
						|
the `position` attribute. The default position is bottom.
 | 
						|
 | 
						|
    <paper-tooltip for="btn" position="left">Tooltip text</paper-tooltip>
 | 
						|
    <paper-tooltip for="btn" position="top">Tooltip text</paper-tooltip>
 | 
						|
 | 
						|
### Styling
 | 
						|
 | 
						|
The following custom properties and mixins are available for styling:
 | 
						|
 | 
						|
Custom property | Description | Default
 | 
						|
----------------|-------------|----------
 | 
						|
`--paper-tooltip-background` | The background color of the tooltip | `#616161`
 | 
						|
`--paper-tooltip-opacity` | The opacity of the tooltip | `0.9`
 | 
						|
`--paper-tooltip-text-color` | The text color of the tooltip | `white`
 | 
						|
`--paper-tooltip` | Mixin applied to the tooltip | `{}`
 | 
						|
 | 
						|
@group Paper Elements
 | 
						|
@element paper-tooltip
 | 
						|
@demo demo/index.html
 | 
						|
-->
 | 
						|
 | 
						|
<dom-module id="paper-tooltip">
 | 
						|
  <template>
 | 
						|
    <style>
 | 
						|
      :host {
 | 
						|
        display: block;
 | 
						|
        position: absolute;
 | 
						|
        outline: none;
 | 
						|
        z-index: 1002;
 | 
						|
        -moz-user-select: none;
 | 
						|
        -ms-user-select: none;
 | 
						|
        -webkit-user-select: none;
 | 
						|
        user-select: none;
 | 
						|
        cursor: default;
 | 
						|
      }
 | 
						|
 | 
						|
      #tooltip {
 | 
						|
        display: block;
 | 
						|
        outline: none;
 | 
						|
        @apply(--paper-font-common-base);
 | 
						|
        font-size: 10px;
 | 
						|
        line-height: 1;
 | 
						|
 | 
						|
        background-color: var(--paper-tooltip-background, #616161);
 | 
						|
        opacity: var(--paper-tooltip-opacity, 0.9);
 | 
						|
        color: var(--paper-tooltip-text-color, white);
 | 
						|
 | 
						|
        padding: 8px;
 | 
						|
        border-radius: 2px;
 | 
						|
 | 
						|
        @apply(--paper-tooltip);
 | 
						|
      }
 | 
						|
 | 
						|
      /* Thanks IE 10. */
 | 
						|
      .hidden {
 | 
						|
        display: none !important;
 | 
						|
      }
 | 
						|
    </style>
 | 
						|
 | 
						|
    <div id="tooltip" class="hidden">
 | 
						|
      <content></content>
 | 
						|
    </div>
 | 
						|
  </template>
 | 
						|
 | 
						|
  <script>
 | 
						|
    Polymer({
 | 
						|
      is: 'paper-tooltip',
 | 
						|
 | 
						|
      hostAttributes: {
 | 
						|
        role: 'tooltip',
 | 
						|
        tabindex: -1
 | 
						|
      },
 | 
						|
 | 
						|
      behaviors: [
 | 
						|
        Polymer.NeonAnimationRunnerBehavior
 | 
						|
      ],
 | 
						|
 | 
						|
      properties: {
 | 
						|
        /**
 | 
						|
         * The id of the element that the tooltip is anchored to. This element
 | 
						|
         * must be a sibling of the tooltip.
 | 
						|
         */
 | 
						|
        for: {
 | 
						|
          type: String,
 | 
						|
          observer: '_findTarget'
 | 
						|
        },
 | 
						|
 | 
						|
        /**
 | 
						|
         * Set this to true if you want to manually control when the tooltip
 | 
						|
         * is shown or hidden.
 | 
						|
         */
 | 
						|
        manualMode: {
 | 
						|
          type: Boolean,
 | 
						|
          value: false,
 | 
						|
          observer: '_manualModeChanged'
 | 
						|
        },
 | 
						|
 | 
						|
        /**
 | 
						|
         * Positions the tooltip to the top, right, bottom, left of its content.
 | 
						|
         */
 | 
						|
        position: {
 | 
						|
          type: String,
 | 
						|
          value: 'bottom'
 | 
						|
        },
 | 
						|
 | 
						|
        /**
 | 
						|
         * If true, no parts of the tooltip will ever be shown offscreen.
 | 
						|
         */
 | 
						|
        fitToVisibleBounds: {
 | 
						|
          type: Boolean,
 | 
						|
          value: false
 | 
						|
        },
 | 
						|
 | 
						|
        /**
 | 
						|
         * The spacing between the top of the tooltip and the element it is
 | 
						|
         * anchored to.
 | 
						|
         */
 | 
						|
        offset: {
 | 
						|
          type: Number,
 | 
						|
          value: 14
 | 
						|
        },
 | 
						|
 | 
						|
        /**
 | 
						|
         * This property is deprecated, but left over so that it doesn't
 | 
						|
         * break exiting code. Please use `offset` instead. If both `offset` and
 | 
						|
         * `marginTop` are provided, `marginTop` will be ignored.
 | 
						|
         * @deprecated since version 1.0.3
 | 
						|
         */
 | 
						|
        marginTop: {
 | 
						|
          type: Number,
 | 
						|
          value: 14
 | 
						|
        },
 | 
						|
 | 
						|
        /**
 | 
						|
         * The delay that will be applied before the `entry` animation is
 | 
						|
         * played when showing the tooltip.
 | 
						|
         */
 | 
						|
        animationDelay: {
 | 
						|
          type: Number,
 | 
						|
          value: 500
 | 
						|
        },
 | 
						|
 | 
						|
        /**
 | 
						|
         * The entry and exit animations that will be played when showing and
 | 
						|
         * hiding the tooltip. If you want to override this, you must ensure
 | 
						|
         * that your animationConfig has the exact format below.
 | 
						|
         */
 | 
						|
        animationConfig: {
 | 
						|
          type: Object,
 | 
						|
          value: function() {
 | 
						|
            return {
 | 
						|
              'entry': [{
 | 
						|
                name: 'fade-in-animation',
 | 
						|
                node: this,
 | 
						|
                timing: {delay: 0}
 | 
						|
              }],
 | 
						|
              'exit': [{
 | 
						|
                name: 'fade-out-animation',
 | 
						|
                node: this
 | 
						|
              }]
 | 
						|
            }
 | 
						|
          }
 | 
						|
        },
 | 
						|
 | 
						|
        _showing: {
 | 
						|
          type: Boolean,
 | 
						|
          value: false
 | 
						|
        }
 | 
						|
      },
 | 
						|
 | 
						|
      listeners: {
 | 
						|
        'neon-animation-finish': '_onAnimationFinish',
 | 
						|
      },
 | 
						|
 | 
						|
      /**
 | 
						|
       * Returns the target element that this tooltip is anchored to. It is
 | 
						|
       * either the element given by the `for` attribute, or the immediate parent
 | 
						|
       * of the tooltip.
 | 
						|
       */
 | 
						|
      get target () {
 | 
						|
        var parentNode = Polymer.dom(this).parentNode;
 | 
						|
        // If the parentNode is a document fragment, then we need to use the host.
 | 
						|
        var ownerRoot = Polymer.dom(this).getOwnerRoot();
 | 
						|
 | 
						|
        var target;
 | 
						|
        if (this.for) {
 | 
						|
          target = Polymer.dom(ownerRoot).querySelector('#' + this.for);
 | 
						|
        } else {
 | 
						|
          target = parentNode.nodeType == Node.DOCUMENT_FRAGMENT_NODE ?
 | 
						|
              ownerRoot.host : parentNode;
 | 
						|
        }
 | 
						|
 | 
						|
        return target;
 | 
						|
      },
 | 
						|
 | 
						|
      attached: function() {
 | 
						|
        this._findTarget();
 | 
						|
      },
 | 
						|
 | 
						|
      detached: function() {
 | 
						|
        if (!this.manualMode)
 | 
						|
          this._removeListeners();
 | 
						|
      },
 | 
						|
 | 
						|
      show: function() {
 | 
						|
        // If the tooltip is already showing, there's nothing to do.
 | 
						|
        if (this._showing)
 | 
						|
          return;
 | 
						|
 | 
						|
        if (Polymer.dom(this).textContent.trim() === ''){
 | 
						|
          // Check if effective children are also empty
 | 
						|
          var allChildrenEmpty = true;
 | 
						|
          var effectiveChildren = Polymer.dom(this).getEffectiveChildNodes();
 | 
						|
          for (var i = 0; i < effectiveChildren.length; i++) {
 | 
						|
            if (effectiveChildren[i].textContent.trim() !== '') {
 | 
						|
              allChildrenEmpty = false;
 | 
						|
              break;
 | 
						|
            }
 | 
						|
          }
 | 
						|
          if (allChildrenEmpty) {
 | 
						|
            return;
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
 | 
						|
        this.cancelAnimation();
 | 
						|
        this._showing = true;
 | 
						|
        this.toggleClass('hidden', false, this.$.tooltip);
 | 
						|
        this.updatePosition();
 | 
						|
 | 
						|
        this.animationConfig.entry[0].timing = this.animationConfig.entry[0].timing || {};
 | 
						|
        this.animationConfig.entry[0].timing.delay = this.animationDelay;
 | 
						|
        this._animationPlaying = true;
 | 
						|
        this.playAnimation('entry');
 | 
						|
      },
 | 
						|
 | 
						|
      hide: function() {
 | 
						|
        // If the tooltip is already hidden, there's nothing to do.
 | 
						|
        if (!this._showing) {
 | 
						|
          return;
 | 
						|
        }
 | 
						|
 | 
						|
        // If the entry animation is still playing, don't try to play the exit
 | 
						|
        // animation since this will reset the opacity to 1. Just end the animation.
 | 
						|
        if (this._animationPlaying) {
 | 
						|
          this.cancelAnimation();
 | 
						|
          this._showing = false;
 | 
						|
          this._onAnimationFinish();
 | 
						|
          return;
 | 
						|
        }
 | 
						|
 | 
						|
        this._showing = false;
 | 
						|
        this._animationPlaying = true;
 | 
						|
        this.playAnimation('exit');
 | 
						|
      },
 | 
						|
 | 
						|
      updatePosition: function() {
 | 
						|
        if (!this._target || !this.offsetParent)
 | 
						|
          return;
 | 
						|
 | 
						|
        var offset = this.offset;
 | 
						|
        // If a marginTop has been provided by the user (pre 1.0.3), use it.
 | 
						|
        if (this.marginTop != 14 && this.offset == 14)
 | 
						|
          offset = this.marginTop;
 | 
						|
 | 
						|
        var parentRect = this.offsetParent.getBoundingClientRect();
 | 
						|
        var targetRect = this._target.getBoundingClientRect();
 | 
						|
        var thisRect = this.getBoundingClientRect();
 | 
						|
 | 
						|
        var horizontalCenterOffset = (targetRect.width - thisRect.width) / 2;
 | 
						|
        var verticalCenterOffset = (targetRect.height - thisRect.height) / 2;
 | 
						|
 | 
						|
        var targetLeft = targetRect.left - parentRect.left;
 | 
						|
        var targetTop = targetRect.top - parentRect.top;
 | 
						|
 | 
						|
        var tooltipLeft, tooltipTop;
 | 
						|
 | 
						|
        switch (this.position) {
 | 
						|
          case 'top':
 | 
						|
            tooltipLeft = targetLeft + horizontalCenterOffset;
 | 
						|
            tooltipTop = targetTop - thisRect.height - offset;
 | 
						|
            break;
 | 
						|
          case 'bottom':
 | 
						|
            tooltipLeft = targetLeft + horizontalCenterOffset;
 | 
						|
            tooltipTop = targetTop + targetRect.height + offset;
 | 
						|
            break;
 | 
						|
          case 'left':
 | 
						|
            tooltipLeft = targetLeft - thisRect.width - offset;
 | 
						|
            tooltipTop = targetTop + verticalCenterOffset;
 | 
						|
            break;
 | 
						|
          case 'right':
 | 
						|
            tooltipLeft = targetLeft + targetRect.width + offset;
 | 
						|
            tooltipTop = targetTop + verticalCenterOffset;
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        // TODO(noms): This should use IronFitBehavior if possible.
 | 
						|
        if (this.fitToVisibleBounds) {
 | 
						|
          // Clip the left/right side
 | 
						|
          if (parentRect.left + tooltipLeft + thisRect.width > window.innerWidth) {
 | 
						|
            this.style.right = '0px';
 | 
						|
            this.style.left = 'auto';
 | 
						|
          } else {
 | 
						|
            this.style.left = Math.max(0, tooltipLeft) + 'px';
 | 
						|
            this.style.right = 'auto';
 | 
						|
          }
 | 
						|
 | 
						|
          // Clip the top/bottom side.
 | 
						|
          if (parentRect.top + tooltipTop + thisRect.height > window.innerHeight) {
 | 
						|
            this.style.bottom = parentRect.height + 'px';
 | 
						|
            this.style.top = 'auto';
 | 
						|
          } else {
 | 
						|
            this.style.top = Math.max(-parentRect.top, tooltipTop) + 'px';
 | 
						|
            this.style.bottom = 'auto';
 | 
						|
          }
 | 
						|
        } else {
 | 
						|
          this.style.left = tooltipLeft + 'px';
 | 
						|
          this.style.top = tooltipTop + 'px';
 | 
						|
        }
 | 
						|
 | 
						|
      },
 | 
						|
 | 
						|
      _addListeners: function() {
 | 
						|
        if (this._target) {
 | 
						|
          this.listen(this._target, 'mouseenter', 'show');
 | 
						|
          this.listen(this._target, 'focus', 'show');
 | 
						|
          this.listen(this._target, 'mouseleave', 'hide');
 | 
						|
          this.listen(this._target, 'blur', 'hide');
 | 
						|
          this.listen(this._target, 'tap', 'hide');
 | 
						|
        }
 | 
						|
        this.listen(this, 'mouseenter', 'hide');
 | 
						|
      },
 | 
						|
 | 
						|
      _findTarget: function() {
 | 
						|
        if (!this.manualMode)
 | 
						|
          this._removeListeners();
 | 
						|
 | 
						|
        this._target = this.target;
 | 
						|
 | 
						|
        if (!this.manualMode)
 | 
						|
          this._addListeners();
 | 
						|
      },
 | 
						|
 | 
						|
      _manualModeChanged: function() {
 | 
						|
        if (this.manualMode)
 | 
						|
          this._removeListeners();
 | 
						|
        else
 | 
						|
          this._addListeners();
 | 
						|
      },
 | 
						|
 | 
						|
      _onAnimationFinish: function() {
 | 
						|
        this._animationPlaying = false;
 | 
						|
        if (!this._showing) {
 | 
						|
          this.toggleClass('hidden', true, this.$.tooltip);
 | 
						|
        }
 | 
						|
      },
 | 
						|
 | 
						|
      _removeListeners: function() {
 | 
						|
        if (this._target) {
 | 
						|
          this.unlisten(this._target, 'mouseenter', 'show');
 | 
						|
          this.unlisten(this._target, 'focus', 'show');
 | 
						|
          this.unlisten(this._target, 'mouseleave', 'hide');
 | 
						|
          this.unlisten(this._target, 'blur', 'hide');
 | 
						|
          this.unlisten(this._target, 'tap', 'hide');
 | 
						|
        }
 | 
						|
        this.unlisten(this, 'mouseenter', 'hide');
 | 
						|
      }
 | 
						|
    });
 | 
						|
  </script>
 | 
						|
</dom-module>
 |