text: import {IronA11yKeysBehavior} from '../iron-a11y-keys-behavior/iron-a11y-keys-behavior.js';
  import {Polymer, dom, html} from '../polymer/polymer_bundled.min.js';
  
  var MAX_RADIUS_PX = 300;
  var MIN_DURATION_MS = 800;
  
  /**
   * @param {number} x1
   * @param {number} y1
   * @param {number} x2
   * @param {number} y2
   * @return {number} The distance between (x1, y1) and (x2, y2).
   */
  var distance = function(x1, y1, x2, y2) {
    var xDelta = x1 - x2;
    var yDelta = y1 - y2;
    return Math.sqrt(xDelta * xDelta + yDelta * yDelta);
  };
  
  Polymer({
    _template: html`
      <style>
        :host {
          bottom: 0;
          display: block;
          left: 0;
          overflow: hidden;
          pointer-events: none;
          position: absolute;
          right: 0;
          top: 0;
          /* For rounded corners: http://jsbin.com/temexa/4. */
          transform: translate3d(0, 0, 0);
        }
  
        .ripple {
          background-color: currentcolor;
          left: 0;
          opacity: var(--paper-ripple-opacity, 0.25);
          pointer-events: none;
          position: absolute;
          will-change: height, transform, width;
        }
  
        .ripple,
        :host(.circle) {
          border-radius: 50%;
        }
      </style>
  `,
  
    is: 'paper-ripple',
    behaviors: [IronA11yKeysBehavior],
  
    properties: {
      center: {type: Boolean, value: false},
      holdDown: {type: Boolean, value: false, observer: '_holdDownChanged'},
      recenters: {type: Boolean, value: false},
      noink: {type: Boolean, value: false},
    },
  
    keyBindings: {
      'enter:keydown': '_onEnterKeydown',
      'space:keydown': '_onSpaceKeydown',
      'space:keyup': '_onSpaceKeyup',
    },
  
    /** @override */
    created: function() {
      /** @type {Array<!Element>} */
      this.ripples = [];
    },
  
    /** @override */
    attached: function() {
      this.keyEventTarget = this.parentNode.nodeType == 11 ?
          dom(this).getOwnerRoot().host : this.parentNode;
      this.keyEventTarget = /** @type {!EventTarget} */ (this.keyEventTarget);
      this.listen(this.keyEventTarget, 'up', 'uiUpAction');
      this.listen(this.keyEventTarget, 'down', 'uiDownAction');
    },
  
    /** @override */
    detached: function() {
      this.unlisten(this.keyEventTarget, 'up', 'uiUpAction');
      this.unlisten(this.keyEventTarget, 'down', 'uiDownAction');
      this.keyEventTarget = null;
    },
  
    simulatedRipple: function() {
      this.downAction();
      // Using a 1ms delay ensures a macro-task.
      this.async(function() { this.upAction(); }.bind(this), 1);
    },
  
    /** @param {Event=} e */
    uiDownAction: function(e) {
      if (!this.noink)
        this.downAction(e);
    },
  
    /** @param {Event=} e */
    downAction: function(e) {
      if (this.ripples.length && this.holdDown)
        return;
      // TODO(dbeam): some things (i.e. paper-icon-button-light) dynamically
      // create ripples on 'up', Ripples register an event listener on their
      // parent (or shadow DOM host) when attached().  This sometimes causes
      // duplicate events to fire on us.
      this.debounce('show ripple', function() { this.__showRipple(e); }, 1);
    },
  
    clear: function() {
      this.__hideRipple();
      this.holdDown = false;
    },
  
    showAndHoldDown: function() {
      this.ripples.forEach(ripple => {
        ripple.remove();
      });
      this.ripples = [];
      this.holdDown = true;
    },
  
    /**
     * @param {Event=} e
     * @private
     * @suppress {checkTypes}
     */
    __showRipple: function(e) {
      var rect = this.getBoundingClientRect();
  
      var roundedCenterX = function() { return Math.round(rect.width / 2); };
      var roundedCenterY = function() { return Math.round(rect.height / 2); };
  
      var centered = !e || this.center;
      if (centered) {
        var x = roundedCenterX();
        var y = roundedCenterY();
      } else {
        var sourceEvent = e.detail.sourceEvent;
        var x = Math.round(sourceEvent.clientX - rect.left);
        var y = Math.round(sourceEvent.clientY - rect.top);
      }
  
      var corners = [
        {x: 0, y: 0},
        {x: rect.width, y: 0},
        {x: 0, y: rect.height},
        {x: rect.width, y: rect.height},
      ];
  
      var cornerDistances = corners.map(function(corner) {
        return Math.round(distance(x, y, corner.x, corner.y));
      });
  
      var radius = Math.min(MAX_RADIUS_PX, Math.max.apply(Math, cornerDistances));
  
      var startTranslate = (x - radius) + 'px, ' + (y - radius) + 'px';
      if (this.recenters && !centered) {
        var endTranslate = (roundedCenterX() - radius) + 'px, ' +
                           (roundedCenterY() - radius) + 'px';
      } else {
        var endTranslate = startTranslate;
      }
  
      var ripple = document.createElement('div');
      ripple.classList.add('ripple');
      ripple.style.height = ripple.style.width = (2 * radius) + 'px';
  
      this.ripples.push(ripple);
      this.shadowRoot.appendChild(ripple);
  
      ripple.animate({
        // TODO(dbeam): scale to 90% of radius at .75 offset?
        transform: ['translate(' + startTranslate + ') scale(0)',
                    'translate(' + endTranslate + ') scale(1)'],
      }, {
        duration: Math.max(MIN_DURATION_MS, Math.log(radius) * radius) || 0,
        easing: 'cubic-bezier(.2, .9, .1, .9)',
        fill: 'forwards',
      });
    },
  
    /** @param {Event=} e */
    uiUpAction: function(e) {
      if (!this.noink)
        this.upAction();
    },
  
    /** @param {Event=} e */
    upAction: function(e) {
      if (!this.holdDown)
        this.debounce('hide ripple', function() { this.__hideRipple(); }, 1);
    },
  
    /**
     * @private
     * @suppress {checkTypes}
     */
    __hideRipple: function() {
      Promise.all(this.ripples.map(function(ripple) {
        return new Promise(function(resolve) {
          var removeRipple = function() {
            ripple.remove();
            resolve();
          };
          var opacity = getComputedStyle(ripple).opacity;
          if (!opacity.length) {
            removeRipple();
          } else {
            var animation = ripple.animate({
              opacity: [opacity, 0],
            }, {
              duration: 150,
              fill: 'forwards',
            });
            animation.addEventListener('finish', removeRipple);
            animation.addEventListener('cancel', removeRipple);
          }
        });
      })).then(function() { this.fire('transitionend'); }.bind(this));
      this.ripples = [];
    },
  
    /** @protected */
    _onEnterKeydown: function() {
      this.uiDownAction();
      this.async(this.uiUpAction, 1);
    },
  
    /** @protected */
    _onSpaceKeydown: function() {
      this.uiDownAction();
    },
  
    /** @protected */
    _onSpaceKeyup: function() {
      this.uiUpAction();
    },
  
    /** @protected */
    _holdDownChanged: function(newHoldDown, oldHoldDown) {
      if (oldHoldDown === undefined)
        return;
      if (newHoldDown)
        this.downAction();
      else
        this.upAction();
    },
  });
  