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();
},
});