Add ICU message format support
[chromium-blink-merge.git] / third_party / polymer / v1_0 / components-chromium / paper-ripple / paper-ripple-extracted.js
bloba760fa244d46173bab46d639507be31dd293fea9
2   (function() {
3     var Utility = {
4       cssColorWithAlpha: function(cssColor, alpha) {
5         var parts = cssColor.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
7         if (typeof alpha == 'undefined') {
8           alpha = 1;
9         }
11         if (!parts) {
12           return 'rgba(255, 255, 255, ' + alpha + ')';
13         }
15         return 'rgba(' + parts[1] + ', ' + parts[2] + ', ' + parts[3] + ', ' + alpha + ')';
16       },
18       distance: function(x1, y1, x2, y2) {
19         var xDelta = (x1 - x2);
20         var yDelta = (y1 - y2);
22         return Math.sqrt(xDelta * xDelta + yDelta * yDelta);
23       },
25       now: (function() {
26         if (window.performance && window.performance.now) {
27           return window.performance.now.bind(window.performance);
28         }
30         return Date.now;
31       })()
32     };
34     /**
35      * @param {HTMLElement} element
36      * @constructor
37      */
38     function ElementMetrics(element) {
39       this.element = element;
40       this.width = this.boundingRect.width;
41       this.height = this.boundingRect.height;
43       this.size = Math.max(this.width, this.height);
44     }
46     ElementMetrics.prototype = {
47       get boundingRect () {
48         return this.element.getBoundingClientRect();
49       },
51       furthestCornerDistanceFrom: function(x, y) {
52         var topLeft = Utility.distance(x, y, 0, 0);
53         var topRight = Utility.distance(x, y, this.width, 0);
54         var bottomLeft = Utility.distance(x, y, 0, this.height);
55         var bottomRight = Utility.distance(x, y, this.width, this.height);
57         return Math.max(topLeft, topRight, bottomLeft, bottomRight);
58       }
59     };
61     /**
62      * @param {HTMLElement} element
63      * @constructor
64      */
65     function Ripple(element) {
66       this.element = element;
67       this.color = window.getComputedStyle(element).color;
69       this.wave = document.createElement('div');
70       this.waveContainer = document.createElement('div');
71       this.wave.style.backgroundColor = this.color;
72       this.wave.classList.add('wave');
73       this.waveContainer.classList.add('wave-container');
74       Polymer.dom(this.waveContainer).appendChild(this.wave);
76       this.resetInteractionState();
77     }
79     Ripple.MAX_RADIUS = 300;
81     Ripple.prototype = {
82       get recenters() {
83         return this.element.recenters;
84       },
86       get center() {
87         return this.element.center;
88       },
90       get mouseDownElapsed() {
91         var elapsed;
93         if (!this.mouseDownStart) {
94           return 0;
95         }
97         elapsed = Utility.now() - this.mouseDownStart;
99         if (this.mouseUpStart) {
100           elapsed -= this.mouseUpElapsed;
101         }
103         return elapsed;
104       },
106       get mouseUpElapsed() {
107         return this.mouseUpStart ?
108           Utility.now () - this.mouseUpStart : 0;
109       },
111       get mouseDownElapsedSeconds() {
112         return this.mouseDownElapsed / 1000;
113       },
115       get mouseUpElapsedSeconds() {
116         return this.mouseUpElapsed / 1000;
117       },
119       get mouseInteractionSeconds() {
120         return this.mouseDownElapsedSeconds + this.mouseUpElapsedSeconds;
121       },
123       get initialOpacity() {
124         return this.element.initialOpacity;
125       },
127       get opacityDecayVelocity() {
128         return this.element.opacityDecayVelocity;
129       },
131       get radius() {
132         var width2 = this.containerMetrics.width * this.containerMetrics.width;
133         var height2 = this.containerMetrics.height * this.containerMetrics.height;
134         var waveRadius = Math.min(
135           Math.sqrt(width2 + height2),
136           Ripple.MAX_RADIUS
137         ) * 1.1 + 5;
139         var duration = 1.1 - 0.2 * (waveRadius / Ripple.MAX_RADIUS);
140         var timeNow = this.mouseInteractionSeconds / duration;
141         var size = waveRadius * (1 - Math.pow(80, -timeNow));
143         return Math.abs(size);
144       },
146       get opacity() {
147         if (!this.mouseUpStart) {
148           return this.initialOpacity;
149         }
151         return Math.max(
152           0,
153           this.initialOpacity - this.mouseUpElapsedSeconds * this.opacityDecayVelocity
154         );
155       },
157       get outerOpacity() {
158         // Linear increase in background opacity, capped at the opacity
159         // of the wavefront (waveOpacity).
160         var outerOpacity = this.mouseUpElapsedSeconds * 0.3;
161         var waveOpacity = this.opacity;
163         return Math.max(
164           0,
165           Math.min(outerOpacity, waveOpacity)
166         );
167       },
169       get isOpacityFullyDecayed() {
170         return this.opacity < 0.01 &&
171           this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS);
172       },
174       get isRestingAtMaxRadius() {
175         return this.opacity >= this.initialOpacity &&
176           this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS);
177       },
179       get isAnimationComplete() {
180         return this.mouseUpStart ?
181           this.isOpacityFullyDecayed : this.isRestingAtMaxRadius;
182       },
184       get translationFraction() {
185         return Math.min(
186           1,
187           this.radius / this.containerMetrics.size * 2 / Math.sqrt(2)
188         );
189       },
191       get xNow() {
192         if (this.xEnd) {
193           return this.xStart + this.translationFraction * (this.xEnd - this.xStart);
194         }
196         return this.xStart;
197       },
199       get yNow() {
200         if (this.yEnd) {
201           return this.yStart + this.translationFraction * (this.yEnd - this.yStart);
202         }
204         return this.yStart;
205       },
207       get isMouseDown() {
208         return this.mouseDownStart && !this.mouseUpStart;
209       },
211       resetInteractionState: function() {
212         this.maxRadius = 0;
213         this.mouseDownStart = 0;
214         this.mouseUpStart = 0;
216         this.xStart = 0;
217         this.yStart = 0;
218         this.xEnd = 0;
219         this.yEnd = 0;
220         this.slideDistance = 0;
222         this.containerMetrics = new ElementMetrics(this.element);
223       },
225       draw: function() {
226         var scale;
227         var translateString;
228         var dx;
229         var dy;
231         this.wave.style.opacity = this.opacity;
233         scale = this.radius / (this.containerMetrics.size / 2);
234         dx = this.xNow - (this.containerMetrics.width / 2);
235         dy = this.yNow - (this.containerMetrics.height / 2);
238         // 2d transform for safari because of border-radius and overflow:hidden clipping bug.
239         // https://bugs.webkit.org/show_bug.cgi?id=98538
240         this.waveContainer.style.webkitTransform = 'translate(' + dx + 'px, ' + dy + 'px)';
241         this.waveContainer.style.transform = 'translate3d(' + dx + 'px, ' + dy + 'px, 0)';
242         this.wave.style.webkitTransform = 'scale(' + scale + ',' + scale + ')';
243         this.wave.style.transform = 'scale3d(' + scale + ',' + scale + ',1)';
244       },
246       /** @param {Event=} event */
247       downAction: function(event) {
248         var xCenter = this.containerMetrics.width / 2;
249         var yCenter = this.containerMetrics.height / 2;
251         this.resetInteractionState();
252         this.mouseDownStart = Utility.now();
254         if (this.center) {
255           this.xStart = xCenter;
256           this.yStart = yCenter;
257           this.slideDistance = Utility.distance(
258             this.xStart, this.yStart, this.xEnd, this.yEnd
259           );
260         } else {
261           this.xStart = event ?
262               event.detail.x - this.containerMetrics.boundingRect.left :
263               this.containerMetrics.width / 2;
264           this.yStart = event ?
265               event.detail.y - this.containerMetrics.boundingRect.top :
266               this.containerMetrics.height / 2;
267         }
269         if (this.recenters) {
270           this.xEnd = xCenter;
271           this.yEnd = yCenter;
272           this.slideDistance = Utility.distance(
273             this.xStart, this.yStart, this.xEnd, this.yEnd
274           );
275         }
277         this.maxRadius = this.containerMetrics.furthestCornerDistanceFrom(
278           this.xStart,
279           this.yStart
280         );
282         this.waveContainer.style.top =
283           (this.containerMetrics.height - this.containerMetrics.size) / 2 + 'px';
284         this.waveContainer.style.left =
285           (this.containerMetrics.width - this.containerMetrics.size) / 2 + 'px';
287         this.waveContainer.style.width = this.containerMetrics.size + 'px';
288         this.waveContainer.style.height = this.containerMetrics.size + 'px';
289       },
291       /** @param {Event=} event */
292       upAction: function(event) {
293         if (!this.isMouseDown) {
294           return;
295         }
297         this.mouseUpStart = Utility.now();
298       },
300       remove: function() {
301         Polymer.dom(this.waveContainer.parentNode).removeChild(
302           this.waveContainer
303         );
304       }
305     };
307     Polymer({
308       is: 'paper-ripple',
310       behaviors: [
311         Polymer.IronA11yKeysBehavior
312       ],
314       properties: {
315         /**
316          * The initial opacity set on the wave.
317          *
318          * @attribute initialOpacity
319          * @type number
320          * @default 0.25
321          */
322         initialOpacity: {
323           type: Number,
324           value: 0.25
325         },
327         /**
328          * How fast (opacity per second) the wave fades out.
329          *
330          * @attribute opacityDecayVelocity
331          * @type number
332          * @default 0.8
333          */
334         opacityDecayVelocity: {
335           type: Number,
336           value: 0.8
337         },
339         /**
340          * If true, ripples will exhibit a gravitational pull towards
341          * the center of their container as they fade away.
342          *
343          * @attribute recenters
344          * @type boolean
345          * @default false
346          */
347         recenters: {
348           type: Boolean,
349           value: false
350         },
352         /**
353          * If true, ripples will center inside its container
354          *
355          * @attribute recenters
356          * @type boolean
357          * @default false
358          */
359         center: {
360           type: Boolean,
361           value: false
362         },
364         /**
365          * A list of the visual ripples.
366          *
367          * @attribute ripples
368          * @type Array
369          * @default []
370          */
371         ripples: {
372           type: Array,
373           value: function() {
374             return [];
375           }
376         },
378         /**
379          * True when there are visible ripples animating within the
380          * element.
381          */
382         animating: {
383           type: Boolean,
384           readOnly: true,
385           reflectToAttribute: true,
386           value: false
387         },
389         /**
390          * If true, the ripple will remain in the "down" state until `holdDown`
391          * is set to false again.
392          */
393         holdDown: {
394           type: Boolean,
395           value: false,
396           observer: '_holdDownChanged'
397         },
399         _animating: {
400           type: Boolean
401         },
403         _boundAnimate: {
404           type: Function,
405           value: function() {
406             return this.animate.bind(this);
407           }
408         }
409       },
411       get target () {
412         var ownerRoot = Polymer.dom(this).getOwnerRoot();
413         var target;
415         if (this.parentNode.nodeType == 11) { // DOCUMENT_FRAGMENT_NODE
416           target = ownerRoot.host;
417         } else {
418           target = this.parentNode;
419         }
421         return target;
422       },
424       keyBindings: {
425         'enter:keydown': '_onEnterKeydown',
426         'space:keydown': '_onSpaceKeydown',
427         'space:keyup': '_onSpaceKeyup'
428       },
430       attached: function() {
431         this.listen(this.target, 'up', 'upAction');
432         this.listen(this.target, 'down', 'downAction');
434         if (!this.target.hasAttribute('noink')) {
435           this.keyEventTarget = this.target;
436         }
437       },
439       get shouldKeepAnimating () {
440         for (var index = 0; index < this.ripples.length; ++index) {
441           if (!this.ripples[index].isAnimationComplete) {
442             return true;
443           }
444         }
446         return false;
447       },
449       simulatedRipple: function() {
450         this.downAction(null);
452         // Please see polymer/polymer#1305
453         this.async(function() {
454           this.upAction();
455         }, 1);
456       },
458       /** @param {Event=} event */
459       downAction: function(event) {
460         if (this.holdDown && this.ripples.length > 0) {
461           return;
462         }
464         var ripple = this.addRipple();
466         ripple.downAction(event);
468         if (!this._animating) {
469           this.animate();
470         }
471       },
473       /** @param {Event=} event */
474       upAction: function(event) {
475         if (this.holdDown) {
476           return;
477         }
479         this.ripples.forEach(function(ripple) {
480           ripple.upAction(event);
481         });
483         this.animate();
484       },
486       onAnimationComplete: function() {
487         this._animating = false;
488         this.$.background.style.backgroundColor = null;
489         this.fire('transitionend');
490       },
492       addRipple: function() {
493         var ripple = new Ripple(this);
495         Polymer.dom(this.$.waves).appendChild(ripple.waveContainer);
496         this.$.background.style.backgroundColor = ripple.color;
497         this.ripples.push(ripple);
499         this._setAnimating(true);
501         return ripple;
502       },
504       removeRipple: function(ripple) {
505         var rippleIndex = this.ripples.indexOf(ripple);
507         if (rippleIndex < 0) {
508           return;
509         }
511         this.ripples.splice(rippleIndex, 1);
513         ripple.remove();
515         if (!this.ripples.length) {
516           this._setAnimating(false);
517         }
518       },
520       animate: function() {
521         var index;
522         var ripple;
524         this._animating = true;
526         for (index = 0; index < this.ripples.length; ++index) {
527           ripple = this.ripples[index];
529           ripple.draw();
531           this.$.background.style.opacity = ripple.outerOpacity;
533           if (ripple.isOpacityFullyDecayed && !ripple.isRestingAtMaxRadius) {
534             this.removeRipple(ripple);
535           }
536         }
538         if (!this.shouldKeepAnimating && this.ripples.length === 0) {
539           this.onAnimationComplete();
540         } else {
541           window.requestAnimationFrame(this._boundAnimate);
542         }
543       },
545       _onEnterKeydown: function() {
546         this.downAction();
547         this.async(this.upAction, 1);
548       },
550       _onSpaceKeydown: function() {
551         this.downAction();
552       },
554       _onSpaceKeyup: function() {
555         this.upAction();
556       },
558       _holdDownChanged: function(holdDown) {
559         if (holdDown) {
560           this.downAction();
561         } else {
562           this.upAction();
563         }
564       }
565     });
566   })();