Service workers: Allow HTTPS pages arrived at via HTTP redirect to use SW
[chromium-blink-merge.git] / third_party / polymer / v1_0 / components-chromium / paper-ripple / paper-ripple-extracted.js
blob3c98a6432f39f3a6fb5da04b73aede6112ccad72
1 (function() {
2     var Utility = {
3       cssColorWithAlpha: function(cssColor, alpha) {
4         var parts = cssColor.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
6         if (typeof alpha == 'undefined') {
7           alpha = 1;
8         }
10         if (!parts) {
11           return 'rgba(255, 255, 255, ' + alpha + ')';
12         }
14         return 'rgba(' + parts[1] + ', ' + parts[2] + ', ' + parts[3] + ', ' + alpha + ')';
15       },
17       distance: function(x1, y1, x2, y2) {
18         var xDelta = (x1 - x2);
19         var yDelta = (y1 - y2);
21         return Math.sqrt(xDelta * xDelta + yDelta * yDelta);
22       },
24       now: (function() {
25         if (window.performance && window.performance.now) {
26           return window.performance.now.bind(window.performance);
27         }
29         return Date.now;
30       })()
31     };
33     /**
34      * @param {HTMLElement} element
35      * @constructor
36      */
37     function ElementMetrics(element) {
38       this.element = element;
39       this.width = this.boundingRect.width;
40       this.height = this.boundingRect.height;
42       this.size = Math.max(this.width, this.height);
43     }
45     ElementMetrics.prototype = {
46       get boundingRect () {
47         return this.element.getBoundingClientRect();
48       },
50       furthestCornerDistanceFrom: function(x, y) {
51         var topLeft = Utility.distance(x, y, 0, 0);
52         var topRight = Utility.distance(x, y, this.width, 0);
53         var bottomLeft = Utility.distance(x, y, 0, this.height);
54         var bottomRight = Utility.distance(x, y, this.width, this.height);
56         return Math.max(topLeft, topRight, bottomLeft, bottomRight);
57       }
58     };
60     /**
61      * @param {HTMLElement} element
62      * @constructor
63      */
64     function Ripple(element) {
65       this.element = element;
66       this.color = window.getComputedStyle(element).color;
68       this.wave = document.createElement('div');
69       this.waveContainer = document.createElement('div');
70       this.wave.style.backgroundColor = this.color;
71       this.wave.classList.add('wave');
72       this.waveContainer.classList.add('wave-container');
73       Polymer.dom(this.waveContainer).appendChild(this.wave);
75       this.resetInteractionState();
76     }
78     Ripple.MAX_RADIUS = 300;
80     Ripple.prototype = {
81       get recenters() {
82         return this.element.recenters;
83       },
85       get center() {
86         return this.element.center;
87       },
89       get mouseDownElapsed() {
90         var elapsed;
92         if (!this.mouseDownStart) {
93           return 0;
94         }
96         elapsed = Utility.now() - this.mouseDownStart;
98         if (this.mouseUpStart) {
99           elapsed -= this.mouseUpElapsed;
100         }
102         return elapsed;
103       },
105       get mouseUpElapsed() {
106         return this.mouseUpStart ?
107           Utility.now () - this.mouseUpStart : 0;
108       },
110       get mouseDownElapsedSeconds() {
111         return this.mouseDownElapsed / 1000;
112       },
114       get mouseUpElapsedSeconds() {
115         return this.mouseUpElapsed / 1000;
116       },
118       get mouseInteractionSeconds() {
119         return this.mouseDownElapsedSeconds + this.mouseUpElapsedSeconds;
120       },
122       get initialOpacity() {
123         return this.element.initialOpacity;
124       },
126       get opacityDecayVelocity() {
127         return this.element.opacityDecayVelocity;
128       },
130       get radius() {
131         var width2 = this.containerMetrics.width * this.containerMetrics.width;
132         var height2 = this.containerMetrics.height * this.containerMetrics.height;
133         var waveRadius = Math.min(
134           Math.sqrt(width2 + height2),
135           Ripple.MAX_RADIUS
136         ) * 1.1 + 5;
138         var duration = 1.1 - 0.2 * (waveRadius / Ripple.MAX_RADIUS);
139         var timeNow = this.mouseInteractionSeconds / duration;
140         var size = waveRadius * (1 - Math.pow(80, -timeNow));
142         return Math.abs(size);
143       },
145       get opacity() {
146         if (!this.mouseUpStart) {
147           return this.initialOpacity;
148         }
150         return Math.max(
151           0,
152           this.initialOpacity - this.mouseUpElapsedSeconds * this.opacityDecayVelocity
153         );
154       },
156       get outerOpacity() {
157         // Linear increase in background opacity, capped at the opacity
158         // of the wavefront (waveOpacity).
159         var outerOpacity = this.mouseUpElapsedSeconds * 0.3;
160         var waveOpacity = this.opacity;
162         return Math.max(
163           0,
164           Math.min(outerOpacity, waveOpacity)
165         );
166       },
168       get isOpacityFullyDecayed() {
169         return this.opacity < 0.01 &&
170           this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS);
171       },
173       get isRestingAtMaxRadius() {
174         return this.opacity >= this.initialOpacity &&
175           this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS);
176       },
178       get isAnimationComplete() {
179         return this.mouseUpStart ?
180           this.isOpacityFullyDecayed : this.isRestingAtMaxRadius;
181       },
183       get translationFraction() {
184         return Math.min(
185           1,
186           this.radius / this.containerMetrics.size * 2 / Math.sqrt(2)
187         );
188       },
190       get xNow() {
191         if (this.xEnd) {
192           return this.xStart + this.translationFraction * (this.xEnd - this.xStart);
193         }
195         return this.xStart;
196       },
198       get yNow() {
199         if (this.yEnd) {
200           return this.yStart + this.translationFraction * (this.yEnd - this.yStart);
201         }
203         return this.yStart;
204       },
206       get isMouseDown() {
207         return this.mouseDownStart && !this.mouseUpStart;
208       },
210       resetInteractionState: function() {
211         this.maxRadius = 0;
212         this.mouseDownStart = 0;
213         this.mouseUpStart = 0;
215         this.xStart = 0;
216         this.yStart = 0;
217         this.xEnd = 0;
218         this.yEnd = 0;
219         this.slideDistance = 0;
221         this.containerMetrics = new ElementMetrics(this.element);
222       },
224       draw: function() {
225         var scale;
226         var translateString;
227         var dx;
228         var dy;
230         this.wave.style.opacity = this.opacity;
232         scale = this.radius / (this.containerMetrics.size / 2);
233         dx = this.xNow - (this.containerMetrics.width / 2);
234         dy = this.yNow - (this.containerMetrics.height / 2);
237         // 2d transform for safari because of border-radius and overflow:hidden clipping bug.
238         // https://bugs.webkit.org/show_bug.cgi?id=98538
239         this.waveContainer.style.webkitTransform = 'translate(' + dx + 'px, ' + dy + 'px)';
240         this.waveContainer.style.transform = 'translate3d(' + dx + 'px, ' + dy + 'px, 0)';
241         this.wave.style.webkitTransform = 'scale(' + scale + ',' + scale + ')';
242         this.wave.style.transform = 'scale3d(' + scale + ',' + scale + ',1)';
243       },
245       /** @param {Event=} event */
246       downAction: function(event) {
247         var xCenter = this.containerMetrics.width / 2;
248         var yCenter = this.containerMetrics.height / 2;
250         this.resetInteractionState();
251         this.mouseDownStart = Utility.now();
253         if (this.center) {
254           this.xStart = xCenter;
255           this.yStart = yCenter;
256           this.slideDistance = Utility.distance(
257             this.xStart, this.yStart, this.xEnd, this.yEnd
258           );
259         } else {
260           this.xStart = event ?
261               event.detail.x - this.containerMetrics.boundingRect.left :
262               this.containerMetrics.width / 2;
263           this.yStart = event ?
264               event.detail.y - this.containerMetrics.boundingRect.top :
265               this.containerMetrics.height / 2;
266         }
268         if (this.recenters) {
269           this.xEnd = xCenter;
270           this.yEnd = yCenter;
271           this.slideDistance = Utility.distance(
272             this.xStart, this.yStart, this.xEnd, this.yEnd
273           );
274         }
276         this.maxRadius = this.containerMetrics.furthestCornerDistanceFrom(
277           this.xStart,
278           this.yStart
279         );
281         this.waveContainer.style.top =
282           (this.containerMetrics.height - this.containerMetrics.size) / 2 + 'px';
283         this.waveContainer.style.left =
284           (this.containerMetrics.width - this.containerMetrics.size) / 2 + 'px';
286         this.waveContainer.style.width = this.containerMetrics.size + 'px';
287         this.waveContainer.style.height = this.containerMetrics.size + 'px';
288       },
290       /** @param {Event=} event */
291       upAction: function(event) {
292         if (!this.isMouseDown) {
293           return;
294         }
296         this.mouseUpStart = Utility.now();
297       },
299       remove: function() {
300         Polymer.dom(this.waveContainer.parentNode).removeChild(
301           this.waveContainer
302         );
303       }
304     };
306     Polymer({
307       is: 'paper-ripple',
309       behaviors: [
310         Polymer.IronA11yKeysBehavior
311       ],
313       properties: {
314         /**
315          * The initial opacity set on the wave.
316          *
317          * @attribute initialOpacity
318          * @type number
319          * @default 0.25
320          */
321         initialOpacity: {
322           type: Number,
323           value: 0.25
324         },
326         /**
327          * How fast (opacity per second) the wave fades out.
328          *
329          * @attribute opacityDecayVelocity
330          * @type number
331          * @default 0.8
332          */
333         opacityDecayVelocity: {
334           type: Number,
335           value: 0.8
336         },
338         /**
339          * If true, ripples will exhibit a gravitational pull towards
340          * the center of their container as they fade away.
341          *
342          * @attribute recenters
343          * @type boolean
344          * @default false
345          */
346         recenters: {
347           type: Boolean,
348           value: false
349         },
351         /**
352          * If true, ripples will center inside its container
353          *
354          * @attribute recenters
355          * @type boolean
356          * @default false
357          */
358         center: {
359           type: Boolean,
360           value: false
361         },
363         /**
364          * A list of the visual ripples.
365          *
366          * @attribute ripples
367          * @type Array
368          * @default []
369          */
370         ripples: {
371           type: Array,
372           value: function() {
373             return [];
374           }
375         },
377         /**
378          * True when there are visible ripples animating within the
379          * element.
380          */
381         animating: {
382           type: Boolean,
383           readOnly: true,
384           reflectToAttribute: true,
385           value: false
386         },
388         /**
389          * If true, the ripple will remain in the "down" state until `holdDown`
390          * is set to false again.
391          */
392         holdDown: {
393           type: Boolean,
394           value: false,
395           observer: '_holdDownChanged'
396         },
398         _animating: {
399           type: Boolean
400         },
402         _boundAnimate: {
403           type: Function,
404           value: function() {
405             return this.animate.bind(this);
406           }
407         }
408       },
410       get target () {
411         var ownerRoot = Polymer.dom(this).getOwnerRoot();
412         var target;
414         if (this.parentNode.nodeType == 11) { // DOCUMENT_FRAGMENT_NODE
415           target = ownerRoot.host;
416         } else {
417           target = this.parentNode;
418         }
420         return target;
421       },
423       keyBindings: {
424         'enter:keydown': '_onEnterKeydown',
425         'space:keydown': '_onSpaceKeydown',
426         'space:keyup': '_onSpaceKeyup'
427       },
429       attached: function() {
430         this.listen(this.target, 'up', 'upAction');
431         this.listen(this.target, 'down', 'downAction');
433         if (!this.target.hasAttribute('noink')) {
434           this.keyEventTarget = this.target;
435         }
436       },
438       get shouldKeepAnimating () {
439         for (var index = 0; index < this.ripples.length; ++index) {
440           if (!this.ripples[index].isAnimationComplete) {
441             return true;
442           }
443         }
445         return false;
446       },
448       simulatedRipple: function() {
449         this.downAction(null);
451         // Please see polymer/polymer#1305
452         this.async(function() {
453           this.upAction();
454         }, 1);
455       },
457       /** @param {Event=} event */
458       downAction: function(event) {
459         if (this.holdDown && this.ripples.length > 0) {
460           return;
461         }
463         var ripple = this.addRipple();
465         ripple.downAction(event);
467         if (!this._animating) {
468           this.animate();
469         }
470       },
472       /** @param {Event=} event */
473       upAction: function(event) {
474         if (this.holdDown) {
475           return;
476         }
478         this.ripples.forEach(function(ripple) {
479           ripple.upAction(event);
480         });
482         this.animate();
483       },
485       onAnimationComplete: function() {
486         this._animating = false;
487         this.$.background.style.backgroundColor = null;
488         this.fire('transitionend');
489       },
491       addRipple: function() {
492         var ripple = new Ripple(this);
494         Polymer.dom(this.$.waves).appendChild(ripple.waveContainer);
495         this.$.background.style.backgroundColor = ripple.color;
496         this.ripples.push(ripple);
498         this._setAnimating(true);
500         return ripple;
501       },
503       removeRipple: function(ripple) {
504         var rippleIndex = this.ripples.indexOf(ripple);
506         if (rippleIndex < 0) {
507           return;
508         }
510         this.ripples.splice(rippleIndex, 1);
512         ripple.remove();
514         if (!this.ripples.length) {
515           this._setAnimating(false);
516         }
517       },
519       animate: function() {
520         var index;
521         var ripple;
523         this._animating = true;
525         for (index = 0; index < this.ripples.length; ++index) {
526           ripple = this.ripples[index];
528           ripple.draw();
530           this.$.background.style.opacity = ripple.outerOpacity;
532           if (ripple.isOpacityFullyDecayed && !ripple.isRestingAtMaxRadius) {
533             this.removeRipple(ripple);
534           }
535         }
537         if (!this.shouldKeepAnimating && this.ripples.length === 0) {
538           this.onAnimationComplete();
539         } else {
540           window.requestAnimationFrame(this._boundAnimate);
541         }
542       },
544       _onEnterKeydown: function() {
545         this.downAction();
546         this.async(this.upAction, 1);
547       },
549       _onSpaceKeydown: function() {
550         this.downAction();
551       },
553       _onSpaceKeyup: function() {
554         this.upAction();
555       },
557       _holdDownChanged: function(holdDown) {
558         if (holdDown) {
559           this.downAction();
560         } else {
561           this.upAction();
562         }
563       }
564     });
565   })();