| <!-- |
| TODO(aghassemi) this is not good, we are declaring a dependency on something |
| outside of where we introduce that dependency. |
| |
| Write a browserify transform so we can know from JS code what web components |
| are needed to be bundled up. |
| So in js, when a component needs to use say paper-button, they just require it: |
| require('wc!paper-button'); |
| and the browserify transforms handles the bundling of wc dependencies at runtime. |
| --><html><head><style shim-shadowdom=""> |
| /******************************* |
| Flex Layout |
| *******************************/ |
| |
| html /deep/ [layout][horizontal], html /deep/ [layout][vertical] { |
| display: -ms-flexbox; |
| display: -webkit-flex; |
| display: flex; |
| } |
| |
| html /deep/ [layout][horizontal][inline], html /deep/ [layout][vertical][inline] { |
| display: -ms-inline-flexbox; |
| display: -webkit-inline-flex; |
| display: inline-flex; |
| } |
| |
| html /deep/ [layout][horizontal] { |
| -ms-flex-direction: row; |
| -webkit-flex-direction: row; |
| flex-direction: row; |
| } |
| |
| html /deep/ [layout][horizontal][reverse] { |
| -ms-flex-direction: row-reverse; |
| -webkit-flex-direction: row-reverse; |
| flex-direction: row-reverse; |
| } |
| |
| html /deep/ [layout][vertical] { |
| -ms-flex-direction: column; |
| -webkit-flex-direction: column; |
| flex-direction: column; |
| } |
| |
| html /deep/ [layout][vertical][reverse] { |
| -ms-flex-direction: column-reverse; |
| -webkit-flex-direction: column-reverse; |
| flex-direction: column-reverse; |
| } |
| |
| html /deep/ [layout][wrap] { |
| -ms-flex-wrap: wrap; |
| -webkit-flex-wrap: wrap; |
| flex-wrap: wrap; |
| } |
| |
| html /deep/ [layout][wrap-reverse] { |
| -ms-flex-wrap: wrap-reverse; |
| -webkit-flex-wrap: wrap-reverse; |
| flex-wrap: wrap-reverse; |
| } |
| |
| html /deep/ [flex] { |
| -ms-flex: 1 1 0.000000001px; |
| -webkit-flex: 1; |
| flex: 1; |
| -webkit-flex-basis: 0.000000001px; |
| flex-basis: 0.000000001px; |
| } |
| |
| html /deep/ [vertical][layout] > [flex][auto-vertical], html /deep/ [vertical][layout]::shadow [flex][auto-vertical] { |
| -ms-flex: 1 1 auto; |
| -webkit-flex-basis: auto; |
| flex-basis: auto; |
| } |
| |
| html /deep/ [flex][auto] { |
| -ms-flex: 1 1 auto; |
| -webkit-flex-basis: auto; |
| flex-basis: auto; |
| } |
| |
| html /deep/ [flex][none] { |
| -ms-flex: none; |
| -webkit-flex: none; |
| flex: none; |
| } |
| |
| html /deep/ [flex][one] { |
| -ms-flex: 1; |
| -webkit-flex: 1; |
| flex: 1; |
| } |
| |
| html /deep/ [flex][two] { |
| -ms-flex: 2; |
| -webkit-flex: 2; |
| flex: 2; |
| } |
| |
| html /deep/ [flex][three] { |
| -ms-flex: 3; |
| -webkit-flex: 3; |
| flex: 3; |
| } |
| |
| html /deep/ [flex][four] { |
| -ms-flex: 4; |
| -webkit-flex: 4; |
| flex: 4; |
| } |
| |
| html /deep/ [flex][five] { |
| -ms-flex: 5; |
| -webkit-flex: 5; |
| flex: 5; |
| } |
| |
| html /deep/ [flex][six] { |
| -ms-flex: 6; |
| -webkit-flex: 6; |
| flex: 6; |
| } |
| |
| html /deep/ [flex][seven] { |
| -ms-flex: 7; |
| -webkit-flex: 7; |
| flex: 7; |
| } |
| |
| html /deep/ [flex][eight] { |
| -ms-flex: 8; |
| -webkit-flex: 8; |
| flex: 8; |
| } |
| |
| html /deep/ [flex][nine] { |
| -ms-flex: 9; |
| -webkit-flex: 9; |
| flex: 9; |
| } |
| |
| html /deep/ [flex][ten] { |
| -ms-flex: 10; |
| -webkit-flex: 10; |
| flex: 10; |
| } |
| |
| html /deep/ [flex][eleven] { |
| -ms-flex: 11; |
| -webkit-flex: 11; |
| flex: 11; |
| } |
| |
| html /deep/ [flex][twelve] { |
| -ms-flex: 12; |
| -webkit-flex: 12; |
| flex: 12; |
| } |
| |
| /* alignment in cross axis */ |
| |
| html /deep/ [layout][start] { |
| -ms-flex-align: start; |
| -webkit-align-items: flex-start; |
| align-items: flex-start; |
| } |
| |
| html /deep/ [layout][center], html /deep/ [layout][center-center] { |
| -ms-flex-align: center; |
| -webkit-align-items: center; |
| align-items: center; |
| } |
| |
| html /deep/ [layout][end] { |
| -ms-flex-align: end; |
| -webkit-align-items: flex-end; |
| align-items: flex-end; |
| } |
| |
| /* alignment in main axis */ |
| |
| html /deep/ [layout][start-justified] { |
| -ms-flex-pack: start; |
| -webkit-justify-content: flex-start; |
| justify-content: flex-start; |
| } |
| |
| html /deep/ [layout][center-justified], html /deep/ [layout][center-center] { |
| -ms-flex-pack: center; |
| -webkit-justify-content: center; |
| justify-content: center; |
| } |
| |
| html /deep/ [layout][end-justified] { |
| -ms-flex-pack: end; |
| -webkit-justify-content: flex-end; |
| justify-content: flex-end; |
| } |
| |
| html /deep/ [layout][around-justified] { |
| -ms-flex-pack: distribute; |
| -webkit-justify-content: space-around; |
| justify-content: space-around; |
| } |
| |
| html /deep/ [layout][justified] { |
| -ms-flex-pack: justify; |
| -webkit-justify-content: space-between; |
| justify-content: space-between; |
| } |
| |
| /* self alignment */ |
| |
| html /deep/ [self-start] { |
| -ms-align-self: flex-start; |
| -webkit-align-self: flex-start; |
| align-self: flex-start; |
| } |
| |
| html /deep/ [self-center] { |
| -ms-align-self: center; |
| -webkit-align-self: center; |
| align-self: center; |
| } |
| |
| html /deep/ [self-end] { |
| -ms-align-self: flex-end; |
| -webkit-align-self: flex-end; |
| align-self: flex-end; |
| } |
| |
| html /deep/ [self-stretch] { |
| -ms-align-self: stretch; |
| -webkit-align-self: stretch; |
| align-self: stretch; |
| } |
| |
| /******************************* |
| Other Layout |
| *******************************/ |
| |
| html /deep/ [block] { |
| display: block; |
| } |
| |
| /* ie support for hidden */ |
| html /deep/ [hidden] { |
| display: none !important; |
| } |
| |
| html /deep/ [relative] { |
| position: relative; |
| } |
| |
| html /deep/ [fit] { |
| position: absolute; |
| top: 0; |
| right: 0; |
| bottom: 0; |
| left: 0; |
| } |
| |
| body[fullbleed] { |
| margin: 0; |
| height: 100vh; |
| } |
| |
| /******************************* |
| Other |
| *******************************/ |
| |
| html /deep/ [segment], html /deep/ segment { |
| display: block; |
| position: relative; |
| -webkit-box-sizing: border-box; |
| -ms-box-sizing: border-box; |
| box-sizing: border-box; |
| margin: 1em 0.5em; |
| padding: 1em; |
| background-color: white; |
| -webkit-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1); |
| box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1); |
| border-radius: 5px 5px 5px 5px; |
| } |
| |
| </style> |
| |
| <script>/** |
| * @license |
| * Copyright (c) 2014 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 |
| */ |
| // @version 0.5.4 |
| window.PolymerGestures = {}; |
| |
| (function(scope) { |
| var hasFullPath = false; |
| |
| // test for full event path support |
| var pathTest = document.createElement('meta'); |
| if (pathTest.createShadowRoot) { |
| var sr = pathTest.createShadowRoot(); |
| var s = document.createElement('span'); |
| sr.appendChild(s); |
| pathTest.addEventListener('testpath', function(ev) { |
| if (ev.path) { |
| // if the span is in the event path, then path[0] is the real source for all events |
| hasFullPath = ev.path[0] === s; |
| } |
| ev.stopPropagation(); |
| }); |
| var ev = new CustomEvent('testpath', {bubbles: true}); |
| // must add node to DOM to trigger event listener |
| document.head.appendChild(pathTest); |
| s.dispatchEvent(ev); |
| pathTest.parentNode.removeChild(pathTest); |
| sr = s = null; |
| } |
| pathTest = null; |
| |
| var target = { |
| shadow: function(inEl) { |
| if (inEl) { |
| return inEl.shadowRoot || inEl.webkitShadowRoot; |
| } |
| }, |
| canTarget: function(shadow) { |
| return shadow && Boolean(shadow.elementFromPoint); |
| }, |
| targetingShadow: function(inEl) { |
| var s = this.shadow(inEl); |
| if (this.canTarget(s)) { |
| return s; |
| } |
| }, |
| olderShadow: function(shadow) { |
| var os = shadow.olderShadowRoot; |
| if (!os) { |
| var se = shadow.querySelector('shadow'); |
| if (se) { |
| os = se.olderShadowRoot; |
| } |
| } |
| return os; |
| }, |
| allShadows: function(element) { |
| var shadows = [], s = this.shadow(element); |
| while(s) { |
| shadows.push(s); |
| s = this.olderShadow(s); |
| } |
| return shadows; |
| }, |
| searchRoot: function(inRoot, x, y) { |
| var t, st, sr, os; |
| if (inRoot) { |
| t = inRoot.elementFromPoint(x, y); |
| if (t) { |
| // found element, check if it has a ShadowRoot |
| sr = this.targetingShadow(t); |
| } else if (inRoot !== document) { |
| // check for sibling roots |
| sr = this.olderShadow(inRoot); |
| } |
| // search other roots, fall back to light dom element |
| return this.searchRoot(sr, x, y) || t; |
| } |
| }, |
| owner: function(element) { |
| if (!element) { |
| return document; |
| } |
| var s = element; |
| // walk up until you hit the shadow root or document |
| while (s.parentNode) { |
| s = s.parentNode; |
| } |
| // the owner element is expected to be a Document or ShadowRoot |
| if (s.nodeType != Node.DOCUMENT_NODE && s.nodeType != Node.DOCUMENT_FRAGMENT_NODE) { |
| s = document; |
| } |
| return s; |
| }, |
| findTarget: function(inEvent) { |
| if (hasFullPath && inEvent.path && inEvent.path.length) { |
| return inEvent.path[0]; |
| } |
| var x = inEvent.clientX, y = inEvent.clientY; |
| // if the listener is in the shadow root, it is much faster to start there |
| var s = this.owner(inEvent.target); |
| // if x, y is not in this root, fall back to document search |
| if (!s.elementFromPoint(x, y)) { |
| s = document; |
| } |
| return this.searchRoot(s, x, y); |
| }, |
| findTouchAction: function(inEvent) { |
| var n; |
| if (hasFullPath && inEvent.path && inEvent.path.length) { |
| var path = inEvent.path; |
| for (var i = 0; i < path.length; i++) { |
| n = path[i]; |
| if (n.nodeType === Node.ELEMENT_NODE && n.hasAttribute('touch-action')) { |
| return n.getAttribute('touch-action'); |
| } |
| } |
| } else { |
| n = inEvent.target; |
| while(n) { |
| if (n.nodeType === Node.ELEMENT_NODE && n.hasAttribute('touch-action')) { |
| return n.getAttribute('touch-action'); |
| } |
| n = n.parentNode || n.host; |
| } |
| } |
| // auto is default |
| return "auto"; |
| }, |
| LCA: function(a, b) { |
| if (a === b) { |
| return a; |
| } |
| if (a && !b) { |
| return a; |
| } |
| if (b && !a) { |
| return b; |
| } |
| if (!b && !a) { |
| return document; |
| } |
| // fast case, a is a direct descendant of b or vice versa |
| if (a.contains && a.contains(b)) { |
| return a; |
| } |
| if (b.contains && b.contains(a)) { |
| return b; |
| } |
| var adepth = this.depth(a); |
| var bdepth = this.depth(b); |
| var d = adepth - bdepth; |
| if (d >= 0) { |
| a = this.walk(a, d); |
| } else { |
| b = this.walk(b, -d); |
| } |
| while (a && b && a !== b) { |
| a = a.parentNode || a.host; |
| b = b.parentNode || b.host; |
| } |
| return a; |
| }, |
| walk: function(n, u) { |
| for (var i = 0; n && (i < u); i++) { |
| n = n.parentNode || n.host; |
| } |
| return n; |
| }, |
| depth: function(n) { |
| var d = 0; |
| while(n) { |
| d++; |
| n = n.parentNode || n.host; |
| } |
| return d; |
| }, |
| deepContains: function(a, b) { |
| var common = this.LCA(a, b); |
| // if a is the common ancestor, it must "deeply" contain b |
| return common === a; |
| }, |
| insideNode: function(node, x, y) { |
| var rect = node.getBoundingClientRect(); |
| return (rect.left <= x) && (x <= rect.right) && (rect.top <= y) && (y <= rect.bottom); |
| }, |
| path: function(event) { |
| var p; |
| if (hasFullPath && event.path && event.path.length) { |
| p = event.path; |
| } else { |
| p = []; |
| var n = this.findTarget(event); |
| while (n) { |
| p.push(n); |
| n = n.parentNode || n.host; |
| } |
| } |
| return p; |
| } |
| }; |
| scope.targetFinding = target; |
| /** |
| * Given an event, finds the "deepest" node that could have been the original target before ShadowDOM retargetting |
| * |
| * @param {Event} Event An event object with clientX and clientY properties |
| * @return {Element} The probable event origninator |
| */ |
| scope.findTarget = target.findTarget.bind(target); |
| /** |
| * Determines if the "container" node deeply contains the "containee" node, including situations where the "containee" is contained by one or more ShadowDOM |
| * roots. |
| * |
| * @param {Node} container |
| * @param {Node} containee |
| * @return {Boolean} |
| */ |
| scope.deepContains = target.deepContains.bind(target); |
| |
| /** |
| * Determines if the x/y position is inside the given node. |
| * |
| * Example: |
| * |
| * function upHandler(event) { |
| * var innode = PolymerGestures.insideNode(event.target, event.clientX, event.clientY); |
| * if (innode) { |
| * // wait for tap? |
| * } else { |
| * // tap will never happen |
| * } |
| * } |
| * |
| * @param {Node} node |
| * @param {Number} x Screen X position |
| * @param {Number} y screen Y position |
| * @return {Boolean} |
| */ |
| scope.insideNode = target.insideNode; |
| |
| })(window.PolymerGestures); |
| |
| (function() { |
| function shadowSelector(v) { |
| return 'html /deep/ ' + selector(v); |
| } |
| function selector(v) { |
| return '[touch-action="' + v + '"]'; |
| } |
| function rule(v) { |
| return '{ -ms-touch-action: ' + v + '; touch-action: ' + v + ';}'; |
| } |
| var attrib2css = [ |
| 'none', |
| 'auto', |
| 'pan-x', |
| 'pan-y', |
| { |
| rule: 'pan-x pan-y', |
| selectors: [ |
| 'pan-x pan-y', |
| 'pan-y pan-x' |
| ] |
| }, |
| 'manipulation' |
| ]; |
| var styles = ''; |
| // only install stylesheet if the browser has touch action support |
| var hasTouchAction = typeof document.head.style.touchAction === 'string'; |
| // only add shadow selectors if shadowdom is supported |
| var hasShadowRoot = !window.ShadowDOMPolyfill && document.head.createShadowRoot; |
| |
| if (hasTouchAction) { |
| attrib2css.forEach(function(r) { |
| if (String(r) === r) { |
| styles += selector(r) + rule(r) + '\n'; |
| if (hasShadowRoot) { |
| styles += shadowSelector(r) + rule(r) + '\n'; |
| } |
| } else { |
| styles += r.selectors.map(selector) + rule(r.rule) + '\n'; |
| if (hasShadowRoot) { |
| styles += r.selectors.map(shadowSelector) + rule(r.rule) + '\n'; |
| } |
| } |
| }); |
| |
| var el = document.createElement('style'); |
| el.textContent = styles; |
| document.head.appendChild(el); |
| } |
| })(); |
| |
| /** |
| * This is the constructor for new PointerEvents. |
| * |
| * New Pointer Events must be given a type, and an optional dictionary of |
| * initialization properties. |
| * |
| * Due to certain platform requirements, events returned from the constructor |
| * identify as MouseEvents. |
| * |
| * @constructor |
| * @param {String} inType The type of the event to create. |
| * @param {Object} [inDict] An optional dictionary of initial event properties. |
| * @return {Event} A new PointerEvent of type `inType` and initialized with properties from `inDict`. |
| */ |
| (function(scope) { |
| |
| var MOUSE_PROPS = [ |
| 'bubbles', |
| 'cancelable', |
| 'view', |
| 'detail', |
| 'screenX', |
| 'screenY', |
| 'clientX', |
| 'clientY', |
| 'ctrlKey', |
| 'altKey', |
| 'shiftKey', |
| 'metaKey', |
| 'button', |
| 'relatedTarget', |
| 'pageX', |
| 'pageY' |
| ]; |
| |
| var MOUSE_DEFAULTS = [ |
| false, |
| false, |
| null, |
| null, |
| 0, |
| 0, |
| 0, |
| 0, |
| false, |
| false, |
| false, |
| false, |
| 0, |
| null, |
| 0, |
| 0 |
| ]; |
| |
| var NOP_FACTORY = function(){ return function(){}; }; |
| |
| var eventFactory = { |
| // TODO(dfreedm): this is overridden by tap recognizer, needs review |
| preventTap: NOP_FACTORY, |
| makeBaseEvent: function(inType, inDict) { |
| var e = document.createEvent('Event'); |
| e.initEvent(inType, inDict.bubbles || false, inDict.cancelable || false); |
| e.preventTap = eventFactory.preventTap(e); |
| return e; |
| }, |
| makeGestureEvent: function(inType, inDict) { |
| inDict = inDict || Object.create(null); |
| |
| var e = this.makeBaseEvent(inType, inDict); |
| for (var i = 0, keys = Object.keys(inDict), k; i < keys.length; i++) { |
| k = keys[i]; |
| e[k] = inDict[k]; |
| } |
| return e; |
| }, |
| makePointerEvent: function(inType, inDict) { |
| inDict = inDict || Object.create(null); |
| |
| var e = this.makeBaseEvent(inType, inDict); |
| // define inherited MouseEvent properties |
| for(var i = 0, p; i < MOUSE_PROPS.length; i++) { |
| p = MOUSE_PROPS[i]; |
| e[p] = inDict[p] || MOUSE_DEFAULTS[i]; |
| } |
| e.buttons = inDict.buttons || 0; |
| |
| // Spec requires that pointers without pressure specified use 0.5 for down |
| // state and 0 for up state. |
| var pressure = 0; |
| if (inDict.pressure) { |
| pressure = inDict.pressure; |
| } else { |
| pressure = e.buttons ? 0.5 : 0; |
| } |
| |
| // add x/y properties aliased to clientX/Y |
| e.x = e.clientX; |
| e.y = e.clientY; |
| |
| // define the properties of the PointerEvent interface |
| e.pointerId = inDict.pointerId || 0; |
| e.width = inDict.width || 0; |
| e.height = inDict.height || 0; |
| e.pressure = pressure; |
| e.tiltX = inDict.tiltX || 0; |
| e.tiltY = inDict.tiltY || 0; |
| e.pointerType = inDict.pointerType || ''; |
| e.hwTimestamp = inDict.hwTimestamp || 0; |
| e.isPrimary = inDict.isPrimary || false; |
| e._source = inDict._source || ''; |
| return e; |
| } |
| }; |
| |
| scope.eventFactory = eventFactory; |
| })(window.PolymerGestures); |
| |
| /** |
| * This module implements an map of pointer states |
| */ |
| (function(scope) { |
| var USE_MAP = window.Map && window.Map.prototype.forEach; |
| var POINTERS_FN = function(){ return this.size; }; |
| function PointerMap() { |
| if (USE_MAP) { |
| var m = new Map(); |
| m.pointers = POINTERS_FN; |
| return m; |
| } else { |
| this.keys = []; |
| this.values = []; |
| } |
| } |
| |
| PointerMap.prototype = { |
| set: function(inId, inEvent) { |
| var i = this.keys.indexOf(inId); |
| if (i > -1) { |
| this.values[i] = inEvent; |
| } else { |
| this.keys.push(inId); |
| this.values.push(inEvent); |
| } |
| }, |
| has: function(inId) { |
| return this.keys.indexOf(inId) > -1; |
| }, |
| 'delete': function(inId) { |
| var i = this.keys.indexOf(inId); |
| if (i > -1) { |
| this.keys.splice(i, 1); |
| this.values.splice(i, 1); |
| } |
| }, |
| get: function(inId) { |
| var i = this.keys.indexOf(inId); |
| return this.values[i]; |
| }, |
| clear: function() { |
| this.keys.length = 0; |
| this.values.length = 0; |
| }, |
| // return value, key, map |
| forEach: function(callback, thisArg) { |
| this.values.forEach(function(v, i) { |
| callback.call(thisArg, v, this.keys[i], this); |
| }, this); |
| }, |
| pointers: function() { |
| return this.keys.length; |
| } |
| }; |
| |
| scope.PointerMap = PointerMap; |
| })(window.PolymerGestures); |
| |
| (function(scope) { |
| var CLONE_PROPS = [ |
| // MouseEvent |
| 'bubbles', |
| 'cancelable', |
| 'view', |
| 'detail', |
| 'screenX', |
| 'screenY', |
| 'clientX', |
| 'clientY', |
| 'ctrlKey', |
| 'altKey', |
| 'shiftKey', |
| 'metaKey', |
| 'button', |
| 'relatedTarget', |
| // DOM Level 3 |
| 'buttons', |
| // PointerEvent |
| 'pointerId', |
| 'width', |
| 'height', |
| 'pressure', |
| 'tiltX', |
| 'tiltY', |
| 'pointerType', |
| 'hwTimestamp', |
| 'isPrimary', |
| // event instance |
| 'type', |
| 'target', |
| 'currentTarget', |
| 'which', |
| 'pageX', |
| 'pageY', |
| 'timeStamp', |
| // gesture addons |
| 'preventTap', |
| 'tapPrevented', |
| '_source' |
| ]; |
| |
| var CLONE_DEFAULTS = [ |
| // MouseEvent |
| false, |
| false, |
| null, |
| null, |
| 0, |
| 0, |
| 0, |
| 0, |
| false, |
| false, |
| false, |
| false, |
| 0, |
| null, |
| // DOM Level 3 |
| 0, |
| // PointerEvent |
| 0, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0, |
| '', |
| 0, |
| false, |
| // event instance |
| '', |
| null, |
| null, |
| 0, |
| 0, |
| 0, |
| 0, |
| function(){}, |
| false |
| ]; |
| |
| var HAS_SVG_INSTANCE = (typeof SVGElementInstance !== 'undefined'); |
| |
| var eventFactory = scope.eventFactory; |
| |
| // set of recognizers to run for the currently handled event |
| var currentGestures; |
| |
| /** |
| * This module is for normalizing events. Mouse and Touch events will be |
| * collected here, and fire PointerEvents that have the same semantics, no |
| * matter the source. |
| * Events fired: |
| * - pointerdown: a pointing is added |
| * - pointerup: a pointer is removed |
| * - pointermove: a pointer is moved |
| * - pointerover: a pointer crosses into an element |
| * - pointerout: a pointer leaves an element |
| * - pointercancel: a pointer will no longer generate events |
| */ |
| var dispatcher = { |
| IS_IOS: false, |
| pointermap: new scope.PointerMap(), |
| requiredGestures: new scope.PointerMap(), |
| eventMap: Object.create(null), |
| // Scope objects for native events. |
| // This exists for ease of testing. |
| eventSources: Object.create(null), |
| eventSourceList: [], |
| gestures: [], |
| // map gesture event -> {listeners: int, index: gestures[int]} |
| dependencyMap: { |
| // make sure down and up are in the map to trigger "register" |
| down: {listeners: 0, index: -1}, |
| up: {listeners: 0, index: -1} |
| }, |
| gestureQueue: [], |
| /** |
| * Add a new event source that will generate pointer events. |
| * |
| * `inSource` must contain an array of event names named `events`, and |
| * functions with the names specified in the `events` array. |
| * @param {string} name A name for the event source |
| * @param {Object} source A new source of platform events. |
| */ |
| registerSource: function(name, source) { |
| var s = source; |
| var newEvents = s.events; |
| if (newEvents) { |
| newEvents.forEach(function(e) { |
| if (s[e]) { |
| this.eventMap[e] = s[e].bind(s); |
| } |
| }, this); |
| this.eventSources[name] = s; |
| this.eventSourceList.push(s); |
| } |
| }, |
| registerGesture: function(name, source) { |
| var obj = Object.create(null); |
| obj.listeners = 0; |
| obj.index = this.gestures.length; |
| for (var i = 0, g; i < source.exposes.length; i++) { |
| g = source.exposes[i].toLowerCase(); |
| this.dependencyMap[g] = obj; |
| } |
| this.gestures.push(source); |
| }, |
| register: function(element, initial) { |
| var l = this.eventSourceList.length; |
| for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { |
| // call eventsource register |
| es.register.call(es, element, initial); |
| } |
| }, |
| unregister: function(element) { |
| var l = this.eventSourceList.length; |
| for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { |
| // call eventsource register |
| es.unregister.call(es, element); |
| } |
| }, |
| // EVENTS |
| down: function(inEvent) { |
| this.requiredGestures.set(inEvent.pointerId, currentGestures); |
| this.fireEvent('down', inEvent); |
| }, |
| move: function(inEvent) { |
| // pipe move events into gesture queue directly |
| inEvent.type = 'move'; |
| this.fillGestureQueue(inEvent); |
| }, |
| up: function(inEvent) { |
| this.fireEvent('up', inEvent); |
| this.requiredGestures.delete(inEvent.pointerId); |
| }, |
| cancel: function(inEvent) { |
| inEvent.tapPrevented = true; |
| this.fireEvent('up', inEvent); |
| this.requiredGestures.delete(inEvent.pointerId); |
| }, |
| addGestureDependency: function(node, currentGestures) { |
| var gesturesWanted = node._pgEvents; |
| if (gesturesWanted && currentGestures) { |
| var gk = Object.keys(gesturesWanted); |
| for (var i = 0, r, ri, g; i < gk.length; i++) { |
| // gesture |
| g = gk[i]; |
| if (gesturesWanted[g] > 0) { |
| // lookup gesture recognizer |
| r = this.dependencyMap[g]; |
| // recognizer index |
| ri = r ? r.index : -1; |
| currentGestures[ri] = true; |
| } |
| } |
| } |
| }, |
| // LISTENER LOGIC |
| eventHandler: function(inEvent) { |
| // This is used to prevent multiple dispatch of events from |
| // platform events. This can happen when two elements in different scopes |
| // are set up to create pointer events, which is relevant to Shadow DOM. |
| |
| var type = inEvent.type; |
| |
| // only generate the list of desired events on "down" |
| if (type === 'touchstart' || type === 'mousedown' || type === 'pointerdown' || type === 'MSPointerDown') { |
| if (!inEvent._handledByPG) { |
| currentGestures = {}; |
| } |
| |
| // in IOS mode, there is only a listener on the document, so this is not re-entrant |
| if (this.IS_IOS) { |
| var ev = inEvent; |
| if (type === 'touchstart') { |
| var ct = inEvent.changedTouches[0]; |
| // set up a fake event to give to the path builder |
| ev = {target: inEvent.target, clientX: ct.clientX, clientY: ct.clientY, path: inEvent.path}; |
| } |
| // use event path if available, otherwise build a path from target finding |
| var nodes = inEvent.path || scope.targetFinding.path(ev); |
| for (var i = 0, n; i < nodes.length; i++) { |
| n = nodes[i]; |
| this.addGestureDependency(n, currentGestures); |
| } |
| } else { |
| this.addGestureDependency(inEvent.currentTarget, currentGestures); |
| } |
| } |
| |
| if (inEvent._handledByPG) { |
| return; |
| } |
| var fn = this.eventMap && this.eventMap[type]; |
| if (fn) { |
| fn(inEvent); |
| } |
| inEvent._handledByPG = true; |
| }, |
| // set up event listeners |
| listen: function(target, events) { |
| for (var i = 0, l = events.length, e; (i < l) && (e = events[i]); i++) { |
| this.addEvent(target, e); |
| } |
| }, |
| // remove event listeners |
| unlisten: function(target, events) { |
| for (var i = 0, l = events.length, e; (i < l) && (e = events[i]); i++) { |
| this.removeEvent(target, e); |
| } |
| }, |
| addEvent: function(target, eventName) { |
| target.addEventListener(eventName, this.boundHandler); |
| }, |
| removeEvent: function(target, eventName) { |
| target.removeEventListener(eventName, this.boundHandler); |
| }, |
| // EVENT CREATION AND TRACKING |
| /** |
| * Creates a new Event of type `inType`, based on the information in |
| * `inEvent`. |
| * |
| * @param {string} inType A string representing the type of event to create |
| * @param {Event} inEvent A platform event with a target |
| * @return {Event} A PointerEvent of type `inType` |
| */ |
| makeEvent: function(inType, inEvent) { |
| var e = eventFactory.makePointerEvent(inType, inEvent); |
| e.preventDefault = inEvent.preventDefault; |
| e.tapPrevented = inEvent.tapPrevented; |
| e._target = e._target || inEvent.target; |
| return e; |
| }, |
| // make and dispatch an event in one call |
| fireEvent: function(inType, inEvent) { |
| var e = this.makeEvent(inType, inEvent); |
| return this.dispatchEvent(e); |
| }, |
| /** |
| * Returns a snapshot of inEvent, with writable properties. |
| * |
| * @param {Event} inEvent An event that contains properties to copy. |
| * @return {Object} An object containing shallow copies of `inEvent`'s |
| * properties. |
| */ |
| cloneEvent: function(inEvent) { |
| var eventCopy = Object.create(null), p; |
| for (var i = 0; i < CLONE_PROPS.length; i++) { |
| p = CLONE_PROPS[i]; |
| eventCopy[p] = inEvent[p] || CLONE_DEFAULTS[i]; |
| // Work around SVGInstanceElement shadow tree |
| // Return the <use> element that is represented by the instance for Safari, Chrome, IE. |
| // This is the behavior implemented by Firefox. |
| if (p === 'target' || p === 'relatedTarget') { |
| if (HAS_SVG_INSTANCE && eventCopy[p] instanceof SVGElementInstance) { |
| eventCopy[p] = eventCopy[p].correspondingUseElement; |
| } |
| } |
| } |
| // keep the semantics of preventDefault |
| eventCopy.preventDefault = function() { |
| inEvent.preventDefault(); |
| }; |
| return eventCopy; |
| }, |
| /** |
| * Dispatches the event to its target. |
| * |
| * @param {Event} inEvent The event to be dispatched. |
| * @return {Boolean} True if an event handler returns true, false otherwise. |
| */ |
| dispatchEvent: function(inEvent) { |
| var t = inEvent._target; |
| if (t) { |
| t.dispatchEvent(inEvent); |
| // clone the event for the gesture system to process |
| // clone after dispatch to pick up gesture prevention code |
| var clone = this.cloneEvent(inEvent); |
| clone.target = t; |
| this.fillGestureQueue(clone); |
| } |
| }, |
| gestureTrigger: function() { |
| // process the gesture queue |
| for (var i = 0, e, rg; i < this.gestureQueue.length; i++) { |
| e = this.gestureQueue[i]; |
| rg = e._requiredGestures; |
| if (rg) { |
| for (var j = 0, g, fn; j < this.gestures.length; j++) { |
| // only run recognizer if an element in the source event's path is listening for those gestures |
| if (rg[j]) { |
| g = this.gestures[j]; |
| fn = g[e.type]; |
| if (fn) { |
| fn.call(g, e); |
| } |
| } |
| } |
| } |
| } |
| this.gestureQueue.length = 0; |
| }, |
| fillGestureQueue: function(ev) { |
| // only trigger the gesture queue once |
| if (!this.gestureQueue.length) { |
| requestAnimationFrame(this.boundGestureTrigger); |
| } |
| ev._requiredGestures = this.requiredGestures.get(ev.pointerId); |
| this.gestureQueue.push(ev); |
| } |
| }; |
| dispatcher.boundHandler = dispatcher.eventHandler.bind(dispatcher); |
| dispatcher.boundGestureTrigger = dispatcher.gestureTrigger.bind(dispatcher); |
| scope.dispatcher = dispatcher; |
| |
| /** |
| * Listen for `gesture` on `node` with the `handler` function |
| * |
| * If `handler` is the first listener for `gesture`, the underlying gesture recognizer is then enabled. |
| * |
| * @param {Element} node |
| * @param {string} gesture |
| * @return Boolean `gesture` is a valid gesture |
| */ |
| scope.activateGesture = function(node, gesture) { |
| var g = gesture.toLowerCase(); |
| var dep = dispatcher.dependencyMap[g]; |
| if (dep) { |
| var recognizer = dispatcher.gestures[dep.index]; |
| if (!node._pgListeners) { |
| dispatcher.register(node); |
| node._pgListeners = 0; |
| } |
| // TODO(dfreedm): re-evaluate bookkeeping to avoid using attributes |
| if (recognizer) { |
| var touchAction = recognizer.defaultActions && recognizer.defaultActions[g]; |
| var actionNode; |
| switch(node.nodeType) { |
| case Node.ELEMENT_NODE: |
| actionNode = node; |
| break; |
| case Node.DOCUMENT_FRAGMENT_NODE: |
| actionNode = node.host; |
| break; |
| default: |
| actionNode = null; |
| break; |
| } |
| if (touchAction && actionNode && !actionNode.hasAttribute('touch-action')) { |
| actionNode.setAttribute('touch-action', touchAction); |
| } |
| } |
| if (!node._pgEvents) { |
| node._pgEvents = {}; |
| } |
| node._pgEvents[g] = (node._pgEvents[g] || 0) + 1; |
| node._pgListeners++; |
| } |
| return Boolean(dep); |
| }; |
| |
| /** |
| * |
| * Listen for `gesture` from `node` with `handler` function. |
| * |
| * @param {Element} node |
| * @param {string} gesture |
| * @param {Function} handler |
| * @param {Boolean} capture |
| */ |
| scope.addEventListener = function(node, gesture, handler, capture) { |
| if (handler) { |
| scope.activateGesture(node, gesture); |
| node.addEventListener(gesture, handler, capture); |
| } |
| }; |
| |
| /** |
| * Tears down the gesture configuration for `node` |
| * |
| * If `handler` is the last listener for `gesture`, the underlying gesture recognizer is disabled. |
| * |
| * @param {Element} node |
| * @param {string} gesture |
| * @return Boolean `gesture` is a valid gesture |
| */ |
| scope.deactivateGesture = function(node, gesture) { |
| var g = gesture.toLowerCase(); |
| var dep = dispatcher.dependencyMap[g]; |
| if (dep) { |
| if (node._pgListeners > 0) { |
| node._pgListeners--; |
| } |
| if (node._pgListeners === 0) { |
| dispatcher.unregister(node); |
| } |
| if (node._pgEvents) { |
| if (node._pgEvents[g] > 0) { |
| node._pgEvents[g]--; |
| } else { |
| node._pgEvents[g] = 0; |
| } |
| } |
| } |
| return Boolean(dep); |
| }; |
| |
| /** |
| * Stop listening for `gesture` from `node` with `handler` function. |
| * |
| * @param {Element} node |
| * @param {string} gesture |
| * @param {Function} handler |
| * @param {Boolean} capture |
| */ |
| scope.removeEventListener = function(node, gesture, handler, capture) { |
| if (handler) { |
| scope.deactivateGesture(node, gesture); |
| node.removeEventListener(gesture, handler, capture); |
| } |
| }; |
| })(window.PolymerGestures); |
| |
| (function(scope) { |
| var dispatcher = scope.dispatcher; |
| var pointermap = dispatcher.pointermap; |
| // radius around touchend that swallows mouse events |
| var DEDUP_DIST = 25; |
| |
| var WHICH_TO_BUTTONS = [0, 1, 4, 2]; |
| |
| var currentButtons = 0; |
| |
| var FIREFOX_LINUX = /Linux.*Firefox\//i; |
| |
| var HAS_BUTTONS = (function() { |
| // firefox on linux returns spec-incorrect values for mouseup.buttons |
| // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent.buttons#See_also |
| // https://codereview.chromium.org/727593003/#msg16 |
| if (FIREFOX_LINUX.test(navigator.userAgent)) { |
| return false; |
| } |
| try { |
| return new MouseEvent('test', {buttons: 1}).buttons === 1; |
| } catch (e) { |
| return false; |
| } |
| })(); |
| |
| // handler block for native mouse events |
| var mouseEvents = { |
| POINTER_ID: 1, |
| POINTER_TYPE: 'mouse', |
| events: [ |
| 'mousedown', |
| 'mousemove', |
| 'mouseup' |
| ], |
| exposes: [ |
| 'down', |
| 'up', |
| 'move' |
| ], |
| register: function(target) { |
| dispatcher.listen(target, this.events); |
| }, |
| unregister: function(target) { |
| if (target === document) { |
| return; |
| } |
| dispatcher.unlisten(target, this.events); |
| }, |
| lastTouches: [], |
| // collide with the global mouse listener |
| isEventSimulatedFromTouch: function(inEvent) { |
| var lts = this.lastTouches; |
| var x = inEvent.clientX, y = inEvent.clientY; |
| for (var i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) { |
| // simulated mouse events will be swallowed near a primary touchend |
| var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y); |
| if (dx <= DEDUP_DIST && dy <= DEDUP_DIST) { |
| return true; |
| } |
| } |
| }, |
| prepareEvent: function(inEvent) { |
| var e = dispatcher.cloneEvent(inEvent); |
| e.pointerId = this.POINTER_ID; |
| e.isPrimary = true; |
| e.pointerType = this.POINTER_TYPE; |
| e._source = 'mouse'; |
| if (!HAS_BUTTONS) { |
| var type = inEvent.type; |
| var bit = WHICH_TO_BUTTONS[inEvent.which] || 0; |
| if (type === 'mousedown') { |
| currentButtons |= bit; |
| } else if (type === 'mouseup') { |
| currentButtons &= ~bit; |
| } |
| e.buttons = currentButtons; |
| } |
| return e; |
| }, |
| mousedown: function(inEvent) { |
| if (!this.isEventSimulatedFromTouch(inEvent)) { |
| var p = pointermap.has(this.POINTER_ID); |
| var e = this.prepareEvent(inEvent); |
| e.target = scope.findTarget(inEvent); |
| pointermap.set(this.POINTER_ID, e.target); |
| dispatcher.down(e); |
| } |
| }, |
| mousemove: function(inEvent) { |
| if (!this.isEventSimulatedFromTouch(inEvent)) { |
| var target = pointermap.get(this.POINTER_ID); |
| if (target) { |
| var e = this.prepareEvent(inEvent); |
| e.target = target; |
| // handle case where we missed a mouseup |
| if ((HAS_BUTTONS ? e.buttons : e.which) === 0) { |
| if (!HAS_BUTTONS) { |
| currentButtons = e.buttons = 0; |
| } |
| dispatcher.cancel(e); |
| this.cleanupMouse(e.buttons); |
| } else { |
| dispatcher.move(e); |
| } |
| } |
| } |
| }, |
| mouseup: function(inEvent) { |
| if (!this.isEventSimulatedFromTouch(inEvent)) { |
| var e = this.prepareEvent(inEvent); |
| e.relatedTarget = scope.findTarget(inEvent); |
| e.target = pointermap.get(this.POINTER_ID); |
| dispatcher.up(e); |
| this.cleanupMouse(e.buttons); |
| } |
| }, |
| cleanupMouse: function(buttons) { |
| if (buttons === 0) { |
| pointermap.delete(this.POINTER_ID); |
| } |
| } |
| }; |
| |
| scope.mouseEvents = mouseEvents; |
| })(window.PolymerGestures); |
| |
| (function(scope) { |
| var dispatcher = scope.dispatcher; |
| var allShadows = scope.targetFinding.allShadows.bind(scope.targetFinding); |
| var pointermap = dispatcher.pointermap; |
| var touchMap = Array.prototype.map.call.bind(Array.prototype.map); |
| // This should be long enough to ignore compat mouse events made by touch |
| var DEDUP_TIMEOUT = 2500; |
| var DEDUP_DIST = 25; |
| var CLICK_COUNT_TIMEOUT = 200; |
| var HYSTERESIS = 20; |
| var ATTRIB = 'touch-action'; |
| // TODO(dfreedm): disable until http://crbug.com/399765 is resolved |
| // var HAS_TOUCH_ACTION = ATTRIB in document.head.style; |
| var HAS_TOUCH_ACTION = false; |
| |
| // handler block for native touch events |
| var touchEvents = { |
| IS_IOS: false, |
| events: [ |
| 'touchstart', |
| 'touchmove', |
| 'touchend', |
| 'touchcancel' |
| ], |
| exposes: [ |
| 'down', |
| 'up', |
| 'move' |
| ], |
| register: function(target, initial) { |
| if (this.IS_IOS ? initial : !initial) { |
| dispatcher.listen(target, this.events); |
| } |
| }, |
| unregister: function(target) { |
| if (!this.IS_IOS) { |
| dispatcher.unlisten(target, this.events); |
| } |
| }, |
| scrollTypes: { |
| EMITTER: 'none', |
| XSCROLLER: 'pan-x', |
| YSCROLLER: 'pan-y', |
| }, |
| touchActionToScrollType: function(touchAction) { |
| var t = touchAction; |
| var st = this.scrollTypes; |
| if (t === st.EMITTER) { |
| return 'none'; |
| } else if (t === st.XSCROLLER) { |
| return 'X'; |
| } else if (t === st.YSCROLLER) { |
| return 'Y'; |
| } else { |
| return 'XY'; |
| } |
| }, |
| POINTER_TYPE: 'touch', |
| firstTouch: null, |
| isPrimaryTouch: function(inTouch) { |
| return this.firstTouch === inTouch.identifier; |
| }, |
| setPrimaryTouch: function(inTouch) { |
| // set primary touch if there no pointers, or the only pointer is the mouse |
| if (pointermap.pointers() === 0 || (pointermap.pointers() === 1 && pointermap.has(1))) { |
| this.firstTouch = inTouch.identifier; |
| this.firstXY = {X: inTouch.clientX, Y: inTouch.clientY}; |
| this.firstTarget = inTouch.target; |
| this.scrolling = null; |
| this.cancelResetClickCount(); |
| } |
| }, |
| removePrimaryPointer: function(inPointer) { |
| if (inPointer.isPrimary) { |
| this.firstTouch = null; |
| this.firstXY = null; |
| this.resetClickCount(); |
| } |
| }, |
| clickCount: 0, |
| resetId: null, |
| resetClickCount: function() { |
| var fn = function() { |
| this.clickCount = 0; |
| this.resetId = null; |
| }.bind(this); |
| this.resetId = setTimeout(fn, CLICK_COUNT_TIMEOUT); |
| }, |
| cancelResetClickCount: function() { |
| if (this.resetId) { |
| clearTimeout(this.resetId); |
| } |
| }, |
| typeToButtons: function(type) { |
| var ret = 0; |
| if (type === 'touchstart' || type === 'touchmove') { |
| ret = 1; |
| } |
| return ret; |
| }, |
| findTarget: function(touch, id) { |
| if (this.currentTouchEvent.type === 'touchstart') { |
| if (this.isPrimaryTouch(touch)) { |
| var fastPath = { |
| clientX: touch.clientX, |
| clientY: touch.clientY, |
| path: this.currentTouchEvent.path, |
| target: this.currentTouchEvent.target |
| }; |
| return scope.findTarget(fastPath); |
| } else { |
| return scope.findTarget(touch); |
| } |
| } |
| // reuse target we found in touchstart |
| return pointermap.get(id); |
| }, |
| touchToPointer: function(inTouch) { |
| var cte = this.currentTouchEvent; |
| var e = dispatcher.cloneEvent(inTouch); |
| // Spec specifies that pointerId 1 is reserved for Mouse. |
| // Touch identifiers can start at 0. |
| // Add 2 to the touch identifier for compatibility. |
| var id = e.pointerId = inTouch.identifier + 2; |
| e.target = this.findTarget(inTouch, id); |
| e.bubbles = true; |
| e.cancelable = true; |
| e.detail = this.clickCount; |
| e.buttons = this.typeToButtons(cte.type); |
| e.width = inTouch.webkitRadiusX || inTouch.radiusX || 0; |
| e.height = inTouch.webkitRadiusY || inTouch.radiusY || 0; |
| e.pressure = inTouch.webkitForce || inTouch.force || 0.5; |
| e.isPrimary = this.isPrimaryTouch(inTouch); |
| e.pointerType = this.POINTER_TYPE; |
| e._source = 'touch'; |
| // forward touch preventDefaults |
| var self = this; |
| e.preventDefault = function() { |
| self.scrolling = false; |
| self.firstXY = null; |
| cte.preventDefault(); |
| }; |
| return e; |
| }, |
| processTouches: function(inEvent, inFunction) { |
| var tl = inEvent.changedTouches; |
| this.currentTouchEvent = inEvent; |
| for (var i = 0, t, p; i < tl.length; i++) { |
| t = tl[i]; |
| p = this.touchToPointer(t); |
| if (inEvent.type === 'touchstart') { |
| pointermap.set(p.pointerId, p.target); |
| } |
| if (pointermap.has(p.pointerId)) { |
| inFunction.call(this, p); |
| } |
| if (inEvent.type === 'touchend' || inEvent._cancel) { |
| this.cleanUpPointer(p); |
| } |
| } |
| }, |
| // For single axis scrollers, determines whether the element should emit |
| // pointer events or behave as a scroller |
| shouldScroll: function(inEvent) { |
| if (this.firstXY) { |
| var ret; |
| var touchAction = scope.targetFinding.findTouchAction(inEvent); |
| var scrollAxis = this.touchActionToScrollType(touchAction); |
| if (scrollAxis === 'none') { |
| // this element is a touch-action: none, should never scroll |
| ret = false; |
| } else if (scrollAxis === 'XY') { |
| // this element should always scroll |
| ret = true; |
| } else { |
| var t = inEvent.changedTouches[0]; |
| // check the intended scroll axis, and other axis |
| var a = scrollAxis; |
| var oa = scrollAxis === 'Y' ? 'X' : 'Y'; |
| var da = Math.abs(t['client' + a] - this.firstXY[a]); |
| var doa = Math.abs(t['client' + oa] - this.firstXY[oa]); |
| // if delta in the scroll axis > delta other axis, scroll instead of |
| // making events |
| ret = da >= doa; |
| } |
| return ret; |
| } |
| }, |
| findTouch: function(inTL, inId) { |
| for (var i = 0, l = inTL.length, t; i < l && (t = inTL[i]); i++) { |
| if (t.identifier === inId) { |
| return true; |
| } |
| } |
| }, |
| // In some instances, a touchstart can happen without a touchend. This |
| // leaves the pointermap in a broken state. |
| // Therefore, on every touchstart, we remove the touches that did not fire a |
| // touchend event. |
| // To keep state globally consistent, we fire a |
| // pointercancel for this "abandoned" touch |
| vacuumTouches: function(inEvent) { |
| var tl = inEvent.touches; |
| // pointermap.pointers() should be < tl.length here, as the touchstart has not |
| // been processed yet. |
| if (pointermap.pointers() >= tl.length) { |
| var d = []; |
| pointermap.forEach(function(value, key) { |
| // Never remove pointerId == 1, which is mouse. |
| // Touch identifiers are 2 smaller than their pointerId, which is the |
| // index in pointermap. |
| if (key !== 1 && !this.findTouch(tl, key - 2)) { |
| var p = value; |
| d.push(p); |
| } |
| }, this); |
| d.forEach(function(p) { |
| this.cancel(p); |
| pointermap.delete(p.pointerId); |
| }, this); |
| } |
| }, |
| touchstart: function(inEvent) { |
| this.vacuumTouches(inEvent); |
| this.setPrimaryTouch(inEvent.changedTouches[0]); |
| this.dedupSynthMouse(inEvent); |
| if (!this.scrolling) { |
| this.clickCount++; |
| this.processTouches(inEvent, this.down); |
| } |
| }, |
| down: function(inPointer) { |
| dispatcher.down(inPointer); |
| }, |
| touchmove: function(inEvent) { |
| if (HAS_TOUCH_ACTION) { |
| // touchevent.cancelable == false is sent when the page is scrolling under native Touch Action in Chrome 36 |
| // https://groups.google.com/a/chromium.org/d/msg/input-dev/wHnyukcYBcA/b9kmtwM1jJQJ |
| if (inEvent.cancelable) { |
| this.processTouches(inEvent, this.move); |
| } |
| } else { |
| if (!this.scrolling) { |
| if (this.scrolling === null && this.shouldScroll(inEvent)) { |
| this.scrolling = true; |
| } else { |
| this.scrolling = false; |
| inEvent.preventDefault(); |
| this.processTouches(inEvent, this.move); |
| } |
| } else if (this.firstXY) { |
| var t = inEvent.changedTouches[0]; |
| var dx = t.clientX - this.firstXY.X; |
| var dy = t.clientY - this.firstXY.Y; |
| var dd = Math.sqrt(dx * dx + dy * dy); |
| if (dd >= HYSTERESIS) { |
| this.touchcancel(inEvent); |
| this.scrolling = true; |
| this.firstXY = null; |
| } |
| } |
| } |
| }, |
| move: function(inPointer) { |
| dispatcher.move(inPointer); |
| }, |
| touchend: function(inEvent) { |
| this.dedupSynthMouse(inEvent); |
| this.processTouches(inEvent, this.up); |
| }, |
| up: function(inPointer) { |
| inPointer.relatedTarget = scope.findTarget(inPointer); |
| dispatcher.up(inPointer); |
| }, |
| cancel: function(inPointer) { |
| dispatcher.cancel(inPointer); |
| }, |
| touchcancel: function(inEvent) { |
| inEvent._cancel = true; |
| this.processTouches(inEvent, this.cancel); |
| }, |
| cleanUpPointer: function(inPointer) { |
| pointermap['delete'](inPointer.pointerId); |
| this.removePrimaryPointer(inPointer); |
| }, |
| // prevent synth mouse events from creating pointer events |
| dedupSynthMouse: function(inEvent) { |
| var lts = scope.mouseEvents.lastTouches; |
| var t = inEvent.changedTouches[0]; |
| // only the primary finger will synth mouse events |
| if (this.isPrimaryTouch(t)) { |
| // remember x/y of last touch |
| var lt = {x: t.clientX, y: t.clientY}; |
| lts.push(lt); |
| var fn = (function(lts, lt){ |
| var i = lts.indexOf(lt); |
| if (i > -1) { |
| lts.splice(i, 1); |
| } |
| }).bind(null, lts, lt); |
| setTimeout(fn, DEDUP_TIMEOUT); |
| } |
| } |
| }; |
| |
| // prevent "ghost clicks" that come from elements that were removed in a touch handler |
| var STOP_PROP_FN = Event.prototype.stopImmediatePropagation || Event.prototype.stopPropagation; |
| document.addEventListener('click', function(ev) { |
| var x = ev.clientX, y = ev.clientY; |
| // check if a click is within DEDUP_DIST px radius of the touchstart |
| var closeTo = function(touch) { |
| var dx = Math.abs(x - touch.x), dy = Math.abs(y - touch.y); |
| return (dx <= DEDUP_DIST && dy <= DEDUP_DIST); |
| }; |
| // if click coordinates are close to touch coordinates, assume the click came from a touch |
| var wasTouched = scope.mouseEvents.lastTouches.some(closeTo); |
| // if the click came from touch, and the touchstart target is not in the path of the click event, |
| // then the touchstart target was probably removed, and the click should be "busted" |
| var path = scope.targetFinding.path(ev); |
| if (wasTouched) { |
| for (var i = 0; i < path.length; i++) { |
| if (path[i] === touchEvents.firstTarget) { |
| return; |
| } |
| } |
| ev.preventDefault(); |
| STOP_PROP_FN.call(ev); |
| } |
| }, true); |
| |
| scope.touchEvents = touchEvents; |
| })(window.PolymerGestures); |
| |
| (function(scope) { |
| var dispatcher = scope.dispatcher; |
| var pointermap = dispatcher.pointermap; |
| var HAS_BITMAP_TYPE = window.MSPointerEvent && typeof window.MSPointerEvent.MSPOINTER_TYPE_MOUSE === 'number'; |
| var msEvents = { |
| events: [ |
| 'MSPointerDown', |
| 'MSPointerMove', |
| 'MSPointerUp', |
| 'MSPointerCancel', |
| ], |
| register: function(target) { |
| dispatcher.listen(target, this.events); |
| }, |
| unregister: function(target) { |
| if (target === document) { |
| return; |
| } |
| dispatcher.unlisten(target, this.events); |
| }, |
| POINTER_TYPES: [ |
| '', |
| 'unavailable', |
| 'touch', |
| 'pen', |
| 'mouse' |
| ], |
| prepareEvent: function(inEvent) { |
| var e = inEvent; |
| e = dispatcher.cloneEvent(inEvent); |
| if (HAS_BITMAP_TYPE) { |
| e.pointerType = this.POINTER_TYPES[inEvent.pointerType]; |
| } |
| e._source = 'ms'; |
| return e; |
| }, |
| cleanup: function(id) { |
| pointermap['delete'](id); |
| }, |
| MSPointerDown: function(inEvent) { |
| var e = this.prepareEvent(inEvent); |
| e.target = scope.findTarget(inEvent); |
| pointermap.set(inEvent.pointerId, e.target); |
| dispatcher.down(e); |
| }, |
| MSPointerMove: function(inEvent) { |
| var target = pointermap.get(inEvent.pointerId); |
| if (target) { |
| var e = this.prepareEvent(inEvent); |
| e.target = target; |
| dispatcher.move(e); |
| } |
| }, |
| MSPointerUp: function(inEvent) { |
| var e = this.prepareEvent(inEvent); |
| e.relatedTarget = scope.findTarget(inEvent); |
| e.target = pointermap.get(e.pointerId); |
| dispatcher.up(e); |
| this.cleanup(inEvent.pointerId); |
| }, |
| MSPointerCancel: function(inEvent) { |
| var e = this.prepareEvent(inEvent); |
| e.relatedTarget = scope.findTarget(inEvent); |
| e.target = pointermap.get(e.pointerId); |
| dispatcher.cancel(e); |
| this.cleanup(inEvent.pointerId); |
| } |
| }; |
| |
| scope.msEvents = msEvents; |
| })(window.PolymerGestures); |
| |
| (function(scope) { |
| var dispatcher = scope.dispatcher; |
| var pointermap = dispatcher.pointermap; |
| var pointerEvents = { |
| events: [ |
| 'pointerdown', |
| 'pointermove', |
| 'pointerup', |
| 'pointercancel' |
| ], |
| prepareEvent: function(inEvent) { |
| var e = dispatcher.cloneEvent(inEvent); |
| e._source = 'pointer'; |
| return e; |
| }, |
| register: function(target) { |
| dispatcher.listen(target, this.events); |
| }, |
| unregister: function(target) { |
| if (target === document) { |
| return; |
| } |
| dispatcher.unlisten(target, this.events); |
| }, |
| cleanup: function(id) { |
| pointermap['delete'](id); |
| }, |
| pointerdown: function(inEvent) { |
| var e = this.prepareEvent(inEvent); |
| e.target = scope.findTarget(inEvent); |
| pointermap.set(e.pointerId, e.target); |
| dispatcher.down(e); |
| }, |
| pointermove: function(inEvent) { |
| var target = pointermap.get(inEvent.pointerId); |
| if (target) { |
| var e = this.prepareEvent(inEvent); |
| e.target = target; |
| dispatcher.move(e); |
| } |
| }, |
| pointerup: function(inEvent) { |
| var e = this.prepareEvent(inEvent); |
| e.relatedTarget = scope.findTarget(inEvent); |
| e.target = pointermap.get(e.pointerId); |
| dispatcher.up(e); |
| this.cleanup(inEvent.pointerId); |
| }, |
| pointercancel: function(inEvent) { |
| var e = this.prepareEvent(inEvent); |
| e.relatedTarget = scope.findTarget(inEvent); |
| e.target = pointermap.get(e.pointerId); |
| dispatcher.cancel(e); |
| this.cleanup(inEvent.pointerId); |
| } |
| }; |
| |
| scope.pointerEvents = pointerEvents; |
| })(window.PolymerGestures); |
| |
| /** |
| * This module contains the handlers for native platform events. |
| * From here, the dispatcher is called to create unified pointer events. |
| * Included are touch events (v1), mouse events, and MSPointerEvents. |
| */ |
| (function(scope) { |
| |
| var dispatcher = scope.dispatcher; |
| var nav = window.navigator; |
| |
| if (window.PointerEvent) { |
| dispatcher.registerSource('pointer', scope.pointerEvents); |
| } else if (nav.msPointerEnabled) { |
| dispatcher.registerSource('ms', scope.msEvents); |
| } else { |
| dispatcher.registerSource('mouse', scope.mouseEvents); |
| if (window.ontouchstart !== undefined) { |
| dispatcher.registerSource('touch', scope.touchEvents); |
| } |
| } |
| |
| // Work around iOS bugs https://bugs.webkit.org/show_bug.cgi?id=135628 and https://bugs.webkit.org/show_bug.cgi?id=136506 |
| var ua = navigator.userAgent; |
| var IS_IOS = ua.match(/iPad|iPhone|iPod/) && 'ontouchstart' in window; |
| |
| dispatcher.IS_IOS = IS_IOS; |
| scope.touchEvents.IS_IOS = IS_IOS; |
| |
| dispatcher.register(document, true); |
| })(window.PolymerGestures); |
| |
| /** |
| * This event denotes the beginning of a series of tracking events. |
| * |
| * @module PointerGestures |
| * @submodule Events |
| * @class trackstart |
| */ |
| /** |
| * Pixels moved in the x direction since trackstart. |
| * @type Number |
| * @property dx |
| */ |
| /** |
| * Pixes moved in the y direction since trackstart. |
| * @type Number |
| * @property dy |
| */ |
| /** |
| * Pixels moved in the x direction since the last track. |
| * @type Number |
| * @property ddx |
| */ |
| /** |
| * Pixles moved in the y direction since the last track. |
| * @type Number |
| * @property ddy |
| */ |
| /** |
| * The clientX position of the track gesture. |
| * @type Number |
| * @property clientX |
| */ |
| /** |
| * The clientY position of the track gesture. |
| * @type Number |
| * @property clientY |
| */ |
| /** |
| * The pageX position of the track gesture. |
| * @type Number |
| * @property pageX |
| */ |
| /** |
| * The pageY position of the track gesture. |
| * @type Number |
| * @property pageY |
| */ |
| /** |
| * The screenX position of the track gesture. |
| * @type Number |
| * @property screenX |
| */ |
| /** |
| * The screenY position of the track gesture. |
| * @type Number |
| * @property screenY |
| */ |
| /** |
| * The last x axis direction of the pointer. |
| * @type Number |
| * @property xDirection |
| */ |
| /** |
| * The last y axis direction of the pointer. |
| * @type Number |
| * @property yDirection |
| */ |
| /** |
| * A shared object between all tracking events. |
| * @type Object |
| * @property trackInfo |
| */ |
| /** |
| * The element currently under the pointer. |
| * @type Element |
| * @property relatedTarget |
| */ |
| /** |
| * The type of pointer that make the track gesture. |
| * @type String |
| * @property pointerType |
| */ |
| /** |
| * |
| * This event fires for all pointer movement being tracked. |
| * |
| * @class track |
| * @extends trackstart |
| */ |
| /** |
| * This event fires when the pointer is no longer being tracked. |
| * |
| * @class trackend |
| * @extends trackstart |
| */ |
| |
| (function(scope) { |
| var dispatcher = scope.dispatcher; |
| var eventFactory = scope.eventFactory; |
| var pointermap = new scope.PointerMap(); |
| var track = { |
| events: [ |
| 'down', |
| 'move', |
| 'up', |
| ], |
| exposes: [ |
| 'trackstart', |
| 'track', |
| 'trackx', |
| 'tracky', |
| 'trackend' |
| ], |
| defaultActions: { |
| 'track': 'none', |
| 'trackx': 'pan-y', |
| 'tracky': 'pan-x' |
| }, |
| WIGGLE_THRESHOLD: 4, |
| clampDir: function(inDelta) { |
| return inDelta > 0 ? 1 : -1; |
| }, |
| calcPositionDelta: function(inA, inB) { |
| var x = 0, y = 0; |
| if (inA && inB) { |
| x = inB.pageX - inA.pageX; |
| y = inB.pageY - inA.pageY; |
| } |
| return {x: x, y: y}; |
| }, |
| fireTrack: function(inType, inEvent, inTrackingData) { |
| var t = inTrackingData; |
| var d = this.calcPositionDelta(t.downEvent, inEvent); |
| var dd = this.calcPositionDelta(t.lastMoveEvent, inEvent); |
| if (dd.x) { |
| t.xDirection = this.clampDir(dd.x); |
| } else if (inType === 'trackx') { |
| return; |
| } |
| if (dd.y) { |
| t.yDirection = this.clampDir(dd.y); |
| } else if (inType === 'tracky') { |
| return; |
| } |
| var gestureProto = { |
| bubbles: true, |
| cancelable: true, |
| trackInfo: t.trackInfo, |
| relatedTarget: inEvent.relatedTarget, |
| pointerType: inEvent.pointerType, |
| pointerId: inEvent.pointerId, |
| _source: 'track' |
| }; |
| if (inType !== 'tracky') { |
| gestureProto.x = inEvent.x; |
| gestureProto.dx = d.x; |
| gestureProto.ddx = dd.x; |
| gestureProto.clientX = inEvent.clientX; |
| gestureProto.pageX = inEvent.pageX; |
| gestureProto.screenX = inEvent.screenX; |
| gestureProto.xDirection = t.xDirection; |
| } |
| if (inType !== 'trackx') { |
| gestureProto.dy = d.y; |
| gestureProto.ddy = dd.y; |
| gestureProto.y = inEvent.y; |
| gestureProto.clientY = inEvent.clientY; |
| gestureProto.pageY = inEvent.pageY; |
| gestureProto.screenY = inEvent.screenY; |
| gestureProto.yDirection = t.yDirection; |
| } |
| var e = eventFactory.makeGestureEvent(inType, gestureProto); |
| t.downTarget.dispatchEvent(e); |
| }, |
| down: function(inEvent) { |
| if (inEvent.isPrimary && (inEvent.pointerType === 'mouse' ? inEvent.buttons === 1 : true)) { |
| var p = { |
| downEvent: inEvent, |
| downTarget: inEvent.target, |
| trackInfo: {}, |
| lastMoveEvent: null, |
| xDirection: 0, |
| yDirection: 0, |
| tracking: false |
| }; |
| pointermap.set(inEvent.pointerId, p); |
| } |
| }, |
| move: function(inEvent) { |
| var p = pointermap.get(inEvent.pointerId); |
| if (p) { |
| if (!p.tracking) { |
| var d = this.calcPositionDelta(p.downEvent, inEvent); |
| var move = d.x * d.x + d.y * d.y; |
| // start tracking only if finger moves more than WIGGLE_THRESHOLD |
| if (move > this.WIGGLE_THRESHOLD) { |
| p.tracking = true; |
| p.lastMoveEvent = p.downEvent; |
| this.fireTrack('trackstart', inEvent, p); |
| } |
| } |
| if (p.tracking) { |
| this.fireTrack('track', inEvent, p); |
| this.fireTrack('trackx', inEvent, p); |
| this.fireTrack('tracky', inEvent, p); |
| } |
| p.lastMoveEvent = inEvent; |
| } |
| }, |
| up: function(inEvent) { |
| var p = pointermap.get(inEvent.pointerId); |
| if (p) { |
| if (p.tracking) { |
| this.fireTrack('trackend', inEvent, p); |
| } |
| pointermap.delete(inEvent.pointerId); |
| } |
| } |
| }; |
| dispatcher.registerGesture('track', track); |
| })(window.PolymerGestures); |
| |
| /** |
| * This event is fired when a pointer is held down for 200ms. |
| * |
| * @module PointerGestures |
| * @submodule Events |
| * @class hold |
| */ |
| /** |
| * Type of pointer that made the holding event. |
| * @type String |
| * @property pointerType |
| */ |
| /** |
| * Screen X axis position of the held pointer |
| * @type Number |
| * @property clientX |
| */ |
| /** |
| * Screen Y axis position of the held pointer |
| * @type Number |
| * @property clientY |
| */ |
| /** |
| * Type of pointer that made the holding event. |
| * @type String |
| * @property pointerType |
| */ |
| /** |
| * This event is fired every 200ms while a pointer is held down. |
| * |
| * @class holdpulse |
| * @extends hold |
| */ |
| /** |
| * Milliseconds pointer has been held down. |
| * @type Number |
| * @property holdTime |
| */ |
| /** |
| * This event is fired when a held pointer is released or moved. |
| * |
| * @class release |
| */ |
| |
| (function(scope) { |
| var dispatcher = scope.dispatcher; |
| var eventFactory = scope.eventFactory; |
| var hold = { |
| // wait at least HOLD_DELAY ms between hold and pulse events |
| HOLD_DELAY: 200, |
| // pointer can move WIGGLE_THRESHOLD pixels before not counting as a hold |
| WIGGLE_THRESHOLD: 16, |
| events: [ |
| 'down', |
| 'move', |
| 'up', |
| ], |
| exposes: [ |
| 'hold', |
| 'holdpulse', |
| 'release' |
| ], |
| heldPointer: null, |
| holdJob: null, |
| pulse: function() { |
| var hold = Date.now() - this.heldPointer.timeStamp; |
| var type = this.held ? 'holdpulse' : 'hold'; |
| this.fireHold(type, hold); |
| this.held = true; |
| }, |
| cancel: function() { |
| clearInterval(this.holdJob); |
| if (this.held) { |
| this.fireHold('release'); |
| } |
| this.held = false; |
| this.heldPointer = null; |
| this.target = null; |
| this.holdJob = null; |
| }, |
| down: function(inEvent) { |
| if (inEvent.isPrimary && !this.heldPointer) { |
| this.heldPointer = inEvent; |
| this.target = inEvent.target; |
| this.holdJob = setInterval(this.pulse.bind(this), this.HOLD_DELAY); |
| } |
| }, |
| up: function(inEvent) { |
| if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId) { |
| this.cancel(); |
| } |
| }, |
| move: function(inEvent) { |
| if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId) { |
| var x = inEvent.clientX - this.heldPointer.clientX; |
| var y = inEvent.clientY - this.heldPointer.clientY; |
| if ((x * x + y * y) > this.WIGGLE_THRESHOLD) { |
| this.cancel(); |
| } |
| } |
| }, |
| fireHold: function(inType, inHoldTime) { |
| var p = { |
| bubbles: true, |
| cancelable: true, |
| pointerType: this.heldPointer.pointerType, |
| pointerId: this.heldPointer.pointerId, |
| x: this.heldPointer.clientX, |
| y: this.heldPointer.clientY, |
| _source: 'hold' |
| }; |
| if (inHoldTime) { |
| p.holdTime = inHoldTime; |
| } |
| var e = eventFactory.makeGestureEvent(inType, p); |
| this.target.dispatchEvent(e); |
| } |
| }; |
| dispatcher.registerGesture('hold', hold); |
| })(window.PolymerGestures); |
| |
| /** |
| * This event is fired when a pointer quickly goes down and up, and is used to |
| * denote activation. |
| * |
| * Any gesture event can prevent the tap event from being created by calling |
| * `event.preventTap`. |
| * |
| * Any pointer event can prevent the tap by setting the `tapPrevented` property |
| * on itself. |
| * |
| * @module PointerGestures |
| * @submodule Events |
| * @class tap |
| */ |
| /** |
| * X axis position of the tap. |
| * @property x |
| * @type Number |
| */ |
| /** |
| * Y axis position of the tap. |
| * @property y |
| * @type Number |
| */ |
| /** |
| * Type of the pointer that made the tap. |
| * @property pointerType |
| * @type String |
| */ |
| (function(scope) { |
| var dispatcher = scope.dispatcher; |
| var eventFactory = scope.eventFactory; |
| var pointermap = new scope.PointerMap(); |
| var tap = { |
| events: [ |
| 'down', |
| 'up' |
| ], |
| exposes: [ |
| 'tap' |
| ], |
| down: function(inEvent) { |
| if (inEvent.isPrimary && !inEvent.tapPrevented) { |
| pointermap.set(inEvent.pointerId, { |
| target: inEvent.target, |
| buttons: inEvent.buttons, |
| x: inEvent.clientX, |
| y: inEvent.clientY |
| }); |
| } |
| }, |
| shouldTap: function(e, downState) { |
| var tap = true; |
| if (e.pointerType === 'mouse') { |
| // only allow left click to tap for mouse |
| tap = (e.buttons ^ 1) && (downState.buttons & 1); |
| } |
| return tap && !e.tapPrevented; |
| }, |
| up: function(inEvent) { |
| var start = pointermap.get(inEvent.pointerId); |
| if (start && this.shouldTap(inEvent, start)) { |
| // up.relatedTarget is target currently under finger |
| var t = scope.targetFinding.LCA(start.target, inEvent.relatedTarget); |
| if (t) { |
| var e = eventFactory.makeGestureEvent('tap', { |
| bubbles: true, |
| cancelable: true, |
| x: inEvent.clientX, |
| y: inEvent.clientY, |
| detail: inEvent.detail, |
| pointerType: inEvent.pointerType, |
| pointerId: inEvent.pointerId, |
| altKey: inEvent.altKey, |
| ctrlKey: inEvent.ctrlKey, |
| metaKey: inEvent.metaKey, |
| shiftKey: inEvent.shiftKey, |
| _source: 'tap' |
| }); |
| t.dispatchEvent(e); |
| } |
| } |
| pointermap.delete(inEvent.pointerId); |
| } |
| }; |
| // patch eventFactory to remove id from tap's pointermap for preventTap calls |
| eventFactory.preventTap = function(e) { |
| return function() { |
| e.tapPrevented = true; |
| pointermap.delete(e.pointerId); |
| }; |
| }; |
| dispatcher.registerGesture('tap', tap); |
| })(window.PolymerGestures); |
| |
| /* |
| * Basic strategy: find the farthest apart points, use as diameter of circle |
| * react to size change and rotation of the chord |
| */ |
| |
| /** |
| * @module pointer-gestures |
| * @submodule Events |
| * @class pinch |
| */ |
| /** |
| * Scale of the pinch zoom gesture |
| * @property scale |
| * @type Number |
| */ |
| /** |
| * Center X position of pointers causing pinch |
| * @property centerX |
| * @type Number |
| */ |
| /** |
| * Center Y position of pointers causing pinch |
| * @property centerY |
| * @type Number |
| */ |
| |
| /** |
| * @module pointer-gestures |
| * @submodule Events |
| * @class rotate |
| */ |
| /** |
| * Angle (in degrees) of rotation. Measured from starting positions of pointers. |
| * @property angle |
| * @type Number |
| */ |
| /** |
| * Center X position of pointers causing rotation |
| * @property centerX |
| * @type Number |
| */ |
| /** |
| * Center Y position of pointers causing rotation |
| * @property centerY |
| * @type Number |
| */ |
| (function(scope) { |
| var dispatcher = scope.dispatcher; |
| var eventFactory = scope.eventFactory; |
| var pointermap = new scope.PointerMap(); |
| var RAD_TO_DEG = 180 / Math.PI; |
| var pinch = { |
| events: [ |
| 'down', |
| 'up', |
| 'move', |
| 'cancel' |
| ], |
| exposes: [ |
| 'pinchstart', |
| 'pinch', |
| 'pinchend', |
| 'rotate' |
| ], |
| defaultActions: { |
| 'pinch': 'none', |
| 'rotate': 'none' |
| }, |
| reference: {}, |
| down: function(inEvent) { |
| pointermap.set(inEvent.pointerId, inEvent); |
| if (pointermap.pointers() == 2) { |
| var points = this.calcChord(); |
| var angle = this.calcAngle(points); |
| this.reference = { |
| angle: angle, |
| diameter: points.diameter, |
| target: scope.targetFinding.LCA(points.a.target, points.b.target) |
| }; |
| |
| this.firePinch('pinchstart', points.diameter, points); |
| } |
| }, |
| up: function(inEvent) { |
| var p = pointermap.get(inEvent.pointerId); |
| var num = pointermap.pointers(); |
| if (p) { |
| if (num === 2) { |
| // fire 'pinchend' before deleting pointer |
| var points = this.calcChord(); |
| this.firePinch('pinchend', points.diameter, points); |
| } |
| pointermap.delete(inEvent.pointerId); |
| } |
| }, |
| move: function(inEvent) { |
| if (pointermap.has(inEvent.pointerId)) { |
| pointermap.set(inEvent.pointerId, inEvent); |
| if (pointermap.pointers() > 1) { |
| this.calcPinchRotate(); |
| } |
| } |
| }, |
| cancel: function(inEvent) { |
| this.up(inEvent); |
| }, |
| firePinch: function(type, diameter, points) { |
| var zoom = diameter / this.reference.diameter; |
| var e = eventFactory.makeGestureEvent(type, { |
| bubbles: true, |
| cancelable: true, |
| scale: zoom, |
| centerX: points.center.x, |
| centerY: points.center.y, |
| _source: 'pinch' |
| }); |
| this.reference.target.dispatchEvent(e); |
| }, |
| fireRotate: function(angle, points) { |
| var diff = Math.round((angle - this.reference.angle) % 360); |
| var e = eventFactory.makeGestureEvent('rotate', { |
| bubbles: true, |
| cancelable: true, |
| angle: diff, |
| centerX: points.center.x, |
| centerY: points.center.y, |
| _source: 'pinch' |
| }); |
| this.reference.target.dispatchEvent(e); |
| }, |
| calcPinchRotate: function() { |
| var points = this.calcChord(); |
| var diameter = points.diameter; |
| var angle = this.calcAngle(points); |
| if (diameter != this.reference.diameter) { |
| this.firePinch('pinch', diameter, points); |
| } |
| if (angle != this.reference.angle) { |
| this.fireRotate(angle, points); |
| } |
| }, |
| calcChord: function() { |
| var pointers = []; |
| pointermap.forEach(function(p) { |
| pointers.push(p); |
| }); |
| var dist = 0; |
| // start with at least two pointers |
| var points = {a: pointers[0], b: pointers[1]}; |
| var x, y, d; |
| for (var i = 0; i < pointers.length; i++) { |
| var a = pointers[i]; |
| for (var j = i + 1; j < pointers.length; j++) { |
| var b = pointers[j]; |
| x = Math.abs(a.clientX - b.clientX); |
| y = Math.abs(a.clientY - b.clientY); |
| d = x + y; |
| if (d > dist) { |
| dist = d; |
| points = {a: a, b: b}; |
| } |
| } |
| } |
| x = Math.abs(points.a.clientX + points.b.clientX) / 2; |
| y = Math.abs(points.a.clientY + points.b.clientY) / 2; |
| points.center = { x: x, y: y }; |
| points.diameter = dist; |
| return points; |
| }, |
| calcAngle: function(points) { |
| var x = points.a.clientX - points.b.clientX; |
| var y = points.a.clientY - points.b.clientY; |
| return (360 + Math.atan2(y, x) * RAD_TO_DEG) % 360; |
| } |
| }; |
| dispatcher.registerGesture('pinch', pinch); |
| })(window.PolymerGestures); |
| |
| (function (global) { |
| 'use strict'; |
| |
| var Token, |
| TokenName, |
| Syntax, |
| Messages, |
| source, |
| index, |
| length, |
| delegate, |
| lookahead, |
| state; |
| |
| Token = { |
| BooleanLiteral: 1, |
| EOF: 2, |
| Identifier: 3, |
| Keyword: 4, |
| NullLiteral: 5, |
| NumericLiteral: 6, |
| Punctuator: 7, |
| StringLiteral: 8 |
| }; |
| |
| TokenName = {}; |
| TokenName[Token.BooleanLiteral] = 'Boolean'; |
| TokenName[Token.EOF] = '<end>'; |
| TokenName[Token.Identifier] = 'Identifier'; |
| TokenName[Token.Keyword] = 'Keyword'; |
| TokenName[Token.NullLiteral] = 'Null'; |
| TokenName[Token.NumericLiteral] = 'Numeric'; |
| TokenName[Token.Punctuator] = 'Punctuator'; |
| TokenName[Token.StringLiteral] = 'String'; |
| |
| Syntax = { |
| ArrayExpression: 'ArrayExpression', |
| BinaryExpression: 'BinaryExpression', |
| CallExpression: 'CallExpression', |
| ConditionalExpression: 'ConditionalExpression', |
| EmptyStatement: 'EmptyStatement', |
| ExpressionStatement: 'ExpressionStatement', |
| Identifier: 'Identifier', |
| Literal: 'Literal', |
| LabeledStatement: 'LabeledStatement', |
| LogicalExpression: 'LogicalExpression', |
| MemberExpression: 'MemberExpression', |
| ObjectExpression: 'ObjectExpression', |
| Program: 'Program', |
| Property: 'Property', |
| ThisExpression: 'ThisExpression', |
| UnaryExpression: 'UnaryExpression' |
| }; |
| |
| // Error messages should be identical to V8. |
| Messages = { |
| UnexpectedToken: 'Unexpected token %0', |
| UnknownLabel: 'Undefined label \'%0\'', |
| Redeclaration: '%0 \'%1\' has already been declared' |
| }; |
| |
| // Ensure the condition is true, otherwise throw an error. |
| // This is only to have a better contract semantic, i.e. another safety net |
| // to catch a logic error. The condition shall be fulfilled in normal case. |
| // Do NOT use this to enforce a certain condition on any user input. |
| |
| function assert(condition, message) { |
| if (!condition) { |
| throw new Error('ASSERT: ' + message); |
| } |
| } |
| |
| function isDecimalDigit(ch) { |
| return (ch >= 48 && ch <= 57); // 0..9 |
| } |
| |
| |
| // 7.2 White Space |
| |
| function isWhiteSpace(ch) { |
| return (ch === 32) || // space |
| (ch === 9) || // tab |
| (ch === 0xB) || |
| (ch === 0xC) || |
| (ch === 0xA0) || |
| (ch >= 0x1680 && '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF'.indexOf(String.fromCharCode(ch)) > 0); |
| } |
| |
| // 7.3 Line Terminators |
| |
| function isLineTerminator(ch) { |
| return (ch === 10) || (ch === 13) || (ch === 0x2028) || (ch === 0x2029); |
| } |
| |
| // 7.6 Identifier Names and Identifiers |
| |
| function isIdentifierStart(ch) { |
| return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) |
| (ch >= 65 && ch <= 90) || // A..Z |
| (ch >= 97 && ch <= 122); // a..z |
| } |
| |
| function isIdentifierPart(ch) { |
| return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) |
| (ch >= 65 && ch <= 90) || // A..Z |
| (ch >= 97 && ch <= 122) || // a..z |
| (ch >= 48 && ch <= 57); // 0..9 |
| } |
| |
| // 7.6.1.1 Keywords |
| |
| function isKeyword(id) { |
| return (id === 'this') |
| } |
| |
| // 7.4 Comments |
| |
| function skipWhitespace() { |
| while (index < length && isWhiteSpace(source.charCodeAt(index))) { |
| ++index; |
| } |
| } |
| |
| function getIdentifier() { |
| var start, ch; |
| |
| start = index++; |
| while (index < length) { |
| ch = source.charCodeAt(index); |
| if (isIdentifierPart(ch)) { |
| ++index; |
| } else { |
| break; |
| } |
| } |
| |
| return source.slice(start, index); |
| } |
| |
| function scanIdentifier() { |
| var start, id, type; |
| |
| start = index; |
| |
| id = getIdentifier(); |
| |
| // There is no keyword or literal with only one character. |
| // Thus, it must be an identifier. |
| if (id.length === 1) { |
| type = Token.Identifier; |
| } else if (isKeyword(id)) { |
| type = Token.Keyword; |
| } else if (id === 'null') { |
| type = Token.NullLiteral; |
| } else if (id === 'true' || id === 'false') { |
| type = Token.BooleanLiteral; |
| } else { |
| type = Token.Identifier; |
| } |
| |
| return { |
| type: type, |
| value: id, |
| range: [start, index] |
| }; |
| } |
| |
| |
| // 7.7 Punctuators |
| |
| function scanPunctuator() { |
| var start = index, |
| code = source.charCodeAt(index), |
| code2, |
| ch1 = source[index], |
| ch2; |
| |
| switch (code) { |
| |
| // Check for most common single-character punctuators. |
| case 46: // . dot |
| case 40: // ( open bracket |
| case 41: // ) close bracket |
| case 59: // ; semicolon |
| case 44: // , comma |
| case 123: // { open curly brace |
| case 125: // } close curly brace |
| case 91: // [ |
| case 93: // ] |
| case 58: // : |
| case 63: // ? |
| ++index; |
| return { |
| type: Token.Punctuator, |
| value: String.fromCharCode(code), |
| range: [start, index] |
| }; |
| |
| default: |
| code2 = source.charCodeAt(index + 1); |
| |
| // '=' (char #61) marks an assignment or comparison operator. |
| if (code2 === 61) { |
| switch (code) { |
| case 37: // % |
| case 38: // & |
| case 42: // *: |
| case 43: // + |
| case 45: // - |
| case 47: // / |
| case 60: // < |
| case 62: // > |
| case 124: // | |
| index += 2; |
| return { |
| type: Token.Punctuator, |
| value: String.fromCharCode(code) + String.fromCharCode(code2), |
| range: [start, index] |
| }; |
| |
| case 33: // ! |
| case 61: // = |
| index += 2; |
| |
| // !== and === |
| if (source.charCodeAt(index) === 61) { |
| ++index; |
| } |
| return { |
| type: Token.Punctuator, |
| value: source.slice(start, index), |
| range: [start, index] |
| }; |
| default: |
| break; |
| } |
| } |
| break; |
| } |
| |
| // Peek more characters. |
| |
| ch2 = source[index + 1]; |
| |
| // Other 2-character punctuators: && || |
| |
| if (ch1 === ch2 && ('&|'.indexOf(ch1) >= 0)) { |
| index += 2; |
| return { |
| type: Token.Punctuator, |
| value: ch1 + ch2, |
| range: [start, index] |
| }; |
| } |
| |
| if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) { |
| ++index; |
| return { |
| type: Token.Punctuator, |
| value: ch1, |
| range: [start, index] |
| }; |
| } |
| |
| throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); |
| } |
| |
| // 7.8.3 Numeric Literals |
| function scanNumericLiteral() { |
| var number, start, ch; |
| |
| ch = source[index]; |
| assert(isDecimalDigit(ch.charCodeAt(0)) || (ch === '.'), |
| 'Numeric literal must start with a decimal digit or a decimal point'); |
| |
| start = index; |
| number = ''; |
| if (ch !== '.') { |
| number = source[index++]; |
| ch = source[index]; |
| |
| // Hex number starts with '0x'. |
| // Octal number starts with '0'. |
| if (number === '0') { |
| // decimal number starts with '0' such as '09' is illegal. |
| if (ch && isDecimalDigit(ch.charCodeAt(0))) { |
| throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); |
| } |
| } |
| |
| while (isDecimalDigit(source.charCodeAt(index))) { |
| number += source[index++]; |
| } |
| ch = source[index]; |
| } |
| |
| if (ch === '.') { |
| number += source[index++]; |
| while (isDecimalDigit(source.charCodeAt(index))) { |
| number += source[index++]; |
| } |
| ch = source[index]; |
| } |
| |
| if (ch === 'e' || ch === 'E') { |
| number += source[index++]; |
| |
| ch = source[index]; |
| if (ch === '+' || ch === '-') { |
| number += source[index++]; |
| } |
| if (isDecimalDigit(source.charCodeAt(index))) { |
| while (isDecimalDigit(source.charCodeAt(index))) { |
| number += source[index++]; |
| } |
| } else { |
| throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); |
| } |
| } |
| |
| if (isIdentifierStart(source.charCodeAt(index))) { |
| throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); |
| } |
| |
| return { |
| type: Token.NumericLiteral, |
| value: parseFloat(number), |
| range: [start, index] |
| }; |
| } |
| |
| // 7.8.4 String Literals |
| |
| function scanStringLiteral() { |
| var str = '', quote, start, ch, octal = false; |
| |
| quote = source[index]; |
| assert((quote === '\'' || quote === '"'), |
| 'String literal must starts with a quote'); |
| |
| start = index; |
| ++index; |
| |
| while (index < length) { |
| ch = source[index++]; |
| |
| if (ch === quote) { |
| quote = ''; |
| break; |
| } else if (ch === '\\') { |
| ch = source[index++]; |
| if (!ch || !isLineTerminator(ch.charCodeAt(0))) { |
| switch (ch) { |
| case 'n': |
| str += '\n'; |
| break; |
| case 'r': |
| str += '\r'; |
| break; |
| case 't': |
| str += '\t'; |
| break; |
| case 'b': |
| str += '\b'; |
| break; |
| case 'f': |
| str += '\f'; |
| break; |
| case 'v': |
| str += '\x0B'; |
| break; |
| |
| default: |
| str += ch; |
| break; |
| } |
| } else { |
| if (ch === '\r' && source[index] === '\n') { |
| ++index; |
| } |
| } |
| } else if (isLineTerminator(ch.charCodeAt(0))) { |
| break; |
| } else { |
| str += ch; |
| } |
| } |
| |
| if (quote !== '') { |
| throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); |
| } |
| |
| return { |
| type: Token.StringLiteral, |
| value: str, |
| octal: octal, |
| range: [start, index] |
| }; |
| } |
| |
| function isIdentifierName(token) { |
| return token.type === Token.Identifier || |
| token.type === Token.Keyword || |
| token.type === Token.BooleanLiteral || |
| token.type === Token.NullLiteral; |
| } |
| |
| function advance() { |
| var ch; |
| |
| skipWhitespace(); |
| |
| if (index >= length) { |
| return { |
| type: Token.EOF, |
| range: [index, index] |
| }; |
| } |
| |
| ch = source.charCodeAt(index); |
| |
| // Very common: ( and ) and ; |
| if (ch === 40 || ch === 41 || ch === 58) { |
| return scanPunctuator(); |
| } |
| |
| // String literal starts with single quote (#39) or double quote (#34). |
| if (ch === 39 || ch === 34) { |
| return scanStringLiteral(); |
| } |
| |
| if (isIdentifierStart(ch)) { |
| return scanIdentifier(); |
| } |
| |
| // Dot (.) char #46 can also start a floating-point number, hence the need |
| // to check the next character. |
| if (ch === 46) { |
| if (isDecimalDigit(source.charCodeAt(index + 1))) { |
| return scanNumericLiteral(); |
| } |
| return scanPunctuator(); |
| } |
| |
| if (isDecimalDigit(ch)) { |
| return scanNumericLiteral(); |
| } |
| |
| return scanPunctuator(); |
| } |
| |
| function lex() { |
| var token; |
| |
| token = lookahead; |
| index = token.range[1]; |
| |
| lookahead = advance(); |
| |
| index = token.range[1]; |
| |
| return token; |
| } |
| |
| function peek() { |
| var pos; |
| |
| pos = index; |
| lookahead = advance(); |
| index = pos; |
| } |
| |
| // Throw an exception |
| |
| function throwError(token, messageFormat) { |
| var error, |
| args = Array.prototype.slice.call(arguments, 2), |
| msg = messageFormat.replace( |
| /%(\d)/g, |
| function (whole, index) { |
| assert(index < args.length, 'Message reference must be in range'); |
| return args[index]; |
| } |
| ); |
| |
| error = new Error(msg); |
| error.index = index; |
| error.description = msg; |
| throw error; |
| } |
| |
| // Throw an exception because of the token. |
| |
| function throwUnexpected(token) { |
| throwError(token, Messages.UnexpectedToken, token.value); |
| } |
| |
| // Expect the next token to match the specified punctuator. |
| // If not, an exception will be thrown. |
| |
| function expect(value) { |
| var token = lex(); |
| if (token.type !== Token.Punctuator || token.value !== value) { |
| throwUnexpected(token); |
| } |
| } |
| |
| // Return true if the next token matches the specified punctuator. |
| |
| function match(value) { |
| return lookahead.type === Token.Punctuator && lookahead.value === value; |
| } |
| |
| // Return true if the next token matches the specified keyword |
| |
| function matchKeyword(keyword) { |
| return lookahead.type === Token.Keyword && lookahead.value === keyword; |
| } |
| |
| function consumeSemicolon() { |
| // Catch the very common case first: immediately a semicolon (char #59). |
| if (source.charCodeAt(index) === 59) { |
| lex(); |
| return; |
| } |
| |
| skipWhitespace(); |
| |
| if (match(';')) { |
| lex(); |
| return; |
| } |
| |
| if (lookahead.type !== Token.EOF && !match('}')) { |
| throwUnexpected(lookahead); |
| } |
| } |
| |
| // 11.1.4 Array Initialiser |
| |
| function parseArrayInitialiser() { |
| var elements = []; |
| |
| expect('['); |
| |
| while (!match(']')) { |
| if (match(',')) { |
| lex(); |
| elements.push(null); |
| } else { |
| elements.push(parseExpression()); |
| |
| if (!match(']')) { |
| expect(','); |
| } |
| } |
| } |
| |
| expect(']'); |
| |
| return delegate.createArrayExpression(elements); |
| } |
| |
| // 11.1.5 Object Initialiser |
| |
| function parseObjectPropertyKey() { |
| var token; |
| |
| skipWhitespace(); |
| token = lex(); |
| |
| // Note: This function is called only from parseObjectProperty(), where |
| // EOF and Punctuator tokens are already filtered out. |
| if (token.type === Token.StringLiteral || token.type === Token.NumericLiteral) { |
| return delegate.createLiteral(token); |
| } |
| |
| return delegate.createIdentifier(token.value); |
| } |
| |
| function parseObjectProperty() { |
| var token, key; |
| |
| token = lookahead; |
| skipWhitespace(); |
| |
| if (token.type === Token.EOF || token.type === Token.Punctuator) { |
| throwUnexpected(token); |
| } |
| |
| key = parseObjectPropertyKey(); |
| expect(':'); |
| return delegate.createProperty('init', key, parseExpression()); |
| } |
| |
| function parseObjectInitialiser() { |
| var properties = []; |
| |
| expect('{'); |
| |
| while (!match('}')) { |
| properties.push(parseObjectProperty()); |
| |
| if (!match('}')) { |
| expect(','); |
| } |
| } |
| |
| expect('}'); |
| |
| return delegate.createObjectExpression(properties); |
| } |
| |
| // 11.1.6 The Grouping Operator |
| |
| function parseGroupExpression() { |
| var expr; |
| |
| expect('('); |
| |
| expr = parseExpression(); |
| |
| expect(')'); |
| |
| return expr; |
| } |
| |
| |
| // 11.1 Primary Expressions |
| |
| function parsePrimaryExpression() { |
| var type, token, expr; |
| |
| if (match('(')) { |
| return parseGroupExpression(); |
| } |
| |
| type = lookahead.type; |
| |
| if (type === Token.Identifier) { |
| expr = delegate.createIdentifier(lex().value); |
| } else if (type === Token.StringLiteral || type === Token.NumericLiteral) { |
| expr = delegate.createLiteral(lex()); |
| } else if (type === Token.Keyword) { |
| if (matchKeyword('this')) { |
| lex(); |
| expr = delegate.createThisExpression(); |
| } |
| } else if (type === Token.BooleanLiteral) { |
| token = lex(); |
| token.value = (token.value === 'true'); |
| expr = delegate.createLiteral(token); |
| } else if (type === Token.NullLiteral) { |
| token = lex(); |
| token.value = null; |
| expr = delegate.createLiteral(token); |
| } else if (match('[')) { |
| expr = parseArrayInitialiser(); |
| } else if (match('{')) { |
| expr = parseObjectInitialiser(); |
| } |
| |
| if (expr) { |
| return expr; |
| } |
| |
| throwUnexpected(lex()); |
| } |
| |
| // 11.2 Left-Hand-Side Expressions |
| |
| function parseArguments() { |
| var args = []; |
| |
| expect('('); |
| |
| if (!match(')')) { |
| while (index < length) { |
| args.push(parseExpression()); |
| if (match(')')) { |
| break; |
| } |
| expect(','); |
| } |
| } |
| |
| expect(')'); |
| |
| return args; |
| } |
| |
| function parseNonComputedProperty() { |
| var token; |
| |
| token = lex(); |
| |
| if (!isIdentifierName(token)) { |
| throwUnexpected(token); |
| } |
| |
| return delegate.createIdentifier(token.value); |
| } |
| |
| function parseNonComputedMember() { |
| expect('.'); |
| |
| return parseNonComputedProperty(); |
| } |
| |
| function parseComputedMember() { |
| var expr; |
| |
| expect('['); |
| |
| expr = parseExpression(); |
| |
| expect(']'); |
| |
| return expr; |
| } |
| |
| function parseLeftHandSideExpression() { |
| var expr, args, property; |
| |
| expr = parsePrimaryExpression(); |
| |
| while (true) { |
| if (match('[')) { |
| property = parseComputedMember(); |
| expr = delegate.createMemberExpression('[', expr, property); |
| } else if (match('.')) { |
| property = parseNonComputedMember(); |
| expr = delegate.createMemberExpression('.', expr, property); |
| } else if (match('(')) { |
| args = parseArguments(); |
| expr = delegate.createCallExpression(expr, args); |
| } else { |
| break; |
| } |
| } |
| |
| return expr; |
| } |
| |
| // 11.3 Postfix Expressions |
| |
| var parsePostfixExpression = parseLeftHandSideExpression; |
| |
| // 11.4 Unary Operators |
| |
| function parseUnaryExpression() { |
| var token, expr; |
| |
| if (lookahead.type !== Token.Punctuator && lookahead.type !== Token.Keyword) { |
| expr = parsePostfixExpression(); |
| } else if (match('+') || match('-') || match('!')) { |
| token = lex(); |
| expr = parseUnaryExpression(); |
| expr = delegate.createUnaryExpression(token.value, expr); |
| } else if (matchKeyword('delete') || matchKeyword('void') || matchKeyword('typeof')) { |
| throwError({}, Messages.UnexpectedToken); |
| } else { |
| expr = parsePostfixExpression(); |
| } |
| |
| return expr; |
| } |
| |
| function binaryPrecedence(token) { |
| var prec = 0; |
| |
| if (token.type !== Token.Punctuator && token.type !== Token.Keyword) { |
| return 0; |
| } |
| |
| switch (token.value) { |
| case '||': |
| prec = 1; |
| break; |
| |
| case '&&': |
| prec = 2; |
| break; |
| |
| case '==': |
| case '!=': |
| case '===': |
| case '!==': |
| prec = 6; |
| break; |
| |
| case '<': |
| case '>': |
| case '<=': |
| case '>=': |
| case 'instanceof': |
| prec = 7; |
| break; |
| |
| case 'in': |
| prec = 7; |
| break; |
| |
| case '+': |
| case '-': |
| prec = 9; |
| break; |
| |
| case '*': |
| case '/': |
| case '%': |
| prec = 11; |
| break; |
| |
| default: |
| break; |
| } |
| |
| return prec; |
| } |
| |
| // 11.5 Multiplicative Operators |
| // 11.6 Additive Operators |
| // 11.7 Bitwise Shift Operators |
| // 11.8 Relational Operators |
| // 11.9 Equality Operators |
| // 11.10 Binary Bitwise Operators |
| // 11.11 Binary Logical Operators |
| |
| function parseBinaryExpression() { |
| var expr, token, prec, stack, right, operator, left, i; |
| |
| left = parseUnaryExpression(); |
| |
| token = lookahead; |
| prec = binaryPrecedence(token); |
| if (prec === 0) { |
| return left; |
| } |
| token.prec = prec; |
| lex(); |
| |
| right = parseUnaryExpression(); |
| |
| stack = [left, token, right]; |
| |
| while ((prec = binaryPrecedence(lookahead)) > 0) { |
| |
| // Reduce: make a binary expression from the three topmost entries. |
| while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec)) { |
| right = stack.pop(); |
| operator = stack.pop().value; |
| left = stack.pop(); |
| expr = delegate.createBinaryExpression(operator, left, right); |
| stack.push(expr); |
| } |
| |
| // Shift. |
| token = lex(); |
| token.prec = prec; |
| stack.push(token); |
| expr = parseUnaryExpression(); |
| stack.push(expr); |
| } |
| |
| // Final reduce to clean-up the stack. |
| i = stack.length - 1; |
| expr = stack[i]; |
| while (i > 1) { |
| expr = delegate.createBinaryExpression(stack[i - 1].value, stack[i - 2], expr); |
| i -= 2; |
| } |
| |
| return expr; |
| } |
| |
| |
| // 11.12 Conditional Operator |
| |
| function parseConditionalExpression() { |
| var expr, consequent, alternate; |
| |
| expr = parseBinaryExpression(); |
| |
| if (match('?')) { |
| lex(); |
| consequent = parseConditionalExpression(); |
| expect(':'); |
| alternate = parseConditionalExpression(); |
| |
| expr = delegate.createConditionalExpression(expr, consequent, alternate); |
| } |
| |
| return expr; |
| } |
| |
| // Simplification since we do not support AssignmentExpression. |
| var parseExpression = parseConditionalExpression; |
| |
| // Polymer Syntax extensions |
| |
| // Filter :: |
| // Identifier |
| // Identifier "(" ")" |
| // Identifier "(" FilterArguments ")" |
| |
| function parseFilter() { |
| var identifier, args; |
| |
| identifier = lex(); |
| |
| if (identifier.type !== Token.Identifier) { |
| throwUnexpected(identifier); |
| } |
| |
| args = match('(') ? parseArguments() : []; |
| |
| return delegate.createFilter(identifier.value, args); |
| } |
| |
| // Filters :: |
| // "|" Filter |
| // Filters "|" Filter |
| |
| function parseFilters() { |
| while (match('|')) { |
| lex(); |
| parseFilter(); |
| } |
| } |
| |
| // TopLevel :: |
| // LabelledExpressions |
| // AsExpression |
| // InExpression |
| // FilterExpression |
| |
| // AsExpression :: |
| // FilterExpression as Identifier |
| |
| // InExpression :: |
| // Identifier, Identifier in FilterExpression |
| // Identifier in FilterExpression |
| |
| // FilterExpression :: |
| // Expression |
| // Expression Filters |
| |
| function parseTopLevel() { |
| skipWhitespace(); |
| peek(); |
| |
| var expr = parseExpression(); |
| if (expr) { |
| if (lookahead.value === ',' || lookahead.value == 'in' && |
| expr.type === Syntax.Identifier) { |
| parseInExpression(expr); |
| } else { |
| parseFilters(); |
| if (lookahead.value === 'as') { |
| parseAsExpression(expr); |
| } else { |
| delegate.createTopLevel(expr); |
| } |
| } |
| } |
| |
| if (lookahead.type !== Token.EOF) { |
| throwUnexpected(lookahead); |
| } |
| } |
| |
| function parseAsExpression(expr) { |
| lex(); // as |
| var identifier = lex().value; |
| delegate.createAsExpression(expr, identifier); |
| } |
| |
| function parseInExpression(identifier) { |
| var indexName; |
| if (lookahead.value === ',') { |
| lex(); |
| if (lookahead.type !== Token.Identifier) |
| throwUnexpected(lookahead); |
| indexName = lex().value; |
| } |
| |
| lex(); // in |
| var expr = parseExpression(); |
| parseFilters(); |
| delegate.createInExpression(identifier.name, indexName, expr); |
| } |
| |
| function parse(code, inDelegate) { |
| delegate = inDelegate; |
| source = code; |
| index = 0; |
| length = source.length; |
| lookahead = null; |
| state = { |
| labelSet: {} |
| }; |
| |
| return parseTopLevel(); |
| } |
| |
| global.esprima = { |
| parse: parse |
| }; |
| })(this); |
| |
| // Copyright (c) 2014 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 |
| |
| (function (global) { |
| 'use strict'; |
| |
| function prepareBinding(expressionText, name, node, filterRegistry) { |
| var expression; |
| try { |
| expression = getExpression(expressionText); |
| if (expression.scopeIdent && |
| (node.nodeType !== Node.ELEMENT_NODE || |
| node.tagName !== 'TEMPLATE' || |
| (name !== 'bind' && name !== 'repeat'))) { |
| throw Error('as and in can only be used within <template bind/repeat>'); |
| } |
| } catch (ex) { |
| console.error('Invalid expression syntax: ' + expressionText, ex); |
| return; |
| } |
| |
| return function(model, node, oneTime) { |
| var binding = expression.getBinding(model, filterRegistry, oneTime); |
| if (expression.scopeIdent && binding) { |
| node.polymerExpressionScopeIdent_ = expression.scopeIdent; |
| if (expression.indexIdent) |
| node.polymerExpressionIndexIdent_ = expression.indexIdent; |
| } |
| |
| return binding; |
| } |
| } |
| |
| // TODO(rafaelw): Implement simple LRU. |
| var expressionParseCache = Object.create(null); |
| |
| function getExpression(expressionText) { |
| var expression = expressionParseCache[expressionText]; |
| if (!expression) { |
| var delegate = new ASTDelegate(); |
| esprima.parse(expressionText, delegate); |
| expression = new Expression(delegate); |
| expressionParseCache[expressionText] = expression; |
| } |
| return expression; |
| } |
| |
| function Literal(value) { |
| this.value = value; |
| this.valueFn_ = undefined; |
| } |
| |
| Literal.prototype = { |
| valueFn: function() { |
| if (!this.valueFn_) { |
| var value = this.value; |
| this.valueFn_ = function() { |
| return value; |
| } |
| } |
| |
| return this.valueFn_; |
| } |
| } |
| |
| function IdentPath(name) { |
| this.name = name; |
| this.path = Path.get(name); |
| } |
| |
| IdentPath.prototype = { |
| valueFn: function() { |
| if (!this.valueFn_) { |
| var name = this.name; |
| var path = this.path; |
| this.valueFn_ = function(model, observer) { |
| if (observer) |
| observer.addPath(model, path); |
| |
| return path.getValueFrom(model); |
| } |
| } |
| |
| return this.valueFn_; |
| }, |
| |
| setValue: function(model, newValue) { |
| if (this.path.length == 1) |
| model = findScope(model, this.path[0]); |
| |
| return this.path.setValueFrom(model, newValue); |
| } |
| }; |
| |
| function MemberExpression(object, property, accessor) { |
| this.computed = accessor == '['; |
| |
| this.dynamicDeps = typeof object == 'function' || |
| object.dynamicDeps || |
| (this.computed && !(property instanceof Literal)); |
| |
| this.simplePath = |
| !this.dynamicDeps && |
| (property instanceof IdentPath || property instanceof Literal) && |
| (object instanceof MemberExpression || object instanceof IdentPath); |
| |
| this.object = this.simplePath ? object : getFn(object); |
| this.property = !this.computed || this.simplePath ? |
| property : getFn(property); |
| } |
| |
| MemberExpression.prototype = { |
| get fullPath() { |
| if (!this.fullPath_) { |
| |
| var parts = this.object instanceof MemberExpression ? |
| this.object.fullPath.slice() : [this.object.name]; |
| parts.push(this.property instanceof IdentPath ? |
| this.property.name : this.property.value); |
| this.fullPath_ = Path.get(parts); |
| } |
| |
| return this.fullPath_; |
| }, |
| |
| valueFn: function() { |
| if (!this.valueFn_) { |
| var object = this.object; |
| |
| if (this.simplePath) { |
| var path = this.fullPath; |
| |
| this.valueFn_ = function(model, observer) { |
| if (observer) |
| observer.addPath(model, path); |
| |
| return path.getValueFrom(model); |
| }; |
| } else if (!this.computed) { |
| var path = Path.get(this.property.name); |
| |
| this.valueFn_ = function(model, observer, filterRegistry) { |
| var context = object(model, observer, filterRegistry); |
| |
| if (observer) |
| observer.addPath(context, path); |
| |
| return path.getValueFrom(context); |
| } |
| } else { |
| // Computed property. |
| var property = this.property; |
| |
| this.valueFn_ = function(model, observer, filterRegistry) { |
| var context = object(model, observer, filterRegistry); |
| var propName = property(model, observer, filterRegistry); |
| if (observer) |
| observer.addPath(context, [propName]); |
| |
| return context ? context[propName] : undefined; |
| }; |
| } |
| } |
| return this.valueFn_; |
| }, |
| |
| setValue: function(model, newValue) { |
| if (this.simplePath) { |
| this.fullPath.setValueFrom(model, newValue); |
| return newValue; |
| } |
| |
| var object = this.object(model); |
| var propName = this.property instanceof IdentPath ? this.property.name : |
| this.property(model); |
| return object[propName] = newValue; |
| } |
| }; |
| |
| function Filter(name, args) { |
| this.name = name; |
| this.args = []; |
| for (var i = 0; i < args.length; i++) { |
| this.args[i] = getFn(args[i]); |
| } |
| } |
| |
| Filter.prototype = { |
| transform: function(model, observer, filterRegistry, toModelDirection, |
| initialArgs) { |
| var fn = filterRegistry[this.name]; |
| var context = model; |
| if (fn) { |
| context = undefined; |
| } else { |
| fn = context[this.name]; |
| if (!fn) { |
| console.error('Cannot find function or filter: ' + this.name); |
| return; |
| } |
| } |
| |
| // If toModelDirection is falsey, then the "normal" (dom-bound) direction |
| // is used. Otherwise, it looks for a 'toModel' property function on the |
| // object. |
| if (toModelDirection) { |
| fn = fn.toModel; |
| } else if (typeof fn.toDOM == 'function') { |
| fn = fn.toDOM; |
| } |
| |
| if (typeof fn != 'function') { |
| console.error('Cannot find function or filter: ' + this.name); |
| return; |
| } |
| |
| var args = initialArgs || []; |
| for (var i = 0; i < this.args.length; i++) { |
| args.push(getFn(this.args[i])(model, observer, filterRegistry)); |
| } |
| |
| return fn.apply(context, args); |
| } |
| }; |
| |
| function notImplemented() { throw Error('Not Implemented'); } |
| |
| var unaryOperators = { |
| '+': function(v) { return +v; }, |
| '-': function(v) { return -v; }, |
| '!': function(v) { return !v; } |
| }; |
| |
| var binaryOperators = { |
| '+': function(l, r) { return l+r; }, |
| '-': function(l, r) { return l-r; }, |
| '*': function(l, r) { return l*r; }, |
| '/': function(l, r) { return l/r; }, |
| '%': function(l, r) { return l%r; }, |
| '<': function(l, r) { return l<r; }, |
| '>': function(l, r) { return l>r; }, |
| '<=': function(l, r) { return l<=r; }, |
| '>=': function(l, r) { return l>=r; }, |
| '==': function(l, r) { return l==r; }, |
| '!=': function(l, r) { return l!=r; }, |
| '===': function(l, r) { return l===r; }, |
| '!==': function(l, r) { return l!==r; }, |
| '&&': function(l, r) { return l&&r; }, |
| '||': function(l, r) { return l||r; }, |
| }; |
| |
| function getFn(arg) { |
| return typeof arg == 'function' ? arg : arg.valueFn(); |
| } |
| |
| function ASTDelegate() { |
| this.expression = null; |
| this.filters = []; |
| this.deps = {}; |
| this.currentPath = undefined; |
| this.scopeIdent = undefined; |
| this.indexIdent = undefined; |
| this.dynamicDeps = false; |
| } |
| |
| ASTDelegate.prototype = { |
| createUnaryExpression: function(op, argument) { |
| if (!unaryOperators[op]) |
| throw Error('Disallowed operator: ' + op); |
| |
| argument = getFn(argument); |
| |
| return function(model, observer, filterRegistry) { |
| return unaryOperators[op](argument(model, observer, filterRegistry)); |
| }; |
| }, |
| |
| createBinaryExpression: function(op, left, right) { |
| if (!binaryOperators[op]) |
| throw Error('Disallowed operator: ' + op); |
| |
| left = getFn(left); |
| right = getFn(right); |
| |
| switch (op) { |
| case '||': |
| this.dynamicDeps = true; |
| return function(model, observer, filterRegistry) { |
| return left(model, observer, filterRegistry) || |
| right(model, observer, filterRegistry); |
| }; |
| case '&&': |
| this.dynamicDeps = true; |
| return function(model, observer, filterRegistry) { |
| return left(model, observer, filterRegistry) && |
| right(model, observer, filterRegistry); |
| }; |
| } |
| |
| return function(model, observer, filterRegistry) { |
| return binaryOperators[op](left(model, observer, filterRegistry), |
| right(model, observer, filterRegistry)); |
| }; |
| }, |
| |
| createConditionalExpression: function(test, consequent, alternate) { |
| test = getFn(test); |
| consequent = getFn(consequent); |
| alternate = getFn(alternate); |
| |
| this.dynamicDeps = true; |
| |
| return function(model, observer, filterRegistry) { |
| return test(model, observer, filterRegistry) ? |
| consequent(model, observer, filterRegistry) : |
| alternate(model, observer, filterRegistry); |
| } |
| }, |
| |
| createIdentifier: function(name) { |
| var ident = new IdentPath(name); |
| ident.type = 'Identifier'; |
| return ident; |
| }, |
| |
| createMemberExpression: function(accessor, object, property) { |
| var ex = new MemberExpression(object, property, accessor); |
| if (ex.dynamicDeps) |
| this.dynamicDeps = true; |
| return ex; |
| }, |
| |
| createCallExpression: function(expression, args) { |
| if (!(expression instanceof IdentPath)) |
| throw Error('Only identifier function invocations are allowed'); |
| |
| var filter = new Filter(expression.name, args); |
| |
| return function(model, observer, filterRegistry) { |
| return filter.transform(model, observer, filterRegistry, false); |
| }; |
| }, |
| |
| createLiteral: function(token) { |
| return new Literal(token.value); |
| }, |
| |
| createArrayExpression: function(elements) { |
| for (var i = 0; i < elements.length; i++) |
| elements[i] = getFn(elements[i]); |
| |
| return function(model, observer, filterRegistry) { |
| var arr = [] |
| for (var i = 0; i < elements.length; i++) |
| arr.push(elements[i](model, observer, filterRegistry)); |
| return arr; |
| } |
| }, |
| |
| createProperty: function(kind, key, value) { |
| return { |
| key: key instanceof IdentPath ? key.name : key.value, |
| value: value |
| }; |
| }, |
| |
| createObjectExpression: function(properties) { |
| for (var i = 0; i < properties.length; i++) |
| properties[i].value = getFn(properties[i].value); |
| |
| return function(model, observer, filterRegistry) { |
| var obj = {}; |
| for (var i = 0; i < properties.length; i++) |
| obj[properties[i].key] = |
| properties[i].value(model, observer, filterRegistry); |
| return obj; |
| } |
| }, |
| |
| createFilter: function(name, args) { |
| this.filters.push(new Filter(name, args)); |
| }, |
| |
| createAsExpression: function(expression, scopeIdent) { |
| this.expression = expression; |
| this.scopeIdent = scopeIdent; |
| }, |
| |
| createInExpression: function(scopeIdent, indexIdent, expression) { |
| this.expression = expression; |
| this.scopeIdent = scopeIdent; |
| this.indexIdent = indexIdent; |
| }, |
| |
| createTopLevel: function(expression) { |
| this.expression = expression; |
| }, |
| |
| createThisExpression: notImplemented |
| } |
| |
| function ConstantObservable(value) { |
| this.value_ = value; |
| } |
| |
| ConstantObservable.prototype = { |
| open: function() { return this.value_; }, |
| discardChanges: function() { return this.value_; }, |
| deliver: function() {}, |
| close: function() {}, |
| } |
| |
| function Expression(delegate) { |
| this.scopeIdent = delegate.scopeIdent; |
| this.indexIdent = delegate.indexIdent; |
| |
| if (!delegate.expression) |
| throw Error('No expression found.'); |
| |
| this.expression = delegate.expression; |
| getFn(this.expression); // forces enumeration of path dependencies |
| |
| this.filters = delegate.filters; |
| this.dynamicDeps = delegate.dynamicDeps; |
| } |
| |
| Expression.prototype = { |
| getBinding: function(model, filterRegistry, oneTime) { |
| if (oneTime) |
| return this.getValue(model, undefined, filterRegistry); |
| |
| var observer = new CompoundObserver(); |
| // captures deps. |
| var firstValue = this.getValue(model, observer, filterRegistry); |
| var firstTime = true; |
| var self = this; |
| |
| function valueFn() { |
| // deps cannot have changed on first value retrieval. |
| if (firstTime) { |
| firstTime = false; |
| return firstValue; |
| } |
| |
| if (self.dynamicDeps) |
| observer.startReset(); |
| |
| var value = self.getValue(model, |
| self.dynamicDeps ? observer : undefined, |
| filterRegistry); |
| if (self.dynamicDeps) |
| observer.finishReset(); |
| |
| return value; |
| } |
| |
| function setValueFn(newValue) { |
| self.setValue(model, newValue, filterRegistry); |
| return newValue; |
| } |
| |
| return new ObserverTransform(observer, valueFn, setValueFn, true); |
| }, |
| |
| getValue: function(model, observer, filterRegistry) { |
| var value = getFn(this.expression)(model, observer, filterRegistry); |
| for (var i = 0; i < this.filters.length; i++) { |
| value = this.filters[i].transform(model, observer, filterRegistry, |
| false, [value]); |
| } |
| |
| return value; |
| }, |
| |
| setValue: function(model, newValue, filterRegistry) { |
| var count = this.filters ? this.filters.length : 0; |
| while (count-- > 0) { |
| newValue = this.filters[count].transform(model, undefined, |
| filterRegistry, true, [newValue]); |
| } |
| |
| if (this.expression.setValue) |
| return this.expression.setValue(model, newValue); |
| } |
| } |
| |
| /** |
| * Converts a style property name to a css property name. For example: |
| * "WebkitUserSelect" to "-webkit-user-select" |
| */ |
| function convertStylePropertyName(name) { |
| return String(name).replace(/[A-Z]/g, function(c) { |
| return '-' + c.toLowerCase(); |
| }); |
| } |
| |
| var parentScopeName = '@' + Math.random().toString(36).slice(2); |
| |
| // Single ident paths must bind directly to the appropriate scope object. |
| // I.e. Pushed values in two-bindings need to be assigned to the actual model |
| // object. |
| function findScope(model, prop) { |
| while (model[parentScopeName] && |
| !Object.prototype.hasOwnProperty.call(model, prop)) { |
| model = model[parentScopeName]; |
| } |
| |
| return model; |
| } |
| |
| function isLiteralExpression(pathString) { |
| switch (pathString) { |
| case '': |
| return false; |
| |
| case 'false': |
| case 'null': |
| case 'true': |
| return true; |
| } |
| |
| if (!isNaN(Number(pathString))) |
| return true; |
| |
| return false; |
| }; |
| |
| function PolymerExpressions() {} |
| |
| PolymerExpressions.prototype = { |
| // "built-in" filters |
| styleObject: function(value) { |
| var parts = []; |
| for (var key in value) { |
| parts.push(convertStylePropertyName(key) + ': ' + value[key]); |
| } |
| return parts.join('; '); |
| }, |
| |
| tokenList: function(value) { |
| var tokens = []; |
| for (var key in value) { |
| if (value[key]) |
| tokens.push(key); |
| } |
| return tokens.join(' '); |
| }, |
| |
| // binding delegate API |
| prepareInstancePositionChanged: function(template) { |
| var indexIdent = template.polymerExpressionIndexIdent_; |
| if (!indexIdent) |
| return; |
| |
| return function(templateInstance, index) { |
| templateInstance.model[indexIdent] = index; |
| }; |
| }, |
| |
| prepareBinding: function(pathString, name, node) { |
| var path = Path.get(pathString); |
| |
| if (!isLiteralExpression(pathString) && path.valid) { |
| if (path.length == 1) { |
| return function(model, node, oneTime) { |
| if (oneTime) |
| return path.getValueFrom(model); |
| |
| var scope = findScope(model, path[0]); |
| return new PathObserver(scope, path); |
| }; |
| } |
| return; // bail out early if pathString is simple path. |
| } |
| |
| return prepareBinding(pathString, name, node, this); |
| }, |
| |
| prepareInstanceModel: function(template) { |
| var scopeName = template.polymerExpressionScopeIdent_; |
| if (!scopeName) |
| return; |
| |
| var parentScope = template.templateInstance ? |
| template.templateInstance.model : |
| template.model; |
| |
| var indexName = template.polymerExpressionIndexIdent_; |
| |
| return function(model) { |
| return createScopeObject(parentScope, model, scopeName, indexName); |
| }; |
| } |
| }; |
| |
| var createScopeObject = ('__proto__' in {}) ? |
| function(parentScope, model, scopeName, indexName) { |
| var scope = {}; |
| scope[scopeName] = model; |
| scope[indexName] = undefined; |
| scope[parentScopeName] = parentScope; |
| scope.__proto__ = parentScope; |
| return scope; |
| } : |
| function(parentScope, model, scopeName, indexName) { |
| var scope = Object.create(parentScope); |
| Object.defineProperty(scope, scopeName, |
| { value: model, configurable: true, writable: true }); |
| Object.defineProperty(scope, indexName, |
| { value: undefined, configurable: true, writable: true }); |
| Object.defineProperty(scope, parentScopeName, |
| { value: parentScope, configurable: true, writable: true }); |
| return scope; |
| }; |
| |
| global.PolymerExpressions = PolymerExpressions; |
| PolymerExpressions.getExpression = getExpression; |
| })(this); |
| |
| Polymer = { |
| version: '0.5.4' |
| }; |
| |
| // TODO(sorvell): this ensures Polymer is an object and not a function |
| // Platform is currently defining it as a function to allow for async loading |
| // of polymer; once we refine the loading process this likely goes away. |
| if (typeof window.Polymer === 'function') { |
| Polymer = {}; |
| } |
| |
| |
| (function(scope) { |
| |
| function withDependencies(task, depends) { |
| depends = depends || []; |
| if (!depends.map) { |
| depends = [depends]; |
| } |
| return task.apply(this, depends.map(marshal)); |
| } |
| |
| function module(name, dependsOrFactory, moduleFactory) { |
| var module; |
| switch (arguments.length) { |
| case 0: |
| return; |
| case 1: |
| module = null; |
| break; |
| case 2: |
| // dependsOrFactory is `factory` in this case |
| module = dependsOrFactory.apply(this); |
| break; |
| default: |
| // dependsOrFactory is `depends` in this case |
| module = withDependencies(moduleFactory, dependsOrFactory); |
| break; |
| } |
| modules[name] = module; |
| }; |
| |
| function marshal(name) { |
| return modules[name]; |
| } |
| |
| var modules = {}; |
| |
| function using(depends, task) { |
| HTMLImports.whenImportsReady(function() { |
| withDependencies(task, depends); |
| }); |
| }; |
| |
| // exports |
| |
| scope.marshal = marshal; |
| // `module` confuses commonjs detectors |
| scope.modularize = module; |
| scope.using = using; |
| |
| })(window); |
| |
| /* |
| Build only script. |
| |
| Ensures scripts needed for basic x-platform compatibility |
| will be run when platform.js is not loaded. |
| */ |
| if (!window.WebComponents) { |
| |
| /* |
| On supported platforms, platform.js is not needed. To retain compatibility |
| with the polyfills, we stub out minimal functionality. |
| */ |
| if (!window.WebComponents) { |
| |
| WebComponents = { |
| flush: function() {}, |
| flags: {log: {}} |
| }; |
| |
| Platform = WebComponents; |
| |
| CustomElements = { |
| useNative: true, |
| ready: true, |
| takeRecords: function() {}, |
| instanceof: function(obj, base) { |
| return obj instanceof base; |
| } |
| }; |
| |
| HTMLImports = { |
| useNative: true |
| }; |
| |
| |
| addEventListener('HTMLImportsLoaded', function() { |
| document.dispatchEvent( |
| new CustomEvent('WebComponentsReady', {bubbles: true}) |
| ); |
| }); |
| |
| |
| // ShadowDOM |
| ShadowDOMPolyfill = null; |
| wrap = unwrap = function(n){ |
| return n; |
| }; |
| |
| } |
| |
| /* |
| Create polyfill scope and feature detect native support. |
| */ |
| window.HTMLImports = window.HTMLImports || {flags:{}}; |
| |
| (function(scope) { |
| |
| /** |
| Basic setup and simple module executer. We collect modules and then execute |
| the code later, only if it's necessary for polyfilling. |
| */ |
| var IMPORT_LINK_TYPE = 'import'; |
| var useNative = Boolean(IMPORT_LINK_TYPE in document.createElement('link')); |
| |
| /** |
| Support `currentScript` on all browsers as `document._currentScript.` |
| |
| NOTE: We cannot polyfill `document.currentScript` because it's not possible |
| both to override and maintain the ability to capture the native value. |
| Therefore we choose to expose `_currentScript` both when native imports |
| and the polyfill are in use. |
| */ |
| // NOTE: ShadowDOMPolyfill intrusion. |
| var hasShadowDOMPolyfill = Boolean(window.ShadowDOMPolyfill); |
| var wrap = function(node) { |
| return hasShadowDOMPolyfill ? ShadowDOMPolyfill.wrapIfNeeded(node) : node; |
| }; |
| var rootDocument = wrap(document); |
| |
| var currentScriptDescriptor = { |
| get: function() { |
| var script = HTMLImports.currentScript || document.currentScript || |
| // NOTE: only works when called in synchronously executing code. |
| // readyState should check if `loading` but IE10 is |
| // interactive when scripts run so we cheat. |
| (document.readyState !== 'complete' ? |
| document.scripts[document.scripts.length - 1] : null); |
| return wrap(script); |
| }, |
| configurable: true |
| }; |
| |
| Object.defineProperty(document, '_currentScript', currentScriptDescriptor); |
| Object.defineProperty(rootDocument, '_currentScript', currentScriptDescriptor); |
| |
| /** |
| Add support for the `HTMLImportsLoaded` event and the `HTMLImports.whenReady` |
| method. This api is necessary because unlike the native implementation, |
| script elements do not force imports to resolve. Instead, users should wrap |
| code in either an `HTMLImportsLoaded` hander or after load time in an |
| `HTMLImports.whenReady(callback)` call. |
| |
| NOTE: This module also supports these apis under the native implementation. |
| Therefore, if this file is loaded, the same code can be used under both |
| the polyfill and native implementation. |
| */ |
| |
| var isIE = /Trident/.test(navigator.userAgent); |
| |
| // call a callback when all HTMLImports in the document at call time |
| // (or at least document ready) have loaded. |
| // 1. ensure the document is in a ready state (has dom), then |
| // 2. watch for loading of imports and call callback when done |
| function whenReady(callback, doc) { |
| doc = doc || rootDocument; |
| // if document is loading, wait and try again |
| whenDocumentReady(function() { |
| watchImportsLoad(callback, doc); |
| }, doc); |
| } |
| |
| // call the callback when the document is in a ready state (has dom) |
| var requiredReadyState = isIE ? 'complete' : 'interactive'; |
| var READY_EVENT = 'readystatechange'; |
| function isDocumentReady(doc) { |
| return (doc.readyState === 'complete' || |
| doc.readyState === requiredReadyState); |
| } |
| |
| // call <callback> when we ensure the document is in a ready state |
| function whenDocumentReady(callback, doc) { |
| if (!isDocumentReady(doc)) { |
| var checkReady = function() { |
| if (doc.readyState === 'complete' || |
| doc.readyState === requiredReadyState) { |
| doc.removeEventListener(READY_EVENT, checkReady); |
| whenDocumentReady(callback, doc); |
| } |
| }; |
| doc.addEventListener(READY_EVENT, checkReady); |
| } else if (callback) { |
| callback(); |
| } |
| } |
| |
| function markTargetLoaded(event) { |
| event.target.__loaded = true; |
| } |
| |
| // call <callback> when we ensure all imports have loaded |
| function watchImportsLoad(callback, doc) { |
| var imports = doc.querySelectorAll('link[rel=import]'); |
| var loaded = 0, l = imports.length; |
| function checkDone(d) { |
| if ((loaded == l) && callback) { |
| callback(); |
| } |
| } |
| function loadedImport(e) { |
| markTargetLoaded(e); |
| loaded++; |
| checkDone(); |
| } |
| if (l) { |
| for (var i=0, imp; (i<l) && (imp=imports[i]); i++) { |
| if (isImportLoaded(imp)) { |
| loadedImport.call(imp, {target: imp}); |
| } else { |
| imp.addEventListener('load', loadedImport); |
| imp.addEventListener('error', loadedImport); |
| } |
| } |
| } else { |
| checkDone(); |
| } |
| } |
| |
| // NOTE: test for native imports loading is based on explicitly watching |
| // all imports (see below). |
| // However, we cannot rely on this entirely without watching the entire document |
| // for import links. For perf reasons, currently only head is watched. |
| // Instead, we fallback to checking if the import property is available |
| // and the document is not itself loading. |
| function isImportLoaded(link) { |
| return useNative ? link.__loaded || |
| (link.import && link.import.readyState !== 'loading') : |
| link.__importParsed; |
| } |
| |
| // TODO(sorvell): Workaround for |
| // https://www.w3.org/Bugs/Public/show_bug.cgi?id=25007, should be removed when |
| // this bug is addressed. |
| // (1) Install a mutation observer to see when HTMLImports have loaded |
| // (2) if this script is run during document load it will watch any existing |
| // imports for loading. |
| // |
| // NOTE: The workaround has restricted functionality: (1) it's only compatible |
| // with imports that are added to document.head since the mutation observer |
| // watches only head for perf reasons, (2) it requires this script |
| // to run before any imports have completed loading. |
| if (useNative) { |
| new MutationObserver(function(mxns) { |
| for (var i=0, l=mxns.length, m; (i < l) && (m=mxns[i]); i++) { |
| if (m.addedNodes) { |
| handleImports(m.addedNodes); |
| } |
| } |
| }).observe(document.head, {childList: true}); |
| |
| function handleImports(nodes) { |
| for (var i=0, l=nodes.length, n; (i<l) && (n=nodes[i]); i++) { |
| if (isImport(n)) { |
| handleImport(n); |
| } |
| } |
| } |
| |
| function isImport(element) { |
| return element.localName === 'link' && element.rel === 'import'; |
| } |
| |
| function handleImport(element) { |
| var loaded = element.import; |
| if (loaded) { |
| markTargetLoaded({target: element}); |
| } else { |
| element.addEventListener('load', markTargetLoaded); |
| element.addEventListener('error', markTargetLoaded); |
| } |
| } |
| |
| // make sure to catch any imports that are in the process of loading |
| // when this script is run. |
| (function() { |
| if (document.readyState === 'loading') { |
| var imports = document.querySelectorAll('link[rel=import]'); |
| for (var i=0, l=imports.length, imp; (i<l) && (imp=imports[i]); i++) { |
| handleImport(imp); |
| } |
| } |
| })(); |
| |
| } |
| |
| // Fire the 'HTMLImportsLoaded' event when imports in document at load time |
| // have loaded. This event is required to simulate the script blocking |
| // behavior of native imports. A main document script that needs to be sure |
| // imports have loaded should wait for this event. |
| whenReady(function() { |
| HTMLImports.ready = true; |
| HTMLImports.readyTime = new Date().getTime(); |
| rootDocument.dispatchEvent( |
| new CustomEvent('HTMLImportsLoaded', {bubbles: true}) |
| ); |
| }); |
| |
| // exports |
| scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE; |
| scope.useNative = useNative; |
| scope.rootDocument = rootDocument; |
| scope.whenReady = whenReady; |
| scope.isIE = isIE; |
| |
| })(HTMLImports); |
| |
| (function(scope) { |
| |
| // TODO(sorvell): It's desireable to provide a default stylesheet |
| // that's convenient for styling unresolved elements, but |
| // it's cumbersome to have to include this manually in every page. |
| // It would make sense to put inside some HTMLImport but |
| // the HTMLImports polyfill does not allow loading of stylesheets |
| // that block rendering. Therefore this injection is tolerated here. |
| var style = document.createElement('style'); |
| style.textContent = '' |
| + 'body {' |
| + 'transition: opacity ease-in 0.2s;' |
| + ' } \n' |
| + 'body[unresolved] {' |
| + 'opacity: 0; display: block; overflow: hidden;' |
| + ' } \n' |
| ; |
| var head = document.querySelector('head'); |
| head.insertBefore(style, head.firstChild); |
| |
| })(Platform); |
| |
| /* |
| Build only script. |
| |
| Ensures scripts needed for basic x-platform compatibility |
| will be run when platform.js is not loaded. |
| */ |
| } |
| (function(global) { |
| 'use strict'; |
| |
| var testingExposeCycleCount = global.testingExposeCycleCount; |
| |
| // Detect and do basic sanity checking on Object/Array.observe. |
| function detectObjectObserve() { |
| if (typeof Object.observe !== 'function' || |
| typeof Array.observe !== 'function') { |
| return false; |
| } |
| |
| var records = []; |
| |
| function callback(recs) { |
| records = recs; |
| } |
| |
| var test = {}; |
| var arr = []; |
| Object.observe(test, callback); |
| Array.observe(arr, callback); |
| test.id = 1; |
| test.id = 2; |
| delete test.id; |
| arr.push(1, 2); |
| arr.length = 0; |
| |
| Object.deliverChangeRecords(callback); |
| if (records.length !== 5) |
| return false; |
| |
| if (records[0].type != 'add' || |
| records[1].type != 'update' || |
| records[2].type != 'delete' || |
| records[3].type != 'splice' || |
| records[4].type != 'splice') { |
| return false; |
| } |
| |
| Object.unobserve(test, callback); |
| Array.unobserve(arr, callback); |
| |
| return true; |
| } |
| |
| var hasObserve = detectObjectObserve(); |
| |
| function detectEval() { |
| // Don't test for eval if we're running in a Chrome App environment. |
| // We check for APIs set that only exist in a Chrome App context. |
| if (typeof chrome !== 'undefined' && chrome.app && chrome.app.runtime) { |
| return false; |
| } |
| |
| // Firefox OS Apps do not allow eval. This feature detection is very hacky |
| // but even if some other platform adds support for this function this code |
| // will continue to work. |
| if (typeof navigator != 'undefined' && navigator.getDeviceStorage) { |
| return false; |
| } |
| |
| try { |
| var f = new Function('', 'return true;'); |
| return f(); |
| } catch (ex) { |
| return false; |
| } |
| } |
| |
| var hasEval = detectEval(); |
| |
| function isIndex(s) { |
| return +s === s >>> 0 && s !== ''; |
| } |
| |
| function toNumber(s) { |
| return +s; |
| } |
| |
| function isObject(obj) { |
| return obj === Object(obj); |
| } |
| |
| var numberIsNaN = global.Number.isNaN || function(value) { |
| return typeof value === 'number' && global.isNaN(value); |
| } |
| |
| function areSameValue(left, right) { |
| if (left === right) |
| return left !== 0 || 1 / left === 1 / right; |
| if (numberIsNaN(left) && numberIsNaN(right)) |
| return true; |
| |
| return left !== left && right !== right; |
| } |
| |
| var createObject = ('__proto__' in {}) ? |
| function(obj) { return obj; } : |
| function(obj) { |
| var proto = obj.__proto__; |
| if (!proto) |
| return obj; |
| var newObject = Object.create(proto); |
| Object.getOwnPropertyNames(obj).forEach(function(name) { |
| Object.defineProperty(newObject, name, |
| Object.getOwnPropertyDescriptor(obj, name)); |
| }); |
| return newObject; |
| }; |
| |
| var identStart = '[\$_a-zA-Z]'; |
| var identPart = '[\$_a-zA-Z0-9]'; |
| var identRegExp = new RegExp('^' + identStart + '+' + identPart + '*' + '$'); |
| |
| function getPathCharType(char) { |
| if (char === undefined) |
| return 'eof'; |
| |
| var code = char.charCodeAt(0); |
| |
| switch(code) { |
| case 0x5B: // [ |
| case 0x5D: // ] |
| case 0x2E: // . |
| case 0x22: // " |
| case 0x27: // ' |
| case 0x30: // 0 |
| return char; |
| |
| case 0x5F: // _ |
| case 0x24: // $ |
| return 'ident'; |
| |
| case 0x20: // Space |
| case 0x09: // Tab |
| case 0x0A: // Newline |
| case 0x0D: // Return |
| case 0xA0: // No-break space |
| case 0xFEFF: // Byte Order Mark |
| case 0x2028: // Line Separator |
| case 0x2029: // Paragraph Separator |
| return 'ws'; |
| } |
| |
| // a-z, A-Z |
| if ((0x61 <= code && code <= 0x7A) || (0x41 <= code && code <= 0x5A)) |
| return 'ident'; |
| |
| // 1-9 |
| if (0x31 <= code && code <= 0x39) |
| return 'number'; |
| |
| return 'else'; |
| } |
| |
| var pathStateMachine = { |
| 'beforePath': { |
| 'ws': ['beforePath'], |
| 'ident': ['inIdent', 'append'], |
| '[': ['beforeElement'], |
| 'eof': ['afterPath'] |
| }, |
| |
| 'inPath': { |
| 'ws': ['inPath'], |
| '.': ['beforeIdent'], |
| '[': ['beforeElement'], |
| 'eof': ['afterPath'] |
| }, |
| |
| 'beforeIdent': { |
| 'ws': ['beforeIdent'], |
| 'ident': ['inIdent', 'append'] |
| }, |
| |
| 'inIdent': { |
| 'ident': ['inIdent', 'append'], |
| '0': ['inIdent', 'append'], |
| 'number': ['inIdent', 'append'], |
| 'ws': ['inPath', 'push'], |
| '.': ['beforeIdent', 'push'], |
| '[': ['beforeElement', 'push'], |
| 'eof': ['afterPath', 'push'] |
| }, |
| |
| 'beforeElement': { |
| 'ws': ['beforeElement'], |
| '0': ['afterZero', 'append'], |
| 'number': ['inIndex', 'append'], |
| "'": ['inSingleQuote', 'append', ''], |
| '"': ['inDoubleQuote', 'append', ''] |
| }, |
| |
| 'afterZero': { |
| 'ws': ['afterElement', 'push'], |
| ']': ['inPath', 'push'] |
| }, |
| |
| 'inIndex': { |
| '0': ['inIndex', 'append'], |
| 'number': ['inIndex', 'append'], |
| 'ws': ['afterElement'], |
| ']': ['inPath', 'push'] |
| }, |
| |
| 'inSingleQuote': { |
| "'": ['afterElement'], |
| 'eof': ['error'], |
| 'else': ['inSingleQuote', 'append'] |
| }, |
| |
| 'inDoubleQuote': { |
| '"': ['afterElement'], |
| 'eof': ['error'], |
| 'else': ['inDoubleQuote', 'append'] |
| }, |
| |
| 'afterElement': { |
| 'ws': ['afterElement'], |
| ']': ['inPath', 'push'] |
| } |
| } |
| |
| function noop() {} |
| |
| function parsePath(path) { |
| var keys = []; |
| var index = -1; |
| var c, newChar, key, type, transition, action, typeMap, mode = 'beforePath'; |
| |
| var actions = { |
| push: function() { |
| if (key === undefined) |
| return; |
| |
| keys.push(key); |
| key = undefined; |
| }, |
| |
| append: function() { |
| if (key === undefined) |
| key = newChar |
| else |
| key += newChar; |
| } |
| }; |
| |
| function maybeUnescapeQuote() { |
| if (index >= path.length) |
| return; |
| |
| var nextChar = path[index + 1]; |
| if ((mode == 'inSingleQuote' && nextChar == "'") || |
| (mode == 'inDoubleQuote' && nextChar == '"')) { |
| index++; |
| newChar = nextChar; |
| actions.append(); |
| return true; |
| } |
| } |
| |
| while (mode) { |
| index++; |
| c = path[index]; |
| |
| if (c == '\\' && maybeUnescapeQuote(mode)) |
| continue; |
| |
| type = getPathCharType(c); |
| typeMap = pathStateMachine[mode]; |
| transition = typeMap[type] || typeMap['else'] || 'error'; |
| |
| if (transition == 'error') |
| return; // parse error; |
| |
| mode = transition[0]; |
| action = actions[transition[1]] || noop; |
| newChar = transition[2] === undefined ? c : transition[2]; |
| action(); |
| |
| if (mode === 'afterPath') { |
| return keys; |
| } |
| } |
| |
| return; // parse error |
| } |
| |
| function isIdent(s) { |
| return identRegExp.test(s); |
| } |
| |
| var constructorIsPrivate = {}; |
| |
| function Path(parts, privateToken) { |
| if (privateToken !== constructorIsPrivate) |
| throw Error('Use Path.get to retrieve path objects'); |
| |
| for (var i = 0; i < parts.length; i++) { |
| this.push(String(parts[i])); |
| } |
| |
| if (hasEval && this.length) { |
| this.getValueFrom = this.compiledGetValueFromFn(); |
| } |
| } |
| |
| // TODO(rafaelw): Make simple LRU cache |
| var pathCache = {}; |
| |
| function getPath(pathString) { |
| if (pathString instanceof Path) |
| return pathString; |
| |
| if (pathString == null || pathString.length == 0) |
| pathString = ''; |
| |
| if (typeof pathString != 'string') { |
| if (isIndex(pathString.length)) { |
| // Constructed with array-like (pre-parsed) keys |
| return new Path(pathString, constructorIsPrivate); |
| } |
| |
| pathString = String(pathString); |
| } |
| |
| var path = pathCache[pathString]; |
| if (path) |
| return path; |
| |
| var parts = parsePath(pathString); |
| if (!parts) |
| return invalidPath; |
| |
| var path = new Path(parts, constructorIsPrivate); |
| pathCache[pathString] = path; |
| return path; |
| } |
| |
| Path.get = getPath; |
| |
| function formatAccessor(key) { |
| if (isIndex(key)) { |
| return '[' + key + ']'; |
| } else { |
| return '["' + key.replace(/"/g, '\\"') + '"]'; |
| } |
| } |
| |
| Path.prototype = createObject({ |
| __proto__: [], |
| valid: true, |
| |
| toString: function() { |
| var pathString = ''; |
| for (var i = 0; i < this.length; i++) { |
| var key = this[i]; |
| if (isIdent(key)) { |
| pathString += i ? '.' + key : key; |
| } else { |
| pathString += formatAccessor(key); |
| } |
| } |
| |
| return pathString; |
| }, |
| |
| getValueFrom: function(obj, directObserver) { |
| for (var i = 0; i < this.length; i++) { |
| if (obj == null) |
| return; |
| obj = obj[this[i]]; |
| } |
| return obj; |
| }, |
| |
| iterateObjects: function(obj, observe) { |
| for (var i = 0; i < this.length; i++) { |
| if (i) |
| obj = obj[this[i - 1]]; |
| if (!isObject(obj)) |
| return; |
| observe(obj, this[i]); |
| } |
| }, |
| |
| compiledGetValueFromFn: function() { |
| var str = ''; |
| var pathString = 'obj'; |
| str += 'if (obj != null'; |
| var i = 0; |
| var key; |
| for (; i < (this.length - 1); i++) { |
| key = this[i]; |
| pathString += isIdent(key) ? '.' + key : formatAccessor(key); |
| str += ' &&\n ' + pathString + ' != null'; |
| } |
| str += ')\n'; |
| |
| var key = this[i]; |
| pathString += isIdent(key) ? '.' + key : formatAccessor(key); |
| |
| str += ' return ' + pathString + ';\nelse\n return undefined;'; |
| return new Function('obj', str); |
| }, |
| |
| setValueFrom: function(obj, value) { |
| if (!this.length) |
| return false; |
| |
| for (var i = 0; i < this.length - 1; i++) { |
| if (!isObject(obj)) |
| return false; |
| obj = obj[this[i]]; |
| } |
| |
| if (!isObject(obj)) |
| return false; |
| |
| obj[this[i]] = value; |
| return true; |
| } |
| }); |
| |
| var invalidPath = new Path('', constructorIsPrivate); |
| invalidPath.valid = false; |
| invalidPath.getValueFrom = invalidPath.setValueFrom = function() {}; |
| |
| var MAX_DIRTY_CHECK_CYCLES = 1000; |
| |
| function dirtyCheck(observer) { |
| var cycles = 0; |
| while (cycles < MAX_DIRTY_CHECK_CYCLES && observer.check_()) { |
| cycles++; |
| } |
| if (testingExposeCycleCount) |
| global.dirtyCheckCycleCount = cycles; |
| |
| return cycles > 0; |
| } |
| |
| function objectIsEmpty(object) { |
| for (var prop in object) |
| return false; |
| return true; |
| } |
| |
| function diffIsEmpty(diff) { |
| return objectIsEmpty(diff.added) && |
| objectIsEmpty(diff.removed) && |
| objectIsEmpty(diff.changed); |
| } |
| |
| function diffObjectFromOldObject(object, oldObject) { |
| var added = {}; |
| var removed = {}; |
| var changed = {}; |
| |
| for (var prop in oldObject) { |
| var newValue = object[prop]; |
| |
| if (newValue !== undefined && newValue === oldObject[prop]) |
| continue; |
| |
| if (!(prop in object)) { |
| removed[prop] = undefined; |
| continue; |
| } |
| |
| if (newValue !== oldObject[prop]) |
| changed[prop] = newValue; |
| } |
| |
| for (var prop in object) { |
| if (prop in oldObject) |
| continue; |
| |
| added[prop] = object[prop]; |
| } |
| |
| if (Array.isArray(object) && object.length !== oldObject.length) |
| changed.length = object.length; |
| |
| return { |
| added: added, |
| removed: removed, |
| changed: changed |
| }; |
| } |
| |
| var eomTasks = []; |
| function runEOMTasks() { |
| if (!eomTasks.length) |
| return false; |
| |
| for (var i = 0; i < eomTasks.length; i++) { |
| eomTasks[i](); |
| } |
| eomTasks.length = 0; |
| return true; |
| } |
| |
| var runEOM = hasObserve ? (function(){ |
| return function(fn) { |
| return Promise.resolve().then(fn); |
| } |
| })() : |
| (function() { |
| return function(fn) { |
| eomTasks.push(fn); |
| }; |
| })(); |
| |
| var observedObjectCache = []; |
| |
| function newObservedObject() { |
| var observer; |
| var object; |
| var discardRecords = false; |
| var first = true; |
| |
| function callback(records) { |
| if (observer && observer.state_ === OPENED && !discardRecords) |
| observer.check_(records); |
| } |
| |
| return { |
| open: function(obs) { |
| if (observer) |
| throw Error('ObservedObject in use'); |
| |
| if (!first) |
| Object.deliverChangeRecords(callback); |
| |
| observer = obs; |
| first = false; |
| }, |
| observe: function(obj, arrayObserve) { |
| object = obj; |
| if (arrayObserve) |
| Array.observe(object, callback); |
| else |
| Object.observe(object, callback); |
| }, |
| deliver: function(discard) { |
| discardRecords = discard; |
| Object.deliverChangeRecords(callback); |
| discardRecords = false; |
| }, |
| close: function() { |
| observer = undefined; |
| Object.unobserve(object, callback); |
| observedObjectCache.push(this); |
| } |
| }; |
| } |
| |
| /* |
| * The observedSet abstraction is a perf optimization which reduces the total |
| * number of Object.observe observations of a set of objects. The idea is that |
| * groups of Observers will have some object dependencies in common and this |
| * observed set ensures that each object in the transitive closure of |
| * dependencies is only observed once. The observedSet acts as a write barrier |
| * such that whenever any change comes through, all Observers are checked for |
| * changed values. |
| * |
| * Note that this optimization is explicitly moving work from setup-time to |
| * change-time. |
| * |
| * TODO(rafaelw): Implement "garbage collection". In order to move work off |
| * the critical path, when Observers are closed, their observed objects are |
| * not Object.unobserve(d). As a result, it's possible that if the observedSet |
| * is kept open, but some Observers have been closed, it could cause "leaks" |
| * (prevent otherwise collectable objects from being collected). At some |
| * point, we should implement incremental "gc" which keeps a list of |
| * observedSets which may need clean-up and does small amounts of cleanup on a |
| * timeout until all is clean. |
| */ |
| |
| function getObservedObject(observer, object, arrayObserve) { |
| var dir = observedObjectCache.pop() || newObservedObject(); |
| dir.open(observer); |
| dir.observe(object, arrayObserve); |
| return dir; |
| } |
| |
| var observedSetCache = []; |
| |
| function newObservedSet() { |
| var observerCount = 0; |
| var observers = []; |
| var objects = []; |
| var rootObj; |
| var rootObjProps; |
| |
| function observe(obj, prop) { |
| if (!obj) |
| return; |
| |
| if (obj === rootObj) |
| rootObjProps[prop] = true; |
| |
| if (objects.indexOf(obj) < 0) { |
| objects.push(obj); |
| Object.observe(obj, callback); |
| } |
| |
| observe(Object.getPrototypeOf(obj), prop); |
| } |
| |
| function allRootObjNonObservedProps(recs) { |
| for (var i = 0; i < recs.length; i++) { |
| var rec = recs[i]; |
| if (rec.object !== rootObj || |
| rootObjProps[rec.name] || |
| rec.type === 'setPrototype') { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| function callback(recs) { |
| if (allRootObjNonObservedProps(recs)) |
| return; |
| |
| var observer; |
| for (var i = 0; i < observers.length; i++) { |
| observer = observers[i]; |
| if (observer.state_ == OPENED) { |
| observer.iterateObjects_(observe); |
| } |
| } |
| |
| for (var i = 0; i < observers.length; i++) { |
| observer = observers[i]; |
| if (observer.state_ == OPENED) { |
| observer.check_(); |
| } |
| } |
| } |
| |
| var record = { |
| objects: objects, |
| get rootObject() { return rootObj; }, |
| set rootObject(value) { |
| rootObj = value; |
| rootObjProps = {}; |
| }, |
| open: function(obs, object) { |
| observers.push(obs); |
| observerCount++; |
| obs.iterateObjects_(observe); |
| }, |
| close: function(obs) { |
| observerCount--; |
| if (observerCount > 0) { |
| return; |
| } |
| |
| for (var i = 0; i < objects.length; i++) { |
| Object.unobserve(objects[i], callback); |
| Observer.unobservedCount++; |
| } |
| |
| observers.length = 0; |
| objects.length = 0; |
| rootObj = undefined; |
| rootObjProps = undefined; |
| observedSetCache.push(this); |
| if (lastObservedSet === this) |
| lastObservedSet = null; |
| }, |
| }; |
| |
| return record; |
| } |
| |
| var lastObservedSet; |
| |
| function getObservedSet(observer, obj) { |
| if (!lastObservedSet || lastObservedSet.rootObject !== obj) { |
| lastObservedSet = observedSetCache.pop() || newObservedSet(); |
| lastObservedSet.rootObject = obj; |
| } |
| lastObservedSet.open(observer, obj); |
| return lastObservedSet; |
| } |
| |
| var UNOPENED = 0; |
| var OPENED = 1; |
| var CLOSED = 2; |
| var RESETTING = 3; |
| |
| var nextObserverId = 1; |
| |
| function Observer() { |
| this.state_ = UNOPENED; |
| this.callback_ = undefined; |
| this.target_ = undefined; // TODO(rafaelw): Should be WeakRef |
| this.directObserver_ = undefined; |
| this.value_ = undefined; |
| this.id_ = nextObserverId++; |
| } |
| |
| Observer.prototype = { |
| open: function(callback, target) { |
| if (this.state_ != UNOPENED) |
| throw Error('Observer has already been opened.'); |
| |
| addToAll(this); |
| this.callback_ = callback; |
| this.target_ = target; |
| this.connect_(); |
| this.state_ = OPENED; |
| return this.value_; |
| }, |
| |
| close: function() { |
| if (this.state_ != OPENED) |
| return; |
| |
| removeFromAll(this); |
| this.disconnect_(); |
| this.value_ = undefined; |
| this.callback_ = undefined; |
| this.target_ = undefined; |
| this.state_ = CLOSED; |
| }, |
| |
| deliver: function() { |
| if (this.state_ != OPENED) |
| return; |
| |
| dirtyCheck(this); |
| }, |
| |
| report_: function(changes) { |
| try { |
| this.callback_.apply(this.target_, changes); |
| } catch (ex) { |
| Observer._errorThrownDuringCallback = true; |
| console.error('Exception caught during observer callback: ' + |
| (ex.stack || ex)); |
| } |
| }, |
| |
| discardChanges: function() { |
| this.check_(undefined, true); |
| return this.value_; |
| } |
| } |
| |
| var collectObservers = !hasObserve; |
| var allObservers; |
| Observer._allObserversCount = 0; |
| |
| if (collectObservers) { |
| allObservers = []; |
| } |
| |
| function addToAll(observer) { |
| Observer._allObserversCount++; |
| if (!collectObservers) |
| return; |
| |
| allObservers.push(observer); |
| } |
| |
| function removeFromAll(observer) { |
| Observer._allObserversCount--; |
| } |
| |
| var runningMicrotaskCheckpoint = false; |
| |
| global.Platform = global.Platform || {}; |
| |
| global.Platform.performMicrotaskCheckpoint = function() { |
| if (runningMicrotaskCheckpoint) |
| return; |
| |
| if (!collectObservers) |
| return; |
| |
| runningMicrotaskCheckpoint = true; |
| |
| var cycles = 0; |
| var anyChanged, toCheck; |
| |
| do { |
| cycles++; |
| toCheck = allObservers; |
| allObservers = []; |
| anyChanged = false; |
| |
| for (var i = 0; i < toCheck.length; i++) { |
| var observer = toCheck[i]; |
| if (observer.state_ != OPENED) |
| continue; |
| |
| if (observer.check_()) |
| anyChanged = true; |
| |
| allObservers.push(observer); |
| } |
| if (runEOMTasks()) |
| anyChanged = true; |
| } while (cycles < MAX_DIRTY_CHECK_CYCLES && anyChanged); |
| |
| if (testingExposeCycleCount) |
| global.dirtyCheckCycleCount = cycles; |
| |
| runningMicrotaskCheckpoint = false; |
| }; |
| |
| if (collectObservers) { |
| global.Platform.clearObservers = function() { |
| allObservers = []; |
| }; |
| } |
| |
| function ObjectObserver(object) { |
| Observer.call(this); |
| this.value_ = object; |
| this.oldObject_ = undefined; |
| } |
| |
| ObjectObserver.prototype = createObject({ |
| __proto__: Observer.prototype, |
| |
| arrayObserve: false, |
| |
| connect_: function(callback, target) { |
| if (hasObserve) { |
| this.directObserver_ = getObservedObject(this, this.value_, |
| this.arrayObserve); |
| } else { |
| this.oldObject_ = this.copyObject(this.value_); |
| } |
| |
| }, |
| |
| copyObject: function(object) { |
| var copy = Array.isArray(object) ? [] : {}; |
| for (var prop in object) { |
| copy[prop] = object[prop]; |
| }; |
| if (Array.isArray(object)) |
| copy.length = object.length; |
| return copy; |
| }, |
| |
| check_: function(changeRecords, skipChanges) { |
| var diff; |
| var oldValues; |
| if (hasObserve) { |
| if (!changeRecords) |
| return false; |
| |
| oldValues = {}; |
| diff = diffObjectFromChangeRecords(this.value_, changeRecords, |
| oldValues); |
| } else { |
| oldValues = this.oldObject_; |
| diff = diffObjectFromOldObject(this.value_, this.oldObject_); |
| } |
| |
| if (diffIsEmpty(diff)) |
| return false; |
| |
| if (!hasObserve) |
| this.oldObject_ = this.copyObject(this.value_); |
| |
| this.report_([ |
| diff.added || {}, |
| diff.removed || {}, |
| diff.changed || {}, |
| function(property) { |
| return oldValues[property]; |
| } |
| ]); |
| |
| return true; |
| }, |
| |
| disconnect_: function() { |
| if (hasObserve) { |
| this.directObserver_.close(); |
| this.directObserver_ = undefined; |
| } else { |
| this.oldObject_ = undefined; |
| } |
| }, |
| |
| deliver: function() { |
| if (this.state_ != OPENED) |
| return; |
| |
| if (hasObserve) |
| this.directObserver_.deliver(false); |
| else |
| dirtyCheck(this); |
| }, |
| |
| discardChanges: function() { |
| if (this.directObserver_) |
| this.directObserver_.deliver(true); |
| else |
| this.oldObject_ = this.copyObject(this.value_); |
| |
| return this.value_; |
| } |
| }); |
| |
| function ArrayObserver(array) { |
| if (!Array.isArray(array)) |
| throw Error('Provided object is not an Array'); |
| ObjectObserver.call(this, array); |
| } |
| |
| ArrayObserver.prototype = createObject({ |
| |
| __proto__: ObjectObserver.prototype, |
| |
| arrayObserve: true, |
| |
| copyObject: function(arr) { |
| return arr.slice(); |
| }, |
| |
| check_: function(changeRecords) { |
| var splices; |
| if (hasObserve) { |
| if (!changeRecords) |
| return false; |
| splices = projectArraySplices(this.value_, changeRecords); |
| } else { |
| splices = calcSplices(this.value_, 0, this.value_.length, |
| this.oldObject_, 0, this.oldObject_.length); |
| } |
| |
| if (!splices || !splices.length) |
| return false; |
| |
| if (!hasObserve) |
| this.oldObject_ = this.copyObject(this.value_); |
| |
| this.report_([splices]); |
| return true; |
| } |
| }); |
| |
| ArrayObserver.applySplices = function(previous, current, splices) { |
| splices.forEach(function(splice) { |
| var spliceArgs = [splice.index, splice.removed.length]; |
| var addIndex = splice.index; |
| while (addIndex < splice.index + splice.addedCount) { |
| spliceArgs.push(current[addIndex]); |
| addIndex++; |
| } |
| |
| Array.prototype.splice.apply(previous, spliceArgs); |
| }); |
| }; |
| |
| function PathObserver(object, path) { |
| Observer.call(this); |
| |
| this.object_ = object; |
| this.path_ = getPath(path); |
| this.directObserver_ = undefined; |
| } |
| |
| PathObserver.prototype = createObject({ |
| __proto__: Observer.prototype, |
| |
| get path() { |
| return this.path_; |
| }, |
| |
| connect_: function() { |
| if (hasObserve) |
| this.directObserver_ = getObservedSet(this, this.object_); |
| |
| this.check_(undefined, true); |
| }, |
| |
| disconnect_: function() { |
| this.value_ = undefined; |
| |
| if (this.directObserver_) { |
| this.directObserver_.close(this); |
| this.directObserver_ = undefined; |
| } |
| }, |
| |
| iterateObjects_: function(observe) { |
| this.path_.iterateObjects(this.object_, observe); |
| }, |
| |
| check_: function(changeRecords, skipChanges) { |
| var oldValue = this.value_; |
| this.value_ = this.path_.getValueFrom(this.object_); |
| if (skipChanges || areSameValue(this.value_, oldValue)) |
| return false; |
| |
| this.report_([this.value_, oldValue, this]); |
| return true; |
| }, |
| |
| setValue: function(newValue) { |
| if (this.path_) |
| this.path_.setValueFrom(this.object_, newValue); |
| } |
| }); |
| |
| function CompoundObserver(reportChangesOnOpen) { |
| Observer.call(this); |
| |
| this.reportChangesOnOpen_ = reportChangesOnOpen; |
| this.value_ = []; |
| this.directObserver_ = undefined; |
| this.observed_ = []; |
| } |
| |
| var observerSentinel = {}; |
| |
| CompoundObserver.prototype = createObject({ |
| __proto__: Observer.prototype, |
| |
| connect_: function() { |
| if (hasObserve) { |
| var object; |
| var needsDirectObserver = false; |
| for (var i = 0; i < this.observed_.length; i += 2) { |
| object = this.observed_[i] |
| if (object !== observerSentinel) { |
| needsDirectObserver = true; |
| break; |
| } |
| } |
| |
| if (needsDirectObserver) |
| this.directObserver_ = getObservedSet(this, object); |
| } |
| |
| this.check_(undefined, !this.reportChangesOnOpen_); |
| }, |
| |
| disconnect_: function() { |
| for (var i = 0; i < this.observed_.length; i += 2) { |
| if (this.observed_[i] === observerSentinel) |
| this.observed_[i + 1].close(); |
| } |
| this.observed_.length = 0; |
| this.value_.length = 0; |
| |
| if (this.directObserver_) { |
| this.directObserver_.close(this); |
| this.directObserver_ = undefined; |
| } |
| }, |
| |
| addPath: function(object, path) { |
| if (this.state_ != UNOPENED && this.state_ != RESETTING) |
| throw Error('Cannot add paths once started.'); |
| |
| var path = getPath(path); |
| this.observed_.push(object, path); |
| if (!this.reportChangesOnOpen_) |
| return; |
| var index = this.observed_.length / 2 - 1; |
| this.value_[index] = path.getValueFrom(object); |
| }, |
| |
| addObserver: function(observer) { |
| if (this.state_ != UNOPENED && this.state_ != RESETTING) |
| throw Error('Cannot add observers once started.'); |
| |
| this.observed_.push(observerSentinel, observer); |
| if (!this.reportChangesOnOpen_) |
| return; |
| var index = this.observed_.length / 2 - 1; |
| this.value_[index] = observer.open(this.deliver, this); |
| }, |
| |
| startReset: function() { |
| if (this.state_ != OPENED) |
| throw Error('Can only reset while open'); |
| |
| this.state_ = RESETTING; |
| this.disconnect_(); |
| }, |
| |
| finishReset: function() { |
| if (this.state_ != RESETTING) |
| throw Error('Can only finishReset after startReset'); |
| this.state_ = OPENED; |
| this.connect_(); |
| |
| return this.value_; |
| }, |
| |
| iterateObjects_: function(observe) { |
| var object; |
| for (var i = 0; i < this.observed_.length; i += 2) { |
| object = this.observed_[i] |
| if (object !== observerSentinel) |
| this.observed_[i + 1].iterateObjects(object, observe) |
| } |
| }, |
| |
| check_: function(changeRecords, skipChanges) { |
| var oldValues; |
| for (var i = 0; i < this.observed_.length; i += 2) { |
| var object = this.observed_[i]; |
| var path = this.observed_[i+1]; |
| var value; |
| if (object === observerSentinel) { |
| var observable = path; |
| value = this.state_ === UNOPENED ? |
| observable.open(this.deliver, this) : |
| observable.discardChanges(); |
| } else { |
| value = path.getValueFrom(object); |
| } |
| |
| if (skipChanges) { |
| this.value_[i / 2] = value; |
| continue; |
| } |
| |
| if (areSameValue(value, this.value_[i / 2])) |
| continue; |
| |
| oldValues = oldValues || []; |
| oldValues[i / 2] = this.value_[i / 2]; |
| this.value_[i / 2] = value; |
| } |
| |
| if (!oldValues) |
| return false; |
| |
| // TODO(rafaelw): Having observed_ as the third callback arg here is |
| // pretty lame API. Fix. |
| this.report_([this.value_, oldValues, this.observed_]); |
| return true; |
| } |
| }); |
| |
| function identFn(value) { return value; } |
| |
| function ObserverTransform(observable, getValueFn, setValueFn, |
| dontPassThroughSet) { |
| this.callback_ = undefined; |
| this.target_ = undefined; |
| this.value_ = undefined; |
| this.observable_ = observable; |
| this.getValueFn_ = getValueFn || identFn; |
| this.setValueFn_ = setValueFn || identFn; |
| // TODO(rafaelw): This is a temporary hack. PolymerExpressions needs this |
| // at the moment because of a bug in it's dependency tracking. |
| this.dontPassThroughSet_ = dontPassThroughSet; |
| } |
| |
| ObserverTransform.prototype = { |
| open: function(callback, target) { |
| this.callback_ = callback; |
| this.target_ = target; |
| this.value_ = |
| this.getValueFn_(this.observable_.open(this.observedCallback_, this)); |
| return this.value_; |
| }, |
| |
| observedCallback_: function(value) { |
| value = this.getValueFn_(value); |
| if (areSameValue(value, this.value_)) |
| return; |
| var oldValue = this.value_; |
| this.value_ = value; |
| this.callback_.call(this.target_, this.value_, oldValue); |
| }, |
| |
| discardChanges: function() { |
| this.value_ = this.getValueFn_(this.observable_.discardChanges()); |
| return this.value_; |
| }, |
| |
| deliver: function() { |
| return this.observable_.deliver(); |
| }, |
| |
| setValue: function(value) { |
| value = this.setValueFn_(value); |
| if (!this.dontPassThroughSet_ && this.observable_.setValue) |
| return this.observable_.setValue(value); |
| }, |
| |
| close: function() { |
| if (this.observable_) |
| this.observable_.close(); |
| this.callback_ = undefined; |
| this.target_ = undefined; |
| this.observable_ = undefined; |
| this.value_ = undefined; |
| this.getValueFn_ = undefined; |
| this.setValueFn_ = undefined; |
| } |
| } |
| |
| var expectedRecordTypes = { |
| add: true, |
| update: true, |
| delete: true |
| }; |
| |
| function diffObjectFromChangeRecords(object, changeRecords, oldValues) { |
| var added = {}; |
| var removed = {}; |
| |
| for (var i = 0; i < changeRecords.length; i++) { |
| var record = changeRecords[i]; |
| if (!expectedRecordTypes[record.type]) { |
| console.error('Unknown changeRecord type: ' + record.type); |
| console.error(record); |
| continue; |
| } |
| |
| if (!(record.name in oldValues)) |
| oldValues[record.name] = record.oldValue; |
| |
| if (record.type == 'update') |
| continue; |
| |
| if (record.type == 'add') { |
| if (record.name in removed) |
| delete removed[record.name]; |
| else |
| added[record.name] = true; |
| |
| continue; |
| } |
| |
| // type = 'delete' |
| if (record.name in added) { |
| delete added[record.name]; |
| delete oldValues[record.name]; |
| } else { |
| removed[record.name] = true; |
| } |
| } |
| |
| for (var prop in added) |
| added[prop] = object[prop]; |
| |
| for (var prop in removed) |
| removed[prop] = undefined; |
| |
| var changed = {}; |
| for (var prop in oldValues) { |
| if (prop in added || prop in removed) |
| continue; |
| |
| var newValue = object[prop]; |
| if (oldValues[prop] !== newValue) |
| changed[prop] = newValue; |
| } |
| |
| return { |
| added: added, |
| removed: removed, |
| changed: changed |
| }; |
| } |
| |
| function newSplice(index, removed, addedCount) { |
| return { |
| index: index, |
| removed: removed, |
| addedCount: addedCount |
| }; |
| } |
| |
| var EDIT_LEAVE = 0; |
| var EDIT_UPDATE = 1; |
| var EDIT_ADD = 2; |
| var EDIT_DELETE = 3; |
| |
| function ArraySplice() {} |
| |
| ArraySplice.prototype = { |
| |
| // Note: This function is *based* on the computation of the Levenshtein |
| // "edit" distance. The one change is that "updates" are treated as two |
| // edits - not one. With Array splices, an update is really a delete |
| // followed by an add. By retaining this, we optimize for "keeping" the |
| // maximum array items in the original array. For example: |
| // |
| // 'xxxx123' -> '123yyyy' |
| // |
| // With 1-edit updates, the shortest path would be just to update all seven |
| // characters. With 2-edit updates, we delete 4, leave 3, and add 4. This |
| // leaves the substring '123' intact. |
| calcEditDistances: function(current, currentStart, currentEnd, |
| old, oldStart, oldEnd) { |
| // "Deletion" columns |
| var rowCount = oldEnd - oldStart + 1; |
| var columnCount = currentEnd - currentStart + 1; |
| var distances = new Array(rowCount); |
| |
| // "Addition" rows. Initialize null column. |
| for (var i = 0; i < rowCount; i++) { |
| distances[i] = new Array(columnCount); |
| distances[i][0] = i; |
| } |
| |
| // Initialize null row |
| for (var j = 0; j < columnCount; j++) |
| distances[0][j] = j; |
| |
| for (var i = 1; i < rowCount; i++) { |
| for (var j = 1; j < columnCount; j++) { |
| if (this.equals(current[currentStart + j - 1], old[oldStart + i - 1])) |
| distances[i][j] = distances[i - 1][j - 1]; |
| else { |
| var north = distances[i - 1][j] + 1; |
| var west = distances[i][j - 1] + 1; |
| distances[i][j] = north < west ? north : west; |
| } |
| } |
| } |
| |
| return distances; |
| }, |
| |
| // This starts at the final weight, and walks "backward" by finding |
| // the minimum previous weight recursively until the origin of the weight |
| // matrix. |
| spliceOperationsFromEditDistances: function(distances) { |
| var i = distances.length - 1; |
| var j = distances[0].length - 1; |
| var current = distances[i][j]; |
| var edits = []; |
| while (i > 0 || j > 0) { |
| if (i == 0) { |
| edits.push(EDIT_ADD); |
| j--; |
| continue; |
| } |
| if (j == 0) { |
| edits.push(EDIT_DELETE); |
| i--; |
| continue; |
| } |
| var northWest = distances[i - 1][j - 1]; |
| var west = distances[i - 1][j]; |
| var north = distances[i][j - 1]; |
| |
| var min; |
| if (west < north) |
| min = west < northWest ? west : northWest; |
| else |
| min = north < northWest ? north : northWest; |
| |
| if (min == northWest) { |
| if (northWest == current) { |
| edits.push(EDIT_LEAVE); |
| } else { |
| edits.push(EDIT_UPDATE); |
| current = northWest; |
| } |
| i--; |
| j--; |
| } else if (min == west) { |
| edits.push(EDIT_DELETE); |
| i--; |
| current = west; |
| } else { |
| edits.push(EDIT_ADD); |
| j--; |
| current = north; |
| } |
| } |
| |
| edits.reverse(); |
| return edits; |
| }, |
| |
| /** |
| * Splice Projection functions: |
| * |
| * A splice map is a representation of how a previous array of items |
| * was transformed into a new array of items. Conceptually it is a list of |
| * tuples of |
| * |
| * <index, removed, addedCount> |
| * |
| * which are kept in ascending index order of. The tuple represents that at |
| * the |index|, |removed| sequence of items were removed, and counting forward |
| * from |index|, |addedCount| items were added. |
| */ |
| |
| /** |
| * Lacking individual splice mutation information, the minimal set of |
| * splices can be synthesized given the previous state and final state of an |
| * array. The basic approach is to calculate the edit distance matrix and |
| * choose the shortest path through it. |
| * |
| * Complexity: O(l * p) |
| * l: The length of the current array |
| * p: The length of the old array |
| */ |
| calcSplices: function(current, currentStart, currentEnd, |
| old, oldStart, oldEnd) { |
| var prefixCount = 0; |
| var suffixCount = 0; |
| |
| var minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart); |
| if (currentStart == 0 && oldStart == 0) |
| prefixCount = this.sharedPrefix(current, old, minLength); |
| |
| if (currentEnd == current.length && oldEnd == old.length) |
| suffixCount = this.sharedSuffix(current, old, minLength - prefixCount); |
| |
| currentStart += prefixCount; |
| oldStart += prefixCount; |
| currentEnd -= suffixCount; |
| oldEnd -= suffixCount; |
| |
| if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0) |
| return []; |
| |
| if (currentStart == currentEnd) { |
| var splice = newSplice(currentStart, [], 0); |
| while (oldStart < oldEnd) |
| splice.removed.push(old[oldStart++]); |
| |
| return [ splice ]; |
| } else if (oldStart == oldEnd) |
| return [ newSplice(currentStart, [], currentEnd - currentStart) ]; |
| |
| var ops = this.spliceOperationsFromEditDistances( |
| this.calcEditDistances(current, currentStart, currentEnd, |
| old, oldStart, oldEnd)); |
| |
| var splice = undefined; |
| var splices = []; |
| var index = currentStart; |
| var oldIndex = oldStart; |
| for (var i = 0; i < ops.length; i++) { |
| switch(ops[i]) { |
| case EDIT_LEAVE: |
| if (splice) { |
| splices.push(splice); |
| splice = undefined; |
| } |
| |
| index++; |
| oldIndex++; |
| break; |
| case EDIT_UPDATE: |
| if (!splice) |
| splice = newSplice(index, [], 0); |
| |
| splice.addedCount++; |
| index++; |
| |
| splice.removed.push(old[oldIndex]); |
| oldIndex++; |
| break; |
| case EDIT_ADD: |
| if (!splice) |
| splice = newSplice(index, [], 0); |
| |
| splice.addedCount++; |
| index++; |
| break; |
| case EDIT_DELETE: |
| if (!splice) |
| splice = newSplice(index, [], 0); |
| |
| splice.removed.push(old[oldIndex]); |
| oldIndex++; |
| break; |
| } |
| } |
| |
| if (splice) { |
| splices.push(splice); |
| } |
| return splices; |
| }, |
| |
| sharedPrefix: function(current, old, searchLength) { |
| for (var i = 0; i < searchLength; i++) |
| if (!this.equals(current[i], old[i])) |
| return i; |
| return searchLength; |
| }, |
| |
| sharedSuffix: function(current, old, searchLength) { |
| var index1 = current.length; |
| var index2 = old.length; |
| var count = 0; |
| while (count < searchLength && this.equals(current[--index1], old[--index2])) |
| count++; |
| |
| return count; |
| }, |
| |
| calculateSplices: function(current, previous) { |
| return this.calcSplices(current, 0, current.length, previous, 0, |
| previous.length); |
| }, |
| |
| equals: function(currentValue, previousValue) { |
| return currentValue === previousValue; |
| } |
| }; |
| |
| var arraySplice = new ArraySplice(); |
| |
| function calcSplices(current, currentStart, currentEnd, |
| old, oldStart, oldEnd) { |
| return arraySplice.calcSplices(current, currentStart, currentEnd, |
| old, oldStart, oldEnd); |
| } |
| |
| function intersect(start1, end1, start2, end2) { |
| // Disjoint |
| if (end1 < start2 || end2 < start1) |
| return -1; |
| |
| // Adjacent |
| if (end1 == start2 || end2 == start1) |
| return 0; |
| |
| // Non-zero intersect, span1 first |
| if (start1 < start2) { |
| if (end1 < end2) |
| return end1 - start2; // Overlap |
| else |
| return end2 - start2; // Contained |
| } else { |
| // Non-zero intersect, span2 first |
| if (end2 < end1) |
| return end2 - start1; // Overlap |
| else |
| return end1 - start1; // Contained |
| } |
| } |
| |
| function mergeSplice(splices, index, removed, addedCount) { |
| |
| var splice = newSplice(index, removed, addedCount); |
| |
| var inserted = false; |
| var insertionOffset = 0; |
| |
| for (var i = 0; i < splices.length; i++) { |
| var current = splices[i]; |
| current.index += insertionOffset; |
| |
| if (inserted) |
| continue; |
| |
| var intersectCount = intersect(splice.index, |
| splice.index + splice.removed.length, |
| current.index, |
| current.index + current.addedCount); |
| |
| if (intersectCount >= 0) { |
| // Merge the two splices |
| |
| splices.splice(i, 1); |
| i--; |
| |
| insertionOffset -= current.addedCount - current.removed.length; |
| |
| splice.addedCount += current.addedCount - intersectCount; |
| var deleteCount = splice.removed.length + |
| current.removed.length - intersectCount; |
| |
| if (!splice.addedCount && !deleteCount) { |
| // merged splice is a noop. discard. |
| inserted = true; |
| } else { |
| var removed = current.removed; |
| |
| if (splice.index < current.index) { |
| // some prefix of splice.removed is prepended to current.removed. |
| var prepend = splice.removed.slice(0, current.index - splice.index); |
| Array.prototype.push.apply(prepend, removed); |
| removed = prepend; |
| } |
| |
| if (splice.index + splice.removed.length > current.index + current.addedCount) { |
| // some suffix of splice.removed is appended to current.removed. |
| var append = splice.removed.slice(current.index + current.addedCount - splice.index); |
| Array.prototype.push.apply(removed, append); |
| } |
| |
| splice.removed = removed; |
| if (current.index < splice.index) { |
| splice.index = current.index; |
| } |
| } |
| } else if (splice.index < current.index) { |
| // Insert splice here. |
| |
| inserted = true; |
| |
| splices.splice(i, 0, splice); |
| i++; |
| |
| var offset = splice.addedCount - splice.removed.length |
| current.index += offset; |
| insertionOffset += offset; |
| } |
| } |
| |
| if (!inserted) |
| splices.push(splice); |
| } |
| |
| function createInitialSplices(array, changeRecords) { |
| var splices = []; |
| |
| for (var i = 0; i < changeRecords.length; i++) { |
| var record = changeRecords[i]; |
| switch(record.type) { |
| case 'splice': |
| mergeSplice(splices, record.index, record.removed.slice(), record.addedCount); |
| break; |
| case 'add': |
| case 'update': |
| case 'delete': |
| if (!isIndex(record.name)) |
| continue; |
| var index = toNumber(record.name); |
| if (index < 0) |
| continue; |
| mergeSplice(splices, index, [record.oldValue], 1); |
| break; |
| default: |
| console.error('Unexpected record type: ' + JSON.stringify(record)); |
| break; |
| } |
| } |
| |
| return splices; |
| } |
| |
| function projectArraySplices(array, changeRecords) { |
| var splices = []; |
| |
| createInitialSplices(array, changeRecords).forEach(function(splice) { |
| if (splice.addedCount == 1 && splice.removed.length == 1) { |
| if (splice.removed[0] !== array[splice.index]) |
| splices.push(splice); |
| |
| return |
| }; |
| |
| splices = splices.concat(calcSplices(array, splice.index, splice.index + splice.addedCount, |
| splice.removed, 0, splice.removed.length)); |
| }); |
| |
| return splices; |
| } |
| |
| // Export the observe-js object for **Node.js**, with |
| // backwards-compatibility for the old `require()` API. If we're in |
| // the browser, export as a global object. |
| |
| var expose = global; |
| |
| if (typeof exports !== 'undefined') { |
| if (typeof module !== 'undefined' && module.exports) { |
| expose = exports = module.exports; |
| } |
| expose = exports; |
| } |
| |
| expose.Observer = Observer; |
| expose.Observer.runEOM_ = runEOM; |
| expose.Observer.observerSentinel_ = observerSentinel; // for testing. |
| expose.Observer.hasObjectObserve = hasObserve; |
| expose.ArrayObserver = ArrayObserver; |
| expose.ArrayObserver.calculateSplices = function(current, previous) { |
| return arraySplice.calculateSplices(current, previous); |
| }; |
| |
| expose.ArraySplice = ArraySplice; |
| expose.ObjectObserver = ObjectObserver; |
| expose.PathObserver = PathObserver; |
| expose.CompoundObserver = CompoundObserver; |
| expose.Path = Path; |
| expose.ObserverTransform = ObserverTransform; |
| |
| })(typeof global !== 'undefined' && global && typeof module !== 'undefined' && module ? global : this || window); |
| |
| // Copyright (c) 2014 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 |
| |
| (function(global) { |
| 'use strict'; |
| |
| var filter = Array.prototype.filter.call.bind(Array.prototype.filter); |
| |
| function getTreeScope(node) { |
| while (node.parentNode) { |
| node = node.parentNode; |
| } |
| |
| return typeof node.getElementById === 'function' ? node : null; |
| } |
| |
| Node.prototype.bind = function(name, observable) { |
| console.error('Unhandled binding to Node: ', this, name, observable); |
| }; |
| |
| Node.prototype.bindFinished = function() {}; |
| |
| function updateBindings(node, name, binding) { |
| var bindings = node.bindings_; |
| if (!bindings) |
| bindings = node.bindings_ = {}; |
| |
| if (bindings[name]) |
| binding[name].close(); |
| |
| return bindings[name] = binding; |
| } |
| |
| function returnBinding(node, name, binding) { |
| return binding; |
| } |
| |
| function sanitizeValue(value) { |
| return value == null ? '' : value; |
| } |
| |
| function updateText(node, value) { |
| node.data = sanitizeValue(value); |
| } |
| |
| function textBinding(node) { |
| return function(value) { |
| return updateText(node, value); |
| }; |
| } |
| |
| var maybeUpdateBindings = returnBinding; |
| |
| Object.defineProperty(Platform, 'enableBindingsReflection', { |
| get: function() { |
| return maybeUpdateBindings === updateBindings; |
| }, |
| set: function(enable) { |
| maybeUpdateBindings = enable ? updateBindings : returnBinding; |
| return enable; |
| }, |
| configurable: true |
| }); |
| |
| Text.prototype.bind = function(name, value, oneTime) { |
| if (name !== 'textContent') |
| return Node.prototype.bind.call(this, name, value, oneTime); |
| |
| if (oneTime) |
| return updateText(this, value); |
| |
| var observable = value; |
| updateText(this, observable.open(textBinding(this))); |
| return maybeUpdateBindings(this, name, observable); |
| } |
| |
| function updateAttribute(el, name, conditional, value) { |
| if (conditional) { |
| if (value) |
| el.setAttribute(name, ''); |
| else |
| el.removeAttribute(name); |
| return; |
| } |
| |
| el.setAttribute(name, sanitizeValue(value)); |
| } |
| |
| function attributeBinding(el, name, conditional) { |
| return function(value) { |
| updateAttribute(el, name, conditional, value); |
| }; |
| } |
| |
| Element.prototype.bind = function(name, value, oneTime) { |
| var conditional = name[name.length - 1] == '?'; |
| if (conditional) { |
| this.removeAttribute(name); |
| name = name.slice(0, -1); |
| } |
| |
| if (oneTime) |
| return updateAttribute(this, name, conditional, value); |
| |
| |
| var observable = value; |
| updateAttribute(this, name, conditional, |
| observable.open(attributeBinding(this, name, conditional))); |
| |
| return maybeUpdateBindings(this, name, observable); |
| }; |
| |
| var checkboxEventType; |
| (function() { |
| // Attempt to feature-detect which event (change or click) is fired first |
| // for checkboxes. |
| var div = document.createElement('div'); |
| var checkbox = div.appendChild(document.createElement('input')); |
| checkbox.setAttribute('type', 'checkbox'); |
| var first; |
| var count = 0; |
| checkbox.addEventListener('click', function(e) { |
| count++; |
| first = first || 'click'; |
| }); |
| checkbox.addEventListener('change', function() { |
| count++; |
| first = first || 'change'; |
| }); |
| |
| var event = document.createEvent('MouseEvent'); |
| event.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, |
| false, false, false, 0, null); |
| checkbox.dispatchEvent(event); |
| // WebKit/Blink don't fire the change event if the element is outside the |
| // document, so assume 'change' for that case. |
| checkboxEventType = count == 1 ? 'change' : first; |
| })(); |
| |
| function getEventForInputType(element) { |
| switch (element.type) { |
| case 'checkbox': |
| return checkboxEventType; |
| case 'radio': |
| case 'select-multiple': |
| case 'select-one': |
| return 'change'; |
| case 'range': |
| if (/Trident|MSIE/.test(navigator.userAgent)) |
| return 'change'; |
| default: |
| return 'input'; |
| } |
| } |
| |
| function updateInput(input, property, value, santizeFn) { |
| input[property] = (santizeFn || sanitizeValue)(value); |
| } |
| |
| function inputBinding(input, property, santizeFn) { |
| return function(value) { |
| return updateInput(input, property, value, santizeFn); |
| } |
| } |
| |
| function noop() {} |
| |
| function bindInputEvent(input, property, observable, postEventFn) { |
| var eventType = getEventForInputType(input); |
| |
| function eventHandler() { |
| observable.setValue(input[property]); |
| observable.discardChanges(); |
| (postEventFn || noop)(input); |
| Platform.performMicrotaskCheckpoint(); |
| } |
| input.addEventListener(eventType, eventHandler); |
| |
| return { |
| close: function() { |
| input.removeEventListener(eventType, eventHandler); |
| observable.close(); |
| }, |
| |
| observable_: observable |
| } |
| } |
| |
| function booleanSanitize(value) { |
| return Boolean(value); |
| } |
| |
| // |element| is assumed to be an HTMLInputElement with |type| == 'radio'. |
| // Returns an array containing all radio buttons other than |element| that |
| // have the same |name|, either in the form that |element| belongs to or, |
| // if no form, in the document tree to which |element| belongs. |
| // |
| // This implementation is based upon the HTML spec definition of a |
| // "radio button group": |
| // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.html#radio-button-group |
| // |
| function getAssociatedRadioButtons(element) { |
| if (element.form) { |
| return filter(element.form.elements, function(el) { |
| return el != element && |
| el.tagName == 'INPUT' && |
| el.type == 'radio' && |
| el.name == element.name; |
| }); |
| } else { |
| var treeScope = getTreeScope(element); |
| if (!treeScope) |
| return []; |
| var radios = treeScope.querySelectorAll( |
| 'input[type="radio"][name="' + element.name + '"]'); |
| return filter(radios, function(el) { |
| return el != element && !el.form; |
| }); |
| } |
| } |
| |
| function checkedPostEvent(input) { |
| // Only the radio button that is getting checked gets an event. We |
| // therefore find all the associated radio buttons and update their |
| // check binding manually. |
| if (input.tagName === 'INPUT' && |
| input.type === 'radio') { |
| getAssociatedRadioButtons(input).forEach(function(radio) { |
| var checkedBinding = radio.bindings_.checked; |
| if (checkedBinding) { |
| // Set the value directly to avoid an infinite call stack. |
| checkedBinding.observable_.setValue(false); |
| } |
| }); |
| } |
| } |
| |
| HTMLInputElement.prototype.bind = function(name, value, oneTime) { |
| if (name !== 'value' && name !== 'checked') |
| return HTMLElement.prototype.bind.call(this, name, value, oneTime); |
| |
| this.removeAttribute(name); |
| var sanitizeFn = name == 'checked' ? booleanSanitize : sanitizeValue; |
| var postEventFn = name == 'checked' ? checkedPostEvent : noop; |
| |
| if (oneTime) |
| return updateInput(this, name, value, sanitizeFn); |
| |
| |
| var observable = value; |
| var binding = bindInputEvent(this, name, observable, postEventFn); |
| updateInput(this, name, |
| observable.open(inputBinding(this, name, sanitizeFn)), |
| sanitizeFn); |
| |
| // Checkboxes may need to update bindings of other checkboxes. |
| return updateBindings(this, name, binding); |
| } |
| |
| HTMLTextAreaElement.prototype.bind = function(name, value, oneTime) { |
| if (name !== 'value') |
| return HTMLElement.prototype.bind.call(this, name, value, oneTime); |
| |
| this.removeAttribute('value'); |
| |
| if (oneTime) |
| return updateInput(this, 'value', value); |
| |
| var observable = value; |
| var binding = bindInputEvent(this, 'value', observable); |
| updateInput(this, 'value', |
| observable.open(inputBinding(this, 'value', sanitizeValue))); |
| return maybeUpdateBindings(this, name, binding); |
| } |
| |
| function updateOption(option, value) { |
| var parentNode = option.parentNode;; |
| var select; |
| var selectBinding; |
| var oldValue; |
| if (parentNode instanceof HTMLSelectElement && |
| parentNode.bindings_ && |
| parentNode.bindings_.value) { |
| select = parentNode; |
| selectBinding = select.bindings_.value; |
| oldValue = select.value; |
| } |
| |
| option.value = sanitizeValue(value); |
| |
| if (select && select.value != oldValue) { |
| selectBinding.observable_.setValue(select.value); |
| selectBinding.observable_.discardChanges(); |
| Platform.performMicrotaskCheckpoint(); |
| } |
| } |
| |
| function optionBinding(option) { |
| return function(value) { |
| updateOption(option, value); |
| } |
| } |
| |
| HTMLOptionElement.prototype.bind = function(name, value, oneTime) { |
| if (name !== 'value') |
| return HTMLElement.prototype.bind.call(this, name, value, oneTime); |
| |
| this.removeAttribute('value'); |
| |
| if (oneTime) |
| return updateOption(this, value); |
| |
| var observable = value; |
| var binding = bindInputEvent(this, 'value', observable); |
| updateOption(this, observable.open(optionBinding(this))); |
| return maybeUpdateBindings(this, name, binding); |
| } |
| |
| HTMLSelectElement.prototype.bind = function(name, value, oneTime) { |
| if (name === 'selectedindex') |
| name = 'selectedIndex'; |
| |
| if (name !== 'selectedIndex' && name !== 'value') |
| return HTMLElement.prototype.bind.call(this, name, value, oneTime); |
| |
| this.removeAttribute(name); |
| |
| if (oneTime) |
| return updateInput(this, name, value); |
| |
| var observable = value; |
| var binding = bindInputEvent(this, name, observable); |
| updateInput(this, name, |
| observable.open(inputBinding(this, name))); |
| |
| // Option update events may need to access select bindings. |
| return updateBindings(this, name, binding); |
| } |
| })(this); |
| |
| // Copyright (c) 2014 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 |
| |
| (function(global) { |
| 'use strict'; |
| |
| function assert(v) { |
| if (!v) |
| throw new Error('Assertion failed'); |
| } |
| |
| var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); |
| |
| function getFragmentRoot(node) { |
| var p; |
| while (p = node.parentNode) { |
| node = p; |
| } |
| |
| return node; |
| } |
| |
| function searchRefId(node, id) { |
| if (!id) |
| return; |
| |
| var ref; |
| var selector = '#' + id; |
| while (!ref) { |
| node = getFragmentRoot(node); |
| |
| if (node.protoContent_) |
| ref = node.protoContent_.querySelector(selector); |
| else if (node.getElementById) |
| ref = node.getElementById(id); |
| |
| if (ref || !node.templateCreator_) |
| break |
| |
| node = node.templateCreator_; |
| } |
| |
| return ref; |
| } |
| |
| function getInstanceRoot(node) { |
| while (node.parentNode) { |
| node = node.parentNode; |
| } |
| return node.templateCreator_ ? node : null; |
| } |
| |
| var Map; |
| if (global.Map && typeof global.Map.prototype.forEach === 'function') { |
| Map = global.Map; |
| } else { |
| Map = function() { |
| this.keys = []; |
| this.values = []; |
| }; |
| |
| Map.prototype = { |
| set: function(key, value) { |
| var index = this.keys.indexOf(key); |
| if (index < 0) { |
| this.keys.push(key); |
| this.values.push(value); |
| } else { |
| this.values[index] = value; |
| } |
| }, |
| |
| get: function(key) { |
| var index = this.keys.indexOf(key); |
| if (index < 0) |
| return; |
| |
| return this.values[index]; |
| }, |
| |
| delete: function(key, value) { |
| var index = this.keys.indexOf(key); |
| if (index < 0) |
| return false; |
| |
| this.keys.splice(index, 1); |
| this.values.splice(index, 1); |
| return true; |
| }, |
| |
| forEach: function(f, opt_this) { |
| for (var i = 0; i < this.keys.length; i++) |
| f.call(opt_this || this, this.values[i], this.keys[i], this); |
| } |
| }; |
| } |
| |
| // JScript does not have __proto__. We wrap all object literals with |
| // createObject which uses Object.create, Object.defineProperty and |
| // Object.getOwnPropertyDescriptor to create a new object that does the exact |
| // same thing. The main downside to this solution is that we have to extract |
| // all those property descriptors for IE. |
| var createObject = ('__proto__' in {}) ? |
| function(obj) { return obj; } : |
| function(obj) { |
| var proto = obj.__proto__; |
| if (!proto) |
| return obj; |
| var newObject = Object.create(proto); |
| Object.getOwnPropertyNames(obj).forEach(function(name) { |
| Object.defineProperty(newObject, name, |
| Object.getOwnPropertyDescriptor(obj, name)); |
| }); |
| return newObject; |
| }; |
| |
| // IE does not support have Document.prototype.contains. |
| if (typeof document.contains != 'function') { |
| Document.prototype.contains = function(node) { |
| if (node === this || node.parentNode === this) |
| return true; |
| return this.documentElement.contains(node); |
| } |
| } |
| |
| var BIND = 'bind'; |
| var REPEAT = 'repeat'; |
| var IF = 'if'; |
| |
| var templateAttributeDirectives = { |
| 'template': true, |
| 'repeat': true, |
| 'bind': true, |
| 'ref': true, |
| 'if': true |
| }; |
| |
| var semanticTemplateElements = { |
| 'THEAD': true, |
| 'TBODY': true, |
| 'TFOOT': true, |
| 'TH': true, |
| 'TR': true, |
| 'TD': true, |
| 'COLGROUP': true, |
| 'COL': true, |
| 'CAPTION': true, |
| 'OPTION': true, |
| 'OPTGROUP': true |
| }; |
| |
| var hasTemplateElement = typeof HTMLTemplateElement !== 'undefined'; |
| if (hasTemplateElement) { |
| // TODO(rafaelw): Remove when fix for |
| // https://codereview.chromium.org/164803002/ |
| // makes it to Chrome release. |
| (function() { |
| var t = document.createElement('template'); |
| var d = t.content.ownerDocument; |
| var html = d.appendChild(d.createElement('html')); |
| var head = html.appendChild(d.createElement('head')); |
| var base = d.createElement('base'); |
| base.href = document.baseURI; |
| head.appendChild(base); |
| })(); |
| } |
| |
| var allTemplatesSelectors = 'template, ' + |
| Object.keys(semanticTemplateElements).map(function(tagName) { |
| return tagName.toLowerCase() + '[template]'; |
| }).join(', '); |
| |
| function isSVGTemplate(el) { |
| return el.tagName == 'template' && |
| el.namespaceURI == 'http://www.w3.org/2000/svg'; |
| } |
| |
| function isHTMLTemplate(el) { |
| return el.tagName == 'TEMPLATE' && |
| el.namespaceURI == 'http://www.w3.org/1999/xhtml'; |
| } |
| |
| function isAttributeTemplate(el) { |
| return Boolean(semanticTemplateElements[el.tagName] && |
| el.hasAttribute('template')); |
| } |
| |
| function isTemplate(el) { |
| if (el.isTemplate_ === undefined) |
| el.isTemplate_ = el.tagName == 'TEMPLATE' || isAttributeTemplate(el); |
| |
| return el.isTemplate_; |
| } |
| |
| // FIXME: Observe templates being added/removed from documents |
| // FIXME: Expose imperative API to decorate and observe templates in |
| // "disconnected tress" (e.g. ShadowRoot) |
| document.addEventListener('DOMContentLoaded', function(e) { |
| bootstrapTemplatesRecursivelyFrom(document); |
| // FIXME: Is this needed? Seems like it shouldn't be. |
| Platform.performMicrotaskCheckpoint(); |
| }, false); |
| |
| function forAllTemplatesFrom(node, fn) { |
| var subTemplates = node.querySelectorAll(allTemplatesSelectors); |
| |
| if (isTemplate(node)) |
| fn(node) |
| forEach(subTemplates, fn); |
| } |
| |
| function bootstrapTemplatesRecursivelyFrom(node) { |
| function bootstrap(template) { |
| if (!HTMLTemplateElement.decorate(template)) |
| bootstrapTemplatesRecursivelyFrom(template.content); |
| } |
| |
| forAllTemplatesFrom(node, bootstrap); |
| } |
| |
| if (!hasTemplateElement) { |
| /** |
| * This represents a <template> element. |
| * @constructor |
| * @extends {HTMLElement} |
| */ |
| global.HTMLTemplateElement = function() { |
| throw TypeError('Illegal constructor'); |
| }; |
| } |
| |
| var hasProto = '__proto__' in {}; |
| |
| function mixin(to, from) { |
| Object.getOwnPropertyNames(from).forEach(function(name) { |
| Object.defineProperty(to, name, |
| Object.getOwnPropertyDescriptor(from, name)); |
| }); |
| } |
| |
| // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#dfn-template-contents-owner |
| function getOrCreateTemplateContentsOwner(template) { |
| var doc = template.ownerDocument |
| if (!doc.defaultView) |
| return doc; |
| var d = doc.templateContentsOwner_; |
| if (!d) { |
| // TODO(arv): This should either be a Document or HTMLDocument depending |
| // on doc. |
| d = doc.implementation.createHTMLDocument(''); |
| while (d.lastChild) { |
| d.removeChild(d.lastChild); |
| } |
| doc.templateContentsOwner_ = d; |
| } |
| return d; |
| } |
| |
| function getTemplateStagingDocument(template) { |
| if (!template.stagingDocument_) { |
| var owner = template.ownerDocument; |
| if (!owner.stagingDocument_) { |
| owner.stagingDocument_ = owner.implementation.createHTMLDocument(''); |
| owner.stagingDocument_.isStagingDocument = true; |
| // TODO(rafaelw): Remove when fix for |
| // https://codereview.chromium.org/164803002/ |
| // makes it to Chrome release. |
| var base = owner.stagingDocument_.createElement('base'); |
| base.href = document.baseURI; |
| owner.stagingDocument_.head.appendChild(base); |
| |
| owner.stagingDocument_.stagingDocument_ = owner.stagingDocument_; |
| } |
| |
| template.stagingDocument_ = owner.stagingDocument_; |
| } |
| |
| return template.stagingDocument_; |
| } |
| |
| // For non-template browsers, the parser will disallow <template> in certain |
| // locations, so we allow "attribute templates" which combine the template |
| // element with the top-level container node of the content, e.g. |
| // |
| // <tr template repeat="{{ foo }}"" class="bar"><td>Bar</td></tr> |
| // |
| // becomes |
| // |
| // <template repeat="{{ foo }}"> |
| // + #document-fragment |
| // + <tr class="bar"> |
| // + <td>Bar</td> |
| // |
| function extractTemplateFromAttributeTemplate(el) { |
| var template = el.ownerDocument.createElement('template'); |
| el.parentNode.insertBefore(template, el); |
| |
| var attribs = el.attributes; |
| var count = attribs.length; |
| while (count-- > 0) { |
| var attrib = attribs[count]; |
| if (templateAttributeDirectives[attrib.name]) { |
| if (attrib.name !== 'template') |
| template.setAttribute(attrib.name, attrib.value); |
| el.removeAttribute(attrib.name); |
| } |
| } |
| |
| return template; |
| } |
| |
| function extractTemplateFromSVGTemplate(el) { |
| var template = el.ownerDocument.createElement('template'); |
| el.parentNode.insertBefore(template, el); |
| |
| var attribs = el.attributes; |
| var count = attribs.length; |
| while (count-- > 0) { |
| var attrib = attribs[count]; |
| template.setAttribute(attrib.name, attrib.value); |
| el.removeAttribute(attrib.name); |
| } |
| |
| el.parentNode.removeChild(el); |
| return template; |
| } |
| |
| function liftNonNativeTemplateChildrenIntoContent(template, el, useRoot) { |
| var content = template.content; |
| if (useRoot) { |
| content.appendChild(el); |
| return; |
| } |
| |
| var child; |
| while (child = el.firstChild) { |
| content.appendChild(child); |
| } |
| } |
| |
| var templateObserver; |
| if (typeof MutationObserver == 'function') { |
| templateObserver = new MutationObserver(function(records) { |
| for (var i = 0; i < records.length; i++) { |
| records[i].target.refChanged_(); |
| } |
| }); |
| } |
| |
| /** |
| * Ensures proper API and content model for template elements. |
| * @param {HTMLTemplateElement} opt_instanceRef The template element which |
| * |el| template element will return as the value of its ref(), and whose |
| * content will be used as source when createInstance() is invoked. |
| */ |
| HTMLTemplateElement.decorate = function(el, opt_instanceRef) { |
| if (el.templateIsDecorated_) |
| return false; |
| |
| var templateElement = el; |
| templateElement.templateIsDecorated_ = true; |
| |
| var isNativeHTMLTemplate = isHTMLTemplate(templateElement) && |
| hasTemplateElement; |
| var bootstrapContents = isNativeHTMLTemplate; |
| var liftContents = !isNativeHTMLTemplate; |
| var liftRoot = false; |
| |
| if (!isNativeHTMLTemplate) { |
| if (isAttributeTemplate(templateElement)) { |
| assert(!opt_instanceRef); |
| templateElement = extractTemplateFromAttributeTemplate(el); |
| templateElement.templateIsDecorated_ = true; |
| isNativeHTMLTemplate = hasTemplateElement; |
| liftRoot = true; |
| } else if (isSVGTemplate(templateElement)) { |
| templateElement = extractTemplateFromSVGTemplate(el); |
| templateElement.templateIsDecorated_ = true; |
| isNativeHTMLTemplate = hasTemplateElement; |
| } |
| } |
| |
| if (!isNativeHTMLTemplate) { |
| fixTemplateElementPrototype(templateElement); |
| var doc = getOrCreateTemplateContentsOwner(templateElement); |
| templateElement.content_ = doc.createDocumentFragment(); |
| } |
| |
| if (opt_instanceRef) { |
| // template is contained within an instance, its direct content must be |
| // empty |
| templateElement.instanceRef_ = opt_instanceRef; |
| } else if (liftContents) { |
| liftNonNativeTemplateChildrenIntoContent(templateElement, |
| el, |
| liftRoot); |
| } else if (bootstrapContents) { |
| bootstrapTemplatesRecursivelyFrom(templateElement.content); |
| } |
| |
| return true; |
| }; |
| |
| // TODO(rafaelw): This used to decorate recursively all templates from a given |
| // node. This happens by default on 'DOMContentLoaded', but may be needed |
| // in subtrees not descendent from document (e.g. ShadowRoot). |
| // Review whether this is the right public API. |
| HTMLTemplateElement.bootstrap = bootstrapTemplatesRecursivelyFrom; |
| |
| var htmlElement = global.HTMLUnknownElement || HTMLElement; |
| |
| var contentDescriptor = { |
| get: function() { |
| return this.content_; |
| }, |
| enumerable: true, |
| configurable: true |
| }; |
| |
| if (!hasTemplateElement) { |
| // Gecko is more picky with the prototype than WebKit. Make sure to use the |
| // same prototype as created in the constructor. |
| HTMLTemplateElement.prototype = Object.create(htmlElement.prototype); |
| |
| Object.defineProperty(HTMLTemplateElement.prototype, 'content', |
| contentDescriptor); |
| } |
| |
| function fixTemplateElementPrototype(el) { |
| if (hasProto) |
| el.__proto__ = HTMLTemplateElement.prototype; |
| else |
| mixin(el, HTMLTemplateElement.prototype); |
| } |
| |
| function ensureSetModelScheduled(template) { |
| if (!template.setModelFn_) { |
| template.setModelFn_ = function() { |
| template.setModelFnScheduled_ = false; |
| var map = getBindings(template, |
| template.delegate_ && template.delegate_.prepareBinding); |
| processBindings(template, map, template.model_); |
| }; |
| } |
| |
| if (!template.setModelFnScheduled_) { |
| template.setModelFnScheduled_ = true; |
| Observer.runEOM_(template.setModelFn_); |
| } |
| } |
| |
| mixin(HTMLTemplateElement.prototype, { |
| bind: function(name, value, oneTime) { |
| if (name != 'ref') |
| return Element.prototype.bind.call(this, name, value, oneTime); |
| |
| var self = this; |
| var ref = oneTime ? value : value.open(function(ref) { |
| self.setAttribute('ref', ref); |
| self.refChanged_(); |
| }); |
| |
| this.setAttribute('ref', ref); |
| this.refChanged_(); |
| if (oneTime) |
| return; |
| |
| if (!this.bindings_) { |
| this.bindings_ = { ref: value }; |
| } else { |
| this.bindings_.ref = value; |
| } |
| |
| return value; |
| }, |
| |
| processBindingDirectives_: function(directives) { |
| if (this.iterator_) |
| this.iterator_.closeDeps(); |
| |
| if (!directives.if && !directives.bind && !directives.repeat) { |
| if (this.iterator_) { |
| this.iterator_.close(); |
| this.iterator_ = undefined; |
| } |
| |
| return; |
| } |
| |
| if (!this.iterator_) { |
| this.iterator_ = new TemplateIterator(this); |
| } |
| |
| this.iterator_.updateDependencies(directives, this.model_); |
| |
| if (templateObserver) { |
| templateObserver.observe(this, { attributes: true, |
| attributeFilter: ['ref'] }); |
| } |
| |
| return this.iterator_; |
| }, |
| |
| createInstance: function(model, bindingDelegate, delegate_) { |
| if (bindingDelegate) |
| delegate_ = this.newDelegate_(bindingDelegate); |
| else if (!delegate_) |
| delegate_ = this.delegate_; |
| |
| if (!this.refContent_) |
| this.refContent_ = this.ref_.content; |
| var content = this.refContent_; |
| if (content.firstChild === null) |
| return emptyInstance; |
| |
| var map = getInstanceBindingMap(content, delegate_); |
| var stagingDocument = getTemplateStagingDocument(this); |
| var instance = stagingDocument.createDocumentFragment(); |
| instance.templateCreator_ = this; |
| instance.protoContent_ = content; |
| instance.bindings_ = []; |
| instance.terminator_ = null; |
| var instanceRecord = instance.templateInstance_ = { |
| firstNode: null, |
| lastNode: null, |
| model: model |
| }; |
| |
| var i = 0; |
| var collectTerminator = false; |
| for (var child = content.firstChild; child; child = child.nextSibling) { |
| // The terminator of the instance is the clone of the last child of the |
| // content. If the last child is an active template, it may produce |
| // instances as a result of production, so simply collecting the last |
| // child of the instance after it has finished producing may be wrong. |
| if (child.nextSibling === null) |
| collectTerminator = true; |
| |
| var clone = cloneAndBindInstance(child, instance, stagingDocument, |
| map.children[i++], |
| model, |
| delegate_, |
| instance.bindings_); |
| clone.templateInstance_ = instanceRecord; |
| if (collectTerminator) |
| instance.terminator_ = clone; |
| } |
| |
| instanceRecord.firstNode = instance.firstChild; |
| instanceRecord.lastNode = instance.lastChild; |
| instance.templateCreator_ = undefined; |
| instance.protoContent_ = undefined; |
| return instance; |
| }, |
| |
| get model() { |
| return this.model_; |
| }, |
| |
| set model(model) { |
| this.model_ = model; |
| ensureSetModelScheduled(this); |
| }, |
| |
| get bindingDelegate() { |
| return this.delegate_ && this.delegate_.raw; |
| }, |
| |
| refChanged_: function() { |
| if (!this.iterator_ || this.refContent_ === this.ref_.content) |
| return; |
| |
| this.refContent_ = undefined; |
| this.iterator_.valueChanged(); |
| this.iterator_.updateIteratedValue(this.iterator_.getUpdatedValue()); |
| }, |
| |
| clear: function() { |
| this.model_ = undefined; |
| this.delegate_ = undefined; |
| if (this.bindings_ && this.bindings_.ref) |
| this.bindings_.ref.close() |
| this.refContent_ = undefined; |
| if (!this.iterator_) |
| return; |
| this.iterator_.valueChanged(); |
| this.iterator_.close() |
| this.iterator_ = undefined; |
| }, |
| |
| setDelegate_: function(delegate) { |
| this.delegate_ = delegate; |
| this.bindingMap_ = undefined; |
| if (this.iterator_) { |
| this.iterator_.instancePositionChangedFn_ = undefined; |
| this.iterator_.instanceModelFn_ = undefined; |
| } |
| }, |
| |
| newDelegate_: function(bindingDelegate) { |
| if (!bindingDelegate) |
| return; |
| |
| function delegateFn(name) { |
| var fn = bindingDelegate && bindingDelegate[name]; |
| if (typeof fn != 'function') |
| return; |
| |
| return function() { |
| return fn.apply(bindingDelegate, arguments); |
| }; |
| } |
| |
| return { |
| bindingMaps: {}, |
| raw: bindingDelegate, |
| prepareBinding: delegateFn('prepareBinding'), |
| prepareInstanceModel: delegateFn('prepareInstanceModel'), |
| prepareInstancePositionChanged: |
| delegateFn('prepareInstancePositionChanged') |
| }; |
| }, |
| |
| set bindingDelegate(bindingDelegate) { |
| if (this.delegate_) { |
| throw Error('Template must be cleared before a new bindingDelegate ' + |
| 'can be assigned'); |
| } |
| |
| this.setDelegate_(this.newDelegate_(bindingDelegate)); |
| }, |
| |
| get ref_() { |
| var ref = searchRefId(this, this.getAttribute('ref')); |
| if (!ref) |
| ref = this.instanceRef_; |
| |
| if (!ref) |
| return this; |
| |
| var nextRef = ref.ref_; |
| return nextRef ? nextRef : ref; |
| } |
| }); |
| |
| // Returns |
| // a) undefined if there are no mustaches. |
| // b) [TEXT, (ONE_TIME?, PATH, DELEGATE_FN, TEXT)+] if there is at least one mustache. |
| function parseMustaches(s, name, node, prepareBindingFn) { |
| if (!s || !s.length) |
| return; |
| |
| var tokens; |
| var length = s.length; |
| var startIndex = 0, lastIndex = 0, endIndex = 0; |
| var onlyOneTime = true; |
| while (lastIndex < length) { |
| var startIndex = s.indexOf('{{', lastIndex); |
| var oneTimeStart = s.indexOf('[[', lastIndex); |
| var oneTime = false; |
| var terminator = '}}'; |
| |
| if (oneTimeStart >= 0 && |
| (startIndex < 0 || oneTimeStart < startIndex)) { |
| startIndex = oneTimeStart; |
| oneTime = true; |
| terminator = ']]'; |
| } |
| |
| endIndex = startIndex < 0 ? -1 : s.indexOf(terminator, startIndex + 2); |
| |
| if (endIndex < 0) { |
| if (!tokens) |
| return; |
| |
| tokens.push(s.slice(lastIndex)); // TEXT |
| break; |
| } |
| |
| tokens = tokens || []; |
| tokens.push(s.slice(lastIndex, startIndex)); // TEXT |
| var pathString = s.slice(startIndex + 2, endIndex).trim(); |
| tokens.push(oneTime); // ONE_TIME? |
| onlyOneTime = onlyOneTime && oneTime; |
| var delegateFn = prepareBindingFn && |
| prepareBindingFn(pathString, name, node); |
| // Don't try to parse the expression if there's a prepareBinding function |
| if (delegateFn == null) { |
| tokens.push(Path.get(pathString)); // PATH |
| } else { |
| tokens.push(null); |
| } |
| tokens.push(delegateFn); // DELEGATE_FN |
| lastIndex = endIndex + 2; |
| } |
| |
| if (lastIndex === length) |
| tokens.push(''); // TEXT |
| |
| tokens.hasOnePath = tokens.length === 5; |
| tokens.isSimplePath = tokens.hasOnePath && |
| tokens[0] == '' && |
| tokens[4] == ''; |
| tokens.onlyOneTime = onlyOneTime; |
| |
| tokens.combinator = function(values) { |
| var newValue = tokens[0]; |
| |
| for (var i = 1; i < tokens.length; i += 4) { |
| var value = tokens.hasOnePath ? values : values[(i - 1) / 4]; |
| if (value !== undefined) |
| newValue += value; |
| newValue += tokens[i + 3]; |
| } |
| |
| return newValue; |
| } |
| |
| return tokens; |
| }; |
| |
| function processOneTimeBinding(name, tokens, node, model) { |
| if (tokens.hasOnePath) { |
| var delegateFn = tokens[3]; |
| var value = delegateFn ? delegateFn(model, node, true) : |
| tokens[2].getValueFrom(model); |
| return tokens.isSimplePath ? value : tokens.combinator(value); |
| } |
| |
| var values = []; |
| for (var i = 1; i < tokens.length; i += 4) { |
| var delegateFn = tokens[i + 2]; |
| values[(i - 1) / 4] = delegateFn ? delegateFn(model, node) : |
| tokens[i + 1].getValueFrom(model); |
| } |
| |
| return tokens.combinator(values); |
| } |
| |
| function processSinglePathBinding(name, tokens, node, model) { |
| var delegateFn = tokens[3]; |
| var observer = delegateFn ? delegateFn(model, node, false) : |
| new PathObserver(model, tokens[2]); |
| |
| return tokens.isSimplePath ? observer : |
| new ObserverTransform(observer, tokens.combinator); |
| } |
| |
| function processBinding(name, tokens, node, model) { |
| if (tokens.onlyOneTime) |
| return processOneTimeBinding(name, tokens, node, model); |
| |
| if (tokens.hasOnePath) |
| return processSinglePathBinding(name, tokens, node, model); |
| |
| var observer = new CompoundObserver(); |
| |
| for (var i = 1; i < tokens.length; i += 4) { |
| var oneTime = tokens[i]; |
| var delegateFn = tokens[i + 2]; |
| |
| if (delegateFn) { |
| var value = delegateFn(model, node, oneTime); |
| if (oneTime) |
| observer.addPath(value) |
| else |
| observer.addObserver(value); |
| continue; |
| } |
| |
| var path = tokens[i + 1]; |
| if (oneTime) |
| observer.addPath(path.getValueFrom(model)) |
| else |
| observer.addPath(model, path); |
| } |
| |
| return new ObserverTransform(observer, tokens.combinator); |
| } |
| |
| function processBindings(node, bindings, model, instanceBindings) { |
| for (var i = 0; i < bindings.length; i += 2) { |
| var name = bindings[i] |
| var tokens = bindings[i + 1]; |
| var value = processBinding(name, tokens, node, model); |
| var binding = node.bind(name, value, tokens.onlyOneTime); |
| if (binding && instanceBindings) |
| instanceBindings.push(binding); |
| } |
| |
| node.bindFinished(); |
| if (!bindings.isTemplate) |
| return; |
| |
| node.model_ = model; |
| var iter = node.processBindingDirectives_(bindings); |
| if (instanceBindings && iter) |
| instanceBindings.push(iter); |
| } |
| |
| function parseWithDefault(el, name, prepareBindingFn) { |
| var v = el.getAttribute(name); |
| return parseMustaches(v == '' ? '{{}}' : v, name, el, prepareBindingFn); |
| } |
| |
| function parseAttributeBindings(element, prepareBindingFn) { |
| assert(element); |
| |
| var bindings = []; |
| var ifFound = false; |
| var bindFound = false; |
| |
| for (var i = 0; i < element.attributes.length; i++) { |
| var attr = element.attributes[i]; |
| var name = attr.name; |
| var value = attr.value; |
| |
| // Allow bindings expressed in attributes to be prefixed with underbars. |
| // We do this to allow correct semantics for browsers that don't implement |
| // <template> where certain attributes might trigger side-effects -- and |
| // for IE which sanitizes certain attributes, disallowing mustache |
| // replacements in their text. |
| while (name[0] === '_') { |
| name = name.substring(1); |
| } |
| |
| if (isTemplate(element) && |
| (name === IF || name === BIND || name === REPEAT)) { |
| continue; |
| } |
| |
| var tokens = parseMustaches(value, name, element, |
| prepareBindingFn); |
| if (!tokens) |
| continue; |
| |
| bindings.push(name, tokens); |
| } |
| |
| if (isTemplate(element)) { |
| bindings.isTemplate = true; |
| bindings.if = parseWithDefault(element, IF, prepareBindingFn); |
| bindings.bind = parseWithDefault(element, BIND, prepareBindingFn); |
| bindings.repeat = parseWithDefault(element, REPEAT, prepareBindingFn); |
| |
| if (bindings.if && !bindings.bind && !bindings.repeat) |
| bindings.bind = parseMustaches('{{}}', BIND, element, prepareBindingFn); |
| } |
| |
| return bindings; |
| } |
| |
| function getBindings(node, prepareBindingFn) { |
| if (node.nodeType === Node.ELEMENT_NODE) |
| return parseAttributeBindings(node, prepareBindingFn); |
| |
| if (node.nodeType === Node.TEXT_NODE) { |
| var tokens = parseMustaches(node.data, 'textContent', node, |
| prepareBindingFn); |
| if (tokens) |
| return ['textContent', tokens]; |
| } |
| |
| return []; |
| } |
| |
| function cloneAndBindInstance(node, parent, stagingDocument, bindings, model, |
| delegate, |
| instanceBindings, |
| instanceRecord) { |
| var clone = parent.appendChild(stagingDocument.importNode(node, false)); |
| |
| var i = 0; |
| for (var child = node.firstChild; child; child = child.nextSibling) { |
| cloneAndBindInstance(child, clone, stagingDocument, |
| bindings.children[i++], |
| model, |
| delegate, |
| instanceBindings); |
| } |
| |
| if (bindings.isTemplate) { |
| HTMLTemplateElement.decorate(clone, node); |
| if (delegate) |
| clone.setDelegate_(delegate); |
| } |
| |
| processBindings(clone, bindings, model, instanceBindings); |
| return clone; |
| } |
| |
| function createInstanceBindingMap(node, prepareBindingFn) { |
| var map = getBindings(node, prepareBindingFn); |
| map.children = {}; |
| var index = 0; |
| for (var child = node.firstChild; child; child = child.nextSibling) { |
| map.children[index++] = createInstanceBindingMap(child, prepareBindingFn); |
| } |
| |
| return map; |
| } |
| |
| var contentUidCounter = 1; |
| |
| // TODO(rafaelw): Setup a MutationObserver on content which clears the id |
| // so that bindingMaps regenerate when the template.content changes. |
| function getContentUid(content) { |
| var id = content.id_; |
| if (!id) |
| id = content.id_ = contentUidCounter++; |
| return id; |
| } |
| |
| // Each delegate is associated with a set of bindingMaps, one for each |
| // content which may be used by a template. The intent is that each binding |
| // delegate gets the opportunity to prepare the instance (via the prepare* |
| // delegate calls) once across all uses. |
| // TODO(rafaelw): Separate out the parse map from the binding map. In the |
| // current implementation, if two delegates need a binding map for the same |
| // content, the second will have to reparse. |
| function getInstanceBindingMap(content, delegate_) { |
| var contentId = getContentUid(content); |
| if (delegate_) { |
| var map = delegate_.bindingMaps[contentId]; |
| if (!map) { |
| map = delegate_.bindingMaps[contentId] = |
| createInstanceBindingMap(content, delegate_.prepareBinding) || []; |
| } |
| return map; |
| } |
| |
| var map = content.bindingMap_; |
| if (!map) { |
| map = content.bindingMap_ = |
| createInstanceBindingMap(content, undefined) || []; |
| } |
| return map; |
| } |
| |
| Object.defineProperty(Node.prototype, 'templateInstance', { |
| get: function() { |
| var instance = this.templateInstance_; |
| return instance ? instance : |
| (this.parentNode ? this.parentNode.templateInstance : undefined); |
| } |
| }); |
| |
| var emptyInstance = document.createDocumentFragment(); |
| emptyInstance.bindings_ = []; |
| emptyInstance.terminator_ = null; |
| |
| function TemplateIterator(templateElement) { |
| this.closed = false; |
| this.templateElement_ = templateElement; |
| this.instances = []; |
| this.deps = undefined; |
| this.iteratedValue = []; |
| this.presentValue = undefined; |
| this.arrayObserver = undefined; |
| } |
| |
| TemplateIterator.prototype = { |
| closeDeps: function() { |
| var deps = this.deps; |
| if (deps) { |
| if (deps.ifOneTime === false) |
| deps.ifValue.close(); |
| if (deps.oneTime === false) |
| deps.value.close(); |
| } |
| }, |
| |
| updateDependencies: function(directives, model) { |
| this.closeDeps(); |
| |
| var deps = this.deps = {}; |
| var template = this.templateElement_; |
| |
| var ifValue = true; |
| if (directives.if) { |
| deps.hasIf = true; |
| deps.ifOneTime = directives.if.onlyOneTime; |
| deps.ifValue = processBinding(IF, directives.if, template, model); |
| |
| ifValue = deps.ifValue; |
| |
| // oneTime if & predicate is false. nothing else to do. |
| if (deps.ifOneTime && !ifValue) { |
| this.valueChanged(); |
| return; |
| } |
| |
| if (!deps.ifOneTime) |
| ifValue = ifValue.open(this.updateIfValue, this); |
| } |
| |
| if (directives.repeat) { |
| deps.repeat = true; |
| deps.oneTime = directives.repeat.onlyOneTime; |
| deps.value = processBinding(REPEAT, directives.repeat, template, model); |
| } else { |
| deps.repeat = false; |
| deps.oneTime = directives.bind.onlyOneTime; |
| deps.value = processBinding(BIND, directives.bind, template, model); |
| } |
| |
| var value = deps.value; |
| if (!deps.oneTime) |
| value = value.open(this.updateIteratedValue, this); |
| |
| if (!ifValue) { |
| this.valueChanged(); |
| return; |
| } |
| |
| this.updateValue(value); |
| }, |
| |
| /** |
| * Gets the updated value of the bind/repeat. This can potentially call |
| * user code (if a bindingDelegate is set up) so we try to avoid it if we |
| * already have the value in hand (from Observer.open). |
| */ |
| getUpdatedValue: function() { |
| var value = this.deps.value; |
| if (!this.deps.oneTime) |
| value = value.discardChanges(); |
| return value; |
| }, |
| |
| updateIfValue: function(ifValue) { |
| if (!ifValue) { |
| this.valueChanged(); |
| return; |
| } |
| |
| this.updateValue(this.getUpdatedValue()); |
| }, |
| |
| updateIteratedValue: function(value) { |
| if (this.deps.hasIf) { |
| var ifValue = this.deps.ifValue; |
| if (!this.deps.ifOneTime) |
| ifValue = ifValue.discardChanges(); |
| if (!ifValue) { |
| this.valueChanged(); |
| return; |
| } |
| } |
| |
| this.updateValue(value); |
| }, |
| |
| updateValue: function(value) { |
| if (!this.deps.repeat) |
| value = [value]; |
| var observe = this.deps.repeat && |
| !this.deps.oneTime && |
| Array.isArray(value); |
| this.valueChanged(value, observe); |
| }, |
| |
| valueChanged: function(value, observeValue) { |
| if (!Array.isArray(value)) |
| value = []; |
| |
| if (value === this.iteratedValue) |
| return; |
| |
| this.unobserve(); |
| this.presentValue = value; |
| if (observeValue) { |
| this.arrayObserver = new ArrayObserver(this.presentValue); |
| this.arrayObserver.open(this.handleSplices, this); |
| } |
| |
| this.handleSplices(ArrayObserver.calculateSplices(this.presentValue, |
| this.iteratedValue)); |
| }, |
| |
| getLastInstanceNode: function(index) { |
| if (index == -1) |
| return this.templateElement_; |
| var instance = this.instances[index]; |
| var terminator = instance.terminator_; |
| if (!terminator) |
| return this.getLastInstanceNode(index - 1); |
| |
| if (terminator.nodeType !== Node.ELEMENT_NODE || |
| this.templateElement_ === terminator) { |
| return terminator; |
| } |
| |
| var subtemplateIterator = terminator.iterator_; |
| if (!subtemplateIterator) |
| return terminator; |
| |
| return subtemplateIterator.getLastTemplateNode(); |
| }, |
| |
| getLastTemplateNode: function() { |
| return this.getLastInstanceNode(this.instances.length - 1); |
| }, |
| |
| insertInstanceAt: function(index, fragment) { |
| var previousInstanceLast = this.getLastInstanceNode(index - 1); |
| var parent = this.templateElement_.parentNode; |
| this.instances.splice(index, 0, fragment); |
| |
| parent.insertBefore(fragment, previousInstanceLast.nextSibling); |
| }, |
| |
| extractInstanceAt: function(index) { |
| var previousInstanceLast = this.getLastInstanceNode(index - 1); |
| var lastNode = this.getLastInstanceNode(index); |
| var parent = this.templateElement_.parentNode; |
| var instance = this.instances.splice(index, 1)[0]; |
| |
| while (lastNode !== previousInstanceLast) { |
| var node = previousInstanceLast.nextSibling; |
| if (node == lastNode) |
| lastNode = previousInstanceLast; |
| |
| instance.appendChild(parent.removeChild(node)); |
| } |
| |
| return instance; |
| }, |
| |
| getDelegateFn: function(fn) { |
| fn = fn && fn(this.templateElement_); |
| return typeof fn === 'function' ? fn : null; |
| }, |
| |
| handleSplices: function(splices) { |
| if (this.closed || !splices.length) |
| return; |
| |
| var template = this.templateElement_; |
| |
| if (!template.parentNode) { |
| this.close(); |
| return; |
| } |
| |
| ArrayObserver.applySplices(this.iteratedValue, this.presentValue, |
| splices); |
| |
| var delegate = template.delegate_; |
| if (this.instanceModelFn_ === undefined) { |
| this.instanceModelFn_ = |
| this.getDelegateFn(delegate && delegate.prepareInstanceModel); |
| } |
| |
| if (this.instancePositionChangedFn_ === undefined) { |
| this.instancePositionChangedFn_ = |
| this.getDelegateFn(delegate && |
| delegate.prepareInstancePositionChanged); |
| } |
| |
| // Instance Removals |
| var instanceCache = new Map; |
| var removeDelta = 0; |
| for (var i = 0; i < splices.length; i++) { |
| var splice = splices[i]; |
| var removed = splice.removed; |
| for (var j = 0; j < removed.length; j++) { |
| var model = removed[j]; |
| var instance = this.extractInstanceAt(splice.index + removeDelta); |
| if (instance !== emptyInstance) { |
| instanceCache.set(model, instance); |
| } |
| } |
| |
| removeDelta -= splice.addedCount; |
| } |
| |
| // Instance Insertions |
| for (var i = 0; i < splices.length; i++) { |
| var splice = splices[i]; |
| var addIndex = splice.index; |
| for (; addIndex < splice.index + splice.addedCount; addIndex++) { |
| var model = this.iteratedValue[addIndex]; |
| var instance = instanceCache.get(model); |
| if (instance) { |
| instanceCache.delete(model); |
| } else { |
| if (this.instanceModelFn_) { |
| model = this.instanceModelFn_(model); |
| } |
| |
| if (model === undefined) { |
| instance = emptyInstance; |
| } else { |
| instance = template.createInstance(model, undefined, delegate); |
| } |
| } |
| |
| this.insertInstanceAt(addIndex, instance); |
| } |
| } |
| |
| instanceCache.forEach(function(instance) { |
| this.closeInstanceBindings(instance); |
| }, this); |
| |
| if (this.instancePositionChangedFn_) |
| this.reportInstancesMoved(splices); |
| }, |
| |
| reportInstanceMoved: function(index) { |
| var instance = this.instances[index]; |
| if (instance === emptyInstance) |
| return; |
| |
| this.instancePositionChangedFn_(instance.templateInstance_, index); |
| }, |
| |
| reportInstancesMoved: function(splices) { |
| var index = 0; |
| var offset = 0; |
| for (var i = 0; i < splices.length; i++) { |
| var splice = splices[i]; |
| if (offset != 0) { |
| while (index < splice.index) { |
| this.reportInstanceMoved(index); |
| index++; |
| } |
| } else { |
| index = splice.index; |
| } |
| |
| while (index < splice.index + splice.addedCount) { |
| this.reportInstanceMoved(index); |
| index++; |
| } |
| |
| offset += splice.addedCount - splice.removed.length; |
| } |
| |
| if (offset == 0) |
| return; |
| |
| var length = this.instances.length; |
| while (index < length) { |
| this.reportInstanceMoved(index); |
| index++; |
| } |
| }, |
| |
| closeInstanceBindings: function(instance) { |
| var bindings = instance.bindings_; |
| for (var i = 0; i < bindings.length; i++) { |
| bindings[i].close(); |
| } |
| }, |
| |
| unobserve: function() { |
| if (!this.arrayObserver) |
| return; |
| |
| this.arrayObserver.close(); |
| this.arrayObserver = undefined; |
| }, |
| |
| close: function() { |
| if (this.closed) |
| return; |
| this.unobserve(); |
| for (var i = 0; i < this.instances.length; i++) { |
| this.closeInstanceBindings(this.instances[i]); |
| } |
| |
| this.instances.length = 0; |
| this.closeDeps(); |
| this.templateElement_.iterator_ = undefined; |
| this.closed = true; |
| } |
| }; |
| |
| // Polyfill-specific API. |
| HTMLTemplateElement.forAllTemplatesFrom_ = forAllTemplatesFrom; |
| })(this); |
| |
| (function(scope) { |
| 'use strict'; |
| |
| // feature detect for URL constructor |
| var hasWorkingUrl = false; |
| if (!scope.forceJURL) { |
| try { |
| var u = new URL('b', 'http://a'); |
| u.pathname = 'c%20d'; |
| hasWorkingUrl = u.href === 'http://a/c%20d'; |
| } catch(e) {} |
| } |
| |
| if (hasWorkingUrl) |
| return; |
| |
| var relative = Object.create(null); |
| relative['ftp'] = 21; |
| relative['file'] = 0; |
| relative['gopher'] = 70; |
| relative['http'] = 80; |
| relative['https'] = 443; |
| relative['ws'] = 80; |
| relative['wss'] = 443; |
| |
| var relativePathDotMapping = Object.create(null); |
| relativePathDotMapping['%2e'] = '.'; |
| relativePathDotMapping['.%2e'] = '..'; |
| relativePathDotMapping['%2e.'] = '..'; |
| relativePathDotMapping['%2e%2e'] = '..'; |
| |
| function isRelativeScheme(scheme) { |
| return relative[scheme] !== undefined; |
| } |
| |
| function invalid() { |
| clear.call(this); |
| this._isInvalid = true; |
| } |
| |
| function IDNAToASCII(h) { |
| if ('' == h) { |
| invalid.call(this) |
| } |
| // XXX |
| return h.toLowerCase() |
| } |
| |
| function percentEscape(c) { |
| var unicode = c.charCodeAt(0); |
| if (unicode > 0x20 && |
| unicode < 0x7F && |
| // " # < > ? ` |
| [0x22, 0x23, 0x3C, 0x3E, 0x3F, 0x60].indexOf(unicode) == -1 |
| ) { |
| return c; |
| } |
| return encodeURIComponent(c); |
| } |
| |
| function percentEscapeQuery(c) { |
| // XXX This actually needs to encode c using encoding and then |
| // convert the bytes one-by-one. |
| |
| var unicode = c.charCodeAt(0); |
| if (unicode > 0x20 && |
| unicode < 0x7F && |
| // " # < > ` (do not escape '?') |
| [0x22, 0x23, 0x3C, 0x3E, 0x60].indexOf(unicode) == -1 |
| ) { |
| return c; |
| } |
| return encodeURIComponent(c); |
| } |
| |
| var EOF = undefined, |
| ALPHA = /[a-zA-Z]/, |
| ALPHANUMERIC = /[a-zA-Z0-9\+\-\.]/; |
| |
| function parse(input, stateOverride, base) { |
| function err(message) { |
| errors.push(message) |
| } |
| |
| var state = stateOverride || 'scheme start', |
| cursor = 0, |
| buffer = '', |
| seenAt = false, |
| seenBracket = false, |
| errors = []; |
| |
| loop: while ((input[cursor - 1] != EOF || cursor == 0) && !this._isInvalid) { |
| var c = input[cursor]; |
| switch (state) { |
| case 'scheme start': |
| if (c && ALPHA.test(c)) { |
| buffer += c.toLowerCase(); // ASCII-safe |
| state = 'scheme'; |
| } else if (!stateOverride) { |
| buffer = ''; |
| state = 'no scheme'; |
| continue; |
| } else { |
| err('Invalid scheme.'); |
| break loop; |
| } |
| break; |
| |
| case 'scheme': |
| if (c && ALPHANUMERIC.test(c)) { |
| buffer += c.toLowerCase(); // ASCII-safe |
| } else if (':' == c) { |
| this._scheme = buffer; |
| buffer = ''; |
| if (stateOverride) { |
| break loop; |
| } |
| if (isRelativeScheme(this._scheme)) { |
| this._isRelative = true; |
| } |
| if ('file' == this._scheme) { |
| state = 'relative'; |
| } else if (this._isRelative && base && base._scheme == this._scheme) { |
| state = 'relative or authority'; |
| } else if (this._isRelative) { |
| state = 'authority first slash'; |
| } else { |
| state = 'scheme data'; |
| } |
| } else if (!stateOverride) { |
| buffer = ''; |
| cursor = 0; |
| state = 'no scheme'; |
| continue; |
| } else if (EOF == c) { |
| break loop; |
| } else { |
| err('Code point not allowed in scheme: ' + c) |
| break loop; |
| } |
| break; |
| |
| case 'scheme data': |
| if ('?' == c) { |
| query = '?'; |
| state = 'query'; |
| } else if ('#' == c) { |
| this._fragment = '#'; |
| state = 'fragment'; |
| } else { |
| // XXX error handling |
| if (EOF != c && '\t' != c && '\n' != c && '\r' != c) { |
| this._schemeData += percentEscape(c); |
| } |
| } |
| break; |
| |
| case 'no scheme': |
| if (!base || !(isRelativeScheme(base._scheme))) { |
| err('Missing scheme.'); |
| invalid.call(this); |
| } else { |
| state = 'relative'; |
| continue; |
| } |
| break; |
| |
| case 'relative or authority': |
| if ('/' == c && '/' == input[cursor+1]) { |
| state = 'authority ignore slashes'; |
| } else { |
| err('Expected /, got: ' + c); |
| state = 'relative'; |
| continue |
| } |
| break; |
| |
| case 'relative': |
| this._isRelative = true; |
| if ('file' != this._scheme) |
| this._scheme = base._scheme; |
| if (EOF == c) { |
| this._host = base._host; |
| this._port = base._port; |
| this._path = base._path.slice(); |
| this._query = base._query; |
| break loop; |
| } else if ('/' == c || '\\' == c) { |
| if ('\\' == c) |
| err('\\ is an invalid code point.'); |
| state = 'relative slash'; |
| } else if ('?' == c) { |
| this._host = base._host; |
| this._port = base._port; |
| this._path = base._path.slice(); |
| this._query = '?'; |
| state = 'query'; |
| } else if ('#' == c) { |
| this._host = base._host; |
| this._port = base._port; |
| this._path = base._path.slice(); |
| this._query = base._query; |
| this._fragment = '#'; |
| state = 'fragment'; |
| } else { |
| var nextC = input[cursor+1] |
| var nextNextC = input[cursor+2] |
| if ( |
| 'file' != this._scheme || !ALPHA.test(c) || |
| (nextC != ':' && nextC != '|') || |
| (EOF != nextNextC && '/' != nextNextC && '\\' != nextNextC && '?' != nextNextC && '#' != nextNextC)) { |
| this._host = base._host; |
| this._port = base._port; |
| this._path = base._path.slice(); |
| this._path.pop(); |
| } |
| state = 'relative path'; |
| continue; |
| } |
| break; |
| |
| case 'relative slash': |
| if ('/' == c || '\\' == c) { |
| if ('\\' == c) { |
| err('\\ is an invalid code point.'); |
| } |
| if ('file' == this._scheme) { |
| state = 'file host'; |
| } else { |
| state = 'authority ignore slashes'; |
| } |
| } else { |
| if ('file' != this._scheme) { |
| this._host = base._host; |
| this._port = base._port; |
| } |
| state = 'relative path'; |
| continue; |
| } |
| break; |
| |
| case 'authority first slash': |
| if ('/' == c) { |
| state = 'authority second slash'; |
| } else { |
| err("Expected '/', got: " + c); |
| state = 'authority ignore slashes'; |
| continue; |
| } |
| break; |
| |
| case 'authority second slash': |
| state = 'authority ignore slashes'; |
| if ('/' != c) { |
| err("Expected '/', got: " + c); |
| continue; |
| } |
| break; |
| |
| case 'authority ignore slashes': |
| if ('/' != c && '\\' != c) { |
| state = 'authority'; |
| continue; |
| } else { |
| err('Expected authority, got: ' + c); |
| } |
| break; |
| |
| case 'authority': |
| if ('@' == c) { |
| if (seenAt) { |
| err('@ already seen.'); |
| buffer += '%40'; |
| } |
| seenAt = true; |
| for (var i = 0; i < buffer.length; i++) { |
| var cp = buffer[i]; |
| if ('\t' == cp || '\n' == cp || '\r' == cp) { |
| err('Invalid whitespace in authority.'); |
| continue; |
| } |
| // XXX check URL code points |
| if (':' == cp && null === this._password) { |
| this._password = ''; |
| continue; |
| } |
| var tempC = percentEscape(cp); |
| (null !== this._password) ? this._password += tempC : this._username += tempC; |
| } |
| buffer = ''; |
| } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) { |
| cursor -= buffer.length; |
| buffer = ''; |
| state = 'host'; |
| continue; |
| } else { |
| buffer += c; |
| } |
| break; |
| |
| case 'file host': |
| if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) { |
| if (buffer.length == 2 && ALPHA.test(buffer[0]) && (buffer[1] == ':' || buffer[1] == '|')) { |
| state = 'relative path'; |
| } else if (buffer.length == 0) { |
| state = 'relative path start'; |
| } else { |
| this._host = IDNAToASCII.call(this, buffer); |
| buffer = ''; |
| state = 'relative path start'; |
| } |
| continue; |
| } else if ('\t' == c || '\n' == c || '\r' == c) { |
| err('Invalid whitespace in file host.'); |
| } else { |
| buffer += c; |
| } |
| break; |
| |
| case 'host': |
| case 'hostname': |
| if (':' == c && !seenBracket) { |
| // XXX host parsing |
| this._host = IDNAToASCII.call(this, buffer); |
| buffer = ''; |
| state = 'port'; |
| if ('hostname' == stateOverride) { |
| break loop; |
| } |
| } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) { |
| this._host = IDNAToASCII.call(this, buffer); |
| buffer = ''; |
| state = 'relative path start'; |
| if (stateOverride) { |
| break loop; |
| } |
| continue; |
| } else if ('\t' != c && '\n' != c && '\r' != c) { |
| if ('[' == c) { |
| seenBracket = true; |
| } else if (']' == c) { |
| seenBracket = false; |
| } |
| buffer += c; |
| } else { |
| err('Invalid code point in host/hostname: ' + c); |
| } |
| break; |
| |
| case 'port': |
| if (/[0-9]/.test(c)) { |
| buffer += c; |
| } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c || stateOverride) { |
| if ('' != buffer) { |
| var temp = parseInt(buffer, 10); |
| if (temp != relative[this._scheme]) { |
| this._port = temp + ''; |
| } |
| buffer = ''; |
| } |
| if (stateOverride) { |
| break loop; |
| } |
| state = 'relative path start'; |
| continue; |
| } else if ('\t' == c || '\n' == c || '\r' == c) { |
| err('Invalid code point in port: ' + c); |
| } else { |
| invalid.call(this); |
| } |
| break; |
| |
| case 'relative path start': |
| if ('\\' == c) |
| err("'\\' not allowed in path."); |
| state = 'relative path'; |
| if ('/' != c && '\\' != c) { |
| continue; |
| } |
| break; |
| |
| case 'relative path': |
| if (EOF == c || '/' == c || '\\' == c || (!stateOverride && ('?' == c || '#' == c))) { |
| if ('\\' == c) { |
| err('\\ not allowed in relative path.'); |
| } |
| var tmp; |
| if (tmp = relativePathDotMapping[buffer.toLowerCase()]) { |
| buffer = tmp; |
| } |
| if ('..' == buffer) { |
| this._path.pop(); |
| if ('/' != c && '\\' != c) { |
| this._path.push(''); |
| } |
| } else if ('.' == buffer && '/' != c && '\\' != c) { |
| this._path.push(''); |
| } else if ('.' != buffer) { |
| if ('file' == this._scheme && this._path.length == 0 && buffer.length == 2 && ALPHA.test(buffer[0]) && buffer[1] == '|') { |
| buffer = buffer[0] + ':'; |
| } |
| this._path.push(buffer); |
| } |
| buffer = ''; |
| if ('?' == c) { |
| this._query = '?'; |
| state = 'query'; |
| } else if ('#' == c) { |
| this._fragment = '#'; |
| state = 'fragment'; |
| } |
| } else if ('\t' != c && '\n' != c && '\r' != c) { |
| buffer += percentEscape(c); |
| } |
| break; |
| |
| case 'query': |
| if (!stateOverride && '#' == c) { |
| this._fragment = '#'; |
| state = 'fragment'; |
| } else if (EOF != c && '\t' != c && '\n' != c && '\r' != c) { |
| this._query += percentEscapeQuery(c); |
| } |
| break; |
| |
| case 'fragment': |
| if (EOF != c && '\t' != c && '\n' != c && '\r' != c) { |
| this._fragment += c; |
| } |
| break; |
| } |
| |
| cursor++; |
| } |
| } |
| |
| function clear() { |
| this._scheme = ''; |
| this._schemeData = ''; |
| this._username = ''; |
| this._password = null; |
| this._host = ''; |
| this._port = ''; |
| this._path = []; |
| this._query = ''; |
| this._fragment = ''; |
| this._isInvalid = false; |
| this._isRelative = false; |
| } |
| |
| // Does not process domain names or IP addresses. |
| // Does not handle encoding for the query parameter. |
| function jURL(url, base /* , encoding */) { |
| if (base !== undefined && !(base instanceof jURL)) |
| base = new jURL(String(base)); |
| |
| this._url = url; |
| clear.call(this); |
| |
| var input = url.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g, ''); |
| // encoding = encoding || 'utf-8' |
| |
| parse.call(this, input, null, base); |
| } |
| |
| jURL.prototype = { |
| get href() { |
| if (this._isInvalid) |
| return this._url; |
| |
| var authority = ''; |
| if ('' != this._username || null != this._password) { |
| authority = this._username + |
| (null != this._password ? ':' + this._password : '') + '@'; |
| } |
| |
| return this.protocol + |
| (this._isRelative ? '//' + authority + this.host : '') + |
| this.pathname + this._query + this._fragment; |
| }, |
| set href(href) { |
| clear.call(this); |
| parse.call(this, href); |
| }, |
| |
| get protocol() { |
| return this._scheme + ':'; |
| }, |
| set protocol(protocol) { |
| if (this._isInvalid) |
| return; |
| parse.call(this, protocol + ':', 'scheme start'); |
| }, |
| |
| get host() { |
| return this._isInvalid ? '' : this._port ? |
| this._host + ':' + this._port : this._host; |
| }, |
| set host(host) { |
| if (this._isInvalid || !this._isRelative) |
| return; |
| parse.call(this, host, 'host'); |
| }, |
| |
| get hostname() { |
| return this._host; |
| }, |
| set hostname(hostname) { |
| if (this._isInvalid || !this._isRelative) |
| return; |
| parse.call(this, hostname, 'hostname'); |
| }, |
| |
| get port() { |
| return this._port; |
| }, |
| set port(port) { |
| if (this._isInvalid || !this._isRelative) |
| return; |
| parse.call(this, port, 'port'); |
| }, |
| |
| get pathname() { |
| return this._isInvalid ? '' : this._isRelative ? |
| '/' + this._path.join('/') : this._schemeData; |
| }, |
| set pathname(pathname) { |
| if (this._isInvalid || !this._isRelative) |
| return; |
| this._path = []; |
| parse.call(this, pathname, 'relative path start'); |
| }, |
| |
| get search() { |
| return this._isInvalid || !this._query || '?' == this._query ? |
| '' : this._query; |
| }, |
| set search(search) { |
| if (this._isInvalid || !this._isRelative) |
| return; |
| this._query = '?'; |
| if ('?' == search[0]) |
| search = search.slice(1); |
| parse.call(this, search, 'query'); |
| }, |
| |
| get hash() { |
| return this._isInvalid || !this._fragment || '#' == this._fragment ? |
| '' : this._fragment; |
| }, |
| set hash(hash) { |
| if (this._isInvalid) |
| return; |
| this._fragment = '#'; |
| if ('#' == hash[0]) |
| hash = hash.slice(1); |
| parse.call(this, hash, 'fragment'); |
| }, |
| |
| get origin() { |
| var host; |
| if (this._isInvalid || !this._scheme) { |
| return ''; |
| } |
| // javascript: Gecko returns String(""), WebKit/Blink String("null") |
| // Gecko throws error for "data://" |
| // data: Gecko returns "", Blink returns "data://", WebKit returns "null" |
| // Gecko returns String("") for file: mailto: |
| // WebKit/Blink returns String("SCHEME://") for file: mailto: |
| switch (this._scheme) { |
| case 'data': |
| case 'file': |
| case 'javascript': |
| case 'mailto': |
| return 'null'; |
| } |
| host = this.host; |
| if (!host) { |
| return ''; |
| } |
| return this._scheme + '://' + host; |
| } |
| }; |
| |
| // Copy over the static methods |
| var OriginalURL = scope.URL; |
| if (OriginalURL) { |
| jURL.createObjectURL = function(blob) { |
| // IE extension allows a second optional options argument. |
| // http://msdn.microsoft.com/en-us/library/ie/hh772302(v=vs.85).aspx |
| return OriginalURL.createObjectURL.apply(OriginalURL, arguments); |
| }; |
| jURL.revokeObjectURL = function(url) { |
| OriginalURL.revokeObjectURL(url); |
| }; |
| } |
| |
| scope.URL = jURL; |
| |
| })(this); |
| |
| (function(scope) { |
| |
| var iterations = 0; |
| var callbacks = []; |
| var twiddle = document.createTextNode(''); |
| |
| function endOfMicrotask(callback) { |
| twiddle.textContent = iterations++; |
| callbacks.push(callback); |
| } |
| |
| function atEndOfMicrotask() { |
| while (callbacks.length) { |
| callbacks.shift()(); |
| } |
| } |
| |
| new (window.MutationObserver || JsMutationObserver)(atEndOfMicrotask) |
| .observe(twiddle, {characterData: true}) |
| ; |
| |
| // exports |
| scope.endOfMicrotask = endOfMicrotask; |
| // bc |
| Platform.endOfMicrotask = endOfMicrotask; |
| |
| })(Polymer); |
| |
| |
| (function(scope) { |
| |
| /** |
| * @class Polymer |
| */ |
| |
| // imports |
| var endOfMicrotask = scope.endOfMicrotask; |
| |
| // logging |
| var log = window.WebComponents ? WebComponents.flags.log : {}; |
| |
| // inject style sheet |
| var style = document.createElement('style'); |
| style.textContent = 'template {display: none !important;} /* injected by platform.js */'; |
| var head = document.querySelector('head'); |
| head.insertBefore(style, head.firstChild); |
| |
| |
| /** |
| * Force any pending data changes to be observed before |
| * the next task. Data changes are processed asynchronously but are guaranteed |
| * to be processed, for example, before painting. This method should rarely be |
| * needed. It does nothing when Object.observe is available; |
| * when Object.observe is not available, Polymer automatically flushes data |
| * changes approximately every 1/10 second. |
| * Therefore, `flush` should only be used when a data mutation should be |
| * observed sooner than this. |
| * |
| * @method flush |
| */ |
| // flush (with logging) |
| var flushing; |
| function flush() { |
| if (!flushing) { |
| flushing = true; |
| endOfMicrotask(function() { |
| flushing = false; |
| log.data && console.group('flush'); |
| Platform.performMicrotaskCheckpoint(); |
| log.data && console.groupEnd(); |
| }); |
| } |
| }; |
| |
| // polling dirty checker |
| // flush periodically if platform does not have object observe. |
| if (!Observer.hasObjectObserve) { |
| var FLUSH_POLL_INTERVAL = 125; |
| window.addEventListener('WebComponentsReady', function() { |
| flush(); |
| // watch document visiblity to toggle dirty-checking |
| var visibilityHandler = function() { |
| // only flush if the page is visibile |
| if (document.visibilityState === 'hidden') { |
| if (scope.flushPoll) { |
| clearInterval(scope.flushPoll); |
| } |
| } else { |
| scope.flushPoll = setInterval(flush, FLUSH_POLL_INTERVAL); |
| } |
| }; |
| if (typeof document.visibilityState === 'string') { |
| document.addEventListener('visibilitychange', visibilityHandler); |
| } |
| visibilityHandler(); |
| }); |
| } else { |
| // make flush a no-op when we have Object.observe |
| flush = function() {}; |
| } |
| |
| if (window.CustomElements && !CustomElements.useNative) { |
| var originalImportNode = Document.prototype.importNode; |
| Document.prototype.importNode = function(node, deep) { |
| var imported = originalImportNode.call(this, node, deep); |
| CustomElements.upgradeAll(imported); |
| return imported; |
| }; |
| } |
| |
| // exports |
| scope.flush = flush; |
| // bc |
| Platform.flush = flush; |
| |
| })(window.Polymer); |
| |
| |
| (function(scope) { |
| |
| var urlResolver = { |
| resolveDom: function(root, url) { |
| url = url || baseUrl(root); |
| this.resolveAttributes(root, url); |
| this.resolveStyles(root, url); |
| // handle template.content |
| var templates = root.querySelectorAll('template'); |
| if (templates) { |
| for (var i = 0, l = templates.length, t; (i < l) && (t = templates[i]); i++) { |
| if (t.content) { |
| this.resolveDom(t.content, url); |
| } |
| } |
| } |
| }, |
| resolveTemplate: function(template) { |
| this.resolveDom(template.content, baseUrl(template)); |
| }, |
| resolveStyles: function(root, url) { |
| var styles = root.querySelectorAll('style'); |
| if (styles) { |
| for (var i = 0, l = styles.length, s; (i < l) && (s = styles[i]); i++) { |
| this.resolveStyle(s, url); |
| } |
| } |
| }, |
| resolveStyle: function(style, url) { |
| url = url || baseUrl(style); |
| style.textContent = this.resolveCssText(style.textContent, url); |
| }, |
| resolveCssText: function(cssText, baseUrl, keepAbsolute) { |
| cssText = replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, CSS_URL_REGEXP); |
| return replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, CSS_IMPORT_REGEXP); |
| }, |
| resolveAttributes: function(root, url) { |
| if (root.hasAttributes && root.hasAttributes()) { |
| this.resolveElementAttributes(root, url); |
| } |
| // search for attributes that host urls |
| var nodes = root && root.querySelectorAll(URL_ATTRS_SELECTOR); |
| if (nodes) { |
| for (var i = 0, l = nodes.length, n; (i < l) && (n = nodes[i]); i++) { |
| this.resolveElementAttributes(n, url); |
| } |
| } |
| }, |
| resolveElementAttributes: function(node, url) { |
| url = url || baseUrl(node); |
| URL_ATTRS.forEach(function(v) { |
| var attr = node.attributes[v]; |
| var value = attr && attr.value; |
| var replacement; |
| if (value && value.search(URL_TEMPLATE_SEARCH) < 0) { |
| if (v === 'style') { |
| replacement = replaceUrlsInCssText(value, url, false, CSS_URL_REGEXP); |
| } else { |
| replacement = resolveRelativeUrl(url, value); |
| } |
| attr.value = replacement; |
| } |
| }); |
| } |
| }; |
| |
| var CSS_URL_REGEXP = /(url\()([^)]*)(\))/g; |
| var CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g; |
| var URL_ATTRS = ['href', 'src', 'action', 'style', 'url']; |
| var URL_ATTRS_SELECTOR = '[' + URL_ATTRS.join('],[') + ']'; |
| var URL_TEMPLATE_SEARCH = '{{.*}}'; |
| var URL_HASH = '#'; |
| |
| function baseUrl(node) { |
| var u = new URL(node.ownerDocument.baseURI); |
| u.search = ''; |
| u.hash = ''; |
| return u; |
| } |
| |
| function replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, regexp) { |
| return cssText.replace(regexp, function(m, pre, url, post) { |
| var urlPath = url.replace(/["']/g, ''); |
| urlPath = resolveRelativeUrl(baseUrl, urlPath, keepAbsolute); |
| return pre + '\'' + urlPath + '\'' + post; |
| }); |
| } |
| |
| function resolveRelativeUrl(baseUrl, url, keepAbsolute) { |
| // do not resolve '/' absolute urls |
| if (url && url[0] === '/') { |
| return url; |
| } |
| // do not resolve '#' links, they are used for routing |
| if (url && url[0] === '#') { |
| return url; |
| } |
| var u = new URL(url, baseUrl); |
| return keepAbsolute ? u.href : makeDocumentRelPath(u.href); |
| } |
| |
| function makeDocumentRelPath(url) { |
| var root = baseUrl(document.documentElement); |
| var u = new URL(url, root); |
| if (u.host === root.host && u.port === root.port && |
| u.protocol === root.protocol) { |
| return makeRelPath(root, u); |
| } else { |
| return url; |
| } |
| } |
| |
| // make a relative path from source to target |
| function makeRelPath(sourceUrl, targetUrl) { |
| var source = sourceUrl.pathname; |
| var target = targetUrl.pathname; |
| var s = source.split('/'); |
| var t = target.split('/'); |
| while (s.length && s[0] === t[0]){ |
| s.shift(); |
| t.shift(); |
| } |
| for (var i = 0, l = s.length - 1; i < l; i++) { |
| t.unshift('..'); |
| } |
| // empty '#' is discarded but we need to preserve it. |
| var hash = (targetUrl.href.slice(-1) === URL_HASH) ? URL_HASH : targetUrl.hash; |
| return t.join('/') + targetUrl.search + hash; |
| } |
| |
| // exports |
| scope.urlResolver = urlResolver; |
| |
| })(Polymer); |
| |
| (function(scope) { |
| var endOfMicrotask = Polymer.endOfMicrotask; |
| |
| // Generic url loader |
| function Loader(regex) { |
| this.cache = Object.create(null); |
| this.map = Object.create(null); |
| this.requests = 0; |
| this.regex = regex; |
| } |
| Loader.prototype = { |
| |
| // TODO(dfreedm): there may be a better factoring here |
| // extract absolute urls from the text (full of relative urls) |
| extractUrls: function(text, base) { |
| var matches = []; |
| var matched, u; |
| while ((matched = this.regex.exec(text))) { |
| u = new URL(matched[1], base); |
| matches.push({matched: matched[0], url: u.href}); |
| } |
| return matches; |
| }, |
| // take a text blob, a root url, and a callback and load all the urls found within the text |
| // returns a map of absolute url to text |
| process: function(text, root, callback) { |
| var matches = this.extractUrls(text, root); |
| |
| // every call to process returns all the text this loader has ever received |
| var done = callback.bind(null, this.map); |
| this.fetch(matches, done); |
| }, |
| // build a mapping of url -> text from matches |
| fetch: function(matches, callback) { |
| var inflight = matches.length; |
| |
| // return early if there is no fetching to be done |
| if (!inflight) { |
| return callback(); |
| } |
| |
| // wait for all subrequests to return |
| var done = function() { |
| if (--inflight === 0) { |
| callback(); |
| } |
| }; |
| |
| // start fetching all subrequests |
| var m, req, url; |
| for (var i = 0; i < inflight; i++) { |
| m = matches[i]; |
| url = m.url; |
| req = this.cache[url]; |
| // if this url has already been requested, skip requesting it again |
| if (!req) { |
| req = this.xhr(url); |
| req.match = m; |
| this.cache[url] = req; |
| } |
| // wait for the request to process its subrequests |
| req.wait(done); |
| } |
| }, |
| handleXhr: function(request) { |
| var match = request.match; |
| var url = match.url; |
| |
| // handle errors with an empty string |
| var response = request.response || request.responseText || ''; |
| this.map[url] = response; |
| this.fetch(this.extractUrls(response, url), request.resolve); |
| }, |
| xhr: function(url) { |
| this.requests++; |
| var request = new XMLHttpRequest(); |
| request.open('GET', url, true); |
| request.send(); |
| request.onerror = request.onload = this.handleXhr.bind(this, request); |
| |
| // queue of tasks to run after XHR returns |
| request.pending = []; |
| request.resolve = function() { |
| var pending = request.pending; |
| for(var i = 0; i < pending.length; i++) { |
| pending[i](); |
| } |
| request.pending = null; |
| }; |
| |
| // if we have already resolved, pending is null, async call the callback |
| request.wait = function(fn) { |
| if (request.pending) { |
| request.pending.push(fn); |
| } else { |
| endOfMicrotask(fn); |
| } |
| }; |
| |
| return request; |
| } |
| }; |
| |
| scope.Loader = Loader; |
| })(Polymer); |
| |
| (function(scope) { |
| |
| var urlResolver = scope.urlResolver; |
| var Loader = scope.Loader; |
| |
| function StyleResolver() { |
| this.loader = new Loader(this.regex); |
| } |
| StyleResolver.prototype = { |
| regex: /@import\s+(?:url)?["'\(]*([^'"\)]*)['"\)]*;/g, |
| // Recursively replace @imports with the text at that url |
| resolve: function(text, url, callback) { |
| var done = function(map) { |
| callback(this.flatten(text, url, map)); |
| }.bind(this); |
| this.loader.process(text, url, done); |
| }, |
| // resolve the textContent of a style node |
| resolveNode: function(style, url, callback) { |
| var text = style.textContent; |
| var done = function(text) { |
| style.textContent = text; |
| callback(style); |
| }; |
| this.resolve(text, url, done); |
| }, |
| // flatten all the @imports to text |
| flatten: function(text, base, map) { |
| var matches = this.loader.extractUrls(text, base); |
| var match, url, intermediate; |
| for (var i = 0; i < matches.length; i++) { |
| match = matches[i]; |
| url = match.url; |
| // resolve any css text to be relative to the importer, keep absolute url |
| intermediate = urlResolver.resolveCssText(map[url], url, true); |
| // flatten intermediate @imports |
| intermediate = this.flatten(intermediate, base, map); |
| text = text.replace(match.matched, intermediate); |
| } |
| return text; |
| }, |
| loadStyles: function(styles, base, callback) { |
| var loaded=0, l = styles.length; |
| // called in the context of the style |
| function loadedStyle(style) { |
| loaded++; |
| if (loaded === l && callback) { |
| callback(); |
| } |
| } |
| for (var i=0, s; (i<l) && (s=styles[i]); i++) { |
| this.resolveNode(s, base, loadedStyle); |
| } |
| } |
| }; |
| |
| var styleResolver = new StyleResolver(); |
| |
| // exports |
| scope.styleResolver = styleResolver; |
| |
| })(Polymer); |
| |
| (function(scope) { |
| |
| // copy own properties from 'api' to 'prototype, with name hinting for 'super' |
| function extend(prototype, api) { |
| if (prototype && api) { |
| // use only own properties of 'api' |
| Object.getOwnPropertyNames(api).forEach(function(n) { |
| // acquire property descriptor |
| var pd = Object.getOwnPropertyDescriptor(api, n); |
| if (pd) { |
| // clone property via descriptor |
| Object.defineProperty(prototype, n, pd); |
| // cache name-of-method for 'super' engine |
| if (typeof pd.value == 'function') { |
| // hint the 'super' engine |
| pd.value.nom = n; |
| } |
| } |
| }); |
| } |
| return prototype; |
| } |
| |
| |
| // mixin |
| |
| // copy all properties from inProps (et al) to inObj |
| function mixin(inObj/*, inProps, inMoreProps, ...*/) { |
| var obj = inObj || {}; |
| for (var i = 1; i < arguments.length; i++) { |
| var p = arguments[i]; |
| try { |
| for (var n in p) { |
| copyProperty(n, p, obj); |
| } |
| } catch(x) { |
| } |
| } |
| return obj; |
| } |
| |
| // copy property inName from inSource object to inTarget object |
| function copyProperty(inName, inSource, inTarget) { |
| var pd = getPropertyDescriptor(inSource, inName); |
| Object.defineProperty(inTarget, inName, pd); |
| } |
| |
| // get property descriptor for inName on inObject, even if |
| // inName exists on some link in inObject's prototype chain |
| function getPropertyDescriptor(inObject, inName) { |
| if (inObject) { |
| var pd = Object.getOwnPropertyDescriptor(inObject, inName); |
| return pd || getPropertyDescriptor(Object.getPrototypeOf(inObject), inName); |
| } |
| } |
| |
| // exports |
| |
| scope.extend = extend; |
| scope.mixin = mixin; |
| |
| // for bc |
| Platform.mixin = mixin; |
| |
| })(Polymer); |
| |
| (function(scope) { |
| |
| // usage |
| |
| // invoke cb.call(this) in 100ms, unless the job is re-registered, |
| // which resets the timer |
| // |
| // this.myJob = this.job(this.myJob, cb, 100) |
| // |
| // returns a job handle which can be used to re-register a job |
| |
| var Job = function(inContext) { |
| this.context = inContext; |
| this.boundComplete = this.complete.bind(this) |
| }; |
| Job.prototype = { |
| go: function(callback, wait) { |
| this.callback = callback; |
| var h; |
| if (!wait) { |
| h = requestAnimationFrame(this.boundComplete); |
| this.handle = function() { |
| cancelAnimationFrame(h); |
| } |
| } else { |
| h = setTimeout(this.boundComplete, wait); |
| this.handle = function() { |
| clearTimeout(h); |
| } |
| } |
| }, |
| stop: function() { |
| if (this.handle) { |
| this.handle(); |
| this.handle = null; |
| } |
| }, |
| complete: function() { |
| if (this.handle) { |
| this.stop(); |
| this.callback.call(this.context); |
| } |
| } |
| }; |
| |
| function job(job, callback, wait) { |
| if (job) { |
| job.stop(); |
| } else { |
| job = new Job(this); |
| } |
| job.go(callback, wait); |
| return job; |
| } |
| |
| // exports |
| |
| scope.job = job; |
| |
| })(Polymer); |
| |
| (function(scope) { |
| |
| // dom polyfill, additions, and utility methods |
| |
| var registry = {}; |
| |
| HTMLElement.register = function(tag, prototype) { |
| registry[tag] = prototype; |
| }; |
| |
| // get prototype mapped to node <tag> |
| HTMLElement.getPrototypeForTag = function(tag) { |
| var prototype = !tag ? HTMLElement.prototype : registry[tag]; |
| // TODO(sjmiles): creating <tag> is likely to have wasteful side-effects |
| return prototype || Object.getPrototypeOf(document.createElement(tag)); |
| }; |
| |
| // we have to flag propagation stoppage for the event dispatcher |
| var originalStopPropagation = Event.prototype.stopPropagation; |
| Event.prototype.stopPropagation = function() { |
| this.cancelBubble = true; |
| originalStopPropagation.apply(this, arguments); |
| }; |
| |
| |
| // polyfill DOMTokenList |
| // * add/remove: allow these methods to take multiple classNames |
| // * toggle: add a 2nd argument which forces the given state rather |
| // than toggling. |
| |
| var add = DOMTokenList.prototype.add; |
| var remove = DOMTokenList.prototype.remove; |
| DOMTokenList.prototype.add = function() { |
| for (var i = 0; i < arguments.length; i++) { |
| add.call(this, arguments[i]); |
| } |
| }; |
| DOMTokenList.prototype.remove = function() { |
| for (var i = 0; i < arguments.length; i++) { |
| remove.call(this, arguments[i]); |
| } |
| }; |
| DOMTokenList.prototype.toggle = function(name, bool) { |
| if (arguments.length == 1) { |
| bool = !this.contains(name); |
| } |
| bool ? this.add(name) : this.remove(name); |
| }; |
| DOMTokenList.prototype.switch = function(oldName, newName) { |
| oldName && this.remove(oldName); |
| newName && this.add(newName); |
| }; |
| |
| // add array() to NodeList, NamedNodeMap, HTMLCollection |
| |
| var ArraySlice = function() { |
| return Array.prototype.slice.call(this); |
| }; |
| |
| var namedNodeMap = (window.NamedNodeMap || window.MozNamedAttrMap || {}); |
| |
| NodeList.prototype.array = ArraySlice; |
| namedNodeMap.prototype.array = ArraySlice; |
| HTMLCollection.prototype.array = ArraySlice; |
| |
| // utility |
| |
| function createDOM(inTagOrNode, inHTML, inAttrs) { |
| var dom = typeof inTagOrNode == 'string' ? |
| document.createElement(inTagOrNode) : inTagOrNode.cloneNode(true); |
| dom.innerHTML = inHTML; |
| if (inAttrs) { |
| for (var n in inAttrs) { |
| dom.setAttribute(n, inAttrs[n]); |
| } |
| } |
| return dom; |
| } |
| |
| // exports |
| |
| scope.createDOM = createDOM; |
| |
| })(Polymer); |
| |
| (function(scope) { |
| // super |
| |
| // `arrayOfArgs` is an optional array of args like one might pass |
| // to `Function.apply` |
| |
| // TODO(sjmiles): |
| // $super must be installed on an instance or prototype chain |
| // as `super`, and invoked via `this`, e.g. |
| // `this.super();` |
| |
| // will not work if function objects are not unique, for example, |
| // when using mixins. |
| // The memoization strategy assumes each function exists on only one |
| // prototype chain i.e. we use the function object for memoizing) |
| // perhaps we can bookkeep on the prototype itself instead |
| function $super(arrayOfArgs) { |
| // since we are thunking a method call, performance is important here: |
| // memoize all lookups, once memoized the fast path calls no other |
| // functions |
| // |
| // find the caller (cannot be `strict` because of 'caller') |
| var caller = $super.caller; |
| // memoized 'name of method' |
| var nom = caller.nom; |
| // memoized next implementation prototype |
| var _super = caller._super; |
| if (!_super) { |
| if (!nom) { |
| nom = caller.nom = nameInThis.call(this, caller); |
| } |
| if (!nom) { |
| console.warn('called super() on a method not installed declaratively (has no .nom property)'); |
| } |
| // super prototype is either cached or we have to find it |
| // by searching __proto__ (at the 'top') |
| // invariant: because we cache _super on fn below, we never reach |
| // here from inside a series of calls to super(), so it's ok to |
| // start searching from the prototype of 'this' (at the 'top') |
| // we must never memoize a null super for this reason |
| _super = memoizeSuper(caller, nom, getPrototypeOf(this)); |
| } |
| // our super function |
| var fn = _super[nom]; |
| if (fn) { |
| // memoize information so 'fn' can call 'super' |
| if (!fn._super) { |
| // must not memoize null, or we lose our invariant above |
| memoizeSuper(fn, nom, _super); |
| } |
| // invoke the inherited method |
| // if 'fn' is not function valued, this will throw |
| return fn.apply(this, arrayOfArgs || []); |
| } |
| } |
| |
| function nameInThis(value) { |
| var p = this.__proto__; |
| while (p && p !== HTMLElement.prototype) { |
| // TODO(sjmiles): getOwnPropertyNames is absurdly expensive |
| var n$ = Object.getOwnPropertyNames(p); |
| for (var i=0, l=n$.length, n; i<l && (n=n$[i]); i++) { |
| var d = Object.getOwnPropertyDescriptor(p, n); |
| if (typeof d.value === 'function' && d.value === value) { |
| return n; |
| } |
| } |
| p = p.__proto__; |
| } |
| } |
| |
| function memoizeSuper(method, name, proto) { |
| // find and cache next prototype containing `name` |
| // we need the prototype so we can do another lookup |
| // from here |
| var s = nextSuper(proto, name, method); |
| if (s[name]) { |
| // `s` is a prototype, the actual method is `s[name]` |
| // tag super method with it's name for quicker lookups |
| s[name].nom = name; |
| } |
| return method._super = s; |
| } |
| |
| function nextSuper(proto, name, caller) { |
| // look for an inherited prototype that implements name |
| while (proto) { |
| if ((proto[name] !== caller) && proto[name]) { |
| return proto; |
| } |
| proto = getPrototypeOf(proto); |
| } |
| // must not return null, or we lose our invariant above |
| // in this case, a super() call was invoked where no superclass |
| // method exists |
| // TODO(sjmiles): thow an exception? |
| return Object; |
| } |
| |
| // NOTE: In some platforms (IE10) the prototype chain is faked via |
| // __proto__. Therefore, always get prototype via __proto__ instead of |
| // the more standard Object.getPrototypeOf. |
| function getPrototypeOf(prototype) { |
| return prototype.__proto__; |
| } |
| |
| // utility function to precompute name tags for functions |
| // in a (unchained) prototype |
| function hintSuper(prototype) { |
| // tag functions with their prototype name to optimize |
| // super call invocations |
| for (var n in prototype) { |
| var pd = Object.getOwnPropertyDescriptor(prototype, n); |
| if (pd && typeof pd.value === 'function') { |
| pd.value.nom = n; |
| } |
| } |
| } |
| |
| // exports |
| |
| scope.super = $super; |
| |
| })(Polymer); |
| |
| (function(scope) { |
| |
| function noopHandler(value) { |
| return value; |
| } |
| |
| // helper for deserializing properties of various types to strings |
| var typeHandlers = { |
| string: noopHandler, |
| 'undefined': noopHandler, |
| date: function(value) { |
| return new Date(Date.parse(value) || Date.now()); |
| }, |
| boolean: function(value) { |
| if (value === '') { |
| return true; |
| } |
| return value === 'false' ? false : !!value; |
| }, |
| number: function(value) { |
| var n = parseFloat(value); |
| // hex values like "0xFFFF" parseFloat as 0 |
| if (n === 0) { |
| n = parseInt(value); |
| } |
| return isNaN(n) ? value : n; |
| // this code disabled because encoded values (like "0xFFFF") |
| // do not round trip to their original format |
| //return (String(floatVal) === value) ? floatVal : value; |
| }, |
| object: function(value, currentValue) { |
| if (currentValue === null) { |
| return value; |
| } |
| try { |
| // If the string is an object, we can parse is with the JSON library. |
| // include convenience replace for single-quotes. If the author omits |
| // quotes altogether, parse will fail. |
| return JSON.parse(value.replace(/'/g, '"')); |
| } catch(e) { |
| // The object isn't valid JSON, return the raw value |
| return value; |
| } |
| }, |
| // avoid deserialization of functions |
| 'function': function(value, currentValue) { |
| return currentValue; |
| } |
| }; |
| |
| function deserializeValue(value, currentValue) { |
| // attempt to infer type from default value |
| var inferredType = typeof currentValue; |
| // invent 'date' type value for Date |
| if (currentValue instanceof Date) { |
| inferredType = 'date'; |
| } |
| // delegate deserialization via type string |
| return typeHandlers[inferredType](value, currentValue); |
| } |
| |
| // exports |
| |
| scope.deserializeValue = deserializeValue; |
| |
| })(Polymer); |
| |
| (function(scope) { |
| |
| // imports |
| |
| var extend = scope.extend; |
| |
| // module |
| |
| var api = {}; |
| |
| api.declaration = {}; |
| api.instance = {}; |
| |
| api.publish = function(apis, prototype) { |
| for (var n in apis) { |
| extend(prototype, apis[n]); |
| } |
| }; |
| |
| // exports |
| |
| scope.api = api; |
| |
| })(Polymer); |
| |
| (function(scope) { |
| |
| /** |
| * @class polymer-base |
| */ |
| |
| var utils = { |
| |
| /** |
| * Invokes a function asynchronously. The context of the callback |
| * function is bound to 'this' automatically. Returns a handle which may |
| * be passed to <a href="#cancelAsync">cancelAsync</a> to cancel the |
| * asynchronous call. |
| * |
| * @method async |
| * @param {Function|String} method |
| * @param {any|Array} args |
| * @param {number} timeout |
| */ |
| async: function(method, args, timeout) { |
| // when polyfilling Object.observe, ensure changes |
| // propagate before executing the async method |
| Polymer.flush(); |
| // second argument to `apply` must be an array |
| args = (args && args.length) ? args : [args]; |
| // function to invoke |
| var fn = function() { |
| (this[method] || method).apply(this, args); |
| }.bind(this); |
| // execute `fn` sooner or later |
| var handle = timeout ? setTimeout(fn, timeout) : |
| requestAnimationFrame(fn); |
| // NOTE: switch on inverting handle to determine which time is used. |
| return timeout ? handle : ~handle; |
| }, |
| |
| /** |
| * Cancels a pending callback that was scheduled via |
| * <a href="#async">async</a>. |
| * |
| * @method cancelAsync |
| * @param {handle} handle Handle of the `async` to cancel. |
| */ |
| cancelAsync: function(handle) { |
| if (handle < 0) { |
| cancelAnimationFrame(~handle); |
| } else { |
| clearTimeout(handle); |
| } |
| }, |
| |
| /** |
| * Fire an event. |
| * |
| * @method fire |
| * @returns {Object} event |
| * @param {string} type An event name. |
| * @param {any} detail |
| * @param {Node} onNode Target node. |
| * @param {Boolean} bubbles Set false to prevent bubbling, defaults to true |
| * @param {Boolean} cancelable Set false to prevent cancellation, defaults to true |
| */ |
| fire: function(type, detail, onNode, bubbles, cancelable) { |
| var node = onNode || this; |
| var detail = detail === null || detail === undefined ? {} : detail; |
| var event = new CustomEvent(type, { |
| bubbles: bubbles !== undefined ? bubbles : true, |
| cancelable: cancelable !== undefined ? cancelable : true, |
| detail: detail |
| }); |
| node.dispatchEvent(event); |
| return event; |
| }, |
| |
| /** |
| * Fire an event asynchronously. |
| * |
| * @method asyncFire |
| * @param {string} type An event name. |
| * @param detail |
| * @param {Node} toNode Target node. |
| */ |
| asyncFire: function(/*inType, inDetail*/) { |
| this.async("fire", arguments); |
| }, |
| |
| /** |
| * Remove class from old, add class to anew, if they exist. |
| * |
| * @param classFollows |
| * @param anew A node. |
| * @param old A node |
| * @param className |
| */ |
| classFollows: function(anew, old, className) { |
| if (old) { |
| old.classList.remove(className); |
| } |
| if (anew) { |
| anew.classList.add(className); |
| } |
| }, |
| |
| /** |
| * Inject HTML which contains markup bound to this element into |
| * a target element (replacing target element content). |
| * |
| * @param String html to inject |
| * @param Element target element |
| */ |
| injectBoundHTML: function(html, element) { |
| var template = document.createElement('template'); |
| template.innerHTML = html; |
| var fragment = this.instanceTemplate(template); |
| if (element) { |
| element.textContent = ''; |
| element.appendChild(fragment); |
| } |
| return fragment; |
| } |
| }; |
| |
| // no-operation function for handy stubs |
| var nop = function() {}; |
| |
| // null-object for handy stubs |
| var nob = {}; |
| |
| // deprecated |
| |
| utils.asyncMethod = utils.async; |
| |
| // exports |
| |
| scope.api.instance.utils = utils; |
| scope.nop = nop; |
| scope.nob = nob; |
| |
| })(Polymer); |
| |
| (function(scope) { |
| |
| // imports |
| |
| var log = window.WebComponents ? WebComponents.flags.log : {}; |
| var EVENT_PREFIX = 'on-'; |
| |
| // instance events api |
| var events = { |
| // read-only |
| EVENT_PREFIX: EVENT_PREFIX, |
| // event listeners on host |
| addHostListeners: function() { |
| var events = this.eventDelegates; |
| log.events && (Object.keys(events).length > 0) && console.log('[%s] addHostListeners:', this.localName, events); |
| // NOTE: host events look like bindings but really are not; |
| // (1) we don't want the attribute to be set and (2) we want to support |
| // multiple event listeners ('host' and 'instance') and Node.bind |
| // by default supports 1 thing being bound. |
| for (var type in events) { |
| var methodName = events[type]; |
| PolymerGestures.addEventListener(this, type, this.element.getEventHandler(this, this, methodName)); |
| } |
| }, |
| // call 'method' or function method on 'obj' with 'args', if the method exists |
| dispatchMethod: function(obj, method, args) { |
| if (obj) { |
| log.events && console.group('[%s] dispatch [%s]', obj.localName, method); |
| var fn = typeof method === 'function' ? method : obj[method]; |
| if (fn) { |
| fn[args ? 'apply' : 'call'](obj, args); |
| } |
| log.events && console.groupEnd(); |
| // NOTE: dirty check right after calling method to ensure |
| // changes apply quickly; in a very complicated app using high |
| // frequency events, this can be a perf concern; in this case, |
| // imperative handlers can be used to avoid flushing. |
| Polymer.flush(); |
| } |
| } |
| }; |
| |
| // exports |
| |
| scope.api.instance.events = events; |
| |
| /** |
| * @class Polymer |
| */ |
| |
| /** |
| * Add a gesture aware event handler to the given `node`. Can be used |
| * in place of `element.addEventListener` and ensures gestures will function |
| * as expected on mobile platforms. Please note that Polymer's declarative |
| * event handlers include this functionality by default. |
| * |
| * @method addEventListener |
| * @param {Node} node node on which to listen |
| * @param {String} eventType name of the event |
| * @param {Function} handlerFn event handler function |
| * @param {Boolean} capture set to true to invoke event capturing |
| * @type Function |
| */ |
| // alias PolymerGestures event listener logic |
| scope.addEventListener = function(node, eventType, handlerFn, capture) { |
| PolymerGestures.addEventListener(wrap(node), eventType, handlerFn, capture); |
| }; |
| |
| /** |
| * Remove a gesture aware event handler on the given `node`. To remove an |
| * event listener, the exact same arguments are required that were passed |
| * to `Polymer.addEventListener`. |
| * |
| * @method removeEventListener |
| * @param {Node} node node on which to listen |
| * @param {String} eventType name of the event |
| * @param {Function} handlerFn event handler function |
| * @param {Boolean} capture set to true to invoke event capturing |
| * @type Function |
| */ |
| scope.removeEventListener = function(node, eventType, handlerFn, capture) { |
| PolymerGestures.removeEventListener(wrap(node), eventType, handlerFn, capture); |
| }; |
| |
| })(Polymer); |
| |
| (function(scope) { |
| |
| // instance api for attributes |
| |
| var attributes = { |
| // copy attributes defined in the element declaration to the instance |
| // e.g. <polymer-element name="x-foo" tabIndex="0"> tabIndex is copied |
| // to the element instance here. |
| copyInstanceAttributes: function () { |
| var a$ = this._instanceAttributes; |
| for (var k in a$) { |
| if (!this.hasAttribute(k)) { |
| this.setAttribute(k, a$[k]); |
| } |
| } |
| }, |
| // for each attribute on this, deserialize value to property as needed |
| takeAttributes: function() { |
| // if we have no publish lookup table, we have no attributes to take |
| // TODO(sjmiles): ad hoc |
| if (this._publishLC) { |
| for (var i=0, a$=this.attributes, l=a$.length, a; (a=a$[i]) && i<l; i++) { |
| this.attributeToProperty(a.name, a.value); |
| } |
| } |
| }, |
| // if attribute 'name' is mapped to a property, deserialize |
| // 'value' into that property |
| attributeToProperty: function(name, value) { |
| // try to match this attribute to a property (attributes are |
| // all lower-case, so this is case-insensitive search) |
| var name = this.propertyForAttribute(name); |
| if (name) { |
| // filter out 'mustached' values, these are to be |
| // replaced with bound-data and are not yet values |
| // themselves |
| if (value && value.search(scope.bindPattern) >= 0) { |
| return; |
| } |
| // get original value |
| var currentValue = this[name]; |
| // deserialize Boolean or Number values from attribute |
| var value = this.deserializeValue(value, currentValue); |
| // only act if the value has changed |
| if (value !== currentValue) { |
| // install new value (has side-effects) |
| this[name] = value; |
| } |
| } |
| }, |
| // return the published property matching name, or undefined |
| propertyForAttribute: function(name) { |
| var match = this._publishLC && this._publishLC[name]; |
| return match; |
| }, |
| // convert representation of `stringValue` based on type of `currentValue` |
| deserializeValue: function(stringValue, currentValue) { |
| return scope.deserializeValue(stringValue, currentValue); |
| }, |
| // convert to a string value based on the type of `inferredType` |
| serializeValue: function(value, inferredType) { |
| if (inferredType === 'boolean') { |
| return value ? '' : undefined; |
| } else if (inferredType !== 'object' && inferredType !== 'function' |
| && value !== undefined) { |
| return value; |
| } |
| }, |
| // serializes `name` property value and updates the corresponding attribute |
| // note that reflection is opt-in. |
| reflectPropertyToAttribute: function(name) { |
| var inferredType = typeof this[name]; |
| // try to intelligently serialize property value |
| var serializedValue = this.serializeValue(this[name], inferredType); |
| // boolean properties must reflect as boolean attributes |
| if (serializedValue !== undefined) { |
| this.setAttribute(name, serializedValue); |
| // TODO(sorvell): we should remove attr for all properties |
| // that have undefined serialization; however, we will need to |
| // refine the attr reflection system to achieve this; pica, for example, |
| // relies on having inferredType object properties not removed as |
| // attrs. |
| } else if (inferredType === 'boolean') { |
| this.removeAttribute(name); |
| } |
| } |
| }; |
| |
| // exports |
| |
| scope.api.instance.attributes = attributes; |
| |
| })(Polymer); |
| |
| (function(scope) { |
| |
| /** |
| * @class polymer-base |
| */ |
| |
| // imports |
| |
| var log = window.WebComponents ? WebComponents.flags.log : {}; |
| |
| // magic words |
| |
| var OBSERVE_SUFFIX = 'Changed'; |
| |
| // element api |
| |
| var empty = []; |
| |
| var updateRecord = { |
| object: undefined, |
| type: 'update', |
| name: undefined, |
| oldValue: undefined |
| }; |
| |
| var numberIsNaN = Number.isNaN || function(value) { |
| return typeof value === 'number' && isNaN(value); |
| }; |
| |
| function areSameValue(left, right) { |
| if (left === right) |
| return left !== 0 || 1 / left === 1 / right; |
| if (numberIsNaN(left) && numberIsNaN(right)) |
| return true; |
| return left !== left && right !== right; |
| } |
| |
| // capture A's value if B's value is null or undefined, |
| // otherwise use B's value |
| function resolveBindingValue(oldValue, value) { |
| if (value === undefined && oldValue === null) { |
| return value; |
| } |
| return (value === null || value === undefined) ? oldValue : value; |
| } |
| |
| var properties = { |
| |
| // creates a CompoundObserver to observe property changes |
| // NOTE, this is only done there are any properties in the `observe` object |
| createPropertyObserver: function() { |
| var n$ = this._observeNames; |
| if (n$ && n$.length) { |
| var o = this._propertyObserver = new CompoundObserver(true); |
| this.registerObserver(o); |
| // TODO(sorvell): may not be kosher to access the value here (this[n]); |
| // previously we looked at the descriptor on the prototype |
| // this doesn't work for inheritance and not for accessors without |
| // a value property |
| for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) { |
| o.addPath(this, n); |
| this.observeArrayValue(n, this[n], null); |
| } |
| } |
| }, |
| |
| // start observing property changes |
| openPropertyObserver: function() { |
| if (this._propertyObserver) { |
| this._propertyObserver.open(this.notifyPropertyChanges, this); |
| } |
| }, |
| |
| // handler for property changes; routes changes to observing methods |
| // note: array valued properties are observed for array splices |
| notifyPropertyChanges: function(newValues, oldValues, paths) { |
| var name, method, called = {}; |
| for (var i in oldValues) { |
| // note: paths is of form [object, path, object, path] |
| name = paths[2 * i + 1]; |
| method = this.observe[name]; |
| if (method) { |
| var ov = oldValues[i], nv = newValues[i]; |
| // observes the value if it is an array |
| this.observeArrayValue(name, nv, ov); |
| if (!called[method]) { |
| // only invoke change method if one of ov or nv is not (undefined | null) |
| if ((ov !== undefined && ov !== null) || (nv !== undefined && nv !== null)) { |
| called[method] = true; |
| // TODO(sorvell): call method with the set of values it's expecting; |
| // e.g. 'foo bar': 'invalidate' expects the new and old values for |
| // foo and bar. Currently we give only one of these and then |
| // deliver all the arguments. |
| this.invokeMethod(method, [ov, nv, arguments]); |
| } |
| } |
| } |
| } |
| }, |
| |
| // call method iff it exists. |
| invokeMethod: function(method, args) { |
| var fn = this[method] || method; |
| if (typeof fn === 'function') { |
| fn.apply(this, args); |
| } |
| }, |
| |
| /** |
| * Force any pending property changes to synchronously deliver to |
| * handlers specified in the `observe` object. |
| * Note, normally changes are processed at microtask time. |
| * |
| * @method deliverChanges |
| */ |
| deliverChanges: function() { |
| if (this._propertyObserver) { |
| this._propertyObserver.deliver(); |
| } |
| }, |
| |
| observeArrayValue: function(name, value, old) { |
| // we only care if there are registered side-effects |
| var callbackName = this.observe[name]; |
| if (callbackName) { |
| // if we are observing the previous value, stop |
| if (Array.isArray(old)) { |
| log.observe && console.log('[%s] observeArrayValue: unregister observer [%s]', this.localName, name); |
| this.closeNamedObserver(name + '__array'); |
| } |
| // if the new value is an array, being observing it |
| if (Array.isArray(value)) { |
| log.observe && console.log('[%s] observeArrayValue: register observer [%s]', this.localName, name, value); |
| var observer = new ArrayObserver(value); |
| observer.open(function(splices) { |
| this.invokeMethod(callbackName, [splices]); |
| }, this); |
| this.registerNamedObserver(name + '__array', observer); |
| } |
| } |
| }, |
| |
| emitPropertyChangeRecord: function(name, value, oldValue) { |
| var object = this; |
| if (areSameValue(value, oldValue)) { |
| return; |
| } |
| // invoke property change side effects |
| this._propertyChanged(name, value, oldValue); |
| // emit change record |
| if (!Observer.hasObjectObserve) { |
| return; |
| } |
| var notifier = this._objectNotifier; |
| if (!notifier) { |
| notifier = this._objectNotifier = Object.getNotifier(this); |
| } |
| updateRecord.object = this; |
| updateRecord.name = name; |
| updateRecord.oldValue = oldValue; |
| notifier.notify(updateRecord); |
| }, |
| |
| _propertyChanged: function(name, value, oldValue) { |
| if (this.reflect[name]) { |
| this.reflectPropertyToAttribute(name); |
| } |
| }, |
| |
| // creates a property binding (called via bind) to a published property. |
| bindProperty: function(property, observable, oneTime) { |
| if (oneTime) { |
| this[property] = observable; |
| return; |
| } |
| var computed = this.element.prototype.computed; |
| // Binding an "out-only" value to a computed property. Note that |
| // since this observer isn't opened, it doesn't need to be closed on |
| // cleanup. |
| if (computed && computed[property]) { |
| var privateComputedBoundValue = property + 'ComputedBoundObservable_'; |
| this[privateComputedBoundValue] = observable; |
| return; |
| } |
| return this.bindToAccessor(property, observable, resolveBindingValue); |
| }, |
| |
| // NOTE property `name` must be published. This makes it an accessor. |
| bindToAccessor: function(name, observable, resolveFn) { |
| var privateName = name + '_'; |
| var privateObservable = name + 'Observable_'; |
| // Present for properties which are computed and published and have a |
| // bound value. |
| var privateComputedBoundValue = name + 'ComputedBoundObservable_'; |
| this[privateObservable] = observable; |
| var oldValue = this[privateName]; |
| // observable callback |
| var self = this; |
| function updateValue(value, oldValue) { |
| self[privateName] = value; |
| var setObserveable = self[privateComputedBoundValue]; |
| if (setObserveable && typeof setObserveable.setValue == 'function') { |
| setObserveable.setValue(value); |
| } |
| self.emitPropertyChangeRecord(name, value, oldValue); |
| } |
| // resolve initial value |
| var value = observable.open(updateValue); |
| if (resolveFn && !areSameValue(oldValue, value)) { |
| var resolvedValue = resolveFn(oldValue, value); |
| if (!areSameValue(value, resolvedValue)) { |
| value = resolvedValue; |
| if (observable.setValue) { |
| observable.setValue(value); |
| } |
| } |
| } |
| updateValue(value, oldValue); |
| // register and return observable |
| var observer = { |
| close: function() { |
| observable.close(); |
| self[privateObservable] = undefined; |
| self[privateComputedBoundValue] = undefined; |
| } |
| }; |
| this.registerObserver(observer); |
| return observer; |
| }, |
| |
| createComputedProperties: function() { |
| if (!this._computedNames) { |
| return; |
| } |
| for (var i = 0; i < this._computedNames.length; i++) { |
| var name = this._computedNames[i]; |
| var expressionText = this.computed[name]; |
| try { |
| var expression = PolymerExpressions.getExpression(expressionText); |
| var observable = expression.getBinding(this, this.element.syntax); |
| this.bindToAccessor(name, observable); |
| } catch (ex) { |
| console.error('Failed to create computed property', ex); |
| } |
| } |
| }, |
| |
| // property bookkeeping |
| registerObserver: function(observer) { |
| if (!this._observers) { |
| this._observers = [observer]; |
| return; |
| } |
| this._observers.push(observer); |
| }, |
| |
| closeObservers: function() { |
| if (!this._observers) { |
| return; |
| } |
| // observer array items are arrays of observers. |
| var observers = this._observers; |
| for (var i = 0; i < observers.length; i++) { |
| var observer = observers[i]; |
| if (observer && typeof observer.close == 'function') { |
| observer.close(); |
| } |
| } |
| this._observers = []; |
| }, |
| |
| // bookkeeping observers for memory management |
| registerNamedObserver: function(name, observer) { |
| var o$ = this._namedObservers || (this._namedObservers = {}); |
| o$[name] = observer; |
| }, |
| |
| closeNamedObserver: function(name) { |
| var o$ = this._namedObservers; |
| if (o$ && o$[name]) { |
| o$[name].close(); |
| o$[name] = null; |
| return true; |
| } |
| }, |
| |
| closeNamedObservers: function() { |
| if (this._namedObservers) { |
| for (var i in this._namedObservers) { |
| this.closeNamedObserver(i); |
| } |
| this._namedObservers = {}; |
| } |
| } |
| |
| }; |
| |
| // logging |
| var LOG_OBSERVE = '[%s] watching [%s]'; |
| var LOG_OBSERVED = '[%s#%s] watch: [%s] now [%s] was [%s]'; |
| var LOG_CHANGED = '[%s#%s] propertyChanged: [%s] now [%s] was [%s]'; |
| |
| // exports |
| |
| scope.api.instance.properties = properties; |
| |
| })(Polymer); |
| |
| (function(scope) { |
| |
| /** |
| * @class polymer-base |
| */ |
| |
| // imports |
| |
| var log = window.WebComponents ? WebComponents.flags.log : {}; |
| |
| // element api supporting mdv |
| var mdv = { |
| |
| /** |
| * Creates dom cloned from the given template, instantiating bindings |
| * with this element as the template model and `PolymerExpressions` as the |
| * binding delegate. |
| * |
| * @method instanceTemplate |
| * @param {Template} template source template from which to create dom. |
| */ |
| instanceTemplate: function(template) { |
| // ensure template is decorated (lets' things like <tr template ...> work) |
| HTMLTemplateElement.decorate(template); |
| // ensure a default bindingDelegate |
| var syntax = this.syntax || (!template.bindingDelegate && |
| this.element.syntax); |
| var dom = template.createInstance(this, syntax); |
| var observers = dom.bindings_; |
| for (var i = 0; i < observers.length; i++) { |
| this.registerObserver(observers[i]); |
| } |
| return dom; |
| }, |
| |
| // Called by TemplateBinding/NodeBind to setup a binding to the given |
| // property. It's overridden here to support property bindings |
| // in addition to attribute bindings that are supported by default. |
| bind: function(name, observable, oneTime) { |
| var property = this.propertyForAttribute(name); |
| if (!property) { |
| // TODO(sjmiles): this mixin method must use the special form |
| // of `super` installed by `mixinMethod` in declaration/prototype.js |
| return this.mixinSuper(arguments); |
| } else { |
| // use n-way Polymer binding |
| var observer = this.bindProperty(property, observable, oneTime); |
| // NOTE: reflecting binding information is typically required only for |
| // tooling. It has a performance cost so it's opt-in in Node.bind. |
| if (Platform.enableBindingsReflection && observer) { |
| observer.path = observable.path_; |
| this._recordBinding(property, observer); |
| } |
| if (this.reflect[property]) { |
| this.reflectPropertyToAttribute(property); |
| } |
| return observer; |
| } |
| }, |
| |
| _recordBinding: function(name, observer) { |
| this.bindings_ = this.bindings_ || {}; |
| this.bindings_[name] = observer; |
| }, |
| |
| // Called by TemplateBinding when all bindings on an element have been |
| // executed. This signals that all element inputs have been gathered |
| // and it's safe to ready the element, create shadow-root and start |
| // data-observation. |
| bindFinished: function() { |
| this.makeElementReady(); |
| }, |
| |
| // called at detached time to signal that an element's bindings should be |
| // cleaned up. This is done asynchronously so that users have the chance |
| // to call `cancelUnbindAll` to prevent unbinding. |
| asyncUnbindAll: function() { |
| if (!this._unbound) { |
| log.unbind && console.log('[%s] asyncUnbindAll', this.localName); |
| this._unbindAllJob = this.job(this._unbindAllJob, this.unbindAll, 0); |
| } |
| }, |
| |
| /** |
| * This method should rarely be used and only if |
| * <a href="#cancelUnbindAll">`cancelUnbindAll`</a> has been called to |
| * prevent element unbinding. In this case, the element's bindings will |
| * not be automatically cleaned up and it cannot be garbage collected |
| * by the system. If memory pressure is a concern or a |
| * large amount of elements need to be managed in this way, `unbindAll` |
| * can be called to deactivate the element's bindings and allow its |
| * memory to be reclaimed. |
| * |
| * @method unbindAll |
| */ |
| unbindAll: function() { |
| if (!this._unbound) { |
| this.closeObservers(); |
| this.closeNamedObservers(); |
| this._unbound = true; |
| } |
| }, |
| |
| /** |
| * Call in `detached` to prevent the element from unbinding when it is |
| * detached from the dom. The element is unbound as a cleanup step that |
| * allows its memory to be reclaimed. |
| * If `cancelUnbindAll` is used, consider calling |
| * <a href="#unbindAll">`unbindAll`</a> when the element is no longer |
| * needed. This will allow its memory to be reclaimed. |
| * |
| * @method cancelUnbindAll |
| */ |
| cancelUnbindAll: function() { |
| if (this._unbound) { |
| log.unbind && console.warn('[%s] already unbound, cannot cancel unbindAll', this.localName); |
| return; |
| } |
| log.unbind && console.log('[%s] cancelUnbindAll', this.localName); |
| if (this._unbindAllJob) { |
| this._unbindAllJob = this._unbindAllJob.stop(); |
| } |
| } |
| |
| }; |
| |
| function unbindNodeTree(node) { |
| forNodeTree(node, _nodeUnbindAll); |
| } |
| |
| function _nodeUnbindAll(node) { |
| node.unbindAll(); |
| } |
| |
| function forNodeTree(node, callback) { |
| if (node) { |
| callback(node); |
| for (var child = node.firstChild; child; child = child.nextSibling) { |
| forNodeTree(child, callback); |
| } |
| } |
| } |
| |
| var mustachePattern = /\{\{([^{}]*)}}/; |
| |
| // exports |
| |
| scope.bindPattern = mustachePattern; |
| scope.api.instance.mdv = mdv; |
| |
| })(Polymer); |
| |
| (function(scope) { |
| |
| /** |
| * Common prototype for all Polymer Elements. |
| * |
| * @class polymer-base |
| * @homepage polymer.github.io |
| */ |
| var base = { |
| /** |
| * Tags this object as the canonical Base prototype. |
| * |
| * @property PolymerBase |
| * @type boolean |
| * @default true |
| */ |
| PolymerBase: true, |
| |
| /** |
| * Debounce signals. |
| * |
| * Call `job` to defer a named signal, and all subsequent matching signals, |
| * until a wait time has elapsed with no new signal. |
| * |
| * debouncedClickAction: function(e) { |
| * // processClick only when it's been 100ms since the last click |
| * this.job('click', function() { |
| * this.processClick; |
| * }, 100); |
| * } |
| * |
| * @method job |
| * @param String {String} job A string identifier for the job to debounce. |
| * @param Function {Function} callback A function that is called (with `this` context) when the wait time elapses. |
| * @param Number {Number} wait Time in milliseconds (ms) after the last signal that must elapse before invoking `callback` |
| * @type Handle |
| */ |
| job: function(job, callback, wait) { |
| if (typeof job === 'string') { |
| var n = '___' + job; |
| this[n] = Polymer.job.call(this, this[n], callback, wait); |
| } else { |
| // TODO(sjmiles): suggest we deprecate this call signature |
| return Polymer.job.call(this, job, callback, wait); |
| } |
| }, |
| |
| /** |
| * Invoke a superclass method. |
| * |
| * Use `super()` to invoke the most recently overridden call to the |
| * currently executing function. |
| * |
| * To pass arguments through, use the literal `arguments` as the parameter |
| * to `super()`. |
| * |
| * nextPageAction: function(e) { |
| * // invoke the superclass version of `nextPageAction` |
| * this.super(arguments); |
| * } |
| * |
| * To pass custom arguments, arrange them in an array. |
| * |
| * appendSerialNo: function(value, serial) { |
| * // prefix the superclass serial number with our lot # before |
| * // invoking the superlcass |
| * return this.super([value, this.lotNo + serial]) |
| * } |
| * |
| * @method super |
| * @type Any |
| * @param {args) An array of arguments to use when calling the superclass method, or null. |
| */ |
| super: Polymer.super, |
| |
| /** |
| * Lifecycle method called when the element is instantiated. |
| * |
| * Override `created` to perform custom create-time tasks. No need to call |
| * super-class `created` unless you are extending another Polymer element. |
| * Created is called before the element creates `shadowRoot` or prepares |
| * data-observation. |
| * |
| * @method created |
| * @type void |
| */ |
| created: function() { |
| }, |
| |
| /** |
| * Lifecycle method called when the element has populated it's `shadowRoot`, |
| * prepared data-observation, and made itself ready for API interaction. |
| * |
| * @method ready |
| * @type void |
| */ |
| ready: function() { |
| }, |
| |
| /** |
| * Low-level lifecycle method called as part of standard Custom Elements |
| * operation. Polymer implements this method to provide basic default |
| * functionality. For custom create-time tasks, implement `created` |
| * instead, which is called immediately after `createdCallback`. |
| * |
| * @method createdCallback |
| */ |
| createdCallback: function() { |
| if (this.templateInstance && this.templateInstance.model) { |
| console.warn('Attributes on ' + this.localName + ' were data bound ' + |
| 'prior to Polymer upgrading the element. This may result in ' + |
| 'incorrect binding types.'); |
| } |
| this.created(); |
| this.prepareElement(); |
| if (!this.ownerDocument.isStagingDocument) { |
| this.makeElementReady(); |
| } |
| }, |
| |
| // system entry point, do not override |
| prepareElement: function() { |
| if (this._elementPrepared) { |
| console.warn('Element already prepared', this.localName); |
| return; |
| } |
| this._elementPrepared = true; |
| // storage for shadowRoots info |
| this.shadowRoots = {}; |
| // install property observers |
| this.createPropertyObserver(); |
| this.openPropertyObserver(); |
| // install boilerplate attributes |
| this.copyInstanceAttributes(); |
| // process input attributes |
| this.takeAttributes(); |
| // add event listeners |
| this.addHostListeners(); |
| }, |
| |
| // system entry point, do not override |
| makeElementReady: function() { |
| if (this._readied) { |
| return; |
| } |
| this._readied = true; |
| this.createComputedProperties(); |
| this.parseDeclarations(this.__proto__); |
| // NOTE: Support use of the `unresolved` attribute to help polyfill |
| // custom elements' `:unresolved` feature. |
| this.removeAttribute('unresolved'); |
| // user entry point |
| this.ready(); |
| }, |
| |
| /** |
| * Low-level lifecycle method called as part of standard Custom Elements |
| * operation. Polymer implements this method to provide basic default |
| * functionality. For custom tasks in your element, implement `attributeChanged` |
| * instead, which is called immediately after `attributeChangedCallback`. |
| * |
| * @method attributeChangedCallback |
| */ |
| attributeChangedCallback: function(name, oldValue) { |
| // TODO(sjmiles): adhoc filter |
| if (name !== 'class' && name !== 'style') { |
| this.attributeToProperty(name, this.getAttribute(name)); |
| } |
| if (this.attributeChanged) { |
| this.attributeChanged.apply(this, arguments); |
| } |
| }, |
| |
| /** |
| * Low-level lifecycle method called as part of standard Custom Elements |
| * operation. Polymer implements this method to provide basic default |
| * functionality. For custom create-time tasks, implement `attached` |
| * instead, which is called immediately after `attachedCallback`. |
| * |
| * @method attachedCallback |
| */ |
| attachedCallback: function() { |
| // when the element is attached, prevent it from unbinding. |
| this.cancelUnbindAll(); |
| // invoke user action |
| if (this.attached) { |
| this.attached(); |
| } |
| if (!this.hasBeenAttached) { |
| this.hasBeenAttached = true; |
| if (this.domReady) { |
| this.async('domReady'); |
| } |
| } |
| }, |
| |
| /** |
| * Implement to access custom elements in dom descendants, ancestors, |
| * or siblings. Because custom elements upgrade in document order, |
| * elements accessed in `ready` or `attached` may not be upgraded. When |
| * `domReady` is called, all registered custom elements are guaranteed |
| * to have been upgraded. |
| * |
| * @method domReady |
| */ |
| |
| /** |
| * Low-level lifecycle method called as part of standard Custom Elements |
| * operation. Polymer implements this method to provide basic default |
| * functionality. For custom create-time tasks, implement `detached` |
| * instead, which is called immediately after `detachedCallback`. |
| * |
| * @method detachedCallback |
| */ |
| detachedCallback: function() { |
| if (!this.preventDispose) { |
| this.asyncUnbindAll(); |
| } |
| // invoke user action |
| if (this.detached) { |
| this.detached(); |
| } |
| // TODO(sorvell): bc |
| if (this.leftView) { |
| this.leftView(); |
| } |
| }, |
| |
| /** |
| * Walks the prototype-chain of this element and allows specific |
| * classes a chance to process static declarations. |
| * |
| * In particular, each polymer-element has it's own `template`. |
| * `parseDeclarations` is used to accumulate all element `template`s |
| * from an inheritance chain. |
| * |
| * `parseDeclaration` static methods implemented in the chain are called |
| * recursively, oldest first, with the `<polymer-element>` associated |
| * with the current prototype passed as an argument. |
| * |
| * An element may override this method to customize shadow-root generation. |
| * |
| * @method parseDeclarations |
| */ |
| parseDeclarations: function(p) { |
| if (p && p.element) { |
| this.parseDeclarations(p.__proto__); |
| p.parseDeclaration.call(this, p.element); |
| } |
| }, |
| |
| /** |
| * Perform init-time actions based on static information in the |
| * `<polymer-element>` instance argument. |
| * |
| * For example, the standard implementation locates the template associated |
| * with the given `<polymer-element>` and stamps it into a shadow-root to |
| * implement shadow inheritance. |
| * |
| * An element may override this method for custom behavior. |
| * |
| * @method parseDeclaration |
| */ |
| parseDeclaration: function(elementElement) { |
| var template = this.fetchTemplate(elementElement); |
| if (template) { |
| var root = this.shadowFromTemplate(template); |
| this.shadowRoots[elementElement.name] = root; |
| } |
| }, |
| |
| /** |
| * Given a `<polymer-element>`, find an associated template (if any) to be |
| * used for shadow-root generation. |
| * |
| * An element may override this method for custom behavior. |
| * |
| * @method fetchTemplate |
| */ |
| fetchTemplate: function(elementElement) { |
| return elementElement.querySelector('template'); |
| }, |
| |
| /** |
| * Create a shadow-root in this host and stamp `template` as it's |
| * content. |
| * |
| * An element may override this method for custom behavior. |
| * |
| * @method shadowFromTemplate |
| */ |
| shadowFromTemplate: function(template) { |
| if (template) { |
| // make a shadow root |
| var root = this.createShadowRoot(); |
| // stamp template |
| // which includes parsing and applying MDV bindings before being |
| // inserted (to avoid {{}} in attribute values) |
| // e.g. to prevent <img src="images/{{icon}}"> from generating a 404. |
| var dom = this.instanceTemplate(template); |
| // append to shadow dom |
| root.appendChild(dom); |
| // perform post-construction initialization tasks on shadow root |
| this.shadowRootReady(root, template); |
| // return the created shadow root |
| return root; |
| } |
| }, |
| |
| // utility function that stamps a <template> into light-dom |
| lightFromTemplate: function(template, refNode) { |
| if (template) { |
| // TODO(sorvell): mark this element as an eventController so that |
| // event listeners on bound nodes inside it will be called on it. |
| // Note, the expectation here is that events on all descendants |
| // should be handled by this element. |
| this.eventController = this; |
| // stamp template |
| // which includes parsing and applying MDV bindings before being |
| // inserted (to avoid {{}} in attribute values) |
| // e.g. to prevent <img src="images/{{icon}}"> from generating a 404. |
| var dom = this.instanceTemplate(template); |
| // append to shadow dom |
| if (refNode) { |
| this.insertBefore(dom, refNode); |
| } else { |
| this.appendChild(dom); |
| } |
| // perform post-construction initialization tasks on ahem, light root |
| this.shadowRootReady(this); |
| // return the created shadow root |
| return dom; |
| } |
| }, |
| |
| shadowRootReady: function(root) { |
| // locate nodes with id and store references to them in this.$ hash |
| this.marshalNodeReferences(root); |
| }, |
| |
| // locate nodes with id and store references to them in this.$ hash |
| marshalNodeReferences: function(root) { |
| // establish $ instance variable |
| var $ = this.$ = this.$ || {}; |
| // populate $ from nodes with ID from the LOCAL tree |
| if (root) { |
| var n$ = root.querySelectorAll("[id]"); |
| for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) { |
| $[n.id] = n; |
| }; |
| } |
| }, |
| |
| /** |
| * Register a one-time callback when a child-list or sub-tree mutation |
| * occurs on node. |
| * |
| * For persistent callbacks, call onMutation from your listener. |
| * |
| * @method onMutation |
| * @param Node {Node} node Node to watch for mutations. |
| * @param Function {Function} listener Function to call on mutation. The function is invoked as `listener.call(this, observer, mutations);` where `observer` is the MutationObserver that triggered the notification, and `mutations` is the native mutation list. |
| */ |
| onMutation: function(node, listener) { |
| var observer = new MutationObserver(function(mutations) { |
| listener.call(this, observer, mutations); |
| observer.disconnect(); |
| }.bind(this)); |
| observer.observe(node, {childList: true, subtree: true}); |
| } |
| }; |
| |
| /** |
| * @class Polymer |
| */ |
| |
| /** |
| * Returns true if the object includes <a href="#polymer-base">polymer-base</a> in it's prototype chain. |
| * |
| * @method isBase |
| * @param Object {Object} object Object to test. |
| * @type Boolean |
| */ |
| function isBase(object) { |
| return object.hasOwnProperty('PolymerBase') |
| } |
| |
| // name a base constructor for dev tools |
| |
| /** |
| * The Polymer base-class constructor. |
| * |
| * @property Base |
| * @type Function |
| */ |
| function PolymerBase() {}; |
| PolymerBase.prototype = base; |
| base.constructor = PolymerBase; |
| |
| // exports |
| |
| scope.Base = PolymerBase; |
| scope.isBase = isBase; |
| scope.api.instance.base = base; |
| |
| })(Polymer); |
| |
| (function(scope) { |
| |
| // imports |
| |
| var log = window.WebComponents ? WebComponents.flags.log : {}; |
| var hasShadowDOMPolyfill = window.ShadowDOMPolyfill; |
| |
| // magic words |
| |
| var STYLE_SCOPE_ATTRIBUTE = 'element'; |
| var STYLE_CONTROLLER_SCOPE = 'controller'; |
| |
| var styles = { |
| STYLE_SCOPE_ATTRIBUTE: STYLE_SCOPE_ATTRIBUTE, |
| /** |
| * Installs external stylesheets and <style> elements with the attribute |
| * polymer-scope='controller' into the scope of element. This is intended |
| * to be a called during custom element construction. |
| */ |
| installControllerStyles: function() { |
| // apply controller styles, but only if they are not yet applied |
| var scope = this.findStyleScope(); |
| if (scope && !this.scopeHasNamedStyle(scope, this.localName)) { |
| // allow inherited controller styles |
| var proto = getPrototypeOf(this), cssText = ''; |
| while (proto && proto.element) { |
| cssText += proto.element.cssTextForScope(STYLE_CONTROLLER_SCOPE); |
| proto = getPrototypeOf(proto); |
| } |
| if (cssText) { |
| this.installScopeCssText(cssText, scope); |
| } |
| } |
| }, |
| installScopeStyle: function(style, name, scope) { |
| var scope = scope || this.findStyleScope(), name = name || ''; |
| if (scope && !this.scopeHasNamedStyle(scope, this.localName + name)) { |
| var cssText = ''; |
| if (style instanceof Array) { |
| for (var i=0, l=style.length, s; (i<l) && (s=style[i]); i++) { |
| cssText += s.textContent + '\n\n'; |
| } |
| } else { |
| cssText = style.textContent; |
| } |
| this.installScopeCssText(cssText, scope, name); |
| } |
| }, |
| installScopeCssText: function(cssText, scope, name) { |
| scope = scope || this.findStyleScope(); |
| name = name || ''; |
| if (!scope) { |
| return; |
| } |
| if (hasShadowDOMPolyfill) { |
| cssText = shimCssText(cssText, scope.host); |
| } |
| var style = this.element.cssTextToScopeStyle(cssText, |
| STYLE_CONTROLLER_SCOPE); |
| Polymer.applyStyleToScope(style, scope); |
| // cache that this style has been applied |
| this.styleCacheForScope(scope)[this.localName + name] = true; |
| }, |
| findStyleScope: function(node) { |
| // find the shadow root that contains this element |
| var n = node || this; |
| while (n.parentNode) { |
| n = n.parentNode; |
| } |
| return n; |
| }, |
| scopeHasNamedStyle: function(scope, name) { |
| var cache = this.styleCacheForScope(scope); |
| return cache[name]; |
| }, |
| styleCacheForScope: function(scope) { |
| if (hasShadowDOMPolyfill) { |
| var scopeName = scope.host ? scope.host.localName : scope.localName; |
| return polyfillScopeStyleCache[scopeName] || (polyfillScopeStyleCache[scopeName] = {}); |
| } else { |
| return scope._scopeStyles = (scope._scopeStyles || {}); |
| } |
| } |
| }; |
| |
| var polyfillScopeStyleCache = {}; |
| |
| // NOTE: use raw prototype traversal so that we ensure correct traversal |
| // on platforms where the protoype chain is simulated via __proto__ (IE10) |
| function getPrototypeOf(prototype) { |
| return prototype.__proto__; |
| } |
| |
| function shimCssText(cssText, host) { |
| var name = '', is = false; |
| if (host) { |
| name = host.localName; |
| is = host.hasAttribute('is'); |
| } |
| var selector = WebComponents.ShadowCSS.makeScopeSelector(name, is); |
| return WebComponents.ShadowCSS.shimCssText(cssText, selector); |
| } |
| |
| // exports |
| |
| scope.api.instance.styles = styles; |
| |
| })(Polymer); |
| |
| (function(scope) { |
| |
| // imports |
| |
| var extend = scope.extend; |
| var api = scope.api; |
| |
| // imperative implementation: Polymer() |
| |
| // specify an 'own' prototype for tag `name` |
| function element(name, prototype) { |
| if (typeof name !== 'string') { |
| var script = prototype || document._currentScript; |
| prototype = name; |
| name = script && script.parentNode && script.parentNode.getAttribute ? |
| script.parentNode.getAttribute('name') : ''; |
| if (!name) { |
| throw 'Element name could not be inferred.'; |
| } |
| } |
| if (getRegisteredPrototype(name)) { |
| throw 'Already registered (Polymer) prototype for element ' + name; |
| } |
| // cache the prototype |
| registerPrototype(name, prototype); |
| // notify the registrar waiting for 'name', if any |
| notifyPrototype(name); |
| } |
| |
| // async prototype source |
| |
| function waitingForPrototype(name, client) { |
| waitPrototype[name] = client; |
| } |
| |
| var waitPrototype = {}; |
| |
| function notifyPrototype(name) { |
| if (waitPrototype[name]) { |
| waitPrototype[name].registerWhenReady(); |
| delete waitPrototype[name]; |
| } |
| } |
| |
| // utility and bookkeeping |
| |
| // maps tag names to prototypes, as registered with |
| // Polymer. Prototypes associated with a tag name |
| // using document.registerElement are available from |
| // HTMLElement.getPrototypeForTag(). |
| // If an element was fully registered by Polymer, then |
| // Polymer.getRegisteredPrototype(name) === |
| // HTMLElement.getPrototypeForTag(name) |
| |
| var prototypesByName = {}; |
| |
| function registerPrototype(name, prototype) { |
| return prototypesByName[name] = prototype || {}; |
| } |
| |
| function getRegisteredPrototype(name) { |
| return prototypesByName[name]; |
| } |
| |
| function instanceOfType(element, type) { |
| if (typeof type !== 'string') { |
| return false; |
| } |
| var proto = HTMLElement.getPrototypeForTag(type); |
| var ctor = proto && proto.constructor; |
| if (!ctor) { |
| return false; |
| } |
| if (CustomElements.instanceof) { |
| return CustomElements.instanceof(element, ctor); |
| } |
| return element instanceof ctor; |
| } |
| |
| // exports |
| |
| scope.getRegisteredPrototype = getRegisteredPrototype; |
| scope.waitingForPrototype = waitingForPrototype; |
| scope.instanceOfType = instanceOfType; |
| |
| // namespace shenanigans so we can expose our scope on the registration |
| // function |
| |
| // make window.Polymer reference `element()` |
| |
| window.Polymer = element; |
| |
| // TODO(sjmiles): find a way to do this that is less terrible |
| // copy window.Polymer properties onto `element()` |
| |
| extend(Polymer, scope); |
| |
| // Under the HTMLImports polyfill, scripts in the main document |
| // do not block on imports; we want to allow calls to Polymer in the main |
| // document. WebComponents collects those calls until we can process them, which |
| // we do here. |
| |
| if (WebComponents.consumeDeclarations) { |
| WebComponents.consumeDeclarations(function(declarations) { |
| if (declarations) { |
| for (var i=0, l=declarations.length, d; (i<l) && (d=declarations[i]); i++) { |
| element.apply(null, d); |
| } |
| } |
| }); |
| } |
| |
| })(Polymer); |
| |
| (function(scope) { |
| |
| /** |
| * @class polymer-base |
| */ |
| |
| /** |
| * Resolve a url path to be relative to a `base` url. If unspecified, `base` |
| * defaults to the element's ownerDocument url. Can be used to resolve |
| * paths from element's in templates loaded in HTMLImports to be relative |
| * to the document containing the element. Polymer automatically does this for |
| * url attributes in element templates; however, if a url, for |
| * example, contains a binding, then `resolvePath` can be used to ensure it is |
| * relative to the element document. For example, in an element's template, |
| * |
| * <a href="{{resolvePath(path)}}">Resolved</a> |
| * |
| * @method resolvePath |
| * @param {String} url Url path to resolve. |
| * @param {String} base Optional base url against which to resolve, defaults |
| * to the element's ownerDocument url. |
| * returns {String} resolved url. |
| */ |
| |
| var path = { |
| resolveElementPaths: function(node) { |
| Polymer.urlResolver.resolveDom(node); |
| }, |
| addResolvePathApi: function() { |
| // let assetpath attribute modify the resolve path |
| var assetPath = this.getAttribute('assetpath') || ''; |
| var root = new URL(assetPath, this.ownerDocument.baseURI); |
| this.prototype.resolvePath = function(urlPath, base) { |
| var u = new URL(urlPath, base || root); |
| return u.href; |
| }; |
| } |
| }; |
| |
| // exports |
| scope.api.declaration.path = path; |
| |
| })(Polymer); |
| |
| (function(scope) { |
| |
| // imports |
| |
| var log = window.WebComponents ? WebComponents.flags.log : {}; |
| var api = scope.api.instance.styles; |
| var STYLE_SCOPE_ATTRIBUTE = api.STYLE_SCOPE_ATTRIBUTE; |
| |
| var hasShadowDOMPolyfill = window.ShadowDOMPolyfill; |
| |
| // magic words |
| |
| var STYLE_SELECTOR = 'style'; |
| var STYLE_LOADABLE_MATCH = '@import'; |
| var SHEET_SELECTOR = 'link[rel=stylesheet]'; |
| var STYLE_GLOBAL_SCOPE = 'global'; |
| var SCOPE_ATTR = 'polymer-scope'; |
| |
| var styles = { |
| // returns true if resources are loading |
| loadStyles: function(callback) { |
| var template = this.fetchTemplate(); |
| var content = template && this.templateContent(); |
| if (content) { |
| this.convertSheetsToStyles(content); |
| var styles = this.findLoadableStyles(content); |
| if (styles.length) { |
| var templateUrl = template.ownerDocument.baseURI; |
| return Polymer.styleResolver.loadStyles(styles, templateUrl, callback); |
| } |
| } |
| if (callback) { |
| callback(); |
| } |
| }, |
| convertSheetsToStyles: function(root) { |
| var s$ = root.querySelectorAll(SHEET_SELECTOR); |
| for (var i=0, l=s$.length, s, c; (i<l) && (s=s$[i]); i++) { |
| c = createStyleElement(importRuleForSheet(s, this.ownerDocument.baseURI), |
| this.ownerDocument); |
| this.copySheetAttributes(c, s); |
| s.parentNode.replaceChild(c, s); |
| } |
| }, |
| copySheetAttributes: function(style, link) { |
| for (var i=0, a$=link.attributes, l=a$.length, a; (a=a$[i]) && i<l; i++) { |
| if (a.name !== 'rel' && a.name !== 'href') { |
| style.setAttribute(a.name, a.value); |
| } |
| } |
| }, |
| findLoadableStyles: function(root) { |
| var loadables = []; |
| if (root) { |
| var s$ = root.querySelectorAll(STYLE_SELECTOR); |
| for (var i=0, l=s$.length, s; (i<l) && (s=s$[i]); i++) { |
| if (s.textContent.match(STYLE_LOADABLE_MATCH)) { |
| loadables.push(s); |
| } |
| } |
| } |
| return loadables; |
| }, |
| /** |
| * Install external stylesheets loaded in <polymer-element> elements into the |
| * element's template. |
| * @param elementElement The <element> element to style. |
| */ |
| installSheets: function() { |
| this.cacheSheets(); |
| this.cacheStyles(); |
| this.installLocalSheets(); |
| this.installGlobalStyles(); |
| }, |
| /** |
| * Remove all sheets from element and store for later use. |
| */ |
| cacheSheets: function() { |
| this.sheets = this.findNodes(SHEET_SELECTOR); |
| this.sheets.forEach(function(s) { |
| if (s.parentNode) { |
| s.parentNode.removeChild(s); |
| } |
| }); |
| }, |
| cacheStyles: function() { |
| this.styles = this.findNodes(STYLE_SELECTOR + '[' + SCOPE_ATTR + ']'); |
| this.styles.forEach(function(s) { |
| if (s.parentNode) { |
| s.parentNode.removeChild(s); |
| } |
| }); |
| }, |
| /** |
| * Takes external stylesheets loaded in an <element> element and moves |
| * their content into a <style> element inside the <element>'s template. |
| * The sheet is then removed from the <element>. This is done only so |
| * that if the element is loaded in the main document, the sheet does |
| * not become active. |
| * Note, ignores sheets with the attribute 'polymer-scope'. |
| * @param elementElement The <element> element to style. |
| */ |
| installLocalSheets: function () { |
| var sheets = this.sheets.filter(function(s) { |
| return !s.hasAttribute(SCOPE_ATTR); |
| }); |
| var content = this.templateContent(); |
| if (content) { |
| var cssText = ''; |
| sheets.forEach(function(sheet) { |
| cssText += cssTextFromSheet(sheet) + '\n'; |
| }); |
| if (cssText) { |
| var style = createStyleElement(cssText, this.ownerDocument); |
| content.insertBefore(style, content.firstChild); |
| } |
| } |
| }, |
| findNodes: function(selector, matcher) { |
| var nodes = this.querySelectorAll(selector).array(); |
| var content = this.templateContent(); |
| if (content) { |
| var templateNodes = content.querySelectorAll(selector).array(); |
| nodes = nodes.concat(templateNodes); |
| } |
| return matcher ? nodes.filter(matcher) : nodes; |
| }, |
| /** |
| * Promotes external stylesheets and <style> elements with the attribute |
| * polymer-scope='global' into global scope. |
| * This is particularly useful for defining @keyframe rules which |
| * currently do not function in scoped or shadow style elements. |
| * (See wkb.ug/72462) |
| * @param elementElement The <element> element to style. |
| */ |
| // TODO(sorvell): remove when wkb.ug/72462 is addressed. |
| installGlobalStyles: function() { |
| var style = this.styleForScope(STYLE_GLOBAL_SCOPE); |
| applyStyleToScope(style, document.head); |
| }, |
| cssTextForScope: function(scopeDescriptor) { |
| var cssText = ''; |
| // handle stylesheets |
| var selector = '[' + SCOPE_ATTR + '=' + scopeDescriptor + ']'; |
| var matcher = function(s) { |
| return matchesSelector(s, selector); |
| }; |
| var sheets = this.sheets.filter(matcher); |
| sheets.forEach(function(sheet) { |
| cssText += cssTextFromSheet(sheet) + '\n\n'; |
| }); |
| // handle cached style elements |
| var styles = this.styles.filter(matcher); |
| styles.forEach(function(style) { |
| cssText += style.textContent + '\n\n'; |
| }); |
| return cssText; |
| }, |
| styleForScope: function(scopeDescriptor) { |
| var cssText = this.cssTextForScope(scopeDescriptor); |
| return this.cssTextToScopeStyle(cssText, scopeDescriptor); |
| }, |
| cssTextToScopeStyle: function(cssText, scopeDescriptor) { |
| if (cssText) { |
| var style = createStyleElement(cssText); |
| style.setAttribute(STYLE_SCOPE_ATTRIBUTE, this.getAttribute('name') + |
| '-' + scopeDescriptor); |
| return style; |
| } |
| } |
| }; |
| |
| function importRuleForSheet(sheet, baseUrl) { |
| var href = new URL(sheet.getAttribute('href'), baseUrl).href; |
| return '@import \'' + href + '\';'; |
| } |
| |
| function applyStyleToScope(style, scope) { |
| if (style) { |
| if (scope === document) { |
| scope = document.head; |
| } |
| if (hasShadowDOMPolyfill) { |
| scope = document.head; |
| } |
| // TODO(sorvell): necessary for IE |
| // see https://connect.microsoft.com/IE/feedback/details/790212/ |
| // cloning-a-style-element-and-adding-to-document-produces |
| // -unexpected-result#details |
| // var clone = style.cloneNode(true); |
| var clone = createStyleElement(style.textContent); |
| var attr = style.getAttribute(STYLE_SCOPE_ATTRIBUTE); |
| if (attr) { |
| clone.setAttribute(STYLE_SCOPE_ATTRIBUTE, attr); |
| } |
| // TODO(sorvell): probably too brittle; try to figure out |
| // where to put the element. |
| var refNode = scope.firstElementChild; |
| if (scope === document.head) { |
| var selector = 'style[' + STYLE_SCOPE_ATTRIBUTE + ']'; |
| var s$ = document.head.querySelectorAll(selector); |
| if (s$.length) { |
| refNode = s$[s$.length-1].nextElementSibling; |
| } |
| } |
| scope.insertBefore(clone, refNode); |
| } |
| } |
| |
| function createStyleElement(cssText, scope) { |
| scope = scope || document; |
| scope = scope.createElement ? scope : scope.ownerDocument; |
| var style = scope.createElement('style'); |
| style.textContent = cssText; |
| return style; |
| } |
| |
| function cssTextFromSheet(sheet) { |
| return (sheet && sheet.__resource) || ''; |
| } |
| |
| function matchesSelector(node, inSelector) { |
| if (matches) { |
| return matches.call(node, inSelector); |
| } |
| } |
| var p = HTMLElement.prototype; |
| var matches = p.matches || p.matchesSelector || p.webkitMatchesSelector |
| || p.mozMatchesSelector; |
| |
| // exports |
| |
| scope.api.declaration.styles = styles; |
| scope.applyStyleToScope = applyStyleToScope; |
| |
| })(Polymer); |
| |
| (function(scope) { |
| |
| // imports |
| |
| var log = window.WebComponents ? WebComponents.flags.log : {}; |
| var api = scope.api.instance.events; |
| var EVENT_PREFIX = api.EVENT_PREFIX; |
| |
| var mixedCaseEventTypes = {}; |
| [ |
| 'webkitAnimationStart', |
| 'webkitAnimationEnd', |
| 'webkitTransitionEnd', |
| 'DOMFocusOut', |
| 'DOMFocusIn', |
| 'DOMMouseScroll' |
| ].forEach(function(e) { |
| mixedCaseEventTypes[e.toLowerCase()] = e; |
| }); |
| |
| // polymer-element declarative api: events feature |
| var events = { |
| parseHostEvents: function() { |
| // our delegates map |
| var delegates = this.prototype.eventDelegates; |
| // extract data from attributes into delegates |
| this.addAttributeDelegates(delegates); |
| }, |
| addAttributeDelegates: function(delegates) { |
| // for each attribute |
| for (var i=0, a; a=this.attributes[i]; i++) { |
| // does it have magic marker identifying it as an event delegate? |
| if (this.hasEventPrefix(a.name)) { |
| // if so, add the info to delegates |
| delegates[this.removeEventPrefix(a.name)] = a.value.replace('{{', '') |
| .replace('}}', '').trim(); |
| } |
| } |
| }, |
| // starts with 'on-' |
| hasEventPrefix: function (n) { |
| return n && (n[0] === 'o') && (n[1] === 'n') && (n[2] === '-'); |
| }, |
| removeEventPrefix: function(n) { |
| return n.slice(prefixLength); |
| }, |
| findController: function(node) { |
| while (node.parentNode) { |
| if (node.eventController) { |
| return node.eventController; |
| } |
| node = node.parentNode; |
| } |
| return node.host; |
| }, |
| getEventHandler: function(controller, target, method) { |
| var events = this; |
| return function(e) { |
| if (!controller || !controller.PolymerBase) { |
| controller = events.findController(target); |
| } |
| |
| var args = [e, e.detail, e.currentTarget]; |
| controller.dispatchMethod(controller, method, args); |
| }; |
| }, |
| prepareEventBinding: function(pathString, name, node) { |
| if (!this.hasEventPrefix(name)) |
| return; |
| |
| var eventType = this.removeEventPrefix(name); |
| eventType = mixedCaseEventTypes[eventType] || eventType; |
| |
| var events = this; |
| |
| return function(model, node, oneTime) { |
| var handler = events.getEventHandler(undefined, node, pathString); |
| PolymerGestures.addEventListener(node, eventType, handler); |
| |
| if (oneTime) |
| return; |
| |
| // TODO(rafaelw): This is really pointless work. Aside from the cost |
| // of these allocations, NodeBind is going to setAttribute back to its |
| // current value. Fixing this would mean changing the TemplateBinding |
| // binding delegate API. |
| function bindingValue() { |
| return '{{ ' + pathString + ' }}'; |
| } |
| |
| return { |
| open: bindingValue, |
| discardChanges: bindingValue, |
| close: function() { |
| PolymerGestures.removeEventListener(node, eventType, handler); |
| } |
| }; |
| }; |
| } |
| }; |
| |
| var prefixLength = EVENT_PREFIX.length; |
| |
| // exports |
| scope.api.declaration.events = events; |
| |
| })(Polymer); |
| |
| (function(scope) { |
| |
| // element api |
| |
| var observationBlacklist = ['attribute']; |
| |
| var properties = { |
| inferObservers: function(prototype) { |
| // called before prototype.observe is chained to inherited object |
| var observe = prototype.observe, property; |
| for (var n in prototype) { |
| if (n.slice(-7) === 'Changed') { |
| property = n.slice(0, -7); |
| if (this.canObserveProperty(property)) { |
| if (!observe) { |
| observe = (prototype.observe = {}); |
| } |
| observe[property] = observe[property] || n; |
| } |
| } |
| } |
| }, |
| canObserveProperty: function(property) { |
| return (observationBlacklist.indexOf(property) < 0); |
| }, |
| explodeObservers: function(prototype) { |
| // called before prototype.observe is chained to inherited object |
| var o = prototype.observe; |
| if (o) { |
| var exploded = {}; |
| for (var n in o) { |
| var names = n.split(' '); |
| for (var i=0, ni; ni=names[i]; i++) { |
| exploded[ni] = o[n]; |
| } |
| } |
| prototype.observe = exploded; |
| } |
| }, |
| optimizePropertyMaps: function(prototype) { |
| if (prototype.observe) { |
| // construct name list |
| var a = prototype._observeNames = []; |
| for (var n in prototype.observe) { |
| var names = n.split(' '); |
| for (var i=0, ni; ni=names[i]; i++) { |
| a.push(ni); |
| } |
| } |
| } |
| if (prototype.publish) { |
| // construct name list |
| var a = prototype._publishNames = []; |
| for (var n in prototype.publish) { |
| a.push(n); |
| } |
| } |
| if (prototype.computed) { |
| // construct name list |
| var a = prototype._computedNames = []; |
| for (var n in prototype.computed) { |
| a.push(n); |
| } |
| } |
| }, |
| publishProperties: function(prototype, base) { |
| // if we have any properties to publish |
| var publish = prototype.publish; |
| if (publish) { |
| // transcribe `publish` entries onto own prototype |
| this.requireProperties(publish, prototype, base); |
| // warn and remove accessor names that are broken on some browsers |
| this.filterInvalidAccessorNames(publish); |
| // construct map of lower-cased property names |
| prototype._publishLC = this.lowerCaseMap(publish); |
| } |
| var computed = prototype.computed; |
| if (computed) { |
| // warn and remove accessor names that are broken on some browsers |
| this.filterInvalidAccessorNames(computed); |
| } |
| }, |
| // Publishing/computing a property where the name might conflict with a |
| // browser property is not currently supported to help users of Polymer |
| // avoid browser bugs: |
| // |
| // https://code.google.com/p/chromium/issues/detail?id=43394 |
| // https://bugs.webkit.org/show_bug.cgi?id=49739 |
| // |
| // We can lift this restriction when those bugs are fixed. |
| filterInvalidAccessorNames: function(propertyNames) { |
| for (var name in propertyNames) { |
| // Check if the name is in our blacklist. |
| if (this.propertyNameBlacklist[name]) { |
| console.warn('Cannot define property "' + name + '" for element "' + |
| this.name + '" because it has the same name as an HTMLElement ' + |
| 'property, and not all browsers support overriding that. ' + |
| 'Consider giving it a different name.'); |
| // Remove the invalid accessor from the list. |
| delete propertyNames[name]; |
| } |
| } |
| }, |
| // |
| // `name: value` entries in the `publish` object may need to generate |
| // matching properties on the prototype. |
| // |
| // Values that are objects may have a `reflect` property, which |
| // signals that the value describes property control metadata. |
| // In metadata objects, the prototype default value (if any) |
| // is encoded in the `value` property. |
| // |
| // publish: { |
| // foo: 5, |
| // bar: {value: true, reflect: true}, |
| // zot: {} |
| // } |
| // |
| // `reflect` metadata property controls whether changes to the property |
| // are reflected back to the attribute (default false). |
| // |
| // A value is stored on the prototype unless it's === `undefined`, |
| // in which case the base chain is checked for a value. |
| // If the basal value is also undefined, `null` is stored on the prototype. |
| // |
| // The reflection data is stored on another prototype object, `reflect` |
| // which also can be specified directly. |
| // |
| // reflect: { |
| // foo: true |
| // } |
| // |
| requireProperties: function(propertyInfos, prototype, base) { |
| // per-prototype storage for reflected properties |
| prototype.reflect = prototype.reflect || {}; |
| // ensure a prototype value for each property |
| // and update the property's reflect to attribute status |
| for (var n in propertyInfos) { |
| var value = propertyInfos[n]; |
| // value has metadata if it has a `reflect` property |
| if (value && value.reflect !== undefined) { |
| prototype.reflect[n] = Boolean(value.reflect); |
| value = value.value; |
| } |
| // only set a value if one is specified |
| if (value !== undefined) { |
| prototype[n] = value; |
| } |
| } |
| }, |
| lowerCaseMap: function(properties) { |
| var map = {}; |
| for (var n in properties) { |
| map[n.toLowerCase()] = n; |
| } |
| return map; |
| }, |
| createPropertyAccessor: function(name, ignoreWrites) { |
| var proto = this.prototype; |
| |
| var privateName = name + '_'; |
| var privateObservable = name + 'Observable_'; |
| proto[privateName] = proto[name]; |
| |
| Object.defineProperty(proto, name, { |
| get: function() { |
| var observable = this[privateObservable]; |
| if (observable) |
| observable.deliver(); |
| |
| return this[privateName]; |
| }, |
| set: function(value) { |
| if (ignoreWrites) { |
| return this[privateName]; |
| } |
| |
| var observable = this[privateObservable]; |
| if (observable) { |
| observable.setValue(value); |
| return; |
| } |
| |
| var oldValue = this[privateName]; |
| this[privateName] = value; |
| this.emitPropertyChangeRecord(name, value, oldValue); |
| |
| return value; |
| }, |
| configurable: true |
| }); |
| }, |
| createPropertyAccessors: function(prototype) { |
| var n$ = prototype._computedNames; |
| if (n$ && n$.length) { |
| for (var i=0, l=n$.length, n, fn; (i<l) && (n=n$[i]); i++) { |
| this.createPropertyAccessor(n, true); |
| } |
| } |
| var n$ = prototype._publishNames; |
| if (n$ && n$.length) { |
| for (var i=0, l=n$.length, n, fn; (i<l) && (n=n$[i]); i++) { |
| // If the property is computed and published, the accessor is created |
| // above. |
| if (!prototype.computed || !prototype.computed[n]) { |
| this.createPropertyAccessor(n); |
| } |
| } |
| } |
| }, |
| // This list contains some property names that people commonly want to use, |
| // but won't work because of Chrome/Safari bugs. It isn't an exhaustive |
| // list. In particular it doesn't contain any property names found on |
| // subtypes of HTMLElement (e.g. name, value). Rather it attempts to catch |
| // some common cases. |
| propertyNameBlacklist: { |
| children: 1, |
| 'class': 1, |
| id: 1, |
| hidden: 1, |
| style: 1, |
| title: 1, |
| } |
| }; |
| |
| // exports |
| |
| scope.api.declaration.properties = properties; |
| |
| })(Polymer); |
| |
| (function(scope) { |
| |
| // magic words |
| |
| var ATTRIBUTES_ATTRIBUTE = 'attributes'; |
| var ATTRIBUTES_REGEX = /\s|,/; |
| |
| // attributes api |
| |
| var attributes = { |
| |
| inheritAttributesObjects: function(prototype) { |
| // chain our lower-cased publish map to the inherited version |
| this.inheritObject(prototype, 'publishLC'); |
| // chain our instance attributes map to the inherited version |
| this.inheritObject(prototype, '_instanceAttributes'); |
| }, |
| |
| publishAttributes: function(prototype, base) { |
| // merge names from 'attributes' attribute into the 'publish' object |
| var attributes = this.getAttribute(ATTRIBUTES_ATTRIBUTE); |
| if (attributes) { |
| // create a `publish` object if needed. |
| // the `publish` object is only relevant to this prototype, the |
| // publishing logic in `declaration/properties.js` is responsible for |
| // managing property values on the prototype chain. |
| // TODO(sjmiles): the `publish` object is later chained to it's |
| // ancestor object, presumably this is only for |
| // reflection or other non-library uses. |
| var publish = prototype.publish || (prototype.publish = {}); |
| // names='a b c' or names='a,b,c' |
| var names = attributes.split(ATTRIBUTES_REGEX); |
| // record each name for publishing |
| for (var i=0, l=names.length, n; i<l; i++) { |
| // remove excess ws |
| n = names[i].trim(); |
| // looks weird, but causes n to exist on `publish` if it does not; |
| // a more careful test would need expensive `in` operator |
| if (n && publish[n] === undefined) { |
| publish[n] = undefined; |
| } |
| } |
| } |
| }, |
| |
| // record clonable attributes from <element> |
| accumulateInstanceAttributes: function() { |
| // inherit instance attributes |
| var clonable = this.prototype._instanceAttributes; |
| // merge attributes from element |
| var a$ = this.attributes; |
| for (var i=0, l=a$.length, a; (i<l) && (a=a$[i]); i++) { |
| if (this.isInstanceAttribute(a.name)) { |
| clonable[a.name] = a.value; |
| } |
| } |
| }, |
| |
| isInstanceAttribute: function(name) { |
| return !this.blackList[name] && name.slice(0,3) !== 'on-'; |
| }, |
| |
| // do not clone these attributes onto instances |
| blackList: { |
| name: 1, |
| 'extends': 1, |
| constructor: 1, |
| noscript: 1, |
| assetpath: 1, |
| 'cache-csstext': 1 |
| } |
| |
| }; |
| |
| // add ATTRIBUTES_ATTRIBUTE to the blacklist |
| attributes.blackList[ATTRIBUTES_ATTRIBUTE] = 1; |
| |
| // exports |
| |
| scope.api.declaration.attributes = attributes; |
| |
| })(Polymer); |
| |
| (function(scope) { |
| |
| // imports |
| var events = scope.api.declaration.events; |
| |
| var syntax = new PolymerExpressions(); |
| var prepareBinding = syntax.prepareBinding; |
| |
| // Polymer takes a first crack at the binding to see if it's a declarative |
| // event handler. |
| syntax.prepareBinding = function(pathString, name, node) { |
| return events.prepareEventBinding(pathString, name, node) || |
| prepareBinding.call(syntax, pathString, name, node); |
| }; |
| |
| // declaration api supporting mdv |
| var mdv = { |
| syntax: syntax, |
| fetchTemplate: function() { |
| return this.querySelector('template'); |
| }, |
| templateContent: function() { |
| var template = this.fetchTemplate(); |
| return template && template.content; |
| }, |
| installBindingDelegate: function(template) { |
| if (template) { |
| template.bindingDelegate = this.syntax; |
| } |
| } |
| }; |
| |
| // exports |
| scope.api.declaration.mdv = mdv; |
| |
| })(Polymer); |
| |
| (function(scope) { |
| |
| // imports |
| |
| var api = scope.api; |
| var isBase = scope.isBase; |
| var extend = scope.extend; |
| |
| var hasShadowDOMPolyfill = window.ShadowDOMPolyfill; |
| |
| // prototype api |
| |
| var prototype = { |
| |
| register: function(name, extendeeName) { |
| // build prototype combining extendee, Polymer base, and named api |
| this.buildPrototype(name, extendeeName); |
| // register our custom element with the platform |
| this.registerPrototype(name, extendeeName); |
| // reference constructor in a global named by 'constructor' attribute |
| this.publishConstructor(); |
| }, |
| |
| buildPrototype: function(name, extendeeName) { |
| // get our custom prototype (before chaining) |
| var extension = scope.getRegisteredPrototype(name); |
| // get basal prototype |
| var base = this.generateBasePrototype(extendeeName); |
| // implement declarative features |
| this.desugarBeforeChaining(extension, base); |
| // join prototypes |
| this.prototype = this.chainPrototypes(extension, base); |
| // more declarative features |
| this.desugarAfterChaining(name, extendeeName); |
| }, |
| |
| desugarBeforeChaining: function(prototype, base) { |
| // back reference declaration element |
| // TODO(sjmiles): replace `element` with `elementElement` or `declaration` |
| prototype.element = this; |
| // transcribe `attributes` declarations onto own prototype's `publish` |
| this.publishAttributes(prototype, base); |
| // `publish` properties to the prototype and to attribute watch |
| this.publishProperties(prototype, base); |
| // infer observers for `observe` list based on method names |
| this.inferObservers(prototype); |
| // desugar compound observer syntax, e.g. 'a b c' |
| this.explodeObservers(prototype); |
| }, |
| |
| chainPrototypes: function(prototype, base) { |
| // chain various meta-data objects to inherited versions |
| this.inheritMetaData(prototype, base); |
| // chain custom api to inherited |
| var chained = this.chainObject(prototype, base); |
| // x-platform fixup |
| ensurePrototypeTraversal(chained); |
| return chained; |
| }, |
| |
| inheritMetaData: function(prototype, base) { |
| // chain observe object to inherited |
| this.inheritObject('observe', prototype, base); |
| // chain publish object to inherited |
| this.inheritObject('publish', prototype, base); |
| // chain reflect object to inherited |
| this.inheritObject('reflect', prototype, base); |
| // chain our lower-cased publish map to the inherited version |
| this.inheritObject('_publishLC', prototype, base); |
| // chain our instance attributes map to the inherited version |
| this.inheritObject('_instanceAttributes', prototype, base); |
| // chain our event delegates map to the inherited version |
| this.inheritObject('eventDelegates', prototype, base); |
| }, |
| |
| // implement various declarative features |
| desugarAfterChaining: function(name, extendee) { |
| // build side-chained lists to optimize iterations |
| this.optimizePropertyMaps(this.prototype); |
| this.createPropertyAccessors(this.prototype); |
| // install mdv delegate on template |
| this.installBindingDelegate(this.fetchTemplate()); |
| // install external stylesheets as if they are inline |
| this.installSheets(); |
| // adjust any paths in dom from imports |
| this.resolveElementPaths(this); |
| // compile list of attributes to copy to instances |
| this.accumulateInstanceAttributes(); |
| // parse on-* delegates declared on `this` element |
| this.parseHostEvents(); |
| // |
| // install a helper method this.resolvePath to aid in |
| // setting resource urls. e.g. |
| // this.$.image.src = this.resolvePath('images/foo.png') |
| this.addResolvePathApi(); |
| // under ShadowDOMPolyfill, transforms to approximate missing CSS features |
| if (hasShadowDOMPolyfill) { |
| WebComponents.ShadowCSS.shimStyling(this.templateContent(), name, |
| extendee); |
| } |
| // allow custom element access to the declarative context |
| if (this.prototype.registerCallback) { |
| this.prototype.registerCallback(this); |
| } |
| }, |
| |
| // if a named constructor is requested in element, map a reference |
| // to the constructor to the given symbol |
| publishConstructor: function() { |
| var symbol = this.getAttribute('constructor'); |
| if (symbol) { |
| window[symbol] = this.ctor; |
| } |
| }, |
| |
| // build prototype combining extendee, Polymer base, and named api |
| generateBasePrototype: function(extnds) { |
| var prototype = this.findBasePrototype(extnds); |
| if (!prototype) { |
| // create a prototype based on tag-name extension |
| var prototype = HTMLElement.getPrototypeForTag(extnds); |
| // insert base api in inheritance chain (if needed) |
| prototype = this.ensureBaseApi(prototype); |
| // memoize this base |
| memoizedBases[extnds] = prototype; |
| } |
| return prototype; |
| }, |
| |
| findBasePrototype: function(name) { |
| return memoizedBases[name]; |
| }, |
| |
| // install Polymer instance api into prototype chain, as needed |
| ensureBaseApi: function(prototype) { |
| if (prototype.PolymerBase) { |
| return prototype; |
| } |
| var extended = Object.create(prototype); |
| // we need a unique copy of base api for each base prototype |
| // therefore we 'extend' here instead of simply chaining |
| api.publish(api.instance, extended); |
| // TODO(sjmiles): sharing methods across prototype chains is |
| // not supported by 'super' implementation which optimizes |
| // by memoizing prototype relationships. |
| // Probably we should have a version of 'extend' that is |
| // share-aware: it could study the text of each function, |
| // look for usage of 'super', and wrap those functions in |
| // closures. |
| // As of now, there is only one problematic method, so |
| // we just patch it manually. |
| // To avoid re-entrancy problems, the special super method |
| // installed is called `mixinSuper` and the mixin method |
| // must use this method instead of the default `super`. |
| this.mixinMethod(extended, prototype, api.instance.mdv, 'bind'); |
| // return buffed-up prototype |
| return extended; |
| }, |
| |
| mixinMethod: function(extended, prototype, api, name) { |
| var $super = function(args) { |
| return prototype[name].apply(this, args); |
| }; |
| extended[name] = function() { |
| this.mixinSuper = $super; |
| return api[name].apply(this, arguments); |
| } |
| }, |
| |
| // ensure prototype[name] inherits from a prototype.prototype[name] |
| inheritObject: function(name, prototype, base) { |
| // require an object |
| var source = prototype[name] || {}; |
| // chain inherited properties onto a new object |
| prototype[name] = this.chainObject(source, base[name]); |
| }, |
| |
| // register 'prototype' to custom element 'name', store constructor |
| registerPrototype: function(name, extendee) { |
| var info = { |
| prototype: this.prototype |
| } |
| // native element must be specified in extends |
| var typeExtension = this.findTypeExtension(extendee); |
| if (typeExtension) { |
| info.extends = typeExtension; |
| } |
| // register the prototype with HTMLElement for name lookup |
| HTMLElement.register(name, this.prototype); |
| // register the custom type |
| this.ctor = document.registerElement(name, info); |
| }, |
| |
| findTypeExtension: function(name) { |
| if (name && name.indexOf('-') < 0) { |
| return name; |
| } else { |
| var p = this.findBasePrototype(name); |
| if (p.element) { |
| return this.findTypeExtension(p.element.extends); |
| } |
| } |
| } |
| |
| }; |
| |
| // memoize base prototypes |
| var memoizedBases = {}; |
| |
| // implementation of 'chainObject' depends on support for __proto__ |
| if (Object.__proto__) { |
| prototype.chainObject = function(object, inherited) { |
| if (object && inherited && object !== inherited) { |
| object.__proto__ = inherited; |
| } |
| return object; |
| } |
| } else { |
| prototype.chainObject = function(object, inherited) { |
| if (object && inherited && object !== inherited) { |
| var chained = Object.create(inherited); |
| object = extend(chained, object); |
| } |
| return object; |
| } |
| } |
| |
| // On platforms that do not support __proto__ (versions of IE), the prototype |
| // chain of a custom element is simulated via installation of __proto__. |
| // Although custom elements manages this, we install it here so it's |
| // available during desugaring. |
| function ensurePrototypeTraversal(prototype) { |
| if (!Object.__proto__) { |
| var ancestor = Object.getPrototypeOf(prototype); |
| prototype.__proto__ = ancestor; |
| if (isBase(ancestor)) { |
| ancestor.__proto__ = Object.getPrototypeOf(ancestor); |
| } |
| } |
| } |
| |
| // exports |
| |
| api.declaration.prototype = prototype; |
| |
| })(Polymer); |
| |
| (function(scope) { |
| |
| /* |
| |
| Elements are added to a registration queue so that they register in |
| the proper order at the appropriate time. We do this for a few reasons: |
| |
| * to enable elements to load resources (like stylesheets) |
| asynchronously. We need to do this until the platform provides an efficient |
| alternative. One issue is that remote @import stylesheets are |
| re-fetched whenever stamped into a shadowRoot. |
| |
| * to ensure elements loaded 'at the same time' (e.g. via some set of |
| imports) are registered as a batch. This allows elements to be enured from |
| upgrade ordering as long as they query the dom tree 1 task after |
| upgrade (aka domReady). This is a performance tradeoff. On the one hand, |
| elements that could register while imports are loading are prevented from |
| doing so. On the other, grouping upgrades into a single task means less |
| incremental work (for example style recalcs), Also, we can ensure the |
| document is in a known state at the single quantum of time when |
| elements upgrade. |
| |
| */ |
| var queue = { |
| |
| // tell the queue to wait for an element to be ready |
| wait: function(element) { |
| if (!element.__queue) { |
| element.__queue = {}; |
| elements.push(element); |
| } |
| }, |
| |
| // enqueue an element to the next spot in the queue. |
| enqueue: function(element, check, go) { |
| var shouldAdd = element.__queue && !element.__queue.check; |
| if (shouldAdd) { |
| queueForElement(element).push(element); |
| element.__queue.check = check; |
| element.__queue.go = go; |
| } |
| return (this.indexOf(element) !== 0); |
| }, |
| |
| indexOf: function(element) { |
| var i = queueForElement(element).indexOf(element); |
| if (i >= 0 && document.contains(element)) { |
| i += (HTMLImports.useNative || HTMLImports.ready) ? |
| importQueue.length : 1e9; |
| } |
| return i; |
| }, |
| |
| // tell the queue an element is ready to be registered |
| go: function(element) { |
| var readied = this.remove(element); |
| if (readied) { |
| element.__queue.flushable = true; |
| this.addToFlushQueue(readied); |
| this.check(); |
| } |
| }, |
| |
| remove: function(element) { |
| var i = this.indexOf(element); |
| if (i !== 0) { |
| //console.warn('queue order wrong', i); |
| return; |
| } |
| return queueForElement(element).shift(); |
| }, |
| |
| check: function() { |
| // next |
| var element = this.nextElement(); |
| if (element) { |
| element.__queue.check.call(element); |
| } |
| if (this.canReady()) { |
| this.ready(); |
| return true; |
| } |
| }, |
| |
| nextElement: function() { |
| return nextQueued(); |
| }, |
| |
| canReady: function() { |
| return !this.waitToReady && this.isEmpty(); |
| }, |
| |
| isEmpty: function() { |
| for (var i=0, l=elements.length, e; (i<l) && |
| (e=elements[i]); i++) { |
| if (e.__queue && !e.__queue.flushable) { |
| return; |
| } |
| } |
| return true; |
| }, |
| |
| addToFlushQueue: function(element) { |
| flushQueue.push(element); |
| }, |
| |
| flush: function() { |
| // prevent re-entrance |
| if (this.flushing) { |
| return; |
| } |
| this.flushing = true; |
| var element; |
| while (flushQueue.length) { |
| element = flushQueue.shift(); |
| element.__queue.go.call(element); |
| element.__queue = null; |
| } |
| this.flushing = false; |
| }, |
| |
| ready: function() { |
| // TODO(sorvell): As an optimization, turn off CE polyfill upgrading |
| // while registering. This way we avoid having to upgrade each document |
| // piecemeal per registration and can instead register all elements |
| // and upgrade once in a batch. Without this optimization, upgrade time |
| // degrades significantly when SD polyfill is used. This is mainly because |
| // querying the document tree for elements is slow under the SD polyfill. |
| var polyfillWasReady = CustomElements.ready; |
| CustomElements.ready = false; |
| this.flush(); |
| if (!CustomElements.useNative) { |
| CustomElements.upgradeDocumentTree(document); |
| } |
| CustomElements.ready = polyfillWasReady; |
| Polymer.flush(); |
| requestAnimationFrame(this.flushReadyCallbacks); |
| }, |
| |
| addReadyCallback: function(callback) { |
| if (callback) { |
| readyCallbacks.push(callback); |
| } |
| }, |
| |
| flushReadyCallbacks: function() { |
| if (readyCallbacks) { |
| var fn; |
| while (readyCallbacks.length) { |
| fn = readyCallbacks.shift(); |
| fn(); |
| } |
| } |
| }, |
| |
| /** |
| Returns a list of elements that have had polymer-elements created but |
| are not yet ready to register. The list is an array of element definitions. |
| */ |
| waitingFor: function() { |
| var e$ = []; |
| for (var i=0, l=elements.length, e; (i<l) && |
| (e=elements[i]); i++) { |
| if (e.__queue && !e.__queue.flushable) { |
| e$.push(e); |
| } |
| } |
| return e$; |
| }, |
| |
| waitToReady: true |
| |
| }; |
| |
| var elements = []; |
| var flushQueue = []; |
| var importQueue = []; |
| var mainQueue = []; |
| var readyCallbacks = []; |
| |
| function queueForElement(element) { |
| return document.contains(element) ? mainQueue : importQueue; |
| } |
| |
| function nextQueued() { |
| return importQueue.length ? importQueue[0] : mainQueue[0]; |
| } |
| |
| function whenReady(callback) { |
| queue.waitToReady = true; |
| Polymer.endOfMicrotask(function() { |
| HTMLImports.whenReady(function() { |
| queue.addReadyCallback(callback); |
| queue.waitToReady = false; |
| queue.check(); |
| }); |
| }); |
| } |
| |
| /** |
| Forces polymer to register any pending elements. Can be used to abort |
| waiting for elements that are partially defined. |
| @param timeout {Integer} Optional timeout in milliseconds |
| */ |
| function forceReady(timeout) { |
| if (timeout === undefined) { |
| queue.ready(); |
| return; |
| } |
| var handle = setTimeout(function() { |
| queue.ready(); |
| }, timeout); |
| Polymer.whenReady(function() { |
| clearTimeout(handle); |
| }); |
| } |
| |
| // exports |
| scope.elements = elements; |
| scope.waitingFor = queue.waitingFor.bind(queue); |
| scope.forceReady = forceReady; |
| scope.queue = queue; |
| scope.whenReady = scope.whenPolymerReady = whenReady; |
| })(Polymer); |
| |
| (function(scope) { |
| |
| // imports |
| |
| var extend = scope.extend; |
| var api = scope.api; |
| var queue = scope.queue; |
| var whenReady = scope.whenReady; |
| var getRegisteredPrototype = scope.getRegisteredPrototype; |
| var waitingForPrototype = scope.waitingForPrototype; |
| |
| // declarative implementation: <polymer-element> |
| |
| var prototype = extend(Object.create(HTMLElement.prototype), { |
| |
| createdCallback: function() { |
| if (this.getAttribute('name')) { |
| this.init(); |
| } |
| }, |
| |
| init: function() { |
| // fetch declared values |
| this.name = this.getAttribute('name'); |
| this.extends = this.getAttribute('extends'); |
| queue.wait(this); |
| // initiate any async resource fetches |
| this.loadResources(); |
| // register when all constraints are met |
| this.registerWhenReady(); |
| }, |
| |
| // TODO(sorvell): we currently queue in the order the prototypes are |
| // registered, but we should queue in the order that polymer-elements |
| // are registered. We are currently blocked from doing this based on |
| // crbug.com/395686. |
| registerWhenReady: function() { |
| if (this.registered |
| || this.waitingForPrototype(this.name) |
| || this.waitingForQueue() |
| || this.waitingForResources()) { |
| return; |
| } |
| queue.go(this); |
| }, |
| |
| _register: function() { |
| //console.log('registering', this.name); |
| // warn if extending from a custom element not registered via Polymer |
| if (isCustomTag(this.extends) && !isRegistered(this.extends)) { |
| console.warn('%s is attempting to extend %s, an unregistered element ' + |
| 'or one that was not registered with Polymer.', this.name, |
| this.extends); |
| } |
| this.register(this.name, this.extends); |
| this.registered = true; |
| }, |
| |
| waitingForPrototype: function(name) { |
| if (!getRegisteredPrototype(name)) { |
| // then wait for a prototype |
| waitingForPrototype(name, this); |
| // emulate script if user is not supplying one |
| this.handleNoScript(name); |
| // prototype not ready yet |
| return true; |
| } |
| }, |
| |
| handleNoScript: function(name) { |
| // if explicitly marked as 'noscript' |
| if (this.hasAttribute('noscript') && !this.noscript) { |
| this.noscript = true; |
| // imperative element registration |
| Polymer(name); |
| } |
| }, |
| |
| waitingForResources: function() { |
| return this._needsResources; |
| }, |
| |
| // NOTE: Elements must be queued in proper order for inheritance/composition |
| // dependency resolution. Previously this was enforced for inheritance, |
| // and by rule for composition. It's now entirely by rule. |
| waitingForQueue: function() { |
| return queue.enqueue(this, this.registerWhenReady, this._register); |
| }, |
| |
| loadResources: function() { |
| this._needsResources = true; |
| this.loadStyles(function() { |
| this._needsResources = false; |
| this.registerWhenReady(); |
| }.bind(this)); |
| } |
| |
| }); |
| |
| // semi-pluggable APIs |
| |
| // TODO(sjmiles): should be fully pluggable (aka decoupled, currently |
| // the various plugins are allowed to depend on each other directly) |
| api.publish(api.declaration, prototype); |
| |
| // utility and bookkeeping |
| |
| function isRegistered(name) { |
| return Boolean(HTMLElement.getPrototypeForTag(name)); |
| } |
| |
| function isCustomTag(name) { |
| return (name && name.indexOf('-') >= 0); |
| } |
| |
| // boot tasks |
| |
| whenReady(function() { |
| document.body.removeAttribute('unresolved'); |
| document.dispatchEvent( |
| new CustomEvent('polymer-ready', {bubbles: true}) |
| ); |
| }); |
| |
| // register polymer-element with document |
| |
| document.registerElement('polymer-element', {prototype: prototype}); |
| |
| })(Polymer); |
| |
| (function(scope) { |
| |
| /** |
| * @class Polymer |
| */ |
| |
| var whenReady = scope.whenReady; |
| |
| /** |
| * Loads the set of HTMLImports contained in `node`. Notifies when all |
| * the imports have loaded by calling the `callback` function argument. |
| * This method can be used to lazily load imports. For example, given a |
| * template: |
| * |
| * <template> |
| * <link rel="import" href="my-import1.html"> |
| * <link rel="import" href="my-import2.html"> |
| * </template> |
| * |
| * Polymer.importElements(template.content, function() { |
| * console.log('imports lazily loaded'); |
| * }); |
| * |
| * @method importElements |
| * @param {Node} node Node containing the HTMLImports to load. |
| * @param {Function} callback Callback called when all imports have loaded. |
| */ |
| function importElements(node, callback) { |
| if (node) { |
| document.head.appendChild(node); |
| whenReady(callback); |
| } else if (callback) { |
| callback(); |
| } |
| } |
| |
| /** |
| * Loads an HTMLImport for each url specified in the `urls` array. |
| * Notifies when all the imports have loaded by calling the `callback` |
| * function argument. This method can be used to lazily load imports. |
| * For example, |
| * |
| * Polymer.import(['my-import1.html', 'my-import2.html'], function() { |
| * console.log('imports lazily loaded'); |
| * }); |
| * |
| * @method import |
| * @param {Array} urls Array of urls to load as HTMLImports. |
| * @param {Function} callback Callback called when all imports have loaded. |
| */ |
| function _import(urls, callback) { |
| if (urls && urls.length) { |
| var frag = document.createDocumentFragment(); |
| for (var i=0, l=urls.length, url, link; (i<l) && (url=urls[i]); i++) { |
| link = document.createElement('link'); |
| link.rel = 'import'; |
| link.href = url; |
| frag.appendChild(link); |
| } |
| importElements(frag, callback); |
| } else if (callback) { |
| callback(); |
| } |
| } |
| |
| // exports |
| scope.import = _import; |
| scope.importElements = importElements; |
| |
| })(Polymer); |
| |
| /** |
| * The `auto-binding` element extends the template element. It provides a quick |
| * and easy way to do data binding without the need to setup a model. |
| * The `auto-binding` element itself serves as the model and controller for the |
| * elements it contains. Both data and event handlers can be bound. |
| * |
| * The `auto-binding` element acts just like a template that is bound to |
| * a model. It stamps its content in the dom adjacent to itself. When the |
| * content is stamped, the `template-bound` event is fired. |
| * |
| * Example: |
| * |
| * <template is="auto-binding"> |
| * <div>Say something: <input value="{{value}}"></div> |
| * <div>You said: {{value}}</div> |
| * <button on-tap="{{buttonTap}}">Tap me!</button> |
| * </template> |
| * <script> |
| * var template = document.querySelector('template'); |
| * template.value = 'something'; |
| * template.buttonTap = function() { |
| * console.log('tap!'); |
| * }; |
| * <\/script> |
| * |
| * @module Polymer |
| * @status stable |
| */ |
| |
| (function() { |
| |
| var element = document.createElement('polymer-element'); |
| element.setAttribute('name', 'auto-binding'); |
| element.setAttribute('extends', 'template'); |
| element.init(); |
| |
| Polymer('auto-binding', { |
| |
| createdCallback: function() { |
| this.syntax = this.bindingDelegate = this.makeSyntax(); |
| // delay stamping until polymer-ready so that auto-binding is not |
| // required to load last. |
| Polymer.whenPolymerReady(function() { |
| this.model = this; |
| this.setAttribute('bind', ''); |
| // we don't bother with an explicit signal here, we could ust a MO |
| // if necessary |
| this.async(function() { |
| // note: this will marshall *all* the elements in the parentNode |
| // rather than just stamped ones. We'd need to use createInstance |
| // to fix this or something else fancier. |
| this.marshalNodeReferences(this.parentNode); |
| // template stamping is asynchronous so stamping isn't complete |
| // by polymer-ready; fire an event so users can use stamped elements |
| this.fire('template-bound'); |
| }); |
| }.bind(this)); |
| }, |
| |
| makeSyntax: function() { |
| var events = Object.create(Polymer.api.declaration.events); |
| var self = this; |
| events.findController = function() { return self.model; }; |
| |
| var syntax = new PolymerExpressions(); |
| var prepareBinding = syntax.prepareBinding; |
| syntax.prepareBinding = function(pathString, name, node) { |
| return events.prepareEventBinding(pathString, name, node) || |
| prepareBinding.call(syntax, pathString, name, node); |
| }; |
| return syntax; |
| } |
| |
| }); |
| |
| })(); |
| </script> |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| <style shim-shadowdom="">/* |
| * @license |
| * Copyright (c) 2014 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 |
| */ |
| |
| html /deep/ paper-shadow, |
| html /deep/ paper-animated-shadow { |
| display: block; |
| position: relative; |
| } |
| |
| html /deep/ paper-shadow::shadow #shadow-bottom, |
| html /deep/ paper-shadow::shadow #shadow-top { |
| border-radius: inherit; |
| pointer-events: none; |
| } |
| |
| html /deep/ paper-shadow::shadow #shadow-bottom[animated], |
| html /deep/ paper-shadow::shadow #shadow-top[animated] { |
| transition: box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1); |
| } |
| |
| html /deep/ .paper-shadow-top-z-1 { |
| box-shadow: none; |
| } |
| |
| html /deep/ .paper-shadow-bottom-z-1 { |
| box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.37); |
| } |
| |
| html /deep/ .paper-shadow-top-z-2 { |
| box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.2); |
| } |
| |
| html /deep/ .paper-shadow-bottom-z-2 { |
| box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.3); |
| } |
| |
| html /deep/ .paper-shadow-top-z-3 { |
| box-shadow: 0 11px 7px 0 rgba(0, 0, 0, 0.19); |
| } |
| |
| html /deep/ .paper-shadow-bottom-z-3 { |
| box-shadow: 0 13px 25px 0 rgba(0, 0, 0, 0.3); |
| } |
| |
| html /deep/ .paper-shadow-top-z-4 { |
| box-shadow: 0 14px 12px 0 rgba(0, 0, 0, 0.17); |
| } |
| |
| html /deep/ .paper-shadow-bottom-z-4 { |
| box-shadow: 0 20px 40px 0 rgba(0, 0, 0, 0.3); |
| } |
| |
| html /deep/ .paper-shadow-top-z-5 { |
| box-shadow: 0 17px 17px 0 rgba(0, 0, 0, 0.15); |
| } |
| |
| html /deep/ .paper-shadow-bottom-z-5 { |
| box-shadow: 0 27px 55px 0 rgba(0, 0, 0, 0.3); |
| }</style> |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| <script> |
| |
| (function(scope) { |
| |
| /** |
| `Polymer.CoreResizable` and `Polymer.CoreResizer` are a set of mixins that can be used |
| in Polymer elements to coordinate the flow of resize events between "resizers" (elements |
| that control the size or hidden state of their children) and "resizables" (elements that |
| need to be notified when they are resized or un-hidden by their parents in order to take |
| action on their new measurements). |
| |
| Elements that perform measurement should add the `Core.Resizable` mixin to their |
| Polymer prototype definition and listen for the `core-resize` event on themselves. |
| This event will be fired when they become showing after having been hidden, |
| when they are resized explicitly by a `CoreResizer`, or when the window has been resized. |
| Note, the `core-resize` event is non-bubbling. |
| |
| `CoreResizable`'s must manually call the `resizableAttachedHandler` from the element's |
| `attached` callback and `resizableDetachedHandler` from the element's `detached` |
| callback. |
| |
| @element CoreResizable |
| @status beta |
| @homepage github.io |
| */ |
| |
| scope.CoreResizable = { |
| |
| /** |
| * User must call from `attached` callback |
| * |
| * @method resizableAttachedHandler |
| */ |
| resizableAttachedHandler: function(cb) { |
| cb = cb || this._notifyResizeSelf; |
| this.async(function() { |
| var detail = {callback: cb, hasParentResizer: false}; |
| this.fire('core-request-resize', detail); |
| if (!detail.hasParentResizer) { |
| this._boundWindowResizeHandler = cb.bind(this); |
| // log('adding window resize handler', null, this); |
| window.addEventListener('resize', this._boundWindowResizeHandler); |
| } |
| }.bind(this)); |
| }, |
| |
| /** |
| * User must call from `detached` callback |
| * |
| * @method resizableDetachedHandler |
| */ |
| resizableDetachedHandler: function() { |
| this.fire('core-request-resize-cancel', null, this, false); |
| if (this._boundWindowResizeHandler) { |
| window.removeEventListener('resize', this._boundWindowResizeHandler); |
| } |
| }, |
| |
| // Private: fire non-bubbling resize event to self; returns whether |
| // preventDefault was called, indicating that children should not |
| // be resized |
| _notifyResizeSelf: function() { |
| return this.fire('core-resize', null, this, false).defaultPrevented; |
| } |
| |
| }; |
| |
| /** |
| `Polymer.CoreResizable` and `Polymer.CoreResizer` are a set of mixins that can be used |
| in Polymer elements to coordinate the flow of resize events between "resizers" (elements |
| that control the size or hidden state of their children) and "resizables" (elements that |
| need to be notified when they are resized or un-hidden by their parents in order to take |
| action on their new measurements). |
| |
| Elements that cause their children to be resized (e.g. a splitter control) or hide/show |
| their children (e.g. overlay) should add the `Core.CoreResizer` mixin to their |
| Polymer prototype definition and then call `this.notifyResize()` any time the element |
| resizes or un-hides its children. |
| |
| `CoreResizer`'s must manually call the `resizerAttachedHandler` from the element's |
| `attached` callback and `resizerDetachedHandler` from the element's `detached` |
| callback. |
| |
| Note: `CoreResizer` extends `CoreResizable`, and can listen for the `core-resize` event |
| on itself if it needs to perform resize work on itself before notifying children. |
| In this case, returning `false` from the `core-resize` event handler (or calling |
| `preventDefault` on the event) will prevent notification of children if required. |
| |
| @element CoreResizer |
| @extends CoreResizable |
| @status beta |
| @homepage github.io |
| */ |
| |
| scope.CoreResizer = Polymer.mixin({ |
| |
| /** |
| * User must call from `attached` callback |
| * |
| * @method resizerAttachedHandler |
| */ |
| resizerAttachedHandler: function() { |
| this.resizableAttachedHandler(this.notifyResize); |
| this._boundResizeRequested = this._boundResizeRequested || this._handleResizeRequested.bind(this); |
| var listener; |
| if (this.resizerIsPeer) { |
| listener = this.parentElement || (this.parentNode && this.parentNode.host); |
| listener._resizerPeers = listener._resizerPeers || []; |
| listener._resizerPeers.push(this); |
| } else { |
| listener = this; |
| } |
| listener.addEventListener('core-request-resize', this._boundResizeRequested); |
| this._resizerListener = listener; |
| }, |
| |
| /** |
| * User must call from `detached` callback |
| * |
| * @method resizerDetachedHandler |
| */ |
| resizerDetachedHandler: function() { |
| this.resizableDetachedHandler(); |
| this._resizerListener.removeEventListener('core-request-resize', this._boundResizeRequested); |
| }, |
| |
| /** |
| * User should call when resizing or un-hiding children |
| * |
| * @method notifyResize |
| */ |
| notifyResize: function() { |
| // Notify self |
| if (!this._notifyResizeSelf()) { |
| // Notify requestors if default was not prevented |
| var r = this.resizeRequestors; |
| if (r) { |
| for (var i=0; i<r.length; i++) { |
| var ri = r[i]; |
| if (!this.resizerShouldNotify || this.resizerShouldNotify(ri.target)) { |
| // log('notifying resize', null, ri.target, true); |
| ri.callback.apply(ri.target); |
| // logEnd(); |
| } |
| } |
| } |
| } |
| }, |
| |
| /** |
| * User should implement to introduce filtering when notifying children. |
| * Generally, children that are hidden by the CoreResizer (e.g. non-active |
| * pages) need not be notified during resize, since they will be notified |
| * again when becoming un-hidden. |
| * |
| * Return `true` if CoreResizable passed as argument should be notified of |
| * resize. |
| * |
| * @method resizeerShouldNotify |
| * @param {Element} el |
| */ |
| // resizeerShouldNotify: function(el) { } // User to implement if needed |
| |
| /** |
| * Set to `true` if the resizer is actually a peer to the elements it |
| * resizes (e.g. splitter); in this case it will listen for resize requests |
| * events from its peers on its parent. |
| * |
| * @property resizerIsPeer |
| * @type Boolean |
| * @default false |
| */ |
| |
| // Private: Handle requests for resize |
| _handleResizeRequested: function(e) { |
| var target = e.path[0]; |
| if ((target == this) || |
| (target == this._resizerListener) || |
| (this._resizerPeers && this._resizerPeers.indexOf(target) < 0)) { |
| return; |
| } |
| // log('resize requested', target, this); |
| if (!this.resizeRequestors) { |
| this.resizeRequestors = []; |
| } |
| this.resizeRequestors.push({target: target, callback: e.detail.callback}); |
| target.addEventListener('core-request-resize-cancel', this._cancelResizeRequested.bind(this)); |
| e.detail.hasParentResizer = true; |
| e.stopPropagation(); |
| }, |
| |
| // Private: Handle cancellation requests for resize |
| _cancelResizeRequested: function(e) { |
| // Exit early if we're already out of the DOM (resizeRequestors will already be null) |
| if (this.resizeRequestors) { |
| for (var i=0; i<this.resizeRequestors.length; i++) { |
| if (this.resizeRequestors[i].target == e.target) { |
| // log('resizeCanceled', e.target, this); |
| this.resizeRequestors.splice(i, 1); |
| break; |
| } |
| } |
| } |
| } |
| |
| }, Polymer.CoreResizable); |
| |
| // function prettyName(el) { |
| // return el.localName + (el.id ? '#' : '') + el.id; |
| // } |
| |
| // function log(what, from, to, group) { |
| // var args = [what]; |
| // if (from) { |
| // args.push('from ' + prettyName(from)); |
| // } |
| // if (to) { |
| // args.push('to ' + prettyName(to)); |
| // } |
| // if (group) { |
| // console.group.apply(console, args); |
| // } else { |
| // console.log.apply(console, args); |
| // } |
| // } |
| |
| // function logEnd() { |
| // console.groupEnd(); |
| // } |
| |
| })(Polymer); |
| |
| </script> |
| |
| |
| <!-- |
| @element core-key-helper |
| --> |
| |
| |
| |
| <!-- |
| @element core-overlay-layer |
| --> |
| |
| |
| <!-- |
| The `core-overlay` element displays overlayed on top of other content. It starts |
| out hidden and is displayed by setting its `opened` property to true. |
| A `core-overlay's` opened state can be toggled by calling the `toggle` |
| method. |
| |
| The `core-overlay` will, by default, show/hide itself when it's opened. The |
| `target` property may be set to another element to cause that element to |
| be shown when the overlay is opened. |
| |
| It's common to want a `core-overlay` to animate to its opened |
| position. The `core-overlay` element uses a `core-transition` to handle |
| animation. The default transition is `core-transition-fade` which |
| causes the overlay to fade in when displayed. See |
| <a href="../core-transition/">`core-transition`</a> for more |
| information about customizing a `core-overlay's` opening animation. The |
| `backdrop` property can be set to true to show a backdrop behind the overlay |
| that will darken the rest of the window. |
| |
| An element that should close the `core-overlay` will automatically |
| do so if it's given the `core-overlay-toggle` attribute. This attribute |
| can be customized with the `closeAttribute` property. You can also use |
| `closeSelector` if more general matching is needed. |
| |
| By default `core-overlay` will close whenever the user taps outside it or |
| presses the escape key. This behavior can be turned off via the |
| `autoCloseDisabled` property. |
| |
| <core-overlay> |
| <h2>Dialog</h2> |
| <input placeholder="say something..." autofocus> |
| <div>I agree with this wholeheartedly.</div> |
| <button core-overlay-toggle>OK</button> |
| </core-overlay> |
| |
| `core-overlay` will automatically size and position itself according to the |
| following rules. The overlay's size is constrained such that it does not |
| overflow the screen. This is done by setting maxHeight/maxWidth on the |
| `sizingTarget`. If the `sizingTarget` already has a setting for one of these |
| properties, it will not be overridden. The overlay should |
| be positioned via css or imperatively using the `core-overlay-position` event. |
| If the overlay is not positioned vertically via setting `top` or `bottom`, it |
| will be centered vertically. The same is true horizontally via a setting to |
| `left` or `right`. In addition, css `margin` can be used to provide some space |
| around the overlay. This can be used to ensure |
| that, for example, a drop shadow is always visible around the overlay. |
| |
| @group Core Elements |
| @element core-overlay |
| @mixins Polymer.CoreResizer https://github.com/polymer/core-resizable |
| @homepage github.io |
| --> |
| <!-- |
| Fired when the `core-overlay`'s `opened` property changes. |
| |
| @event core-overlay-open |
| @param {Object} detail |
| @param {Object} detail.opened the opened state |
| --> |
| <!-- |
| Fired when the `core-overlay` has completely opened. |
| |
| @event core-overlay-open-completed |
| --> |
| <!-- |
| Fired when the `core-overlay` has completely closed. |
| |
| @event core-overlay-close-completed |
| --> |
| <!-- |
| Fired when the `core-overlay` needs to position itself. Optionally, implement |
| in order to position an overlay via code. If the overlay was not otherwise |
| positioned, it's important to indicate how the overlay has been positioned by |
| setting the `dimensions.position` object. For example, if the overlay has been |
| positioned via setting `right` and `top`, set dimensions.position to an |
| object like this: `{v: 'top', h: 'right'}`. |
| |
| @event core-overlay-position |
| @param {Object} detail |
| @param {Object} detail.target the overlay target |
| @param {Object} detail.sizingTarget the overlay sizing target |
| @param {Object} detail.opened the opened state |
| --> |
| <style> |
| .core-overlay-backdrop { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100vw; |
| height: 100vh; |
| background-color: black; |
| opacity: 0; |
| transition: opacity 0.2s; |
| } |
| |
| .core-overlay-backdrop.core-opened { |
| opacity: 0.6; |
| } |
| </style> |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| <style shim-shadowdom="">/* Copyright (c) 2014 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 */ |
| |
| html /deep/ core-icon { |
| display: inline-block; |
| vertical-align: middle; |
| background-repeat: no-repeat; |
| fill: currentcolor; |
| position: relative; |
| height: 24px; |
| width: 24px; |
| }</style> |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| <style shim-shadowdom=""> |
| html /deep/ core-a11y-keys { |
| display: none; |
| } |
| </style> |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| <script>Polymer.mixin2 = function(prototype, mixin) { |
| |
| // adds a single mixin to prototype |
| |
| if (mixin.mixinPublish) { |
| prototype.publish = prototype.publish || {}; |
| Polymer.mixin(prototype.publish, mixin.mixinPublish); |
| } |
| |
| if (mixin.mixinDelegates) { |
| prototype.eventDelegates = prototype.eventDelegates || {}; |
| for (var e in mixin.mixinDelegates) { |
| if (!prototype.eventDelegates[e]) { |
| prototype.eventDelegates[e] = mixin.mixinDelegates[e]; |
| } |
| } |
| } |
| |
| if (mixin.mixinObserve) { |
| prototype.observe = prototype.observe || {}; |
| for (var o in mixin.mixinObserve) { |
| if (!prototype.observe[o] && !prototype[o + 'Changed']) { |
| prototype.observe[o] = mixin.mixinObserve[o]; |
| } |
| } |
| } |
| |
| Polymer.mixin(prototype, mixin); |
| |
| delete prototype.mixinPublish; |
| delete prototype.mixinDelegates; |
| delete prototype.mixinObserve; |
| |
| return prototype; |
| };</script> |
| <script>/** |
| * @group Polymer Mixins |
| * |
| * `Polymer.CoreFocusable` is a mixin for elements that the user can interact with. |
| * Elements using this mixin will receive attributes reflecting the focus, pressed |
| * and disabled states. |
| * |
| * @element Polymer.CoreFocusable |
| * @status unstable |
| */ |
| |
| Polymer.CoreFocusable = { |
| |
| mixinPublish: { |
| |
| /** |
| * If true, the element is currently active either because the |
| * user is touching it, or the button is a toggle |
| * and is currently in the active state. |
| * |
| * @attribute active |
| * @type boolean |
| * @default false |
| */ |
| active: {value: false, reflect: true}, |
| |
| /** |
| * If true, the element currently has focus due to keyboard |
| * navigation. |
| * |
| * @attribute focused |
| * @type boolean |
| * @default false |
| */ |
| focused: {value: false, reflect: true}, |
| |
| /** |
| * If true, the user is currently holding down the button. |
| * |
| * @attribute pressed |
| * @type boolean |
| * @default false |
| */ |
| pressed: {value: false, reflect: true}, |
| |
| /** |
| * If true, the user cannot interact with this element. |
| * |
| * @attribute disabled |
| * @type boolean |
| * @default false |
| */ |
| disabled: {value: false, reflect: true}, |
| |
| /** |
| * If true, the button toggles the active state with each tap. |
| * Otherwise, the button becomes active when the user is holding |
| * it down. |
| * |
| * @attribute toggle |
| * @type boolean |
| * @default false |
| */ |
| toggle: false |
| |
| }, |
| |
| mixinDelegates: { |
| contextMenu: '_contextMenuAction', |
| down: '_downAction', |
| up: '_upAction', |
| focus: '_focusAction', |
| blur: '_blurAction' |
| }, |
| |
| mixinObserve: { |
| disabled: '_disabledChanged' |
| }, |
| |
| _disabledChanged: function() { |
| if (this.disabled) { |
| this.style.pointerEvents = 'none'; |
| this.removeAttribute('tabindex'); |
| this.setAttribute('aria-disabled', ''); |
| } else { |
| this.style.pointerEvents = ''; |
| this.setAttribute('tabindex', 0); |
| this.removeAttribute('aria-disabled'); |
| } |
| }, |
| |
| _downAction: function() { |
| this.pressed = true; |
| |
| if (this.toggle) { |
| this.active = !this.active; |
| } else { |
| this.active = true; |
| } |
| }, |
| |
| // Pulling up the context menu for an item should focus it; but we need to |
| // be careful about how we deal with down/up events surrounding context |
| // menus. The up event typically does not fire until the context menu |
| // closes: so we focus immediately. |
| // |
| // This fires _after_ downAction. |
| _contextMenuAction: function(e) { |
| // Note that upAction may fire _again_ on the actual up event. |
| this._upAction(e); |
| this._focusAction(); |
| }, |
| |
| _upAction: function() { |
| this.pressed = false; |
| |
| if (!this.toggle) { |
| this.active = false; |
| } |
| }, |
| |
| _focusAction: function() { |
| if (!this.pressed) { |
| // Only render the "focused" state if the element gains focus due to |
| // keyboard navigation. |
| this.focused = true; |
| } |
| }, |
| |
| _blurAction: function() { |
| this.focused = false; |
| } |
| |
| } |
| </script> |
| |
| |
| |
| <!-- TODO: would be nice to inherit from label to get .htmlFor, and .control, |
| but the latter is readonly. --> |
| <!-- TODO: support off center arrows. --> |
| <!-- TODO: detect mobile and apply the .large class, instead of manual |
| control. --> |
| <!-- TODO: possibly reuse core-overlay. --> |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| <style shim-shadowdom=""> |
| /* FIXME consider theming */ |
| |
| html /deep/ input[is=core-input] { |
| width: 20em; |
| font: inherit; |
| margin: 0; |
| padding: 0; |
| background-color: transparent; |
| border: 0; |
| outline: none; |
| } |
| </style> |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| <!-- |
| Element providing material design circular spinner. |
| |
| ##### Example |
| |
| <paper-spinner active></paper-spinner> |
| |
| The default spinner cycles between blue, red, yellow and green. It can be customized so |
| that it uses one color only. |
| |
| ##### Example |
| |
| <style shim-shadowdom> |
| paper-spinner.blue::shadow .circle { |
| border-color: #4285f4; |
| } |
| </style> |
| |
| <paper-spinner class="blue" active></paper-spinner> |
| |
| Alt attribute should be set to provide adequate context for accessibility. If not provided, |
| it defaults to 'loading'. |
| Empty alt can be provided to mark the element as decorative if alternative content is provided |
| in another form (e.g. a text block following the spinner). |
| |
| ##### Example |
| <paper-spinner alt="Loading contacts list" active></paper-spinner> |
| |
| @element paper-spinner |
| @blurb Element providing material design circular spinner. |
| @status alpha |
| @homepage http://polymerlabs.github.io/paper-spinner |
| --> |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| <meta charset="UTF-8"></head><body><div hidden=""><polymer-element name="core-media-query" attributes="query queryMatches" assetpath="../bower_components/core-media-query/"> |
| <template> |
| <style> |
| :host { |
| display: none; |
| } |
| </style> |
| </template> |
| <script> |
| Polymer('core-media-query', { |
| |
| /** |
| * The Boolean return value of the media query |
| * |
| * @attribute queryMatches |
| * @type Boolean |
| * @default false |
| */ |
| queryMatches: false, |
| |
| /** |
| * The CSS media query to evaulate |
| * |
| * @attribute query |
| * @type string |
| * @default '' |
| */ |
| query: '', |
| ready: function() { |
| this._mqHandler = this.queryHandler.bind(this); |
| this._mq = null; |
| }, |
| queryChanged: function() { |
| if (this._mq) { |
| this._mq.removeListener(this._mqHandler); |
| } |
| var query = this.query; |
| if (query[0] !== '(') { |
| query = '(' + this.query + ')'; |
| } |
| this._mq = window.matchMedia(query); |
| this._mq.addListener(this._mqHandler); |
| this.queryHandler(this._mq); |
| }, |
| queryHandler: function(mq) { |
| this.queryMatches = mq.matches; |
| this.asyncFire('core-media-change', mq); |
| } |
| }); |
| </script> |
| </polymer-element> |
| |
| <polymer-element name="core-selection" attributes="multi" hidden="" assetpath="../bower_components/core-selection/"> |
| <script> |
| Polymer('core-selection', { |
| /** |
| * If true, multiple selections are allowed. |
| * |
| * @attribute multi |
| * @type boolean |
| * @default false |
| */ |
| multi: false, |
| ready: function() { |
| this.clear(); |
| }, |
| clear: function() { |
| this.selection = []; |
| }, |
| /** |
| * Retrieves the selected item(s). |
| * @method getSelection |
| * @returns Returns the selected item(s). If the multi property is true, |
| * getSelection will return an array, otherwise it will return |
| * the selected item or undefined if there is no selection. |
| */ |
| getSelection: function() { |
| return this.multi ? this.selection : this.selection[0]; |
| }, |
| /** |
| * Indicates if a given item is selected. |
| * @method isSelected |
| * @param {any} item The item whose selection state should be checked. |
| * @returns Returns true if `item` is selected. |
| */ |
| isSelected: function(item) { |
| return this.selection.indexOf(item) >= 0; |
| }, |
| setItemSelected: function(item, isSelected) { |
| if (item !== undefined && item !== null) { |
| if (isSelected) { |
| this.selection.push(item); |
| } else { |
| var i = this.selection.indexOf(item); |
| if (i >= 0) { |
| this.selection.splice(i, 1); |
| } |
| } |
| this.fire("core-select", {isSelected: isSelected, item: item}); |
| } |
| }, |
| /** |
| * Set the selection state for a given `item`. If the multi property |
| * is true, then the selected state of `item` will be toggled; otherwise |
| * the `item` will be selected. |
| * @method select |
| * @param {any} item: The item to select. |
| */ |
| select: function(item) { |
| if (this.multi) { |
| this.toggle(item); |
| } else if (this.getSelection() !== item) { |
| this.setItemSelected(this.getSelection(), false); |
| this.setItemSelected(item, true); |
| } |
| }, |
| /** |
| * Toggles the selection state for `item`. |
| * @method toggle |
| * @param {any} item: The item to toggle. |
| */ |
| toggle: function(item) { |
| this.setItemSelected(item, !this.isSelected(item)); |
| } |
| }); |
| </script> |
| </polymer-element> |
| <polymer-element name="core-selector" attributes="selected multi valueattr selectedClass selectedProperty selectedAttribute selectedItem selectedModel selectedIndex notap excludedLocalNames target itemsSelector activateEvent" assetpath="../bower_components/core-selector/"> |
| |
| <template> |
| <core-selection id="selection" multi="{{multi}}" on-core-select="{{selectionSelect}}"></core-selection> |
| <content id="items" select="*"></content> |
| </template> |
| |
| <script> |
| |
| Polymer('core-selector', { |
| |
| /** |
| * Gets or sets the selected element. Default to use the index |
| * of the item element. |
| * |
| * If you want a specific attribute value of the element to be |
| * used instead of index, set "valueattr" to that attribute name. |
| * |
| * Example: |
| * |
| * <core-selector valueattr="label" selected="foo"> |
| * <div label="foo"></div> |
| * <div label="bar"></div> |
| * <div label="zot"></div> |
| * </core-selector> |
| * |
| * In multi-selection this should be an array of values. |
| * |
| * Example: |
| * |
| * <core-selector id="selector" valueattr="label" multi> |
| * <div label="foo"></div> |
| * <div label="bar"></div> |
| * <div label="zot"></div> |
| * </core-selector> |
| * |
| * this.$.selector.selected = ['foo', 'zot']; |
| * |
| * @attribute selected |
| * @type Object |
| * @default null |
| */ |
| selected: null, |
| |
| /** |
| * If true, multiple selections are allowed. |
| * |
| * @attribute multi |
| * @type boolean |
| * @default false |
| */ |
| multi: false, |
| |
| /** |
| * Specifies the attribute to be used for "selected" attribute. |
| * |
| * @attribute valueattr |
| * @type string |
| * @default 'name' |
| */ |
| valueattr: 'name', |
| |
| /** |
| * Specifies the CSS class to be used to add to the selected element. |
| * |
| * @attribute selectedClass |
| * @type string |
| * @default 'core-selected' |
| */ |
| selectedClass: 'core-selected', |
| |
| /** |
| * Specifies the property to be used to set on the selected element |
| * to indicate its active state. |
| * |
| * @attribute selectedProperty |
| * @type string |
| * @default '' |
| */ |
| selectedProperty: '', |
| |
| /** |
| * Specifies the attribute to set on the selected element to indicate |
| * its active state. |
| * |
| * @attribute selectedAttribute |
| * @type string |
| * @default 'active' |
| */ |
| selectedAttribute: 'active', |
| |
| /** |
| * Returns the currently selected element. In multi-selection this returns |
| * an array of selected elements. |
| * Note that you should not use this to set the selection. Instead use |
| * `selected`. |
| * |
| * @attribute selectedItem |
| * @type Object |
| * @default null |
| */ |
| selectedItem: null, |
| |
| /** |
| * In single selection, this returns the model associated with the |
| * selected element. |
| * Note that you should not use this to set the selection. Instead use |
| * `selected`. |
| * |
| * @attribute selectedModel |
| * @type Object |
| * @default null |
| */ |
| selectedModel: null, |
| |
| /** |
| * In single selection, this returns the selected index. |
| * Note that you should not use this to set the selection. Instead use |
| * `selected`. |
| * |
| * @attribute selectedIndex |
| * @type number |
| * @default -1 |
| */ |
| selectedIndex: -1, |
| |
| /** |
| * Nodes with local name that are in the list will not be included |
| * in the selection items. In the following example, `items` returns four |
| * `core-item`'s and doesn't include `h3` and `hr`. |
| * |
| * <core-selector excludedLocalNames="h3 hr"> |
| * <h3>Header</h3> |
| * <core-item>Item1</core-item> |
| * <core-item>Item2</core-item> |
| * <hr> |
| * <core-item>Item3</core-item> |
| * <core-item>Item4</core-item> |
| * </core-selector> |
| * |
| * @attribute excludedLocalNames |
| * @type string |
| * @default '' |
| */ |
| excludedLocalNames: '', |
| |
| /** |
| * The target element that contains items. If this is not set |
| * core-selector is the container. |
| * |
| * @attribute target |
| * @type Object |
| * @default null |
| */ |
| target: null, |
| |
| /** |
| * This can be used to query nodes from the target node to be used for |
| * selection items. Note this only works if `target` is set |
| * and is not `core-selector` itself. |
| * |
| * Example: |
| * |
| * <core-selector target="{{$.myForm}}" itemsSelector="input[type=radio]"></core-selector> |
| * <form id="myForm"> |
| * <label><input type="radio" name="color" value="red"> Red</label> <br> |
| * <label><input type="radio" name="color" value="green"> Green</label> <br> |
| * <label><input type="radio" name="color" value="blue"> Blue</label> <br> |
| * <p>color = {{color}}</p> |
| * </form> |
| * |
| * @attribute itemsSelector |
| * @type string |
| * @default '' |
| */ |
| itemsSelector: '', |
| |
| /** |
| * The event that would be fired from the item element to indicate |
| * it is being selected. |
| * |
| * @attribute activateEvent |
| * @type string |
| * @default 'tap' |
| */ |
| activateEvent: 'tap', |
| |
| /** |
| * Set this to true to disallow changing the selection via the |
| * `activateEvent`. |
| * |
| * @attribute notap |
| * @type boolean |
| * @default false |
| */ |
| notap: false, |
| |
| defaultExcludedLocalNames: 'template', |
| |
| observe: { |
| 'selected multi': 'selectedChanged' |
| }, |
| |
| ready: function() { |
| this.activateListener = this.activateHandler.bind(this); |
| this.itemFilter = this.filterItem.bind(this); |
| this.excludedLocalNamesChanged(); |
| this.observer = new MutationObserver(this.updateSelected.bind(this)); |
| if (!this.target) { |
| this.target = this; |
| } |
| }, |
| |
| /** |
| * Returns an array of all items. |
| * |
| * @property items |
| * @type array |
| */ |
| get items() { |
| if (!this.target) { |
| return []; |
| } |
| var nodes = this.target !== this ? (this.itemsSelector ? |
| this.target.querySelectorAll(this.itemsSelector) : |
| this.target.children) : this.$.items.getDistributedNodes(); |
| return Array.prototype.filter.call(nodes, this.itemFilter); |
| }, |
| |
| filterItem: function(node) { |
| return !this._excludedNames[node.localName]; |
| }, |
| |
| excludedLocalNamesChanged: function() { |
| this._excludedNames = {}; |
| var s = this.defaultExcludedLocalNames; |
| if (this.excludedLocalNames) { |
| s += ' ' + this.excludedLocalNames; |
| } |
| s.split(/\s+/g).forEach(function(n) { |
| this._excludedNames[n] = 1; |
| }, this); |
| }, |
| |
| targetChanged: function(old) { |
| if (old) { |
| this.removeListener(old); |
| this.observer.disconnect(); |
| this.clearSelection(); |
| } |
| if (this.target) { |
| this.addListener(this.target); |
| this.observer.observe(this.target, {childList: true}); |
| this.updateSelected(); |
| } |
| }, |
| |
| addListener: function(node) { |
| Polymer.addEventListener(node, this.activateEvent, this.activateListener); |
| }, |
| |
| removeListener: function(node) { |
| Polymer.removeEventListener(node, this.activateEvent, this.activateListener); |
| }, |
| |
| /** |
| * Returns the selected item(s). If the `multi` property is true, |
| * this will return an array, otherwise it will return |
| * the selected item or undefined if there is no selection. |
| */ |
| get selection() { |
| return this.$.selection.getSelection(); |
| }, |
| |
| selectedChanged: function() { |
| // TODO(ffu): Right now this is the only way to know that the `selected` |
| // is an array and was mutated, as opposed to newly assigned. |
| if (arguments.length === 1) { |
| this.processSplices(arguments[0]); |
| } else { |
| this.updateSelected(); |
| } |
| }, |
| |
| updateSelected: function() { |
| this.validateSelected(); |
| if (this.multi) { |
| this.clearSelection(this.selected); |
| this.selected && this.selected.forEach(function(s) { |
| this.setValueSelected(s, true); |
| }, this); |
| } else { |
| this.valueToSelection(this.selected); |
| } |
| }, |
| |
| validateSelected: function() { |
| // convert to an array for multi-selection |
| if (this.multi && !Array.isArray(this.selected) && |
| this.selected != null) { |
| this.selected = [this.selected]; |
| // use the first selected in the array for single-selection |
| } else if (!this.multi && Array.isArray(this.selected)) { |
| var s = this.selected[0]; |
| this.clearSelection([s]); |
| this.selected = s; |
| } |
| }, |
| |
| processSplices: function(splices) { |
| for (var i = 0, splice; splice = splices[i]; i++) { |
| for (var j = 0; j < splice.removed.length; j++) { |
| this.setValueSelected(splice.removed[j], false); |
| } |
| for (var j = 0; j < splice.addedCount; j++) { |
| this.setValueSelected(this.selected[splice.index + j], true); |
| } |
| } |
| }, |
| |
| clearSelection: function(excludes) { |
| this.$.selection.selection.slice().forEach(function(item) { |
| var v = this.valueForNode(item) || this.items.indexOf(item); |
| if (!excludes || excludes.indexOf(v) < 0) { |
| this.$.selection.setItemSelected(item, false); |
| } |
| }, this); |
| }, |
| |
| valueToSelection: function(value) { |
| var item = this.valueToItem(value); |
| this.$.selection.select(item); |
| }, |
| |
| setValueSelected: function(value, isSelected) { |
| var item = this.valueToItem(value); |
| if (isSelected ^ this.$.selection.isSelected(item)) { |
| this.$.selection.setItemSelected(item, isSelected); |
| } |
| }, |
| |
| updateSelectedItem: function() { |
| this.selectedItem = this.selection; |
| }, |
| |
| selectedItemChanged: function() { |
| if (this.selectedItem) { |
| var t = this.selectedItem.templateInstance; |
| this.selectedModel = t ? t.model : undefined; |
| } else { |
| this.selectedModel = null; |
| } |
| this.selectedIndex = this.selectedItem ? |
| parseInt(this.valueToIndex(this.selected)) : -1; |
| }, |
| |
| valueToItem: function(value) { |
| return (value === null || value === undefined) ? |
| null : this.items[this.valueToIndex(value)]; |
| }, |
| |
| valueToIndex: function(value) { |
| // find an item with value == value and return it's index |
| for (var i=0, items=this.items, c; (c=items[i]); i++) { |
| if (this.valueForNode(c) == value) { |
| return i; |
| } |
| } |
| // if no item found, the value itself is probably the index |
| return value; |
| }, |
| |
| valueForNode: function(node) { |
| return node[this.valueattr] || node.getAttribute(this.valueattr); |
| }, |
| |
| // events fired from <core-selection> object |
| selectionSelect: function(e, detail) { |
| this.updateSelectedItem(); |
| if (detail.item) { |
| this.applySelection(detail.item, detail.isSelected); |
| } |
| }, |
| |
| applySelection: function(item, isSelected) { |
| if (this.selectedClass) { |
| item.classList.toggle(this.selectedClass, isSelected); |
| } |
| if (this.selectedProperty) { |
| item[this.selectedProperty] = isSelected; |
| } |
| if (this.selectedAttribute && item.setAttribute) { |
| if (isSelected) { |
| item.setAttribute(this.selectedAttribute, ''); |
| } else { |
| item.removeAttribute(this.selectedAttribute); |
| } |
| } |
| }, |
| |
| // event fired from host |
| activateHandler: function(e) { |
| if (!this.notap) { |
| var i = this.findDistributedTarget(e.target, this.items); |
| if (i >= 0) { |
| var item = this.items[i]; |
| var s = this.valueForNode(item) || i; |
| if (this.multi) { |
| if (this.selected) { |
| this.addRemoveSelected(s); |
| } else { |
| this.selected = [s]; |
| } |
| } else { |
| this.selected = s; |
| } |
| this.asyncFire('core-activate', {item: item}); |
| } |
| } |
| }, |
| |
| addRemoveSelected: function(value) { |
| var i = this.selected.indexOf(value); |
| if (i >= 0) { |
| this.selected.splice(i, 1); |
| } else { |
| this.selected.push(value); |
| } |
| }, |
| |
| findDistributedTarget: function(target, nodes) { |
| // find first ancestor of target (including itself) that |
| // is in nodes, if any |
| while (target && target != this) { |
| var i = Array.prototype.indexOf.call(nodes, target); |
| if (i >= 0) { |
| return i; |
| } |
| target = target.parentNode; |
| } |
| }, |
| |
| selectIndex: function(index) { |
| var item = this.items[index]; |
| if (item) { |
| this.selected = this.valueForNode(item) || index; |
| return item; |
| } |
| }, |
| |
| /** |
| * Selects the previous item. This should be used in single selection only. |
| * |
| * @method selectPrevious |
| * @param {boolean} wrapped if true and it is already at the first item, |
| * wrap to the end |
| * @returns the previous item or undefined if there is none |
| */ |
| selectPrevious: function(wrapped) { |
| var i = wrapped && !this.selectedIndex ? |
| this.items.length - 1 : this.selectedIndex - 1; |
| return this.selectIndex(i); |
| }, |
| |
| /** |
| * Selects the next item. This should be used in single selection only. |
| * |
| * @method selectNext |
| * @param {boolean} wrapped if true and it is already at the last item, |
| * wrap to the front |
| * @returns the next item or undefined if there is none |
| */ |
| selectNext: function(wrapped) { |
| var i = wrapped && this.selectedIndex >= this.items.length - 1 ? |
| 0 : this.selectedIndex + 1; |
| return this.selectIndex(i); |
| } |
| |
| }); |
| </script> |
| </polymer-element> |
| <polymer-element name="core-drawer-panel" touch-action="auto" assetpath="../bower_components/core-drawer-panel/"> |
| <template> |
| |
| <style>/* |
| Copyright (c) 2014 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 |
| */ |
| |
| :host { |
| display: block; |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| overflow: hidden; |
| } |
| |
| core-selector > #drawer { |
| position: absolute; |
| top: 0; |
| left: 0; |
| height: 100%; |
| will-change: transform; |
| box-sizing: border-box; |
| -moz-box-sizing: border-box; |
| } |
| |
| .transition > #drawer { |
| transition: -webkit-transform ease-in-out 0.3s, width ease-in-out 0.3s; |
| transition: transform ease-in-out 0.3s, width ease-in-out 0.3s; |
| } |
| |
| /* |
| right-drawer: make drawer on the right side |
| */ |
| .right-drawer > #drawer { |
| left: auto; |
| right: 0; |
| } |
| |
| polyfill-next-selector { content: ':host [drawer]'; } |
| ::content[select="[drawer]"] > * { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| box-sizing: border-box; |
| -moz-box-sizing: border-box; |
| } |
| |
| core-selector > #main { |
| position: absolute; |
| top: 0; |
| right: 0; |
| bottom: 0; |
| } |
| |
| .transition > #main { |
| transition: left ease-in-out 0.3s, padding ease-in-out 0.3s; |
| } |
| |
| .right-drawer > #main { |
| left: 0; |
| } |
| |
| .right-drawer.transition > #main { |
| transition: right ease-in-out 0.3s, padding ease-in-out 0.3s; |
| } |
| |
| polyfill-next-selector { content: '#main > [main]'; } |
| ::content[select="[main]"] > * { |
| height: 100%; |
| } |
| |
| #scrim { |
| position: absolute; |
| top: 0; |
| right: 0; |
| bottom: 0; |
| left: 0; |
| background-color: rgba(0, 0, 0, 0.3); |
| visibility: hidden; |
| opacity: 0; |
| transition: opacity ease-in-out 0.38s, visibility ease-in-out 0.38s; |
| } |
| |
| #edgeSwipeOverlay { |
| position: absolute; |
| top: 0; |
| bottom: 0; |
| left: 0; |
| width: 20px; |
| } |
| |
| .right-drawer > #main > #edgeSwipeOverlay { |
| right: 0; |
| left: auto; |
| } |
| |
| /* |
| narrow layout |
| */ |
| .narrow-layout > #drawer.core-selected { |
| box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.15); |
| } |
| |
| .right-drawer.narrow-layout > #drawer.core-selected { |
| box-shadow: -2px 2px 4px rgba(0, 0, 0, 0.15); |
| } |
| |
| polyfill-next-selector { content: ':host .narrow-layout > #drawer > [drawer]'; } |
| .narrow-layout > #drawer > ::content[select="[drawer]"] > * { |
| border: 0; |
| } |
| |
| .narrow-layout > #drawer:not(.core-selected) { |
| -webkit-transform: translateX(-100%); |
| transform: translateX(-100%); |
| } |
| |
| .right-drawer.narrow-layout > #drawer:not(.core-selected) { |
| left: auto; |
| -webkit-transform: translateX(100%); |
| transform: translateX(100%); |
| } |
| |
| .narrow-layout > #main { |
| left: 0 !important; |
| padding: 0; |
| } |
| |
| .right-drawer.narrow-layout > #main { |
| left: 0; |
| right: 0; |
| padding: 0; |
| } |
| |
| .narrow-layout > #main:not(.core-selected) > #scrim, |
| .dragging #scrim { |
| visibility: visible; |
| opacity: 1; |
| } |
| |
| polyfill-next-selector { content: ':host .narrow-layout > #main > [main]'; } |
| .narrow-layout > #main > ::content[select="[main]"] > * { |
| margin: 0; |
| min-height: 100%; |
| left: 0; |
| right: 0; |
| box-sizing: border-box; |
| -moz-box-sizing: border-box; |
| } |
| |
| polyfill-next-selector { content: 'core-selector:not(.narrow-layout) [core-drawer-toggle]'; } |
| core-selector:not(.narrow-layout) ::content [core-drawer-toggle] { |
| display: none; |
| } |
| </style> |
| |
| <core-media-query query="max-width: {{forceNarrow ? '' : responsiveWidth}}" querymatches="{{queryMatches}}"></core-media-query> |
| |
| <core-selector class="{{ {'narrow-layout' : narrow, transition : transition, dragging : dragging, 'right-drawer': rightDrawer} | tokenList }}" valueattr="id" selected="{{selected}}"> |
| |
| <div id="main" _style="left: {{ narrow || rightDrawer ? '0' : drawerWidth }}; right: {{ rightDrawer ? (narrow ? '' : drawerWidth) : '' }};"> |
| <content select="[main]"></content> |
| <div id="scrim" on-tap="{{togglePanel}}"></div> |
| <div id="edgeSwipeOverlay" hidden?="{{!narrow || disableEdgeSwipe}}"></div> |
| </div> |
| |
| <div id="drawer" _style="width: {{ drawerWidth }}"> |
| <content select="[drawer]"></content> |
| </div> |
| |
| </core-selector> |
| |
| </template> |
| <script> |
| |
| Polymer('core-drawer-panel', { |
| |
| /** |
| * Fired when the narrow layout changes. |
| * |
| * @event core-responsive-change |
| * @param {Object} detail |
| * @param {boolean} detail.narrow true if the panel is in narrow layout. |
| */ |
| |
| /** |
| * Fired when the selected panel changes. |
| * |
| * Listening for this event is an alternative to observing changes in the `selected` attribute. |
| * This event is fired both when a panel is selected and deselected. |
| * The `isSelected` detail property contains the selection state. |
| * |
| * @event core-select |
| * @param {Object} detail |
| * @param {boolean} detail.isSelected true for selection and false for deselection |
| * @param {Object} detail.item the panel that the event refers to |
| */ |
| |
| publish: { |
| |
| /** |
| * Width of the drawer panel. |
| * |
| * @attribute drawerWidth |
| * @type string |
| * @default '256px' |
| */ |
| drawerWidth: '256px', |
| |
| /** |
| * Max-width when the panel changes to narrow layout. |
| * |
| * @attribute responsiveWidth |
| * @type string |
| * @default '640px' |
| */ |
| responsiveWidth: '640px', |
| |
| /** |
| * The panel that is being selected. `drawer` for the drawer panel and |
| * `main` for the main panel. |
| * |
| * @attribute selected |
| * @type string |
| * @default null |
| */ |
| selected: {value: null, reflect: true}, |
| |
| /** |
| * The panel to be selected when `core-drawer-panel` changes to narrow |
| * layout. |
| * |
| * @attribute defaultSelected |
| * @type string |
| * @default 'main' |
| */ |
| defaultSelected: 'main', |
| |
| /** |
| * Returns true if the panel is in narrow layout. This is useful if you |
| * need to show/hide elements based on the layout. |
| * |
| * @attribute narrow |
| * @type boolean |
| * @default false |
| */ |
| narrow: {value: false, reflect: true}, |
| |
| /** |
| * If true, position the drawer to the right. |
| * |
| * @attribute rightDrawer |
| * @type boolean |
| * @default false |
| */ |
| rightDrawer: false, |
| |
| /** |
| * If true, swipe to open/close the drawer is disabled. |
| * |
| * @attribute disableSwipe |
| * @type boolean |
| * @default false |
| */ |
| disableSwipe: false, |
| |
| /** |
| * If true, ignore `responsiveWidth` setting and force the narrow layout. |
| * |
| * @attribute forceNarrow |
| * @type boolean |
| * @default false |
| */ |
| forceNarrow: false, |
| |
| /** |
| * If true, swipe from the edge is disabled. |
| * |
| * @attribute disableEdgeSwipe |
| * @type boolean |
| * @default false |
| */ |
| disableEdgeSwipe: false |
| }, |
| |
| eventDelegates: { |
| trackstart: 'trackStart', |
| trackx: 'trackx', |
| trackend: 'trackEnd', |
| down: 'downHandler', |
| up: 'upHandler', |
| tap: 'tapHandler' |
| }, |
| |
| // Whether the transition is enabled. |
| transition: false, |
| |
| // How many pixels on the side of the screen are sensitive to edge swipes and peek. |
| edgeSwipeSensitivity: 15, |
| |
| // Whether the drawer is peeking out from the edge. |
| peeking: false, |
| |
| // Whether the user is dragging the drawer interactively. |
| dragging: false, |
| |
| // Whether the browser has support for the transform CSS property. |
| hasTransform: true, |
| |
| // Whether the browser has support for the will-change CSS property. |
| hasWillChange: true, |
| |
| // The attribute on elements that should toggle the drawer on tap, also |
| // elements will automatically be hidden in wide layout. |
| toggleAttribute: 'core-drawer-toggle', |
| |
| created: function() { |
| this.hasTransform = 'transform' in this.style; |
| this.hasWillChange = 'willChange' in this.style; |
| }, |
| |
| domReady: function() { |
| // to avoid transition at the beginning e.g. page loads |
| // NOTE: domReady is already raf delayed and delaying another frame |
| // ensures a layout has occurred. |
| this.async(function() { |
| this.transition = true; |
| }); |
| }, |
| |
| /** |
| * Toggles the panel open and closed. |
| * |
| * @method togglePanel |
| */ |
| togglePanel: function() { |
| this.selected = this.isMainSelected() ? 'drawer' : 'main'; |
| }, |
| |
| /** |
| * Opens the drawer. |
| * |
| * @method openDrawer |
| */ |
| openDrawer: function() { |
| this.selected = 'drawer'; |
| }, |
| |
| /** |
| * Closes the drawer. |
| * |
| * @method closeDrawer |
| */ |
| closeDrawer: function() { |
| this.selected = 'main'; |
| }, |
| |
| queryMatchesChanged: function() { |
| this.narrow = this.queryMatches || this.forceNarrow; |
| if (this.narrow) { |
| this.selected = this.defaultSelected; |
| } |
| this.setAttribute('touch-action', this.swipeAllowed() ? 'pan-y' : ''); |
| this.fire('core-responsive-change', {narrow: this.narrow}); |
| }, |
| |
| forceNarrowChanged: function() { |
| this.queryMatchesChanged(); |
| }, |
| |
| swipeAllowed: function() { |
| return this.narrow && !this.disableSwipe; |
| }, |
| |
| isMainSelected: function() { |
| return this.selected === 'main'; |
| }, |
| |
| startEdgePeek: function() { |
| this.width = this.$.drawer.offsetWidth; |
| this.moveDrawer(this.translateXForDeltaX(this.rightDrawer ? |
| -this.edgeSwipeSensitivity : this.edgeSwipeSensitivity)); |
| this.peeking = true; |
| }, |
| |
| stopEdgePeak: function() { |
| if (this.peeking) { |
| this.peeking = false; |
| this.moveDrawer(null); |
| } |
| }, |
| |
| downHandler: function(e) { |
| if (!this.dragging && this.isMainSelected() && this.isEdgeTouch(e)) { |
| this.startEdgePeek(); |
| } |
| }, |
| |
| upHandler: function(e) { |
| this.stopEdgePeak(); |
| }, |
| |
| tapHandler: function(e) { |
| if (e.target && this.toggleAttribute && |
| e.target.hasAttribute(this.toggleAttribute)) { |
| this.togglePanel(); |
| } |
| }, |
| |
| isEdgeTouch: function(e) { |
| return !this.disableEdgeSwipe && this.swipeAllowed() && |
| (this.rightDrawer ? |
| e.pageX >= this.offsetWidth - this.edgeSwipeSensitivity : |
| e.pageX <= this.edgeSwipeSensitivity); |
| }, |
| |
| trackStart : function(e) { |
| if (this.swipeAllowed()) { |
| this.dragging = true; |
| |
| if (this.isMainSelected()) { |
| this.dragging = this.peeking || this.isEdgeTouch(e); |
| } |
| |
| if (this.dragging) { |
| this.width = this.$.drawer.offsetWidth; |
| this.transition = false; |
| e.preventTap(); |
| } |
| } |
| }, |
| |
| translateXForDeltaX: function(deltaX) { |
| var isMain = this.isMainSelected(); |
| if (this.rightDrawer) { |
| return Math.max(0, isMain ? this.width + deltaX : deltaX); |
| } else { |
| return Math.min(0, isMain ? deltaX - this.width : deltaX); |
| } |
| }, |
| |
| trackx : function(e) { |
| if (this.dragging) { |
| if (this.peeking) { |
| if (Math.abs(e.dx) <= this.edgeSwipeSensitivity) { |
| return; // Ignore trackx until we move past the edge peek. |
| } |
| this.peeking = false; |
| } |
| this.moveDrawer(this.translateXForDeltaX(e.dx)); |
| } |
| }, |
| |
| trackEnd : function(e) { |
| if (this.dragging) { |
| this.dragging = false; |
| this.transition = true; |
| this.moveDrawer(null); |
| |
| if (this.rightDrawer) { |
| this.selected = e.xDirection > 0 ? 'main' : 'drawer'; |
| } else { |
| this.selected = e.xDirection > 0 ? 'drawer' : 'main'; |
| } |
| } |
| }, |
| |
| transformForTranslateX: function(translateX) { |
| if (translateX === null) { |
| return ''; |
| } |
| return this.hasWillChange ? 'translateX(' + translateX + 'px)' : |
| 'translate3d(' + translateX + 'px, 0, 0)'; |
| }, |
| |
| moveDrawer: function(translateX) { |
| var s = this.$.drawer.style; |
| |
| if (this.hasTransform) { |
| s.transform = this.transformForTranslateX(translateX); |
| } else { |
| s.webkitTransform = this.transformForTranslateX(translateX); |
| } |
| } |
| |
| }); |
| |
| </script> |
| </polymer-element> |
| |
| <polymer-element name="paper-shadow" assetpath="../bower_components/paper-shadow/"> |
| |
| <template> |
| |
| <div id="shadow-bottom" fit="" animated?="[[animated]]" class="paper-shadow-bottom-z-[[z]]"></div> |
| <div id="shadow-top" fit="" animated?="[[animated]]" class="paper-shadow-top-z-[[z]]"></div> |
| |
| <content></content> |
| |
| </template> |
| |
| <script> |
| Polymer('paper-shadow',{ |
| |
| publish: { |
| |
| /** |
| * The z-depth of this shadow, from 0-5. Setting this property |
| * after element creation has no effect. Use `setZ()` instead. |
| * |
| * @attribute z |
| * @type number |
| * @default 1 |
| */ |
| z: 1, |
| |
| /** |
| * Set this to true to animate the shadow when setting a new |
| * `z` value. |
| * |
| * @attribute animated |
| * @type boolean |
| * @default false |
| */ |
| animated: false |
| |
| }, |
| |
| /** |
| * Set the z-depth of the shadow. This should be used after element |
| * creation instead of setting the z property directly. |
| * |
| * @method setZ |
| * @param {Number} newZ |
| */ |
| setZ: function(newZ) { |
| if (this.z !== newZ) { |
| this.$['shadow-bottom'].classList.remove('paper-shadow-bottom-z-' + this.z); |
| this.$['shadow-bottom'].classList.add('paper-shadow-bottom-z-' + newZ); |
| this.$['shadow-top'].classList.remove('paper-shadow-top-z-' + this.z); |
| this.$['shadow-top'].classList.add('paper-shadow-top-z-' + newZ); |
| this.z = newZ; |
| } |
| } |
| |
| }); |
| </script> |
| </polymer-element> |
| |
| <polymer-element name="core-meta" attributes="label type" hidden="" assetpath="../bower_components/core-meta/"> |
| <script> |
| |
| (function() { |
| |
| var SKIP_ID = 'meta'; |
| var metaData = {}, metaArray = {}; |
| |
| Polymer('core-meta', { |
| |
| /** |
| * The type of meta-data. All meta-data with the same type with be |
| * stored together. |
| * |
| * @attribute type |
| * @type string |
| * @default 'default' |
| */ |
| type: 'default', |
| |
| alwaysPrepare: true, |
| |
| ready: function() { |
| this.register(this.id); |
| }, |
| |
| get metaArray() { |
| var t = this.type; |
| if (!metaArray[t]) { |
| metaArray[t] = []; |
| } |
| return metaArray[t]; |
| }, |
| |
| get metaData() { |
| var t = this.type; |
| if (!metaData[t]) { |
| metaData[t] = {}; |
| } |
| return metaData[t]; |
| }, |
| |
| register: function(id, old) { |
| if (id && id !== SKIP_ID) { |
| this.unregister(this, old); |
| this.metaData[id] = this; |
| this.metaArray.push(this); |
| } |
| }, |
| |
| unregister: function(meta, id) { |
| delete this.metaData[id || meta.id]; |
| var i = this.metaArray.indexOf(meta); |
| if (i >= 0) { |
| this.metaArray.splice(i, 1); |
| } |
| }, |
| |
| /** |
| * Returns a list of all meta-data elements with the same type. |
| * |
| * @property list |
| * @type array |
| * @default [] |
| */ |
| get list() { |
| return this.metaArray; |
| }, |
| |
| /** |
| * Retrieves meta-data by ID. |
| * |
| * @method byId |
| * @param {String} id The ID of the meta-data to be returned. |
| * @returns Returns meta-data. |
| */ |
| byId: function(id) { |
| return this.metaData[id]; |
| } |
| |
| }); |
| |
| })(); |
| |
| </script> |
| </polymer-element> |
| <polymer-element name="core-transition" extends="core-meta" assetpath="../bower_components/core-transition/"> |
| |
| <script> |
| Polymer('core-transition', { |
| |
| type: 'transition', |
| |
| /** |
| * Run the animation. |
| * |
| * @method go |
| * @param {Node} node The node to apply the animation on |
| * @param {Object} state State info |
| */ |
| go: function(node, state) { |
| this.complete(node); |
| }, |
| |
| /** |
| * Set up the animation. This may include injecting a stylesheet, |
| * applying styles, creating a web animations object, etc.. This |
| * |
| * @method setup |
| * @param {Node} node The animated node |
| */ |
| setup: function(node) { |
| }, |
| |
| /** |
| * Tear down the animation. |
| * |
| * @method teardown |
| * @param {Node} node The animated node |
| */ |
| teardown: function(node) { |
| }, |
| |
| /** |
| * Called when the animation completes. This function also fires the |
| * `core-transitionend` event. |
| * |
| * @method complete |
| * @param {Node} node The animated node |
| */ |
| complete: function(node) { |
| this.fire('core-transitionend', null, node); |
| }, |
| |
| /** |
| * Utility function to listen to an event on a node once. |
| * |
| * @method listenOnce |
| * @param {Node} node The animated node |
| * @param {string} event Name of an event |
| * @param {Function} fn Event handler |
| * @param {Array} args Additional arguments to pass to `fn` |
| */ |
| listenOnce: function(node, event, fn, args) { |
| var self = this; |
| var listener = function() { |
| fn.apply(self, args); |
| node.removeEventListener(event, listener, false); |
| } |
| node.addEventListener(event, listener, false); |
| } |
| |
| }); |
| </script> |
| </polymer-element> |
| |
| |
| <polymer-element name="core-key-helper" assetpath="../bower_components/core-overlay/"> |
| <script> |
| Polymer('core-key-helper', { |
| ENTER_KEY: 13, |
| ESCAPE_KEY: 27 |
| }); |
| </script> |
| </polymer-element> |
| |
| <polymer-element name="core-overlay-layer" assetpath="../bower_components/core-overlay/"> |
| <template> |
| <style> |
| :host { |
| position: fixed; |
| top: 0; |
| left: 0; |
| z-index: 1000; |
| display: none; |
| } |
| |
| :host(.core-opened) { |
| display: block; |
| } |
| </style> |
| <content></content> |
| </template> |
| <script> |
| (function() { |
| |
| Polymer('core-overlay-layer', { |
| publish: { |
| opened: false |
| }, |
| openedChanged: function() { |
| this.classList.toggle('core-opened', this.opened); |
| }, |
| /** |
| * Adds an element to the overlay layer |
| */ |
| addElement: function(element) { |
| if (!this.parentNode) { |
| document.querySelector('body').appendChild(this); |
| } |
| if (element.parentNode !== this) { |
| element.__contents = []; |
| var ip$ = element.querySelectorAll('content'); |
| for (var i=0, l=ip$.length, n; (i<l) && (n = ip$[i]); i++) { |
| this.moveInsertedElements(n); |
| this.cacheDomLocation(n); |
| n.parentNode.removeChild(n); |
| element.__contents.push(n); |
| } |
| this.cacheDomLocation(element); |
| this.updateEventController(element); |
| var h = this.makeHost(); |
| h.shadowRoot.appendChild(element); |
| element.__host = h; |
| } |
| }, |
| makeHost: function() { |
| var h = document.createElement('overlay-host'); |
| h.createShadowRoot(); |
| this.appendChild(h); |
| return h; |
| }, |
| moveInsertedElements: function(insertionPoint) { |
| var n$ = insertionPoint.getDistributedNodes(); |
| var parent = insertionPoint.parentNode; |
| insertionPoint.__contents = []; |
| for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) { |
| this.cacheDomLocation(n); |
| this.updateEventController(n); |
| insertionPoint.__contents.push(n); |
| parent.appendChild(n); |
| } |
| }, |
| updateEventController: function(element) { |
| element.eventController = this.element.findController(element); |
| }, |
| /** |
| * Removes an element from the overlay layer |
| */ |
| removeElement: function(element) { |
| element.eventController = null; |
| this.replaceElement(element); |
| var h = element.__host; |
| if (h) { |
| h.parentNode.removeChild(h); |
| } |
| }, |
| replaceElement: function(element) { |
| if (element.__contents) { |
| for (var i=0, c$=element.__contents, c; (c=c$[i]); i++) { |
| this.replaceElement(c); |
| } |
| element.__contents = null; |
| } |
| if (element.__parentNode) { |
| var n = element.__nextElementSibling && element.__nextElementSibling |
| === element.__parentNode ? element.__nextElementSibling : null; |
| element.__parentNode.insertBefore(element, n); |
| } |
| }, |
| cacheDomLocation: function(element) { |
| element.__nextElementSibling = element.nextElementSibling; |
| element.__parentNode = element.parentNode; |
| } |
| }); |
| |
| })(); |
| </script> |
| </polymer-element> |
| <polymer-element name="core-overlay" assetpath="../bower_components/core-overlay/"> |
| <script> |
| (function() { |
| |
| Polymer('core-overlay',Polymer.mixin({ |
| |
| publish: { |
| /** |
| * The target element that will be shown when the overlay is |
| * opened. If unspecified, the core-overlay itself is the target. |
| * |
| * @attribute target |
| * @type Object |
| * @default the overlay element |
| */ |
| target: null, |
| |
| |
| /** |
| * A `core-overlay`'s size is guaranteed to be |
| * constrained to the window size. To achieve this, the sizingElement |
| * is sized with a max-height/width. By default this element is the |
| * target element, but it can be specifically set to a specific element |
| * inside the target if that is more appropriate. This is useful, for |
| * example, when a region inside the overlay should scroll if needed. |
| * |
| * @attribute sizingTarget |
| * @type Object |
| * @default the target element |
| */ |
| sizingTarget: null, |
| |
| /** |
| * Set opened to true to show an overlay and to false to hide it. |
| * A `core-overlay` may be made initially opened by setting its |
| * `opened` attribute. |
| * @attribute opened |
| * @type boolean |
| * @default false |
| */ |
| opened: false, |
| |
| /** |
| * If true, the overlay has a backdrop darkening the rest of the screen. |
| * The backdrop element is attached to the document body and may be styled |
| * with the class `core-overlay-backdrop`. When opened the `core-opened` |
| * class is applied. |
| * |
| * @attribute backdrop |
| * @type boolean |
| * @default false |
| */ |
| backdrop: false, |
| |
| /** |
| * If true, the overlay is guaranteed to display above page content. |
| * |
| * @attribute layered |
| * @type boolean |
| * @default false |
| */ |
| layered: false, |
| |
| /** |
| * By default an overlay will close automatically if the user |
| * taps outside it or presses the escape key. Disable this |
| * behavior by setting the `autoCloseDisabled` property to true. |
| * @attribute autoCloseDisabled |
| * @type boolean |
| * @default false |
| */ |
| autoCloseDisabled: false, |
| |
| /** |
| * By default an overlay will focus its target or an element inside |
| * it with the `autoFocus` attribute. Disable this |
| * behavior by setting the `autoFocusDisabled` property to true. |
| * @attribute autoFocusDisabled |
| * @type boolean |
| * @default false |
| */ |
| autoFocusDisabled: false, |
| |
| /** |
| * This property specifies an attribute on elements that should |
| * close the overlay on tap. Should not set `closeSelector` if this |
| * is set. |
| * |
| * @attribute closeAttribute |
| * @type string |
| * @default "core-overlay-toggle" |
| */ |
| closeAttribute: 'core-overlay-toggle', |
| |
| /** |
| * This property specifies a selector matching elements that should |
| * close the overlay on tap. Should not set `closeAttribute` if this |
| * is set. |
| * |
| * @attribute closeSelector |
| * @type string |
| * @default "" |
| */ |
| closeSelector: '', |
| |
| /** |
| * The transition property specifies a string which identifies a |
| * <a href="../core-transition/">`core-transition`</a> element that |
| * will be used to help the overlay open and close. The default |
| * `core-transition-fade` will cause the overlay to fade in and out. |
| * |
| * @attribute transition |
| * @type string |
| * @default 'core-transition-fade' |
| */ |
| transition: 'core-transition-fade' |
| |
| }, |
| |
| captureEventName: 'tap', |
| targetListeners: { |
| 'tap': 'tapHandler', |
| 'keydown': 'keydownHandler', |
| 'core-transitionend': 'transitionend' |
| }, |
| |
| attached: function() { |
| this.resizerAttachedHandler(); |
| }, |
| |
| detached: function() { |
| this.resizerDetachedHandler(); |
| }, |
| |
| resizerShouldNotify: function() { |
| return this.opened; |
| }, |
| |
| registerCallback: function(element) { |
| this.layer = document.createElement('core-overlay-layer'); |
| this.keyHelper = document.createElement('core-key-helper'); |
| this.meta = document.createElement('core-transition'); |
| this.scrim = document.createElement('div'); |
| this.scrim.className = 'core-overlay-backdrop'; |
| }, |
| |
| ready: function() { |
| this.target = this.target || this; |
| // flush to ensure styles are installed before paint |
| Polymer.flush(); |
| }, |
| |
| /** |
| * Toggle the opened state of the overlay. |
| * @method toggle |
| */ |
| toggle: function() { |
| this.opened = !this.opened; |
| }, |
| |
| /** |
| * Open the overlay. This is equivalent to setting the `opened` |
| * property to true. |
| * @method open |
| */ |
| open: function() { |
| this.opened = true; |
| }, |
| |
| /** |
| * Close the overlay. This is equivalent to setting the `opened` |
| * property to false. |
| * @method close |
| */ |
| close: function() { |
| this.opened = false; |
| }, |
| |
| domReady: function() { |
| this.ensureTargetSetup(); |
| }, |
| |
| targetChanged: function(old) { |
| if (this.target) { |
| // really make sure tabIndex is set |
| if (this.target.tabIndex < 0) { |
| this.target.tabIndex = -1; |
| } |
| this.addElementListenerList(this.target, this.targetListeners); |
| this.target.style.display = 'none'; |
| this.target.__overlaySetup = false; |
| } |
| if (old) { |
| this.removeElementListenerList(old, this.targetListeners); |
| var transition = this.getTransition(); |
| if (transition) { |
| transition.teardown(old); |
| } else { |
| old.style.position = ''; |
| old.style.outline = ''; |
| } |
| old.style.display = ''; |
| } |
| }, |
| |
| transitionChanged: function(old) { |
| if (!this.target) { |
| return; |
| } |
| if (old) { |
| this.getTransition(old).teardown(this.target); |
| } |
| this.target.__overlaySetup = false; |
| }, |
| |
| // NOTE: wait to call this until we're as sure as possible that target |
| // is styled. |
| ensureTargetSetup: function() { |
| if (!this.target || this.target.__overlaySetup) { |
| return; |
| } |
| if (!this.sizingTarget) { |
| this.sizingTarget = this.target; |
| } |
| this.target.__overlaySetup = true; |
| this.target.style.display = ''; |
| var transition = this.getTransition(); |
| if (transition) { |
| transition.setup(this.target); |
| } |
| var style = this.target.style; |
| var computed = getComputedStyle(this.target); |
| if (computed.position === 'static') { |
| style.position = 'fixed'; |
| } |
| style.outline = 'none'; |
| style.display = 'none'; |
| }, |
| |
| openedChanged: function() { |
| this.transitioning = true; |
| this.ensureTargetSetup(); |
| this.prepareRenderOpened(); |
| // async here to allow overlay layer to become visible. |
| this.async(function() { |
| this.target.style.display = ''; |
| // force layout to ensure transitions will go |
| this.target.offsetWidth; |
| this.renderOpened(); |
| }); |
| this.fire('core-overlay-open', this.opened); |
| }, |
| |
| // tasks which must occur before opening; e.g. making the element visible |
| prepareRenderOpened: function() { |
| if (this.opened) { |
| addOverlay(this); |
| } |
| this.prepareBackdrop(); |
| // async so we don't auto-close immediately via a click. |
| this.async(function() { |
| if (!this.autoCloseDisabled) { |
| this.enableElementListener(this.opened, document, |
| this.captureEventName, 'captureHandler', true); |
| } |
| }); |
| this.enableElementListener(this.opened, window, 'resize', |
| 'resizeHandler'); |
| |
| if (this.opened) { |
| // force layout so SD Polyfill renders |
| this.target.offsetHeight; |
| this.discoverDimensions(); |
| // if we are showing, then take care when positioning |
| this.preparePositioning(); |
| this.positionTarget(); |
| this.updateTargetDimensions(); |
| this.finishPositioning(); |
| if (this.layered) { |
| this.layer.addElement(this.target); |
| this.layer.opened = this.opened; |
| } |
| } |
| }, |
| |
| // tasks which cause the overlay to actually open; typically play an |
| // animation |
| renderOpened: function() { |
| this.notifyResize(); |
| var transition = this.getTransition(); |
| if (transition) { |
| transition.go(this.target, {opened: this.opened}); |
| } else { |
| this.transitionend(); |
| } |
| this.renderBackdropOpened(); |
| }, |
| |
| // finishing tasks; typically called via a transition |
| transitionend: function(e) { |
| // make sure this is our transition event. |
| if (e && e.target !== this.target) { |
| return; |
| } |
| this.transitioning = false; |
| if (!this.opened) { |
| this.resetTargetDimensions(); |
| this.target.style.display = 'none'; |
| this.completeBackdrop(); |
| removeOverlay(this); |
| if (this.layered) { |
| if (!currentOverlay()) { |
| this.layer.opened = this.opened; |
| } |
| this.layer.removeElement(this.target); |
| } |
| } |
| this.fire('core-overlay-' + (this.opened ? 'open' : 'close') + |
| '-completed'); |
| this.applyFocus(); |
| }, |
| |
| prepareBackdrop: function() { |
| if (this.backdrop && this.opened) { |
| if (!this.scrim.parentNode) { |
| document.body.appendChild(this.scrim); |
| this.scrim.style.zIndex = currentOverlayZ() - 1; |
| } |
| trackBackdrop(this); |
| } |
| }, |
| |
| renderBackdropOpened: function() { |
| if (this.backdrop && getBackdrops().length < 2) { |
| this.scrim.classList.toggle('core-opened', this.opened); |
| } |
| }, |
| |
| completeBackdrop: function() { |
| if (this.backdrop) { |
| trackBackdrop(this); |
| if (getBackdrops().length === 0) { |
| this.scrim.parentNode.removeChild(this.scrim); |
| } |
| } |
| }, |
| |
| preparePositioning: function() { |
| this.target.style.transition = this.target.style.webkitTransition = 'none'; |
| this.target.style.transform = this.target.style.webkitTransform = 'none'; |
| this.target.style.display = ''; |
| }, |
| |
| discoverDimensions: function() { |
| if (this.dimensions) { |
| return; |
| } |
| var target = getComputedStyle(this.target); |
| var sizer = getComputedStyle(this.sizingTarget); |
| this.dimensions = { |
| position: { |
| v: target.top !== 'auto' ? 'top' : (target.bottom !== 'auto' ? |
| 'bottom' : null), |
| h: target.left !== 'auto' ? 'left' : (target.right !== 'auto' ? |
| 'right' : null), |
| css: target.position |
| }, |
| size: { |
| v: sizer.maxHeight !== 'none', |
| h: sizer.maxWidth !== 'none' |
| }, |
| margin: { |
| top: parseInt(target.marginTop) || 0, |
| right: parseInt(target.marginRight) || 0, |
| bottom: parseInt(target.marginBottom) || 0, |
| left: parseInt(target.marginLeft) || 0 |
| } |
| }; |
| }, |
| |
| finishPositioning: function(target) { |
| this.target.style.display = 'none'; |
| this.target.style.transform = this.target.style.webkitTransform = ''; |
| // force layout to avoid application of transform |
| this.target.offsetWidth; |
| this.target.style.transition = this.target.style.webkitTransition = ''; |
| }, |
| |
| getTransition: function(name) { |
| return this.meta.byId(name || this.transition); |
| }, |
| |
| getFocusNode: function() { |
| return this.target.querySelector('[autofocus]') || this.target; |
| }, |
| |
| applyFocus: function() { |
| var focusNode = this.getFocusNode(); |
| if (this.opened) { |
| if (!this.autoFocusDisabled) { |
| focusNode.focus(); |
| } |
| } else { |
| focusNode.blur(); |
| if (currentOverlay() == this) { |
| console.warn('Current core-overlay is attempting to focus itself as next! (bug)'); |
| } else { |
| focusOverlay(); |
| } |
| } |
| }, |
| |
| positionTarget: function() { |
| // fire positioning event |
| this.fire('core-overlay-position', {target: this.target, |
| sizingTarget: this.sizingTarget, opened: this.opened}); |
| if (!this.dimensions.position.v) { |
| this.target.style.top = '0px'; |
| } |
| if (!this.dimensions.position.h) { |
| this.target.style.left = '0px'; |
| } |
| }, |
| |
| updateTargetDimensions: function() { |
| this.sizeTarget(); |
| this.repositionTarget(); |
| }, |
| |
| sizeTarget: function() { |
| this.sizingTarget.style.boxSizing = 'border-box'; |
| var dims = this.dimensions; |
| var rect = this.target.getBoundingClientRect(); |
| if (!dims.size.v) { |
| this.sizeDimension(rect, dims.position.v, 'top', 'bottom', 'Height'); |
| } |
| if (!dims.size.h) { |
| this.sizeDimension(rect, dims.position.h, 'left', 'right', 'Width'); |
| } |
| }, |
| |
| sizeDimension: function(rect, positionedBy, start, end, extent) { |
| var dims = this.dimensions; |
| var flip = (positionedBy === end); |
| var m = flip ? start : end; |
| var ws = window['inner' + extent]; |
| var o = dims.margin[m] + (flip ? ws - rect[end] : |
| rect[start]); |
| var offset = 'offset' + extent; |
| var o2 = this.target[offset] - this.sizingTarget[offset]; |
| this.sizingTarget.style['max' + extent] = (ws - o - o2) + 'px'; |
| }, |
| |
| // vertically and horizontally center if not positioned |
| repositionTarget: function() { |
| // only center if position fixed. |
| if (this.dimensions.position.css !== 'fixed') { |
| return; |
| } |
| if (!this.dimensions.position.v) { |
| var t = (window.innerHeight - this.target.offsetHeight) / 2; |
| t -= this.dimensions.margin.top; |
| this.target.style.top = t + 'px'; |
| } |
| |
| if (!this.dimensions.position.h) { |
| var l = (window.innerWidth - this.target.offsetWidth) / 2; |
| l -= this.dimensions.margin.left; |
| this.target.style.left = l + 'px'; |
| } |
| }, |
| |
| resetTargetDimensions: function() { |
| if (!this.dimensions || !this.dimensions.size.v) { |
| this.sizingTarget.style.maxHeight = ''; |
| this.target.style.top = ''; |
| } |
| if (!this.dimensions || !this.dimensions.size.h) { |
| this.sizingTarget.style.maxWidth = ''; |
| this.target.style.left = ''; |
| } |
| this.dimensions = null; |
| }, |
| |
| tapHandler: function(e) { |
| // closeSelector takes precedence since closeAttribute has a default non-null value. |
| if (e.target && |
| (this.closeSelector && e.target.matches(this.closeSelector)) || |
| (this.closeAttribute && e.target.hasAttribute(this.closeAttribute))) { |
| this.toggle(); |
| } else { |
| if (this.autoCloseJob) { |
| this.autoCloseJob.stop(); |
| this.autoCloseJob = null; |
| } |
| } |
| }, |
| |
| // We use the traditional approach of capturing events on document |
| // to to determine if the overlay needs to close. However, due to |
| // ShadowDOM event retargeting, the event target is not useful. Instead |
| // of using it, we attempt to close asynchronously and prevent the close |
| // if a tap event is immediately heard on the target. |
| // TODO(sorvell): This approach will not work with modal. For |
| // this we need a scrim. |
| captureHandler: function(e) { |
| if (!this.autoCloseDisabled && (currentOverlay() == this)) { |
| this.autoCloseJob = this.job(this.autoCloseJob, function() { |
| this.close(); |
| }); |
| } |
| }, |
| |
| keydownHandler: function(e) { |
| if (!this.autoCloseDisabled && (e.keyCode == this.keyHelper.ESCAPE_KEY)) { |
| this.close(); |
| e.stopPropagation(); |
| } |
| }, |
| |
| /** |
| * Extensions of core-overlay should implement the `resizeHandler` |
| * method to adjust the size and position of the overlay when the |
| * browser window resizes. |
| * @method resizeHandler |
| */ |
| resizeHandler: function() { |
| this.updateTargetDimensions(); |
| }, |
| |
| // TODO(sorvell): these utility methods should not be here. |
| addElementListenerList: function(node, events) { |
| for (var i in events) { |
| this.addElementListener(node, i, events[i]); |
| } |
| }, |
| |
| removeElementListenerList: function(node, events) { |
| for (var i in events) { |
| this.removeElementListener(node, i, events[i]); |
| } |
| }, |
| |
| enableElementListener: function(enable, node, event, methodName, capture) { |
| if (enable) { |
| this.addElementListener(node, event, methodName, capture); |
| } else { |
| this.removeElementListener(node, event, methodName, capture); |
| } |
| }, |
| |
| addElementListener: function(node, event, methodName, capture) { |
| var fn = this._makeBoundListener(methodName); |
| if (node && fn) { |
| Polymer.addEventListener(node, event, fn, capture); |
| } |
| }, |
| |
| removeElementListener: function(node, event, methodName, capture) { |
| var fn = this._makeBoundListener(methodName); |
| if (node && fn) { |
| Polymer.removeEventListener(node, event, fn, capture); |
| } |
| }, |
| |
| _makeBoundListener: function(methodName) { |
| var self = this, method = this[methodName]; |
| if (!method) { |
| return; |
| } |
| var bound = '_bound' + methodName; |
| if (!this[bound]) { |
| this[bound] = function(e) { |
| method.call(self, e); |
| }; |
| } |
| return this[bound]; |
| } |
| |
| }, Polymer.CoreResizer)); |
| |
| // TODO(sorvell): This should be an element with private state so it can |
| // be independent of overlay. |
| // track overlays for z-index and focus managemant |
| var overlays = []; |
| function addOverlay(overlay) { |
| var z0 = currentOverlayZ(); |
| overlays.push(overlay); |
| var z1 = currentOverlayZ(); |
| if (z1 <= z0) { |
| applyOverlayZ(overlay, z0); |
| } |
| } |
| |
| function removeOverlay(overlay) { |
| var i = overlays.indexOf(overlay); |
| if (i >= 0) { |
| overlays.splice(i, 1); |
| setZ(overlay, ''); |
| } |
| } |
| |
| function applyOverlayZ(overlay, aboveZ) { |
| setZ(overlay.target, aboveZ + 2); |
| } |
| |
| function setZ(element, z) { |
| element.style.zIndex = z; |
| } |
| |
| function currentOverlay() { |
| return overlays[overlays.length-1]; |
| } |
| |
| var DEFAULT_Z = 10; |
| |
| function currentOverlayZ() { |
| var z; |
| var current = currentOverlay(); |
| if (current) { |
| var z1 = window.getComputedStyle(current.target).zIndex; |
| if (!isNaN(z1)) { |
| z = Number(z1); |
| } |
| } |
| return z || DEFAULT_Z; |
| } |
| |
| function focusOverlay() { |
| var current = currentOverlay(); |
| // We have to be careful to focus the next overlay _after_ any current |
| // transitions are complete (due to the state being toggled prior to the |
| // transition). Otherwise, we risk infinite recursion when a transitioning |
| // (closed) overlay becomes the current overlay. |
| // |
| // NOTE: We make the assumption that any overlay that completes a transition |
| // will call into focusOverlay to kick the process back off. Currently: |
| // transitionend -> applyFocus -> focusOverlay. |
| if (current && !current.transitioning) { |
| current.applyFocus(); |
| } |
| } |
| |
| var backdrops = []; |
| function trackBackdrop(element) { |
| if (element.opened) { |
| backdrops.push(element); |
| } else { |
| var i = backdrops.indexOf(element); |
| if (i >= 0) { |
| backdrops.splice(i, 1); |
| } |
| } |
| } |
| |
| function getBackdrops() { |
| return backdrops; |
| } |
| })(); |
| </script> |
| </polymer-element> |
| |
| <polymer-element name="core-transition-css" extends="core-transition" attributes="transitionType" assetpath="../bower_components/core-transition/"> |
| <template> |
| <style no-shim="">/* Copyright (c) 2014 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 */ |
| |
| :host(.core-transition) { |
| outline: none; |
| overflow: auto; |
| opacity: 0; |
| -webkit-transition: -webkit-transform 0.2s ease-in-out, opacity 0.2s ease-in; |
| transition: transform 0.2s ease-in-out, opacity 0.2s ease-in; |
| } |
| |
| |
| :host(.core-transition.core-opened) { |
| opacity: 1; |
| -webkit-transform: translateZ(0); |
| transform: translateZ(0); |
| } |
| |
| :host(.core-transition-center) { |
| -webkit-transform: scale(0.5); |
| transform: scale(0.5); |
| } |
| |
| :host(.core-transition-top) { |
| -webkit-transform: translateY(-200%); |
| transform: translateY(-200%); |
| } |
| |
| :host(.core-transition-bottom) { |
| -webkit-transform: translateY(200%); |
| transform: translateY(200%); |
| } |
| |
| :host(.core-transition-left) { |
| -webkit-transform: translateX(-200%); |
| transform: translateX(-200%); |
| } |
| |
| :host(.core-transition-right) { |
| -webkit-transform: translateX(200%); |
| transform: translateX(200%); |
| }</style> |
| </template> |
| <script> |
| |
| Polymer('core-transition-css', { |
| |
| /** |
| * The class that will be applied to all animated nodes. |
| * |
| * @attribute baseClass |
| * @type string |
| * @default "core-transition" |
| */ |
| baseClass: 'core-transition', |
| |
| /** |
| * The class that will be applied to nodes in the opened state. |
| * |
| * @attribute openedClass |
| * @type string |
| * @default "core-opened" |
| */ |
| openedClass: 'core-opened', |
| |
| /** |
| * The class that will be applied to nodes in the closed state. |
| * |
| * @attribute closedClass |
| * @type string |
| * @default "core-closed" |
| */ |
| closedClass: 'core-closed', |
| |
| /** |
| * Event to listen to for animation completion. |
| * |
| * @attribute completeEventName |
| * @type string |
| * @default "transitionEnd" |
| */ |
| completeEventName: 'transitionend', |
| |
| publish: { |
| /** |
| * A secondary configuration attribute for the animation. The class |
| * `<baseClass>-<transitionType` is applied to the animated node during |
| * `setup`. |
| * |
| * @attribute transitionType |
| * @type string |
| */ |
| transitionType: null |
| }, |
| |
| registerCallback: function(element) { |
| this.transitionStyle = element.templateContent().firstElementChild; |
| }, |
| |
| // template is just for loading styles, we don't need a shadowRoot |
| fetchTemplate: function() { |
| return null; |
| }, |
| |
| go: function(node, state) { |
| if (state.opened !== undefined) { |
| this.transitionOpened(node, state.opened); |
| } |
| }, |
| |
| setup: function(node) { |
| if (!node._hasTransitionStyle) { |
| if (!node.shadowRoot) { |
| node.createShadowRoot().innerHTML = '<content></content>'; |
| } |
| this.installScopeStyle(this.transitionStyle, 'transition', |
| node.shadowRoot); |
| node._hasTransitionStyle = true; |
| } |
| node.classList.add(this.baseClass); |
| if (this.transitionType) { |
| node.classList.add(this.baseClass + '-' + this.transitionType); |
| } |
| }, |
| |
| teardown: function(node) { |
| node.classList.remove(this.baseClass); |
| if (this.transitionType) { |
| node.classList.remove(this.baseClass + '-' + this.transitionType); |
| } |
| }, |
| |
| transitionOpened: function(node, opened) { |
| this.listenOnce(node, this.completeEventName, function() { |
| if (!opened) { |
| node.classList.remove(this.closedClass); |
| } |
| this.complete(node); |
| }); |
| node.classList.toggle(this.openedClass, opened); |
| node.classList.toggle(this.closedClass, !opened); |
| } |
| |
| }); |
| </script> |
| </polymer-element> |
| |
| <core-transition-css id="core-transition-fade"></core-transition-css> |
| <core-transition-css id="core-transition-center" transitiontype="center"></core-transition-css> |
| <core-transition-css id="core-transition-top" transitiontype="top"></core-transition-css> |
| <core-transition-css id="core-transition-bottom" transitiontype="bottom"></core-transition-css> |
| <core-transition-css id="core-transition-left" transitiontype="left"></core-transition-css> |
| <core-transition-css id="core-transition-right" transitiontype="right"></core-transition-css> |
| <polymer-element name="paper-dialog-base" extends="core-overlay" role="dialog" on-core-overlay-open="{{openAction}}" assetpath="../bower_components/paper-dialog/"> |
| |
| <script> |
| |
| Polymer('paper-dialog-base',{ |
| |
| publish: { |
| |
| /** |
| * The title of the dialog. |
| * |
| * @attribute heading |
| * @type string |
| * @default '' |
| */ |
| heading: '', |
| |
| /** |
| * @attribute transition |
| * @type string |
| * @default '' |
| */ |
| transition: '', |
| |
| /** |
| * @attribute layered |
| * @type boolean |
| * @default true |
| */ |
| layered: true |
| }, |
| |
| ready: function() { |
| this.super(); |
| this.sizingTarget = this.$.scroller; |
| }, |
| |
| headingChanged: function(old) { |
| var label = this.getAttribute('aria-label'); |
| if (!label || label === old) { |
| this.setAttribute('aria-label', this.heading); |
| } |
| }, |
| |
| openAction: function() { |
| if (this.$.scroller.scrollTop) { |
| this.$.scroller.scrollTop = 0; |
| } |
| } |
| |
| }); |
| |
| </script> |
| |
| </polymer-element> |
| <polymer-element name="paper-dialog" extends="paper-dialog-base" role="dialog" layout="" vertical="" assetpath="../bower_components/paper-dialog/"> |
| |
| <template> |
| |
| <style> |
| :host { |
| background: #fff; |
| color: rgba(0, 0, 0, 0.87); |
| margin: 32px; |
| overflow: visible !important; |
| } |
| |
| h1 { |
| font-size: 20px; |
| } |
| |
| #scroller { |
| overflow: auto; |
| box-sizing: border-box; |
| padding: 24px; |
| } |
| </style> |
| |
| <paper-shadow z="3" fit=""></paper-shadow> |
| |
| <!-- need this because the host needs to be overflow: visible --> |
| <div id="scroller" relative="" flex="" auto=""> |
| <template if="{{heading}}"> |
| <h1>{{heading}}</h1> |
| </template> |
| |
| <content></content> |
| </div> |
| |
| </template> |
| |
| <script>Polymer('paper-dialog');</script></polymer-element> |
| |
| <polymer-element name="paper-action-dialog" extends="paper-dialog-base" role="dialog" layout="" vertical="" assetpath="../bower_components/paper-dialog/"> |
| |
| <template> |
| |
| <style> |
| :host { |
| background: #fff; |
| color: rgba(0, 0, 0, 0.87); |
| margin: 32px; |
| overflow: visible !important; |
| } |
| |
| h1 { |
| font-size: 20px; |
| } |
| |
| #scroller { |
| overflow: auto; |
| box-sizing: border-box; |
| padding: 24px 24px 0 24px; |
| } |
| |
| #actions { |
| padding: 16px; |
| } |
| </style> |
| |
| <paper-shadow z="3" fit=""></paper-shadow> |
| |
| <!-- need this because the host needs to be overflow: visible --> |
| <div id="scroller" relative="" flex="" auto=""> |
| <template if="{{heading}}"> |
| <h1>{{heading}}</h1> |
| </template> |
| |
| <content select=":not([affirmative]):not([dismissive])"></content> |
| </div> |
| |
| <div id="actions" relative="" layout="" horizontal=""> |
| <content select="[dismissive]"></content> |
| <div flex=""></div> |
| <content select="[affirmative]"></content> |
| </div> |
| |
| </template> |
| |
| <script> |
| |
| Polymer('paper-action-dialog',{ |
| |
| publish: { |
| |
| /** |
| * @attribute closeSelector |
| * @type string |
| * @default '[affirmative],[dismissive]' |
| */ |
| closeSelector: '[affirmative],[dismissive]' |
| } |
| |
| }); |
| |
| </script> |
| |
| </polymer-element> |
| |
| <polymer-element name="core-header-panel" assetpath="../bower_components/core-header-panel/"> |
| <template> |
| |
| <style>/* |
| Copyright (c) 2014 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 |
| */ |
| |
| :host { |
| display: block; |
| position: relative; |
| } |
| |
| #outerContainer { |
| position: absolute; |
| top: 0; |
| right: 0; |
| bottom: 0; |
| left: 0; |
| } |
| |
| #mainPanel { |
| position: relative; |
| } |
| |
| #mainContainer { |
| position: relative; |
| overflow-y: auto; |
| overflow-x: hidden; |
| -webkit-overflow-scrolling: touch; |
| -webkit-transform: translateZ(0); |
| transform: translateZ(0); /* github.com/Polymer/core-header-panel/issues/27 */ |
| } |
| |
| #dropShadow { |
| position: absolute; |
| top: 0; |
| left: 0; |
| right: 0; |
| height: 6px; |
| box-shadow: inset 0px 5px 6px -3px rgba(0, 0, 0, 0.4); |
| } |
| |
| #dropShadow.hidden { |
| display: none; |
| } |
| |
| /* |
| mode: scroll |
| */ |
| :host([mode=scroll]) #mainContainer { |
| overflow: visible; |
| } |
| |
| :host([mode=scroll]) #outerContainer { |
| overflow-y: auto; |
| overflow-x: hidden; |
| -webkit-overflow-scrolling: touch; |
| } |
| |
| /* |
| mode: cover |
| */ |
| :host([mode=cover]) #mainPanel { |
| position: static; |
| } |
| |
| :host([mode=cover]) #mainContainer { |
| position: absolute; |
| top: 0; |
| right: 0; |
| bottom: 0; |
| left: 0; |
| } |
| |
| :host([mode=cover]) #dropShadow { |
| position: static; |
| width: 100%; |
| } |
| </style> |
| |
| <div id="outerContainer" vertical="" layout=""> |
| |
| <content id="headerContent" select="core-toolbar, .core-header"></content> |
| |
| <div id="mainPanel" flex="" vertical="" layout=""> |
| |
| <div id="mainContainer" flex?="{{mode !== 'cover'}}"> |
| <content id="mainContent" select="*"></content> |
| </div> |
| |
| <div id="dropShadow"></div> |
| |
| </div> |
| |
| </div> |
| |
| </template> |
| <script> |
| |
| Polymer('core-header-panel',{ |
| |
| /** |
| * Fired when the content has been scrolled. `event.detail.target` returns |
| * the scrollable element which you can use to access scroll info such as |
| * `scrollTop`. |
| * |
| * <core-header-panel on-scroll="{{scrollHandler}}"> |
| * ... |
| * </core-header-panel> |
| * |
| * |
| * scrollHandler: function(event) { |
| * var scroller = event.detail.target; |
| * console.log(scroller.scrollTop); |
| * } |
| * |
| * @event scroll |
| */ |
| |
| publish: { |
| /** |
| * Controls header and scrolling behavior. Options are |
| * `standard`, `seamed`, `waterfall`, `waterfall-tall`, `scroll` and |
| * `cover`. Default is `standard`. |
| * |
| * `standard`: The header is a step above the panel. The header will consume the |
| * panel at the point of entry, preventing it from passing through to the |
| * opposite side. |
| * |
| * `seamed`: The header is presented as seamed with the panel. |
| * |
| * `waterfall`: Similar to standard mode, but header is initially presented as |
| * seamed with panel, but then separates to form the step. |
| * |
| * `waterfall-tall`: The header is initially taller (`tall` class is added to |
| * the header). As the user scrolls, the header separates (forming an edge) |
| * while condensing (`tall` class is removed from the header). |
| * |
| * `scroll`: The header keeps its seam with the panel, and is pushed off screen. |
| * |
| * `cover`: The panel covers the whole `core-header-panel` including the |
| * header. This allows user to style the panel in such a way that the panel is |
| * partially covering the header. |
| * |
| * <style> |
| * core-header-panel[mode=cover]::shadow #mainContainer { |
| * left: 80px; |
| * } |
| * .content { |
| * margin: 60px 60px 60px 0; |
| * } |
| * </style> |
| * |
| * <core-header-panel mode="cover"> |
| * <core-toolbar class="tall"> |
| * <core-icon-button icon="menu"></core-icon-button> |
| * </core-toolbar> |
| * <div class="content"></div> |
| * </core-header-panel> |
| * |
| * @attribute mode |
| * @type string |
| * @default '' |
| */ |
| mode: {value: '', reflect: true}, |
| |
| /** |
| * The class used in waterfall-tall mode. Change this if the header |
| * accepts a different class for toggling height, e.g. "medium-tall" |
| * |
| * @attribute tallClass |
| * @type string |
| * @default 'tall' |
| */ |
| tallClass: 'tall', |
| |
| /** |
| * If true, the drop-shadow is always shown no matter what mode is set to. |
| * |
| * @attribute shadow |
| * @type boolean |
| * @default false |
| */ |
| shadow: false |
| }, |
| |
| animateDuration: 200, |
| |
| modeConfigs: { |
| shadowMode: {'waterfall': 1, 'waterfall-tall': 1}, |
| noShadow: {'seamed': 1, 'cover': 1, 'scroll': 1}, |
| tallMode: {'waterfall-tall': 1}, |
| outerScroll: {'scroll': 1} |
| }, |
| |
| ready: function() { |
| this.scrollHandler = this.scroll.bind(this); |
| this.addListener(); |
| }, |
| |
| detached: function() { |
| this.removeListener(this.mode); |
| }, |
| |
| addListener: function() { |
| this.scroller.addEventListener('scroll', this.scrollHandler); |
| }, |
| |
| removeListener: function(mode) { |
| var s = this.getScrollerForMode(mode); |
| s.removeEventListener('scroll', this.scrollHandler); |
| }, |
| |
| domReady: function() { |
| this.async('scroll'); |
| }, |
| |
| modeChanged: function(old) { |
| var configs = this.modeConfigs; |
| var header = this.header; |
| if (header) { |
| // in tallMode it may add tallClass to the header; so do the cleanup |
| // when mode is changed from tallMode to not tallMode |
| if (configs.tallMode[old] && !configs.tallMode[this.mode]) { |
| header.classList.remove(this.tallClass); |
| this.async(function() { |
| header.classList.remove('animate'); |
| }, null, this.animateDuration); |
| } else { |
| header.classList.toggle('animate', configs.tallMode[this.mode]); |
| } |
| } |
| if (configs && (configs.outerScroll[this.mode] || configs.outerScroll[old])) { |
| this.removeListener(old); |
| this.addListener(); |
| } |
| this.scroll(); |
| }, |
| |
| get header() { |
| return this.$.headerContent.getDistributedNodes()[0]; |
| }, |
| |
| getScrollerForMode: function(mode) { |
| return this.modeConfigs.outerScroll[mode] ? |
| this.$.outerContainer : this.$.mainContainer; |
| }, |
| |
| /** |
| * Returns the scrollable element. |
| * |
| * @property scroller |
| * @type Object |
| */ |
| get scroller() { |
| return this.getScrollerForMode(this.mode); |
| }, |
| |
| scroll: function() { |
| var configs = this.modeConfigs; |
| var main = this.$.mainContainer; |
| var header = this.header; |
| |
| var sTop = main.scrollTop; |
| var atTop = sTop === 0; |
| |
| this.$.dropShadow.classList.toggle('hidden', !this.shadow && |
| (atTop && configs.shadowMode[this.mode] || configs.noShadow[this.mode])); |
| |
| if (header && configs.tallMode[this.mode]) { |
| header.classList.toggle(this.tallClass, atTop || |
| header.classList.contains(this.tallClass) && |
| main.scrollHeight < this.$.outerContainer.offsetHeight); |
| } |
| |
| this.fire('scroll', {target: this.scroller}, this, false); |
| } |
| |
| }); |
| |
| </script> |
| </polymer-element> |
| |
| <polymer-element name="core-iconset" extends="core-meta" attributes="src width icons iconSize" assetpath="../bower_components/core-iconset/"> |
| |
| <script> |
| |
| Polymer('core-iconset', { |
| |
| /** |
| * The URL of the iconset image. |
| * |
| * @attribute src |
| * @type string |
| * @default '' |
| */ |
| src: '', |
| |
| /** |
| * The width of the iconset image. This must only be specified if the |
| * icons are arranged into separate rows inside the image. |
| * |
| * @attribute width |
| * @type number |
| * @default 0 |
| */ |
| width: 0, |
| |
| /** |
| * A space separated list of names corresponding to icons in the iconset |
| * image file. This list must be ordered the same as the icon images |
| * in the image file. |
| * |
| * @attribute icons |
| * @type string |
| * @default '' |
| */ |
| icons: '', |
| |
| /** |
| * The size of an individual icon. Note that icons must be square. |
| * |
| * @attribute iconSize |
| * @type number |
| * @default 24 |
| */ |
| iconSize: 24, |
| |
| /** |
| * The horizontal offset of the icon images in the inconset src image. |
| * This is typically used if the image resource contains additional images |
| * beside those intended for the iconset. |
| * |
| * @attribute offsetX |
| * @type number |
| * @default 0 |
| */ |
| offsetX: 0, |
| /** |
| * The vertical offset of the icon images in the inconset src image. |
| * This is typically used if the image resource contains additional images |
| * beside those intended for the iconset. |
| * |
| * @attribute offsetY |
| * @type number |
| * @default 0 |
| */ |
| offsetY: 0, |
| type: 'iconset', |
| |
| created: function() { |
| this.iconMap = {}; |
| this.iconNames = []; |
| this.themes = {}; |
| }, |
| |
| ready: function() { |
| // TODO(sorvell): ensure iconset's src is always relative to the main |
| // document |
| if (this.src && (this.ownerDocument !== document)) { |
| this.src = this.resolvePath(this.src, this.ownerDocument.baseURI); |
| } |
| this.super(); |
| this.updateThemes(); |
| }, |
| |
| iconsChanged: function() { |
| var ox = this.offsetX; |
| var oy = this.offsetY; |
| this.icons && this.icons.split(/\s+/g).forEach(function(name, i) { |
| this.iconNames.push(name); |
| this.iconMap[name] = { |
| offsetX: ox, |
| offsetY: oy |
| } |
| if (ox + this.iconSize < this.width) { |
| ox += this.iconSize; |
| } else { |
| ox = this.offsetX; |
| oy += this.iconSize; |
| } |
| }, this); |
| }, |
| |
| updateThemes: function() { |
| var ts = this.querySelectorAll('property[theme]'); |
| ts && ts.array().forEach(function(t) { |
| this.themes[t.getAttribute('theme')] = { |
| offsetX: parseInt(t.getAttribute('offsetX')) || 0, |
| offsetY: parseInt(t.getAttribute('offsetY')) || 0 |
| }; |
| }, this); |
| }, |
| |
| // TODO(ffu): support retrived by index e.g. getOffset(10); |
| /** |
| * Returns an object containing `offsetX` and `offsetY` properties which |
| * specify the pixel locaion in the iconset's src file for the given |
| * `icon` and `theme`. It's uncommon to call this method. It is useful, |
| * for example, to manually position a css backgroundImage to the proper |
| * offset. It's more common to use the `applyIcon` method. |
| * |
| * @method getOffset |
| * @param {String|Number} icon The name of the icon or the index of the |
| * icon within in the icon image. |
| * @param {String} theme The name of the theme. |
| * @returns {Object} An object specifying the offset of the given icon |
| * within the icon resource file; `offsetX` is the horizontal offset and |
| * `offsetY` is the vertical offset. Both values are in pixel units. |
| */ |
| getOffset: function(icon, theme) { |
| var i = this.iconMap[icon]; |
| if (!i) { |
| var n = this.iconNames[Number(icon)]; |
| i = this.iconMap[n]; |
| } |
| var t = this.themes[theme]; |
| if (i && t) { |
| return { |
| offsetX: i.offsetX + t.offsetX, |
| offsetY: i.offsetY + t.offsetY |
| } |
| } |
| return i; |
| }, |
| |
| /** |
| * Applies an icon to the given element as a css background image. This |
| * method does not size the element, and it's often necessary to set |
| * the element's height and width so that the background image is visible. |
| * |
| * @method applyIcon |
| * @param {Element} element The element to which the background is |
| * applied. |
| * @param {String|Number} icon The name or index of the icon to apply. |
| * @param {Number} scale (optional, defaults to 1) A scaling factor |
| * with which the icon can be magnified. |
| * @return {Element} The icon element. |
| */ |
| applyIcon: function(element, icon, scale) { |
| var offset = this.getOffset(icon); |
| scale = scale || 1; |
| if (element && offset) { |
| var icon = element._icon || document.createElement('div'); |
| var style = icon.style; |
| style.backgroundImage = 'url(' + this.src + ')'; |
| style.backgroundPosition = (-offset.offsetX * scale + 'px') + |
| ' ' + (-offset.offsetY * scale + 'px'); |
| style.backgroundSize = scale === 1 ? 'auto' : |
| this.width * scale + 'px'; |
| if (icon.parentNode !== element) { |
| element.appendChild(icon); |
| } |
| return icon; |
| } |
| } |
| |
| }); |
| |
| </script> |
| |
| </polymer-element> |
| <polymer-element name="core-icon" attributes="src icon alt" assetpath="../bower_components/core-icon/"> |
| <script> |
| (function() { |
| |
| // mono-state |
| var meta; |
| |
| Polymer('core-icon', { |
| |
| /** |
| * The URL of an image for the icon. If the src property is specified, |
| * the icon property should not be. |
| * |
| * @attribute src |
| * @type string |
| * @default '' |
| */ |
| src: '', |
| |
| /** |
| * Specifies the icon name or index in the set of icons available in |
| * the icon's icon set. If the icon property is specified, |
| * the src property should not be. |
| * |
| * @attribute icon |
| * @type string |
| * @default '' |
| */ |
| icon: '', |
| |
| /** |
| * Alternative text content for accessibility support. |
| * If alt is present and not empty, it will set the element's role to img and add an aria-label whose content matches alt. |
| * If alt is present and is an empty string, '', it will hide the element from the accessibility layer |
| * If alt is not present, it will set the element's role to img and the element will fallback to using the icon attribute for its aria-label. |
| * |
| * @attribute alt |
| * @type string |
| * @default '' |
| */ |
| alt: null, |
| |
| observe: { |
| 'icon': 'updateIcon', |
| 'alt': 'updateAlt' |
| }, |
| |
| defaultIconset: 'icons', |
| |
| ready: function() { |
| if (!meta) { |
| meta = document.createElement('core-iconset'); |
| } |
| |
| // Allow user-provided `aria-label` in preference to any other text alternative. |
| if (this.hasAttribute('aria-label')) { |
| // Set `role` if it has not been overridden. |
| if (!this.hasAttribute('role')) { |
| this.setAttribute('role', 'img'); |
| } |
| return; |
| } |
| this.updateAlt(); |
| }, |
| |
| srcChanged: function() { |
| var icon = this._icon || document.createElement('div'); |
| icon.textContent = ''; |
| icon.setAttribute('fit', ''); |
| icon.style.backgroundImage = 'url(' + this.src + ')'; |
| icon.style.backgroundPosition = 'center'; |
| icon.style.backgroundSize = '100%'; |
| if (!icon.parentNode) { |
| this.appendChild(icon); |
| } |
| this._icon = icon; |
| }, |
| |
| getIconset: function(name) { |
| return meta.byId(name || this.defaultIconset); |
| }, |
| |
| updateIcon: function(oldVal, newVal) { |
| if (!this.icon) { |
| this.updateAlt(); |
| return; |
| } |
| var parts = String(this.icon).split(':'); |
| var icon = parts.pop(); |
| if (icon) { |
| var set = this.getIconset(parts.pop()); |
| if (set) { |
| this._icon = set.applyIcon(this, icon); |
| if (this._icon) { |
| this._icon.setAttribute('fit', ''); |
| } |
| } |
| } |
| // Check to see if we're using the old icon's name for our a11y fallback |
| if (oldVal) { |
| if (oldVal.split(':').pop() == this.getAttribute('aria-label')) { |
| this.updateAlt(); |
| } |
| } |
| }, |
| |
| updateAlt: function() { |
| // Respect the user's decision to remove this element from |
| // the a11y tree |
| if (this.getAttribute('aria-hidden')) { |
| return; |
| } |
| |
| // Remove element from a11y tree if `alt` is empty, otherwise |
| // use `alt` as `aria-label`. |
| if (this.alt === '') { |
| this.setAttribute('aria-hidden', 'true'); |
| if (this.hasAttribute('role')) { |
| this.removeAttribute('role'); |
| } |
| if (this.hasAttribute('aria-label')) { |
| this.removeAttribute('aria-label'); |
| } |
| } else { |
| this.setAttribute('aria-label', this.alt || |
| this.icon.split(':').pop()); |
| if (!this.hasAttribute('role')) { |
| this.setAttribute('role', 'img'); |
| } |
| if (this.hasAttribute('aria-hidden')) { |
| this.removeAttribute('aria-hidden'); |
| } |
| } |
| } |
| |
| }); |
| |
| })(); |
| </script> |
| |
| </polymer-element> |
| |
| <polymer-element name="core-iconset-svg" extends="core-meta" attributes="iconSize" assetpath="../bower_components/core-iconset-svg/"> |
| |
| <script> |
| |
| Polymer('core-iconset-svg', { |
| |
| |
| /** |
| * The size of an individual icon. Note that icons must be square. |
| * |
| * @attribute iconSize |
| * @type number |
| * @default 24 |
| */ |
| iconSize: 24, |
| type: 'iconset', |
| |
| created: function() { |
| this._icons = {}; |
| }, |
| |
| ready: function() { |
| this.super(); |
| this.updateIcons(); |
| }, |
| |
| iconById: function(id) { |
| return this._icons[id] || (this._icons[id] = this.querySelector('[id="' + id +'"]')); |
| }, |
| |
| cloneIcon: function(id) { |
| var icon = this.iconById(id); |
| if (icon) { |
| var content = icon.cloneNode(true); |
| content.removeAttribute('id'); |
| var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); |
| svg.setAttribute('viewBox', '0 0 ' + this.iconSize + ' ' + |
| this.iconSize); |
| // NOTE(dfreedm): work around https://crbug.com/370136 |
| svg.style.pointerEvents = 'none'; |
| svg.appendChild(content); |
| return svg; |
| } |
| }, |
| |
| get iconNames() { |
| if (!this._iconNames) { |
| this._iconNames = this.findIconNames(); |
| } |
| return this._iconNames; |
| }, |
| |
| findIconNames: function() { |
| var icons = this.querySelectorAll('[id]').array(); |
| if (icons.length) { |
| return icons.map(function(n){ return n.id }); |
| } |
| }, |
| |
| /** |
| * Applies an icon to the given element. The svg icon is added to the |
| * element's shadowRoot if one exists or directly to itself. |
| * |
| * @method applyIcon |
| * @param {Element} element The element to which the icon is |
| * applied. |
| * @param {String|Number} icon The name the icon to apply. |
| * @return {Element} The icon element |
| */ |
| applyIcon: function(element, icon) { |
| var root = element; |
| // remove old |
| var old = root.querySelector('svg'); |
| if (old) { |
| old.remove(); |
| } |
| // install new |
| var svg = this.cloneIcon(icon); |
| if (!svg) { |
| return; |
| } |
| svg.setAttribute('height', '100%'); |
| svg.setAttribute('width', '100%'); |
| svg.setAttribute('preserveAspectRatio', 'xMidYMid meet'); |
| svg.style.display = 'block'; |
| root.insertBefore(svg, root.firstElementChild); |
| return svg; |
| }, |
| |
| /** |
| * Tell users of the iconset, that the set has loaded. |
| * This finds all elements matching the selector argument and calls |
| * the method argument on them. |
| * @method updateIcons |
| * @param selector {string} css selector to identify iconset users, |
| * defaults to '[icon]' |
| * @param method {string} method to call on found elements, |
| * defaults to 'updateIcon' |
| */ |
| updateIcons: function(selector, method) { |
| selector = selector || '[icon]'; |
| method = method || 'updateIcon'; |
| var deep = window.ShadowDOMPolyfill ? '' : 'html /deep/ '; |
| var i$ = document.querySelectorAll(deep + selector); |
| for (var i=0, e; e=i$[i]; i++) { |
| if (e[method]) { |
| e[method].call(e); |
| } |
| } |
| } |
| |
| |
| }); |
| |
| </script> |
| |
| </polymer-element> |
| <core-iconset-svg id="av" iconsize="24"> |
| <svg><defs> |
| <g id="album"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 14.5c-2.49 0-4.5-2.01-4.5-4.5S9.51 7.5 12 7.5s4.5 2.01 4.5 4.5-2.01 4.5-4.5 4.5zm0-5.5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1z"></path></g> |
| <g id="av-timer"><path d="M11 17c0 .55.45 1 1 1s1-.45 1-1-.45-1-1-1-1 .45-1 1zm0-14v4h2V5.08c3.39.49 6 3.39 6 6.92 0 3.87-3.13 7-7 7s-7-3.13-7-7c0-1.68.59-3.22 1.58-4.42L12 13l1.41-1.41-6.8-6.8v.02C4.42 6.45 3 9.05 3 12c0 4.97 4.02 9 9 9 4.97 0 9-4.03 9-9s-4.03-9-9-9h-1zm7 9c0-.55-.45-1-1-1s-1 .45-1 1 .45 1 1 1 1-.45 1-1zM6 12c0 .55.45 1 1 1s1-.45 1-1-.45-1-1-1-1 .45-1 1z"></path></g> |
| <g id="closed-caption"><path d="M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1c0 .55-.45 1-1 1H7c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1c0 .55-.45 1-1 1h-3c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1z"></path></g> |
| <g id="equalizer"><path d="M10 20h4V4h-4v16zm-6 0h4v-8H4v8zM16 9v11h4V9h-4z"></path></g> |
| <g id="explicit"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-4 6h-4v2h4v2h-4v2h4v2H9V7h6v2z"></path></g> |
| <g id="fast-forward"><path d="M4 18l8.5-6L4 6v12zm9-12v12l8.5-6L13 6z"></path></g> |
| <g id="fast-rewind"><path d="M11 18V6l-8.5 6 8.5 6zm.5-6l8.5 6V6l-8.5 6z"></path></g> |
| <g id="games"><path d="M15 7.5V2H9v5.5l3 3 3-3zM7.5 9H2v6h5.5l3-3-3-3zM9 16.5V22h6v-5.5l-3-3-3 3zM16.5 9l-3 3 3 3H22V9h-5.5z"></path></g> |
| <g id="hearing"><path d="M17 20c-.29 0-.56-.06-.76-.15-.71-.37-1.21-.88-1.71-2.38-.51-1.56-1.47-2.29-2.39-3-.79-.61-1.61-1.24-2.32-2.53C9.29 10.98 9 9.93 9 9c0-2.8 2.2-5 5-5s5 2.2 5 5h2c0-3.93-3.07-7-7-7S7 5.07 7 9c0 1.26.38 2.65 1.07 3.9.91 1.65 1.98 2.48 2.85 3.15.81.62 1.39 1.07 1.71 2.05.6 1.82 1.37 2.84 2.73 3.55.51.23 1.07.35 1.64.35 2.21 0 4-1.79 4-4h-2c0 1.1-.9 2-2 2zM7.64 2.64L6.22 1.22C4.23 3.21 3 5.96 3 9s1.23 5.79 3.22 7.78l1.41-1.41C6.01 13.74 5 11.49 5 9s1.01-4.74 2.64-6.36zM11.5 9c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5-1.12-2.5-2.5-2.5-2.5 1.12-2.5 2.5z"></path></g> |
| <g id="high-quality"><path d="M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-8 11H9.5v-2h-2v2H6V9h1.5v2.5h2V9H11v6zm7-1c0 .55-.45 1-1 1h-.75v1.5h-1.5V15H14c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v4zm-3.5-.5h2v-3h-2v3z"></path></g> |
| <g id="loop"><path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"></path></g> |
| <g id="mic"><path d="M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z"></path></g> |
| <g id="mic-none"><path d="M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm-1.2-9.1c0-.66.54-1.2 1.2-1.2.66 0 1.2.54 1.2 1.2l-.01 6.2c0 .66-.53 1.2-1.19 1.2-.66 0-1.2-.54-1.2-1.2V4.9zm6.5 6.1c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z"></path></g> |
| <g id="mic-off"><path d="M19 11h-1.7c0 .74-.16 1.43-.43 2.05l1.23 1.23c.56-.98.9-2.09.9-3.28zm-4.02.17c0-.06.02-.11.02-.17V5c0-1.66-1.34-3-3-3S9 3.34 9 5v.18l5.98 5.99zM4.27 3L3 4.27l6.01 6.01V11c0 1.66 1.33 3 2.99 3 .22 0 .44-.03.65-.08l1.66 1.66c-.71.33-1.5.52-2.31.52-2.76 0-5.3-2.1-5.3-5.1H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c.91-.13 1.77-.45 2.54-.9L19.73 21 21 19.73 4.27 3z"></path></g> |
| <g id="movie"><path d="M18 4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4h-4z"></path></g> |
| <g id="my-library-add"><path d="M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-1 9h-4v4h-2v-4H9V9h4V5h2v4h4v2z"></path></g> |
| <g id="my-library-books"><path d="M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-1 9H9V9h10v2zm-4 4H9v-2h6v2zm4-8H9V5h10v2z"></path></g> |
| <g id="my-library-music"><path d="M20 2H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2 5h-3v5.5c0 1.38-1.12 2.5-2.5 2.5S10 13.88 10 12.5s1.12-2.5 2.5-2.5c.57 0 1.08.19 1.5.51V5h4v2zM4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6z"></path></g> |
| <g id="new-releases"><path d="M23 12l-2.44-2.78.34-3.68-3.61-.82-1.89-3.18L12 3 8.6 1.54 6.71 4.72l-3.61.81.34 3.68L1 12l2.44 2.78-.34 3.69 3.61.82 1.89 3.18L12 21l3.4 1.46 1.89-3.18 3.61-.82-.34-3.68L23 12zm-10 5h-2v-2h2v2zm0-4h-2V7h2v6z"></path></g> |
| <g id="not-interested"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8 0-1.85.63-3.55 1.69-4.9L16.9 18.31C15.55 19.37 13.85 20 12 20zm6.31-3.1L7.1 5.69C8.45 4.63 10.15 4 12 4c4.42 0 8 3.58 8 8 0 1.85-.63 3.55-1.69 4.9z"></path></g> |
| <g id="pause"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"></path></g> |
| <g id="pause-circle-fill"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 14H9V8h2v8zm4 0h-2V8h2v8z"></path></g> |
| <g id="pause-circle-outline"><path d="M9 16h2V8H9v8zm3-14C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm1-4h2V8h-2v8z"></path></g> |
| <g id="play-arrow"><path d="M8 5v14l11-7z"></path></g> |
| <g id="play-circle-fill"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 14.5v-9l6 4.5-6 4.5z"></path></g> |
| <g id="play-circle-outline"><path d="M10 16.5l6-4.5-6-4.5v9zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"></path></g> |
| <g id="play-shopping-bag"><path d="M16 6V4c0-1.11-.89-2-2-2h-4c-1.11 0-2 .89-2 2v2H2v13c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V6h-6zm-6-2h4v2h-4V4zM9 18V9l7.5 4L9 18z"></path></g> |
| <g id="playlist-add"><path d="M14 10H2v2h12v-2zm0-4H2v2h12V6zm4 8v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zM2 16h8v-2H2v2z"></path></g> |
| <g id="queue"><path d="M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-1 9h-4v4h-2v-4H9V9h4V5h2v4h4v2z"></path></g> |
| <g id="queue-music"><path d="M15 6H3v2h12V6zm0 4H3v2h12v-2zM3 16h8v-2H3v2zM17 6v8.18c-.31-.11-.65-.18-1-.18-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3V8h3V6h-5z"></path></g> |
| <g id="radio"><path d="M3.24 6.15C2.51 6.43 2 7.17 2 8v12c0 1.1.89 2 2 2h16c1.11 0 2-.9 2-2V8c0-1.11-.89-2-2-2H8.3l8.26-3.34L15.88 1 3.24 6.15zM7 20c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm13-8h-2v-2h-2v2H4V8h16v4z"></path></g> |
| <g id="recent-actors"><path d="M21 5v14h2V5h-2zm-4 14h2V5h-2v14zM14 5H2c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V6c0-.55-.45-1-1-1zM8 7.75c1.24 0 2.25 1.01 2.25 2.25S9.24 12.25 8 12.25 5.75 11.24 5.75 10 6.76 7.75 8 7.75zM12.5 17h-9v-.75c0-1.5 3-2.25 4.5-2.25s4.5.75 4.5 2.25V17z"></path></g> |
| <g id="repeat"><path d="M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z"></path></g> |
| <g id="repeat-one"><path d="M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4zm-4-2V9h-1l-2 1v1h1.5v4H13z"></path></g> |
| <g id="replay"><path d="M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"></path></g> |
| <g id="shuffle"><path d="M10.59 9.17L5.41 4 4 5.41l5.17 5.17 1.42-1.41zM14.5 4l2.04 2.04L4 18.59 5.41 20 17.96 7.46 20 9.5V4h-5.5zm.33 9.41l-1.41 1.41 3.13 3.13L14.5 20H20v-5.5l-2.04 2.04-3.13-3.13z"></path></g> |
| <g id="skip-next"><path d="M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z"></path></g> |
| <g id="skip-previous"><path d="M6 6h2v12H6zm3.5 6l8.5 6V6z"></path></g> |
| <g id="snooze"><path d="M7.88 3.39L6.6 1.86 2 5.71l1.29 1.53 4.59-3.85zM22 5.72l-4.6-3.86-1.29 1.53 4.6 3.86L22 5.72zM12 4c-4.97 0-9 4.03-9 9s4.02 9 9 9c4.97 0 9-4.03 9-9s-4.03-9-9-9zm0 16c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7zm-3-9h3.63L9 15.2V17h6v-2h-3.63L15 10.8V9H9v2z"></path></g> |
| <g id="stop"><path d="M6 6h12v12H6z"></path></g> |
| <g id="subtitles"><path d="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zM4 12h4v2H4v-2zm10 6H4v-2h10v2zm6 0h-4v-2h4v2zm0-4H10v-2h10v2z"></path></g> |
| <g id="surround-sound"><path d="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zM7.76 16.24l-1.41 1.41C4.78 16.1 4 14.05 4 12c0-2.05.78-4.1 2.34-5.66l1.41 1.41C6.59 8.93 6 10.46 6 12s.59 3.07 1.76 4.24zM12 16c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4zm5.66 1.66l-1.41-1.41C17.41 15.07 18 13.54 18 12s-.59-3.07-1.76-4.24l1.41-1.41C19.22 7.9 20 9.95 20 12c0 2.05-.78 4.1-2.34 5.66zM12 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"></path></g> |
| <g id="video-collection"><path d="M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-8 12.5v-9l6 4.5-6 4.5z"></path></g> |
| <g id="videocam"><path d="M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z"></path></g> |
| <g id="videocam-off"><path d="M21 6.5l-4 4V7c0-.55-.45-1-1-1H9.82L21 17.18V6.5zM3.27 2L2 3.27 4.73 6H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.21 0 .39-.08.54-.18L19.73 21 21 19.73 3.27 2z"></path></g> |
| <g id="volume-down"><path d="M18.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM5 9v6h4l5 5V4L9 9H5z"></path></g> |
| <g id="volume-mute"><path d="M7 9v6h4l5 5V4l-5 5H7z"></path></g> |
| <g id="volume-off"><path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"></path></g> |
| <g id="volume-up"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"></path></g> |
| <g id="web"><path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-5 14H4v-4h11v4zm0-5H4V9h11v4zm5 5h-4V9h4v9z"></path></g> |
| </defs></svg> |
| </core-iconset-svg> |
| |
| <core-iconset-svg id="icons" iconsize="24"> |
| <svg><defs> |
| <g id="3d-rotation"><path d="M7.52 21.48C4.25 19.94 1.91 16.76 1.55 13H.05C.56 19.16 5.71 24 12 24l.66-.03-3.81-3.81-1.33 1.32zm.89-6.52c-.19 0-.37-.03-.52-.08-.16-.06-.29-.13-.4-.24-.11-.1-.2-.22-.26-.37-.06-.14-.09-.3-.09-.47h-1.3c0 .36.07.68.21.95.14.27.33.5.56.69.24.18.51.32.82.41.3.1.62.15.96.15.37 0 .72-.05 1.03-.15.32-.1.6-.25.83-.44s.42-.43.55-.72c.13-.29.2-.61.2-.97 0-.19-.02-.38-.07-.56-.05-.18-.12-.35-.23-.51-.1-.16-.24-.3-.4-.43-.17-.13-.37-.23-.61-.31.2-.09.37-.2.52-.33.15-.13.27-.27.37-.42.1-.15.17-.3.22-.46.05-.16.07-.32.07-.48 0-.36-.06-.68-.18-.96-.12-.28-.29-.51-.51-.69-.2-.19-.47-.33-.77-.43C9.1 8.05 8.76 8 8.39 8c-.36 0-.69.05-1 .16-.3.11-.57.26-.79.45-.21.19-.38.41-.51.67-.12.26-.18.54-.18.85h1.3c0-.17.03-.32.09-.45s.14-.25.25-.34c.11-.09.23-.17.38-.22.15-.05.3-.08.48-.08.4 0 .7.1.89.31.19.2.29.49.29.86 0 .18-.03.34-.08.49-.05.15-.14.27-.25.37-.11.1-.25.18-.41.24-.16.06-.36.09-.58.09H7.5v1.03h.77c.22 0 .42.02.6.07s.33.13.45.23c.12.11.22.24.29.4.07.16.1.35.1.57 0 .41-.12.72-.35.93-.23.23-.55.33-.95.33zm8.55-5.92c-.32-.33-.7-.59-1.14-.77-.43-.18-.92-.27-1.46-.27H12v8h2.3c.55 0 1.06-.09 1.51-.27.45-.18.84-.43 1.16-.76.32-.33.57-.73.74-1.19.17-.47.26-.99.26-1.57v-.4c0-.58-.09-1.1-.26-1.57-.18-.47-.43-.87-.75-1.2zm-.39 3.16c0 .42-.05.79-.14 1.13-.1.33-.24.62-.43.85-.19.23-.43.41-.71.53-.29.12-.62.18-.99.18h-.91V9.12h.97c.72 0 1.27.23 1.64.69.38.46.57 1.12.57 1.99v.4zM12 0l-.66.03 3.81 3.81 1.33-1.33c3.27 1.55 5.61 4.72 5.96 8.48h1.5C23.44 4.84 18.29 0 12 0z"></path></g> |
| <g id="accessibility"><path d="M12 2c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm9 7h-6v13h-2v-6h-2v6H9V9H3V7h18v2z"></path></g> |
| <g id="account-balance"><path d="M4 10v7h3v-7H4zm6 0v7h3v-7h-3zM2 22h19v-3H2v3zm14-12v7h3v-7h-3zm-4.5-9L2 6v2h19V6l-9.5-5z"></path></g> |
| <g id="account-balance-wallet"><path d="M21 18v1c0 1.1-.9 2-2 2H5c-1.11 0-2-.9-2-2V5c0-1.1.89-2 2-2h14c1.1 0 2 .9 2 2v1h-9c-1.11 0-2 .9-2 2v8c0 1.1.89 2 2 2h9zm-9-2h10V8H12v8zm4-2.5c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"></path></g> |
| <g id="account-box"><path d="M3 5v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2H5c-1.11 0-2 .9-2 2zm12 4c0 1.66-1.34 3-3 3s-3-1.34-3-3 1.34-3 3-3 3 1.34 3 3zm-9 8c0-2 4-3.1 6-3.1s6 1.1 6 3.1v1H6v-1z"></path></g> |
| <g id="account-child"><circle cx="12" cy="13.49" r="1.5"></circle><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 2.5c1.24 0 2.25 1.01 2.25 2.25S13.24 9 12 9 9.75 7.99 9.75 6.75 10.76 4.5 12 4.5zm5 10.56v2.5c-.45.41-.96.77-1.5 1.05v-.68c0-.34-.17-.65-.46-.92-.65-.62-1.89-1.02-3.04-1.02-.96 0-1.96.28-2.65.73l-.17.12-.21.17c.78.47 1.63.72 2.54.82l1.33.15c.37.04.66.36.66.75 0 .29-.16.53-.4.66-.28.15-.64.09-.95.09-.35 0-.69-.01-1.03-.05-.5-.06-.99-.17-1.46-.33-.49-.16-.97-.38-1.42-.64-.22-.13-.44-.27-.65-.43l-.31-.24c-.04-.02-.28-.18-.28-.23v-4.28c0-1.58 2.63-2.78 5-2.78s5 1.2 5 2.78v1.78z"></path></g> |
| <g id="account-circle"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z"></path></g> |
| <g id="add"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"></path></g> |
| <g id="add-box"><path d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"></path></g> |
| <g id="add-circle"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"></path></g> |
| <g id="add-circle-outline"><path d="M13 7h-2v4H7v2h4v4h2v-4h4v-2h-4V7zm-1-5C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"></path></g> |
| <g id="add-shopping-cart"><path d="M11 9h2V6h3V4h-3V1h-2v3H8v2h3v3zm-4 9c-1.1 0-1.99.9-1.99 2S5.9 22 7 22s2-.9 2-2-.9-2-2-2zm10 0c-1.1 0-1.99.9-1.99 2s.89 2 1.99 2 2-.9 2-2-.9-2-2-2zm-9.83-3.25l.03-.12.9-1.63h7.45c.75 0 1.41-.41 1.75-1.03l3.86-7.01L19.42 4h-.01l-1.1 2-2.76 5H8.53l-.13-.27L6.16 6l-.95-2-.94-2H1v2h2l3.6 7.59-1.35 2.45c-.16.28-.25.61-.25.96 0 1.1.9 2 2 2h12v-2H7.42c-.13 0-.25-.11-.25-.25z"></path></g> |
| <g id="alarm"><path d="M22 5.72l-4.6-3.86-1.29 1.53 4.6 3.86L22 5.72zM7.88 3.39L6.6 1.86 2 5.71l1.29 1.53 4.59-3.85zM12.5 8H11v6l4.75 2.85.75-1.23-4-2.37V8zM12 4c-4.97 0-9 4.03-9 9s4.02 9 9 9c4.97 0 9-4.03 9-9s-4.03-9-9-9zm0 16c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z"></path></g> |
| <g id="alarm-add"><path d="M7.88 3.39L6.6 1.86 2 5.71l1.29 1.53 4.59-3.85zM22 5.72l-4.6-3.86-1.29 1.53 4.6 3.86L22 5.72zM12 4c-4.97 0-9 4.03-9 9s4.02 9 9 9c4.97 0 9-4.03 9-9s-4.03-9-9-9zm0 16c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7zm1-11h-2v3H8v2h3v3h2v-3h3v-2h-3V9z"></path></g> |
| <g id="alarm-off"><path d="M12 6c3.87 0 7 3.13 7 7 0 .84-.16 1.65-.43 2.4l1.52 1.52c.58-1.19.91-2.51.91-3.92 0-4.97-4.03-9-9-9-1.41 0-2.73.33-3.92.91L9.6 6.43C10.35 6.16 11.16 6 12 6zm10-.28l-4.6-3.86-1.29 1.53 4.6 3.86L22 5.72zM2.92 2.29L1.65 3.57 2.98 4.9l-1.11.93 1.42 1.42 1.11-.94.8.8C3.83 8.69 3 10.75 3 13c0 4.97 4.02 9 9 9 2.25 0 4.31-.83 5.89-2.2l2.2 2.2 1.27-1.27L3.89 3.27l-.97-.98zm13.55 16.1C15.26 19.39 13.7 20 12 20c-3.87 0-7-3.13-7-7 0-1.7.61-3.26 1.61-4.47l9.86 9.86zM8.02 3.28L6.6 1.86l-.86.71 1.42 1.42.86-.71z"></path></g> |
| <g id="alarm-on"><path d="M22 5.72l-4.6-3.86-1.29 1.53 4.6 3.86L22 5.72zM7.88 3.39L6.6 1.86 2 5.71l1.29 1.53 4.59-3.85zM12 4c-4.97 0-9 4.03-9 9s4.02 9 9 9c4.97 0 9-4.03 9-9s-4.03-9-9-9zm0 16c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7zm-1.46-5.47L8.41 12.4l-1.06 1.06 3.18 3.18 6-6-1.06-1.06-4.93 4.95z"></path></g> |
| <g id="android"><path d="M6 18c0 .55.45 1 1 1h1v3.5c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5V19h2v3.5c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5V19h1c.55 0 1-.45 1-1V8H6v10zM3.5 8C2.67 8 2 8.67 2 9.5v7c0 .83.67 1.5 1.5 1.5S5 17.33 5 16.5v-7C5 8.67 4.33 8 3.5 8zm17 0c-.83 0-1.5.67-1.5 1.5v7c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5v-7c0-.83-.67-1.5-1.5-1.5zm-4.97-5.84l1.3-1.3c.2-.2.2-.51 0-.71-.2-.2-.51-.2-.71 0l-1.48 1.48C13.85 1.23 12.95 1 12 1c-.96 0-1.86.23-2.66.63L7.85.15c-.2-.2-.51-.2-.71 0-.2.2-.2.51 0 .71l1.31 1.31C6.97 3.26 6 5.01 6 7h12c0-1.99-.97-3.75-2.47-4.84zM10 5H9V4h1v1zm5 0h-1V4h1v1z"></path></g> |
| <g id="announcement"><path d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 9h-2V5h2v6zm0 4h-2v-2h2v2z"></path></g> |
| <g id="apps"><path d="M4 8h4V4H4v4zm6 12h4v-4h-4v4zm-6 0h4v-4H4v4zm0-6h4v-4H4v4zm6 0h4v-4h-4v4zm6-10v4h4V4h-4zm-6 4h4V4h-4v4zm6 6h4v-4h-4v4zm0 6h4v-4h-4v4z"></path></g> |
| <g id="archive"><path d="M20.54 5.23l-1.39-1.68C18.88 3.21 18.47 3 18 3H6c-.47 0-.88.21-1.16.55L3.46 5.23C3.17 5.57 3 6.02 3 6.5V19c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6.5c0-.48-.17-.93-.46-1.27zM12 17.5L6.5 12H10v-2h4v2h3.5L12 17.5zM5.12 5l.81-1h12l.94 1H5.12z"></path></g> |
| <g id="arrow-back"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"></path></g> |
| <g id="arrow-drop-down"><path d="M7 10l5 5 5-5z"></path></g> |
| <g id="arrow-drop-down-circle"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 12l-4-4h8l-4 4z"></path></g> |
| <g id="arrow-drop-up"><path d="M7 14l5-5 5 5z"></path></g> |
| <g id="arrow-forward"><path d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z"></path></g> |
| <g id="aspect-ratio"><path d="M19 12h-2v3h-3v2h5v-5zM7 9h3V7H5v5h2V9zm14-6H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16.01H3V4.99h18v14.02z"></path></g> |
| <g id="assessment"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z"></path></g> |
| <g id="assignment"><path d="M19 3h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm2 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"></path></g> |
| <g id="assignment-ind"><path d="M19 3h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm0 4c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm6 12H6v-1.4c0-2 4-3.1 6-3.1s6 1.1 6 3.1V19z"></path></g> |
| <g id="assignment-late"><path d="M19 3h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-6 15h-2v-2h2v2zm0-4h-2V8h2v6zm-1-9c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1z"></path></g> |
| <g id="assignment-return"><path d="M19 3h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm4 12h-4v3l-5-5 5-5v3h4v4z"></path></g> |
| <g id="assignment-returned"><path d="M19 3h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm0 15l-5-5h3V9h4v4h3l-5 5z"></path></g> |
| <g id="assignment-turned-in"><path d="M19 3h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm-2 14l-4-4 1.41-1.41L10 14.17l6.59-6.59L18 9l-8 8z"></path></g> |
| <g id="attachment"><path d="M7.5 18C4.46 18 2 15.54 2 12.5S4.46 7 7.5 7H18c2.21 0 4 1.79 4 4s-1.79 4-4 4H9.5C8.12 15 7 13.88 7 12.5S8.12 10 9.5 10H17v1.5H9.5c-.55 0-1 .45-1 1s.45 1 1 1H18c1.38 0 2.5-1.12 2.5-2.5S19.38 8.5 18 8.5H7.5c-2.21 0-4 1.79-4 4s1.79 4 4 4H17V18H7.5z"></path></g> |
| <g id="autorenew"><path d="M12 6v3l4-4-4-4v3c-4.42 0-8 3.58-8 8 0 1.57.46 3.03 1.24 4.26L6.7 14.8c-.45-.83-.7-1.79-.7-2.8 0-3.31 2.69-6 6-6zm6.76 1.74L17.3 9.2c.44.84.7 1.79.7 2.8 0 3.31-2.69 6-6 6v-3l-4 4 4 4v-3c4.42 0 8-3.58 8-8 0-1.57-.46-3.03-1.24-4.26z"></path></g> |
| <g id="backspace"><path d="M22 3H7c-.69 0-1.23.35-1.59.88L0 12l5.41 8.11c.36.53.9.89 1.59.89h15c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-3 12.59L17.59 17 14 13.41 10.41 17 9 15.59 12.59 12 9 8.41 10.41 7 14 10.59 17.59 7 19 8.41 15.41 12 19 15.59z"></path></g> |
| <g id="backup"><path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM14 13v4h-4v-4H7l5-5 5 5h-3z"></path></g> |
| <g id="block"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zM4 12c0-4.42 3.58-8 8-8 1.85 0 3.55.63 4.9 1.69L5.69 16.9C4.63 15.55 4 13.85 4 12zm8 8c-1.85 0-3.55-.63-4.9-1.69L18.31 7.1C19.37 8.45 20 10.15 20 12c0 4.42-3.58 8-8 8z"></path></g> |
| <g id="book"><path d="M18 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM6 4h5v8l-2.5-1.5L6 12V4z"></path></g> |
| <g id="bookmark"><path d="M17 3H7c-1.1 0-1.99.9-1.99 2L5 21l7-3 7 3V5c0-1.1-.9-2-2-2z"></path></g> |
| <g id="bookmark-outline"><path d="M17 3H7c-1.1 0-1.99.9-1.99 2L5 21l7-3 7 3V5c0-1.1-.9-2-2-2zm0 15l-5-2.18L7 18V5h10v13z"></path></g> |
| <g id="bug-report"><path d="M20 8h-2.81c-.45-.78-1.07-1.45-1.82-1.96L17 4.41 15.59 3l-2.17 2.17C12.96 5.06 12.49 5 12 5c-.49 0-.96.06-1.41.17L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8zm-6 8h-4v-2h4v2zm0-4h-4v-2h4v2z"></path></g> |
| <g id="cached"><path d="M19 8l-4 4h3c0 3.31-2.69 6-6 6-1.01 0-1.97-.25-2.8-.7l-1.46 1.46C8.97 19.54 10.43 20 12 20c4.42 0 8-3.58 8-8h3l-4-4zM6 12c0-3.31 2.69-6 6-6 1.01 0 1.97.25 2.8.7l1.46-1.46C15.03 4.46 13.57 4 12 4c-4.42 0-8 3.58-8 8H1l4 4 4-4H6z"></path></g> |
| <g id="cancel"><path d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z"></path></g> |
| <g id="check"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"></path></g> |
| <g id="check-box"><path d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"></path></g> |
| <g id="check-box-outline-blank"><path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"></path></g> |
| <g id="check-circle"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"></path></g> |
| <g id="chevron-left"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"></path></g> |
| <g id="chevron-right"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"></path></g> |
| <g id="class"><path d="M18 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM6 4h5v8l-2.5-1.5L6 12V4z"></path></g> |
| <g id="clear"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path></g> |
| <g id="close"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path></g> |
| <g id="cloud"><path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96z"></path></g> |
| <g id="cloud-circle"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4.5 14H8c-1.66 0-3-1.34-3-3s1.34-3 3-3l.14.01C8.58 8.28 10.13 7 12 7c2.21 0 4 1.79 4 4h.5c1.38 0 2.5 1.12 2.5 2.5S17.88 16 16.5 16z"></path></g> |
| <g id="cloud-done"><path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM10 17l-3.5-3.5 1.41-1.41L10 14.17 15.18 9l1.41 1.41L10 17z"></path></g> |
| <g id="cloud-download"><path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM17 13l-5 5-5-5h3V9h4v4h3z"></path></g> |
| <g id="cloud-off"><path d="M19.35 10.04C18.67 6.59 15.64 4 12 4c-1.48 0-2.85.43-4.01 1.17l1.46 1.46C10.21 6.23 11.08 6 12 6c3.04 0 5.5 2.46 5.5 5.5v.5H19c1.66 0 3 1.34 3 3 0 1.13-.64 2.11-1.56 2.62l1.45 1.45C23.16 18.16 24 16.68 24 15c0-2.64-2.05-4.78-4.65-4.96zM3 5.27l2.75 2.74C2.56 8.15 0 10.77 0 14c0 3.31 2.69 6 6 6h11.73l2 2L21 20.73 4.27 4 3 5.27zM7.73 10l8 8H6c-2.21 0-4-1.79-4-4s1.79-4 4-4h1.73z"></path></g> |
| <g id="cloud-queue"><path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM19 18H6c-2.21 0-4-1.79-4-4s1.79-4 4-4h.71C7.37 7.69 9.48 6 12 6c3.04 0 5.5 2.46 5.5 5.5v.5H19c1.66 0 3 1.34 3 3s-1.34 3-3 3z"></path></g> |
| <g id="cloud-upload"><path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM14 13v4h-4v-4H7l5-5 5 5h-3z"></path></g> |
| <g id="content-copy"><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"></path></g> |
| <g id="content-cut"><path d="M9.64 7.64c.23-.5.36-1.05.36-1.64 0-2.21-1.79-4-4-4S2 3.79 2 6s1.79 4 4 4c.59 0 1.14-.13 1.64-.36L10 12l-2.36 2.36C7.14 14.13 6.59 14 6 14c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4c0-.59-.13-1.14-.36-1.64L12 14l7 7h3v-1L9.64 7.64zM6 8c-1.1 0-2-.89-2-2s.9-2 2-2 2 .89 2 2-.9 2-2 2zm0 12c-1.1 0-2-.89-2-2s.9-2 2-2 2 .89 2 2-.9 2-2 2zm6-7.5c-.28 0-.5-.22-.5-.5s.22-.5.5-.5.5.22.5.5-.22.5-.5.5zM19 3l-6 6 2 2 7-7V3z"></path></g> |
| <g id="content-paste"><path d="M19 2h-4.18C14.4.84 13.3 0 12 0c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm7 18H5V4h2v3h10V4h2v16z"></path></g> |
| <g id="create"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"></path></g> |
| <g id="credit-card"><path d="M20 4H4c-1.11 0-1.99.89-1.99 2L2 18c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V6c0-1.11-.89-2-2-2zm0 14H4v-6h16v6zm0-10H4V6h16v2z"></path></g> |
| <g id="dashboard"><path d="M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z"></path></g> |
| <g id="delete"><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"></path></g> |
| <g id="description"><path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"></path></g> |
| <g id="dns"><path d="M20 13H4c-.55 0-1 .45-1 1v6c0 .55.45 1 1 1h16c.55 0 1-.45 1-1v-6c0-.55-.45-1-1-1zM7 19c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zM20 3H4c-.55 0-1 .45-1 1v6c0 .55.45 1 1 1h16c.55 0 1-.45 1-1V4c0-.55-.45-1-1-1zM7 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z"></path></g> |
| <g id="done"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"></path></g> |
| <g id="done-all"><path d="M18 7l-1.41-1.41-6.34 6.34 1.41 1.41L18 7zm4.24-1.41L11.66 16.17 7.48 12l-1.41 1.41L11.66 19l12-12-1.42-1.41zM.41 13.41L6 19l1.41-1.41L1.83 12 .41 13.41z"></path></g> |
| <g id="drafts"><path d="M21.99 8c0-.72-.37-1.35-.94-1.7L12 1 2.95 6.3C2.38 6.65 2 7.28 2 8v10c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2l-.01-10zM12 13L3.74 7.84 12 3l8.26 4.84L12 13z"></path></g> |
| <g id="error"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"></path></g> |
| <g id="event"><path d="M17 12h-5v5h5v-5zM16 1v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2h-1V1h-2zm3 18H5V8h14v11z"></path></g> |
| <g id="exit-to-app"><path d="M10.09 15.59L11.5 17l5-5-5-5-1.41 1.41L12.67 11H3v2h9.67l-2.58 2.59zM19 3H5c-1.11 0-2 .9-2 2v4h2V5h14v14H5v-4H3v4c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"></path></g> |
| <g id="expand-less"><path d="M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z"></path></g> |
| <g id="expand-more"><path d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"></path></g> |
| <g id="explore"><path d="M12 10.9c-.61 0-1.1.49-1.1 1.1s.49 1.1 1.1 1.1c.61 0 1.1-.49 1.1-1.1s-.49-1.1-1.1-1.1zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm2.19 12.19L6 18l3.81-8.19L18 6l-3.81 8.19z"></path></g> |
| <g id="extension"><path d="M20.5 11H19V7c0-1.1-.9-2-2-2h-4V3.5C13 2.12 11.88 1 10.5 1S8 2.12 8 3.5V5H4c-1.1 0-1.99.9-1.99 2v3.8H3.5c1.49 0 2.7 1.21 2.7 2.7s-1.21 2.7-2.7 2.7H2V20c0 1.1.9 2 2 2h3.8v-1.5c0-1.49 1.21-2.7 2.7-2.7 1.49 0 2.7 1.21 2.7 2.7V22H17c1.1 0 2-.9 2-2v-4h1.5c1.38 0 2.5-1.12 2.5-2.5S21.88 11 20.5 11z"></path></g> |
| <g id="face"><path d="M14.69 17.1c-.74.58-1.7.9-2.69.9s-1.95-.32-2.69-.9c-.22-.17-.53-.13-.7.09-.17.22-.13.53.09.7.91.72 2.09 1.11 3.3 1.11s2.39-.39 3.31-1.1c.22-.17.26-.48.09-.7-.17-.23-.49-.26-.71-.1z"></path><circle cx="8.5" cy="12.5" r="1"></circle><path d="M12 0C5.37 0 0 5.37 0 12s5.37 12 12 12 12-5.37 12-12S18.63 0 12 0zm7.96 14.82c-1.09 3.74-4.27 6.46-8.04 6.46-3.78 0-6.96-2.72-8.04-6.47-1.19-.11-2.13-1.18-2.13-2.52 0-1.27.85-2.31 1.97-2.5 2.09-1.46 3.8-3.49 4.09-5.05v-.01c1.35 2.63 6.3 5.19 11.83 5.06l.3-.03c1.28 0 2.31 1.14 2.31 2.54 0 1.38-1.02 2.51-2.29 2.52z"></path><circle cx="15.5" cy="12.5" r="1"></circle></g> |
| <g id="favorite"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"></path></g> |
| <g id="favorite-outline"><path d="M16.5 3c-1.74 0-3.41.81-4.5 2.09C10.91 3.81 9.24 3 7.5 3 4.42 3 2 5.42 2 8.5c0 3.78 3.4 6.86 8.55 11.54L12 21.35l1.45-1.32C18.6 15.36 22 12.28 22 8.5 22 5.42 19.58 3 16.5 3zm-4.4 15.55l-.1.1-.1-.1C7.14 14.24 4 11.39 4 8.5 4 6.5 5.5 5 7.5 5c1.54 0 3.04.99 3.57 2.36h1.87C13.46 5.99 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5 0 2.89-3.14 5.74-7.9 10.05z"></path></g> |
| <g id="file-download"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"></path></g> |
| <g id="file-upload"><path d="M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z"></path></g> |
| <g id="filter-list"><path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"></path></g> |
| <g id="find-in-page"><path d="M20 19.59V8l-6-6H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c.45 0 .85-.15 1.19-.4l-4.43-4.43c-.8.52-1.74.83-2.76.83-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5c0 1.02-.31 1.96-.83 2.75L20 19.59zM9 13c0 1.66 1.34 3 3 3s3-1.34 3-3-1.34-3-3-3-3 1.34-3 3z"></path></g> |
| <g id="find-replace"><path d="M11 6c1.38 0 2.63.56 3.54 1.46L12 10h6V4l-2.05 2.05C14.68 4.78 12.93 4 11 4c-3.53 0-6.43 2.61-6.92 6H6.1c.46-2.28 2.48-4 4.9-4zm5.64 9.14c.66-.9 1.12-1.97 1.28-3.14H15.9c-.46 2.28-2.48 4-4.9 4-1.38 0-2.63-.56-3.54-1.46L10 12H4v6l2.05-2.05C7.32 17.22 9.07 18 11 18c1.55 0 2.98-.51 4.14-1.36L20 21.49 21.49 20l-4.85-4.86z"></path></g> |
| <g id="flag"><path d="M14.4 6L14 4H5v17h2v-7h5.6l.4 2h7V6z"></path></g> |
| <g id="flip-to-back"><path d="M9 7H7v2h2V7zm0 4H7v2h2v-2zm0-8c-1.11 0-2 .9-2 2h2V3zm4 12h-2v2h2v-2zm6-12v2h2c0-1.1-.9-2-2-2zm-6 0h-2v2h2V3zM9 17v-2H7c0 1.1.89 2 2 2zm10-4h2v-2h-2v2zm0-4h2V7h-2v2zm0 8c1.1 0 2-.9 2-2h-2v2zM5 7H3v12c0 1.1.89 2 2 2h12v-2H5V7zm10-2h2V3h-2v2zm0 12h2v-2h-2v2z"></path></g> |
| <g id="flip-to-front"><path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm2 4v-2H3c0 1.1.89 2 2 2zM3 9h2V7H3v2zm12 12h2v-2h-2v2zm4-18H9c-1.11 0-2 .9-2 2v10c0 1.1.89 2 2 2h10c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 12H9V5h10v10zm-8 6h2v-2h-2v2zm-4 0h2v-2H7v2z"></path></g> |
| <g id="folder"><path d="M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"></path></g> |
| <g id="folder-open"><path d="M20 6h-8l-2-2H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 12H4V8h16v10z"></path></g> |
| <g id="folder-shared"><path d="M20 6h-8l-2-2H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm-5 3c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm4 8h-8v-1c0-1.33 2.67-2 4-2s4 .67 4 2v1z"></path></g> |
| <g id="forward"><path d="M12 8V4l8 8-8 8v-4H4V8z"></path></g> |
| <g id="fullscreen"><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"></path></g> |
| <g id="fullscreen-exit"><path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"></path></g> |
| <g id="gesture"><path d="M4.59 6.89c.7-.71 1.4-1.35 1.71-1.22.5.2 0 1.03-.3 1.52-.25.42-2.86 3.89-2.86 6.31 0 1.28.48 2.34 1.34 2.98.75.56 1.74.73 2.64.46 1.07-.31 1.95-1.4 3.06-2.77 1.21-1.49 2.83-3.44 4.08-3.44 1.63 0 1.65 1.01 1.76 1.79-3.78.64-5.38 3.67-5.38 5.37 0 1.7 1.44 3.09 3.21 3.09 1.63 0 4.29-1.33 4.69-6.1H21v-2.5h-2.47c-.15-1.65-1.09-4.2-4.03-4.2-2.25 0-4.18 1.91-4.94 2.84-.58.73-2.06 2.48-2.29 2.72-.25.3-.68.84-1.11.84-.45 0-.72-.83-.36-1.92.35-1.09 1.4-2.86 1.85-3.52.78-1.14 1.3-1.92 1.3-3.28C8.95 3.69 7.31 3 6.44 3 5.12 3 3.97 4 3.72 4.25c-.36.36-.66.66-.88.93l1.75 1.71zm9.29 11.66c-.31 0-.74-.26-.74-.72 0-.6.73-2.2 2.87-2.76-.3 2.69-1.43 3.48-2.13 3.48z"></path></g> |
| <g id="get-app"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"></path></g> |
| <g id="grade"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"></path></g> |
| <g id="group-work"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zM8 17.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5zM9.5 8c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5S9.5 9.38 9.5 8zm6.5 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"></path></g> |
| <g id="help"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z"></path></g> |
| <g id="highlight-remove"><path d="M14.59 8L12 10.59 9.41 8 8 9.41 10.59 12 8 14.59 9.41 16 12 13.41 14.59 16 16 14.59 13.41 12 16 9.41 14.59 8zM12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"></path></g> |
| <g id="history"><path opacity=".9" d="M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9zm-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8H12z"></path></g> |
| <g id="home"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"></path></g> |
| <g id="https"><path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"></path></g> |
| <g id="inbox"><path d="M19 3H4.99c-1.1 0-1.98.9-1.98 2L3 19c0 1.1.89 2 1.99 2H19c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 12h-4c0 1.66-1.34 3-3 3s-3-1.34-3-3H4.99V5H19v10zm-3-5h-2V7h-4v3H8l4 4 4-4z"></path></g> |
| <g id="info"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"></path></g> |
| <g id="info-outline"><path d="M11 17h2v-6h-2v6zm1-15C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zM11 9h2V7h-2v2z"></path></g> |
| <g id="input"><path d="M21 3.01H3c-1.1 0-2 .9-2 2V9h2V4.99h18v14.03H3V15H1v4.01c0 1.1.9 1.98 2 1.98h18c1.1 0 2-.88 2-1.98v-14c0-1.11-.9-2-2-2zM11 16l4-4-4-4v3H1v2h10v3z"></path></g> |
| <g id="invert-colors"><path d="M17.66 7.93L12 2.27 6.34 7.93c-3.12 3.12-3.12 8.19 0 11.31C7.9 20.8 9.95 21.58 12 21.58c2.05 0 4.1-.78 5.66-2.34 3.12-3.12 3.12-8.19 0-11.31zM12 19.59c-1.6 0-3.11-.62-4.24-1.76C6.62 16.69 6 15.19 6 13.59s.62-3.11 1.76-4.24L12 5.1v14.49z"></path></g> |
| <g id="label"><path d="M17.63 5.84C17.27 5.33 16.67 5 16 5L5 5.01C3.9 5.01 3 5.9 3 7v10c0 1.1.9 1.99 2 1.99L16 19c.67 0 1.27-.33 1.63-.84L22 12l-4.37-6.16z"></path></g> |
| <g id="label-outline"><path d="M17.63 5.84C17.27 5.33 16.67 5 16 5L5 5.01C3.9 5.01 3 5.9 3 7v10c0 1.1.9 1.99 2 1.99L16 19c.67 0 1.27-.33 1.63-.84L22 12l-4.37-6.16zM16 17H5V7h11l3.55 5L16 17z"></path></g> |
| <g id="language"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95c-.32-1.25-.78-2.45-1.38-3.56 1.84.63 3.37 1.91 4.33 3.56zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2 0 .68.06 1.34.14 2H4.26zm.82 2h2.95c.32 1.25.78 2.45 1.38 3.56-1.84-.63-3.37-1.9-4.33-3.56zm2.95-8H5.08c.96-1.66 2.49-2.93 4.33-3.56C8.81 5.55 8.35 6.75 8.03 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2 0-.68.07-1.35.16-2h4.68c.09.65.16 1.32.16 2 0 .68-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95c-.96 1.65-2.49 2.93-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2 0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z"></path></g> |
| <g id="launch"><path d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"></path></g> |
| <g id="link"><path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"></path></g> |
| <g id="list"><path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z"></path></g> |
| <g id="lock"><path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"></path></g> |
| <g id="lock-open"><path d="M12 17c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm6-9h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6h1.9c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm0 12H6V10h12v10z"></path></g> |
| <g id="lock-outline"><path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6-5.1c1.71 0 3.1 1.39 3.1 3.1v2H9V6h-.1c0-1.71 1.39-3.1 3.1-3.1zM18 20H6V10h12v10zm-6-3c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z"></path></g> |
| <g id="loyalty"><path d="M21.41 11.58l-9-9C12.05 2.22 11.55 2 11 2H4c-1.1 0-2 .9-2 2v7c0 .55.22 1.05.59 1.42l9 9c.36.36.86.58 1.41.58.55 0 1.05-.22 1.41-.59l7-7c.37-.36.59-.86.59-1.41 0-.55-.23-1.06-.59-1.42zM5.5 7C4.67 7 4 6.33 4 5.5S4.67 4 5.5 4 7 4.67 7 5.5 6.33 7 5.5 7zm11.77 8.27L13 19.54l-4.27-4.27C8.28 14.81 8 14.19 8 13.5c0-1.38 1.12-2.5 2.5-2.5.69 0 1.32.28 1.77.74l.73.72.73-.73c.45-.45 1.08-.73 1.77-.73 1.38 0 2.5 1.12 2.5 2.5 0 .69-.28 1.32-.73 1.77z"></path></g> |
| <g id="mail"><path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"></path></g> |
| <g id="markunread"><path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"></path></g> |
| <g id="markunread-mailbox"><path d="M20 6H10v6H8V4h6V0H6v6H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2z"></path></g> |
| <g id="menu"><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"></path></g> |
| <g id="more-horiz"><path d="M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"></path></g> |
| <g id="more-vert"><path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"></path></g> |
| <g id="note-add"><path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 14h-3v3h-2v-3H8v-2h3v-3h2v3h3v2zm-3-7V3.5L18.5 9H13z"></path></g> |
| <g id="open-in-browser"><path d="M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h4v-2H5V8h14v10h-4v2h4c1.1 0 2-.9 2-2V6c0-1.1-.89-2-2-2zm-7 6l-4 4h3v6h2v-6h3l-4-4z"></path></g> |
| <g id="open-in-new"><path d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"></path></g> |
| <g id="open-with"><path d="M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z"></path></g> |
| <g id="pageview"><path d="M11 8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3zm8-5H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1.41 16l-3.83-3.83c-.8.52-1.74.83-2.76.83-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5c0 1.02-.31 1.96-.83 2.75L19 17.59 17.59 19z"></path></g> |
| <g id="payment"><path d="M20 4H4c-1.11 0-1.99.89-1.99 2L2 18c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V6c0-1.11-.89-2-2-2zm0 14H4v-6h16v6zm0-10H4V6h16v2z"></path></g> |
| <g id="perm-camera-mic"><path d="M20 5h-3.17L15 3H9L7.17 5H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h7v-2.09c-2.83-.48-5-2.94-5-5.91h2c0 2.21 1.79 4 4 4s4-1.79 4-4h2c0 2.97-2.17 5.43-5 5.91V21h7c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm-6 8c0 1.1-.9 2-2 2s-2-.9-2-2V9c0-1.1.9-2 2-2s2 .9 2 2v4z"></path></g> |
| <g id="perm-contact-cal"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm6 12H6v-1c0-2 4-3.1 6-3.1s6 1.1 6 3.1v1z"></path></g> |
| <g id="perm-data-setting"><path d="M18.99 11.5c.34 0 .67.03 1 .07L20 0 0 20h11.56c-.04-.33-.07-.66-.07-1 0-4.14 3.36-7.5 7.5-7.5zm3.71 7.99c.02-.16.04-.32.04-.49 0-.17-.01-.33-.04-.49l1.06-.83c.09-.08.12-.21.06-.32l-1-1.73c-.06-.11-.19-.15-.31-.11l-1.24.5c-.26-.2-.54-.37-.85-.49l-.19-1.32c-.01-.12-.12-.21-.24-.21h-2c-.12 0-.23.09-.25.21l-.19 1.32c-.3.13-.59.29-.85.49l-1.24-.5c-.11-.04-.24 0-.31.11l-1 1.73c-.06.11-.04.24.06.32l1.06.83c-.02.16-.03.32-.03.49 0 .17.01.33.03.49l-1.06.83c-.09.08-.12.21-.06.32l1 1.73c.06.11.19.15.31.11l1.24-.5c.26.2.54.37.85.49l.19 1.32c.02.12.12.21.25.21h2c.12 0 .23-.09.25-.21l.19-1.32c.3-.13.59-.29.84-.49l1.25.5c.11.04.24 0 .31-.11l1-1.73c.06-.11.03-.24-.06-.32l-1.07-.83zm-3.71 1.01c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"></path></g> |
| <g id="perm-device-info"><path d="M13 7h-2v2h2V7zm0 4h-2v6h2v-6zm4-9.99L7 1c-1.1 0-2 .9-2 2v18c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-1.99-2-1.99zM17 19H7V5h10v14z"></path></g> |
| <g id="perm-identity"><path d="M12 5.9c1.16 0 2.1.94 2.1 2.1s-.94 2.1-2.1 2.1S9.9 9.16 9.9 8s.94-2.1 2.1-2.1m0 9c2.97 0 6.1 1.46 6.1 2.1v1.1H5.9V17c0-.64 3.13-2.1 6.1-2.1M12 4C9.79 4 8 5.79 8 8s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 9c-2.67 0-8 1.34-8 4v3h16v-3c0-2.66-5.33-4-8-4z"></path></g> |
| <g id="perm-media"><path d="M2 6H0v5h.01L0 20c0 1.1.9 2 2 2h18v-2H2V6zm20-2h-8l-2-2H6c-1.1 0-1.99.9-1.99 2L4 16c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zM7 15l4.5-6 3.5 4.51 2.5-3.01L21 15H7z"></path></g> |
| <g id="perm-phone-msg"><path d="M20 15.5c-1.25 0-2.45-.2-3.57-.57-.35-.11-.74-.03-1.02.24l-2.2 2.2c-2.83-1.44-5.15-3.75-6.59-6.58l2.2-2.21c.28-.27.36-.66.25-1.01C8.7 6.45 8.5 5.25 8.5 4c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1 0 9.39 7.61 17 17 17 .55 0 1-.45 1-1v-3.5c0-.55-.45-1-1-1zM12 3v10l3-3h6V3h-9z"></path></g> |
| <g id="perm-scan-wifi"><path d="M12 3C6.95 3 3.15 4.85 0 7.23L12 22 24 7.25C20.85 4.87 17.05 3 12 3zm1 13h-2v-6h2v6zm-2-8V6h2v2h-2z"></path></g> |
| <g id="picture-in-picture"><path d="M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 1.98 2 1.98h18c1.1 0 2-.88 2-1.98V5c0-1.1-.9-2-2-2zm0 16.01H3V4.98h18v14.03z"></path></g> |
| <g id="polymer"><path d="M19 4h-4L7.11 16.63 4.5 12 9 4H5L.5 12 5 20h4l7.89-12.63L19.5 12 15 20h4l4.5-8z"></path></g> |
| <g id="print"><path d="M19 8H5c-1.66 0-3 1.34-3 3v6h4v4h12v-4h4v-6c0-1.66-1.34-3-3-3zm-3 11H8v-5h8v5zm3-7c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-1-9H6v4h12V3z"></path></g> |
| <g id="query-builder"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zM12.5 7H11v6l5.25 3.15.75-1.23-4.5-2.67z"></path></g> |
| <g id="question-answer"><path d="M21 6h-2v9H6v2c0 .55.45 1 1 1h11l4 4V7c0-.55-.45-1-1-1zm-4 6V3c0-.55-.45-1-1-1H3c-.55 0-1 .45-1 1v14l4-4h10c.55 0 1-.45 1-1z"></path></g> |
| <g id="radio-button-off"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"></path></g> |
| <g id="radio-button-on"><path d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zm0-5C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"></path></g> |
| <g id="receipt"><path d="M18 17H6v-2h12v2zm0-4H6v-2h12v2zm0-4H6V7h12v2zM3 22l1.5-1.5L6 22l1.5-1.5L9 22l1.5-1.5L12 22l1.5-1.5L15 22l1.5-1.5L18 22l1.5-1.5L21 22V2l-1.5 1.5L18 2l-1.5 1.5L15 2l-1.5 1.5L12 2l-1.5 1.5L9 2 7.5 3.5 6 2 4.5 3.5 3 2v20z"></path></g> |
| <g id="redeem"><path d="M20 6h-2.18c.11-.31.18-.65.18-1 0-1.66-1.34-3-3-3-1.05 0-1.96.54-2.5 1.35l-.5.67-.5-.68C10.96 2.54 10.05 2 9 2 7.34 2 6 3.34 6 5c0 .35.07.69.18 1H4c-1.11 0-1.99.89-1.99 2L2 19c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zm-5-2c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zM9 4c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm11 15H4v-2h16v2zm0-5H4V8h5.08L7 10.83 8.62 12 11 8.76l1-1.36 1 1.36L15.38 12 17 10.83 14.92 8H20v6z"></path></g> |
| <g id="redo"><path d="M18.4 10.6C16.55 8.99 14.15 8 11.5 8c-4.65 0-8.58 3.03-9.96 7.22L3.9 16c1.05-3.19 4.05-5.5 7.6-5.5 1.95 0 3.73.72 5.12 1.88L13 16h9V7l-3.6 3.6z"></path></g> |
| <g id="refresh"><path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"></path></g> |
| <g id="remove"><path d="M19 13H5v-2h14v2z"></path></g> |
| <g id="remove-circle"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11H7v-2h10v2z"></path></g> |
| <g id="remove-circle-outline"><path d="M7 11v2h10v-2H7zm5-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"></path></g> |
| <g id="reorder"><path d="M3 15h18v-2H3v2zm0 4h18v-2H3v2zm0-8h18V9H3v2zm0-6v2h18V5H3z"></path></g> |
| <g id="reply"><path d="M10 9V5l-7 7 7 7v-4.1c5 0 8.5 1.6 11 5.1-1-5-4-10-11-11z"></path></g> |
| <g id="reply-all"><path d="M7 8V5l-7 7 7 7v-3l-4-4 4-4zm6 1V5l-7 7 7 7v-4.1c5 0 8.5 1.6 11 5.1-1-5-4-10-11-11z"></path></g> |
| <g id="report"><path d="M15.73 3H8.27L3 8.27v7.46L8.27 21h7.46L21 15.73V8.27L15.73 3zM12 17.3c-.72 0-1.3-.58-1.3-1.3 0-.72.58-1.3 1.3-1.3.72 0 1.3.58 1.3 1.3 0 .72-.58 1.3-1.3 1.3zm1-4.3h-2V7h2v6z"></path></g> |
| <g id="report-problem"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"></path></g> |
| <g id="restore"><path d="M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9zm-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8H12z"></path></g> |
| <g id="room"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"></path></g> |
| <g id="save"><path d="M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z"></path></g> |
| <g id="schedule"><path fill-opacity=".9" d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zM12.5 7H11v6l5.25 3.15.75-1.23-4.5-2.67z"></path></g> |
| <g id="search"><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"></path></g> |
| <g id="select-all"><path d="M3 5h2V3c-1.1 0-2 .9-2 2zm0 8h2v-2H3v2zm4 8h2v-2H7v2zM3 9h2V7H3v2zm10-6h-2v2h2V3zm6 0v2h2c0-1.1-.9-2-2-2zM5 21v-2H3c0 1.1.9 2 2 2zm-2-4h2v-2H3v2zM9 3H7v2h2V3zm2 18h2v-2h-2v2zm8-8h2v-2h-2v2zm0 8c1.1 0 2-.9 2-2h-2v2zm0-12h2V7h-2v2zm0 8h2v-2h-2v2zm-4 4h2v-2h-2v2zm0-16h2V3h-2v2zM7 17h10V7H7v10zm2-8h6v6H9V9z"></path></g> |
| <g id="send"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"></path></g> |
| <g id="settings"><path d="M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.3-.61-.22l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.23-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98s.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.23.09.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z"></path></g> |
| <g id="settings-applications"><path d="M12 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm7-7H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-1.75 9c0 .23-.02.46-.05.68l1.48 1.16c.13.11.17.3.08.45l-1.4 2.42c-.09.15-.27.21-.43.15l-1.74-.7c-.36.28-.76.51-1.18.69l-.26 1.85c-.03.17-.18.3-.35.3h-2.8c-.17 0-.32-.13-.35-.29l-.26-1.85c-.43-.18-.82-.41-1.18-.69l-1.74.7c-.16.06-.34 0-.43-.15l-1.4-2.42c-.09-.15-.05-.34.08-.45l1.48-1.16c-.03-.23-.05-.46-.05-.69 0-.23.02-.46.05-.68l-1.48-1.16c-.13-.11-.17-.3-.08-.45l1.4-2.42c.09-.15.27-.21.43-.15l1.74.7c.36-.28.76-.51 1.18-.69l.26-1.85c.03-.17.18-.3.35-.3h2.8c.17 0 .32.13.35.29l.26 1.85c.43.18.82.41 1.18.69l1.74-.7c.16-.06.34 0 .43.15l1.4 2.42c.09.15.05.34-.08.45l-1.48 1.16c.03.23.05.46.05.69z"></path></g> |
| <g id="settings-backup-restore"><path d="M14 12c0-1.1-.9-2-2-2s-2 .9-2 2 .9 2 2 2 2-.9 2-2zm-2-9c-4.97 0-9 4.03-9 9H0l4 4 4-4H5c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.51 0-2.91-.49-4.06-1.3l-1.42 1.44C8.04 20.3 9.94 21 12 21c4.97 0 9-4.03 9-9s-4.03-9-9-9z"></path></g> |
| <g id="settings-bluetooth"><path d="M11 24h2v-2h-2v2zm-4 0h2v-2H7v2zm8 0h2v-2h-2v2zm2.71-18.29L12 0h-1v7.59L6.41 3 5 4.41 10.59 10 5 15.59 6.41 17 11 12.41V20h1l5.71-5.71-4.3-4.29 4.3-4.29zM13 3.83l1.88 1.88L13 7.59V3.83zm1.88 10.46L13 16.17v-3.76l1.88 1.88z"></path></g> |
| <g id="settings-cell"><path d="M7 24h2v-2H7v2zm4 0h2v-2h-2v2zm4 0h2v-2h-2v2zM16 .01L8 0C6.9 0 6 .9 6 2v16c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V2c0-1.1-.9-1.99-2-1.99zM16 16H8V4h8v12z"></path></g> |
| <g id="settings-display"><path d="M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16.01H3V4.99h18v14.02zM8 16h2.5l1.5 1.5 1.5-1.5H16v-2.5l1.5-1.5-1.5-1.5V8h-2.5L12 6.5 10.5 8H8v2.5L6.5 12 8 13.5V16zm4-7c1.66 0 3 1.34 3 3s-1.34 3-3 3V9z"></path></g> |
| <g id="settings-ethernet"><path d="M7.77 6.76L6.23 5.48.82 12l5.41 6.52 1.54-1.28L3.42 12l4.35-5.24zM7 13h2v-2H7v2zm10-2h-2v2h2v-2zm-6 2h2v-2h-2v2zm6.77-7.52l-1.54 1.28L20.58 12l-4.35 5.24 1.54 1.28L23.18 12l-5.41-6.52z"></path></g> |
| <g id="settings-input-antenna"><path d="M12 5c-3.87 0-7 3.13-7 7h2c0-2.76 2.24-5 5-5s5 2.24 5 5h2c0-3.87-3.13-7-7-7zm1 9.29c.88-.39 1.5-1.26 1.5-2.29 0-1.38-1.12-2.5-2.5-2.5S9.5 10.62 9.5 12c0 1.02.62 1.9 1.5 2.29v3.3L7.59 21 9 22.41l3-3 3 3L16.41 21 13 17.59v-3.3zM12 1C5.93 1 1 5.93 1 12h2c0-4.97 4.03-9 9-9s9 4.03 9 9h2c0-6.07-4.93-11-11-11z"></path></g> |
| <g id="settings-input-component"><path d="M5 2c0-.55-.45-1-1-1s-1 .45-1 1v4H1v6h6V6H5V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2H9v2zm-8 0c0 1.3.84 2.4 2 2.82V23h2v-4.18C6.16 18.4 7 17.3 7 16v-2H1v2zM21 6V2c0-.55-.45-1-1-1s-1 .45-1 1v4h-2v6h6V6h-2zm-8-4c0-.55-.45-1-1-1s-1 .45-1 1v4H9v6h6V6h-2V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2h-6v2z"></path></g> |
| <g id="settings-input-composite"><path d="M5 2c0-.55-.45-1-1-1s-1 .45-1 1v4H1v6h6V6H5V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2H9v2zm-8 0c0 1.3.84 2.4 2 2.82V23h2v-4.18C6.16 18.4 7 17.3 7 16v-2H1v2zM21 6V2c0-.55-.45-1-1-1s-1 .45-1 1v4h-2v6h6V6h-2zm-8-4c0-.55-.45-1-1-1s-1 .45-1 1v4H9v6h6V6h-2V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2h-6v2z"></path></g> |
| <g id="settings-input-hdmi"><path d="M18 7V4c0-1.1-.9-2-2-2H8c-1.1 0-2 .9-2 2v3H5v6l3 6v3h8v-3l3-6V7h-1zM8 4h8v3h-2V5h-1v2h-2V5h-1v2H8V4z"></path></g> |
| <g id="settings-input-svideo"><path d="M8 11.5c0-.83-.67-1.5-1.5-1.5S5 10.67 5 11.5 5.67 13 6.5 13 8 12.33 8 11.5zm7-5c0-.83-.67-1.5-1.5-1.5h-3C9.67 5 9 5.67 9 6.5S9.67 8 10.5 8h3c.83 0 1.5-.67 1.5-1.5zM8.5 15c-.83 0-1.5.67-1.5 1.5S7.67 18 8.5 18s1.5-.67 1.5-1.5S9.33 15 8.5 15zM12 1C5.93 1 1 5.93 1 12s4.93 11 11 11 11-4.93 11-11S18.07 1 12 1zm0 20c-4.96 0-9-4.04-9-9s4.04-9 9-9 9 4.04 9 9-4.04 9-9 9zm5.5-11c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm-2 5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5z"></path></g> |
| <g id="settings-overscan"><path d="M12.01 5.5L10 8h4l-1.99-2.5zM18 10v4l2.5-1.99L18 10zM6 10l-2.5 2.01L6 14v-4zm8 6h-4l2.01 2.5L14 16zm7-13H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16.01H3V4.99h18v14.02z"></path></g> |
| <g id="settings-phone"><path d="M13 9h-2v2h2V9zm4 0h-2v2h2V9zm3 6.5c-1.25 0-2.45-.2-3.57-.57-.35-.11-.74-.03-1.02.24l-2.2 2.2c-2.83-1.44-5.15-3.75-6.59-6.58l2.2-2.21c.28-.27.36-.66.25-1.01C8.7 6.45 8.5 5.25 8.5 4c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1 0 9.39 7.61 17 17 17 .55 0 1-.45 1-1v-3.5c0-.55-.45-1-1-1zM19 9v2h2V9h-2z"></path></g> |
| <g id="settings-power"><path d="M7 24h2v-2H7v2zm4 0h2v-2h-2v2zm2-22h-2v10h2V2zm3.56 2.44l-1.45 1.45C16.84 6.94 18 8.83 18 11c0 3.31-2.69 6-6 6s-6-2.69-6-6c0-2.17 1.16-4.06 2.88-5.12L7.44 4.44C5.36 5.88 4 8.28 4 11c0 4.42 3.58 8 8 8s8-3.58 8-8c0-2.72-1.36-5.12-3.44-6.56zM15 24h2v-2h-2v2z"></path></g> |
| <g id="settings-remote"><path d="M15 9H9c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h6c.55 0 1-.45 1-1V10c0-.55-.45-1-1-1zm-3 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zM7.05 6.05l1.41 1.41C9.37 6.56 10.62 6 12 6s2.63.56 3.54 1.46l1.41-1.41C15.68 4.78 13.93 4 12 4s-3.68.78-4.95 2.05zM12 0C8.96 0 6.21 1.23 4.22 3.22l1.41 1.41C7.26 3.01 9.51 2 12 2s4.74 1.01 6.36 2.64l1.41-1.41C17.79 1.23 15.04 0 12 0z"></path></g> |
| <g id="settings-voice"><path d="M7 24h2v-2H7v2zm5-11c1.66 0 2.99-1.34 2.99-3L15 4c0-1.66-1.34-3-3-3S9 2.34 9 4v6c0 1.66 1.34 3 3 3zm-1 11h2v-2h-2v2zm4 0h2v-2h-2v2zm4-14h-1.7c0 3-2.54 5.1-5.3 5.1S6.7 13 6.7 10H5c0 3.41 2.72 6.23 6 6.72V20h2v-3.28c3.28-.49 6-3.31 6-6.72z"></path></g> |
| <g id="shop"><path d="M16 6V4c0-1.11-.89-2-2-2h-4c-1.11 0-2 .89-2 2v2H2v13c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V6h-6zm-6-2h4v2h-4V4zM9 18V9l7.5 4L9 18z"></path></g> |
| <g id="shop-two"><path d="M3 9H1v11c0 1.11.89 2 2 2h14c1.11 0 2-.89 2-2H3V9zm15-4V3c0-1.11-.89-2-2-2h-4c-1.11 0-2 .89-2 2v2H5v11c0 1.11.89 2 2 2h14c1.11 0 2-.89 2-2V5h-5zm-6-2h4v2h-4V3zm0 12V8l5.5 3-5.5 4z"></path></g> |
| <g id="shopping-basket"><path d="M17.21 9l-4.38-6.56c-.19-.28-.51-.42-.83-.42-.32 0-.64.14-.83.43L6.79 9H2c-.55 0-1 .45-1 1 0 .09.01.18.04.27l2.54 9.27c.23.84 1 1.46 1.92 1.46h13c.92 0 1.69-.62 1.93-1.46l2.54-9.27L23 10c0-.55-.45-1-1-1h-4.79zM9 9l3-4.4L15 9H9zm3 8c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z"></path></g> |
| <g id="shopping-cart"><path d="M7 18c-1.1 0-1.99.9-1.99 2S5.9 22 7 22s2-.9 2-2-.9-2-2-2zM1 2v2h2l3.6 7.59-1.35 2.45c-.16.28-.25.61-.25.96 0 1.1.9 2 2 2h12v-2H7.42c-.14 0-.25-.11-.25-.25l.03-.12.9-1.63h7.45c.75 0 1.41-.41 1.75-1.03l3.58-6.49c.08-.14.12-.31.12-.48 0-.55-.45-1-1-1H5.21l-.94-2H1zm16 16c-1.1 0-1.99.9-1.99 2s.89 2 1.99 2 2-.9 2-2-.9-2-2-2z"></path></g> |
| <g id="sort"><path d="M3 18h6v-2H3v2zM3 6v2h18V6H3zm0 7h12v-2H3v2z"></path></g> |
| <g id="speaker-notes"><path d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM8 14H6v-2h2v2zm0-3H6V9h2v2zm0-3H6V6h2v2zm7 6h-5v-2h5v2zm3-3h-8V9h8v2zm0-3h-8V6h8v2z"></path></g> |
| <g id="spellcheck"><path d="M12.45 16h2.09L9.43 3H7.57L2.46 16h2.09l1.12-3h5.64l1.14 3zm-6.02-5L8.5 5.48 10.57 11H6.43zm15.16.59l-8.09 8.09L9.83 16l-1.41 1.41 5.09 5.09L23 13l-1.41-1.41z"></path></g> |
| <g id="star"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"></path></g> |
| <g id="star-half"><path d="M22 9.74l-7.19-.62L12 2.5 9.19 9.13 2 9.74l5.46 4.73-1.64 7.03L12 17.77l6.18 3.73-1.63-7.03L22 9.74zM12 15.9V6.6l1.71 4.04 4.38.38-3.32 2.88 1 4.28L12 15.9z"></path></g> |
| <g id="star-outline"><path d="M22 9.24l-7.19-.62L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27 18.18 21l-1.63-7.03L22 9.24zM12 15.4l-3.76 2.27 1-4.28-3.32-2.88 4.38-.38L12 6.1l1.71 4.04 4.38.38-3.32 2.88 1 4.28L12 15.4z"></path></g> |
| <g id="star-rate"><path d="M12 14.3l3.71 2.7-1.42-4.36L18 10h-4.55L12 5.5 10.55 10H6l3.71 2.64L8.29 17z"></path></g> |
| <g id="stars"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm4.24 16L12 15.45 7.77 18l1.12-4.81-3.73-3.23 4.92-.42L12 5l1.92 4.53 4.92.42-3.73 3.23L16.23 18z"></path></g> |
| <g id="store"><path d="M20 4H4v2h16V4zm1 10v-2l-1-5H4l-1 5v2h1v6h10v-6h4v6h2v-6h1zm-9 4H6v-4h6v4z"></path></g> |
| <g id="subject"><path d="M14 17H4v2h10v-2zm6-8H4v2h16V9zM4 15h16v-2H4v2zM4 5v2h16V5H4z"></path></g> |
| <g id="supervisor-account"><path d="M16.5 12c1.38 0 2.49-1.12 2.49-2.5S17.88 7 16.5 7C15.12 7 14 8.12 14 9.5s1.12 2.5 2.5 2.5zM9 11c1.66 0 2.99-1.34 2.99-3S10.66 5 9 5C7.34 5 6 6.34 6 8s1.34 3 3 3zm7.5 3c-1.83 0-5.5.92-5.5 2.75V19h11v-2.25c0-1.83-3.67-2.75-5.5-2.75zM9 13c-2.33 0-7 1.17-7 3.5V19h7v-2.25c0-.85.33-2.34 2.37-3.47C10.5 13.1 9.66 13 9 13z"></path></g> |
| <g id="swap-horiz"><path d="M6.99 11L3 15l3.99 4v-3H14v-2H6.99v-3zM21 9l-3.99-4v3H10v2h7.01v3L21 9z"></path></g> |
| <g id="swap-vert"><path d="M16 17.01V10h-2v7.01h-3L15 21l4-3.99h-3zM9 3L5 6.99h3V14h2V6.99h3L9 3z"></path></g> |
| <g id="swap-vert-circle"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zM6.5 9L10 5.5 13.5 9H11v4H9V9H6.5zm11 6L14 18.5 10.5 15H13v-4h2v4h2.5z"></path></g> |
| <g id="system-update-tv"><path d="M12 16.5l4-4h-3v-9h-2v9H8l4 4zm9-13h-6v1.99h6v14.03H3V5.49h6V3.5H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2v-14c0-1.1-.9-2-2-2z"></path></g> |
| <g id="tab"><path d="M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h10v4h8v10z"></path></g> |
| <g id="tab-unselected"><path d="M1 9h2V7H1v2zm0 4h2v-2H1v2zm0-8h2V3c-1.1 0-2 .9-2 2zm8 16h2v-2H9v2zm-8-4h2v-2H1v2zm2 4v-2H1c0 1.1.9 2 2 2zM21 3h-8v6h10V5c0-1.1-.9-2-2-2zm0 14h2v-2h-2v2zM9 5h2V3H9v2zM5 21h2v-2H5v2zM5 5h2V3H5v2zm16 16c1.1 0 2-.9 2-2h-2v2zm0-8h2v-2h-2v2zm-8 8h2v-2h-2v2zm4 0h2v-2h-2v2z"></path></g> |
| <g id="text-format"><path d="M5 17v2h14v-2H5zm4.5-4.2h5l.9 2.2h2.1L12.75 4h-1.5L6.5 15h2.1l.9-2.2zM12 5.98L13.87 11h-3.74L12 5.98z"></path></g> |
| <g id="theaters"><path d="M18 3v2h-2V3H8v2H6V3H4v18h2v-2h2v2h8v-2h2v2h2V3h-2zM8 17H6v-2h2v2zm0-4H6v-2h2v2zm0-4H6V7h2v2zm10 8h-2v-2h2v2zm0-4h-2v-2h2v2zm0-4h-2V7h2v2z"></path></g> |
| <g id="thumb-down"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v1.91l.01.01L1 14c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"></path></g> |
| <g id="thumb-up"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-1.91l-.01-.01L23 10z"></path></g> |
| <g id="thumbs-up-down"><path d="M12 6c0-.55-.45-1-1-1H5.82l.66-3.18.02-.23c0-.31-.13-.59-.33-.8L5.38 0 .44 4.94C.17 5.21 0 5.59 0 6v6.5c0 .83.67 1.5 1.5 1.5h6.75c.62 0 1.15-.38 1.38-.91l2.26-5.29c.07-.17.11-.36.11-.55V6zm10.5 4h-6.75c-.62 0-1.15.38-1.38.91l-2.26 5.29c-.07.17-.11.36-.11.55V18c0 .55.45 1 1 1h5.18l-.66 3.18-.02.24c0 .31.13.59.33.8l.79.78 4.94-4.94c.27-.27.44-.65.44-1.06v-6.5c0-.83-.67-1.5-1.5-1.5z"></path></g> |
| <g id="toc"><path d="M3 9h14V7H3v2zm0 4h14v-2H3v2zm0 4h14v-2H3v2zm16 0h2v-2h-2v2zm0-10v2h2V7h-2zm0 6h2v-2h-2v2z"></path></g> |
| <g id="today"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7z"></path></g> |
| <g id="track-changes"><path fill="#231F20" d="M19.07 4.93l-1.41 1.41C19.1 7.79 20 9.79 20 12c0 4.42-3.58 8-8 8s-8-3.58-8-8c0-4.08 3.05-7.44 7-7.93v2.02C8.16 6.57 6 9.03 6 12c0 3.31 2.69 6 6 6s6-2.69 6-6c0-1.66-.67-3.16-1.76-4.24l-1.41 1.41C15.55 9.9 16 10.9 16 12c0 2.21-1.79 4-4 4s-4-1.79-4-4c0-1.86 1.28-3.41 3-3.86v2.14c-.6.35-1 .98-1 1.72 0 1.1.9 2 2 2s2-.9 2-2c0-.74-.4-1.38-1-1.72V2h-1C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10c0-2.76-1.12-5.26-2.93-7.07z"></path></g> |
| <g id="translate"><path d="M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z"></path></g> |
| <g id="trending-down"><path d="M16 18l2.29-2.29-4.88-4.88-4 4L2 7.41 3.41 6l6 6 4-4 6.3 6.29L22 12v6z"></path></g> |
| <g id="trending-neutral"><path d="M22 12l-4-4v3H3v2h15v3z"></path></g> |
| <g id="trending-up"><path d="M16 6l2.29 2.29-4.88 4.88-4-4L2 16.59 3.41 18l6-6 4 4 6.3-6.29L22 12V6z"></path></g> |
| <g id="turned-in"><path d="M17 3H7c-1.1 0-1.99.9-1.99 2L5 21l7-3 7 3V5c0-1.1-.9-2-2-2z"></path></g> |
| <g id="turned-in-not"><path d="M17 3H7c-1.1 0-1.99.9-1.99 2L5 21l7-3 7 3V5c0-1.1-.9-2-2-2zm0 15l-5-2.18L7 18V5h10v13z"></path></g> |
| <g id="undo"><path d="M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z"></path></g> |
| <g id="unfold-less"><path d="M7.41 18.59L8.83 20 12 16.83 15.17 20l1.41-1.41L12 14l-4.59 4.59zm9.18-13.18L15.17 4 12 7.17 8.83 4 7.41 5.41 12 10l4.59-4.59z"></path></g> |
| <g id="unfold-more"><path d="M12 5.83L15.17 9l1.41-1.41L12 3 7.41 7.59 8.83 9 12 5.83zm0 12.34L8.83 15l-1.41 1.41L12 21l4.59-4.59L15.17 15 12 18.17z"></path></g> |
| <g id="verified-user"><path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm-2 16l-4-4 1.41-1.41L10 14.17l6.59-6.59L18 9l-8 8z"></path></g> |
| <g id="view-agenda"><path d="M20 13H3c-.55 0-1 .45-1 1v6c0 .55.45 1 1 1h17c.55 0 1-.45 1-1v-6c0-.55-.45-1-1-1zm0-10H3c-.55 0-1 .45-1 1v6c0 .55.45 1 1 1h17c.55 0 1-.45 1-1V4c0-.55-.45-1-1-1z"></path></g> |
| <g id="view-array"><path d="M4 18h3V5H4v13zM18 5v13h3V5h-3zM8 18h9V5H8v13z"></path></g> |
| <g id="view-carousel"><path d="M7 19h10V4H7v15zm-5-2h4V6H2v11zM18 6v11h4V6h-4z"></path></g> |
| <g id="view-column"><path d="M10 18h5V5h-5v13zm-6 0h5V5H4v13zM16 5v13h5V5h-5z"></path></g> |
| <g id="view-day"><path d="M2 21h19v-3H2v3zM20 8H3c-.55 0-1 .45-1 1v6c0 .55.45 1 1 1h17c.55 0 1-.45 1-1V9c0-.55-.45-1-1-1zM2 3v3h19V3H2z"></path></g> |
| <g id="view-headline"><path d="M4 15h17v-2H4v2zm0 4h17v-2H4v2zm0-8h17V9H4v2zm0-6v2h17V5H4z"></path></g> |
| <g id="view-list"><path d="M4 14h4v-4H4v4zm0 5h4v-4H4v4zM4 9h4V5H4v4zm5 5h12v-4H9v4zm0 5h12v-4H9v4zM9 5v4h12V5H9z"></path></g> |
| <g id="view-module"><path d="M4 11h5V5H4v6zm0 7h5v-6H4v6zm6 0h5v-6h-5v6zm6 0h5v-6h-5v6zm-6-7h5V5h-5v6zm6-6v6h5V5h-5z"></path></g> |
| <g id="view-quilt"><path d="M10 18h5v-6h-5v6zm-6 0h5V5H4v13zm12 0h5v-6h-5v6zM10 5v6h11V5H10z"></path></g> |
| <g id="view-stream"><path d="M4 18h17v-6H4v6zM4 5v6h17V5H4z"></path></g> |
| <g id="view-week"><path d="M6 5H3c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h3c.55 0 1-.45 1-1V6c0-.55-.45-1-1-1zm14 0h-3c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h3c.55 0 1-.45 1-1V6c0-.55-.45-1-1-1zm-7 0h-3c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h3c.55 0 1-.45 1-1V6c0-.55-.45-1-1-1z"></path></g> |
| <g id="visibility"><path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"></path></g> |
| <g id="visibility-off"><path d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z"></path></g> |
| <g id="wallet-giftcard"><path d="M20 6h-2.18c.11-.31.18-.65.18-1 0-1.66-1.34-3-3-3-1.05 0-1.96.54-2.5 1.35l-.5.67-.5-.68C10.96 2.54 10.05 2 9 2 7.34 2 6 3.34 6 5c0 .35.07.69.18 1H4c-1.11 0-1.99.89-1.99 2L2 19c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zm-5-2c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zM9 4c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm11 15H4v-2h16v2zm0-5H4V8h5.08L7 10.83 8.62 12 11 8.76l1-1.36 1 1.36L15.38 12 17 10.83 14.92 8H20v6z"></path></g> |
| <g id="wallet-membership"><path d="M20 2H4c-1.11 0-2 .89-2 2v11c0 1.11.89 2 2 2h4v5l4-2 4 2v-5h4c1.11 0 2-.89 2-2V4c0-1.11-.89-2-2-2zm0 13H4v-2h16v2zm0-5H4V4h16v6z"></path></g> |
| <g id="wallet-travel"><path d="M20 6h-3V4c0-1.11-.89-2-2-2H9c-1.11 0-2 .89-2 2v2H4c-1.11 0-2 .89-2 2v11c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zM9 4h6v2H9V4zm11 15H4v-2h16v2zm0-5H4V8h3v2h2V8h6v2h2V8h3v6z"></path></g> |
| <g id="warning"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"></path></g> |
| <g id="work"><path d="M20 6h-4V4c0-1.11-.89-2-2-2h-4c-1.11 0-2 .89-2 2v2H4c-1.11 0-1.99.89-1.99 2L2 19c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zm-6 0h-4V4h4v2z"></path></g> |
| </defs></svg> |
| </core-iconset-svg> |
| |
| <core-iconset-svg id="image" iconsize="24"> |
| <svg><defs> |
| <g id="add-to-photos"><path d="M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-1 9h-4v4h-2v-4H9V9h4V5h2v4h4v2z"></path></g> |
| <g id="adjust"><path d="M12 2C6.49 2 2 6.49 2 12s4.49 10 10 10 10-4.49 10-10S17.51 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm3-8c0 1.66-1.34 3-3 3s-3-1.34-3-3 1.34-3 3-3 3 1.34 3 3z"></path></g> |
| <g id="assistant-photo"><path d="M14.4 6L14 4H5v17h2v-7h5.6l.4 2h7V6z"></path></g> |
| <g id="audiotrack"><path d="M12 3v9.28c-.47-.17-.97-.28-1.5-.28C8.01 12 6 14.01 6 16.5S8.01 21 10.5 21c2.31 0 4.2-1.75 4.45-4H15V6h4V3h-7z"></path></g> |
| <g id="blur-circular"><path d="M10 9c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0 4c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zM7 9.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm3 7c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm-3-3c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm3-6c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zM14 9c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0-1.5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zm3 6c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm0-4c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm2-3.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm0-3.5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1z"></path></g> |
| <g id="blur-linear"><path d="M5 17.5c.83 0 1.5-.67 1.5-1.5s-.67-1.5-1.5-1.5-1.5.67-1.5 1.5.67 1.5 1.5 1.5zM9 13c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm0-4c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zM3 21h18v-2H3v2zM5 9.5c.83 0 1.5-.67 1.5-1.5S5.83 6.5 5 6.5 3.5 7.17 3.5 8 4.17 9.5 5 9.5zm0 4c.83 0 1.5-.67 1.5-1.5s-.67-1.5-1.5-1.5-1.5.67-1.5 1.5.67 1.5 1.5 1.5zM9 17c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm8-.5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zM3 3v2h18V3H3zm14 5.5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zm0 4c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zM13 9c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm0 4c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm0 4c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1z"></path></g> |
| <g id="blur-off"><path d="M14 7c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm-.2 4.48l.2.02c.83 0 1.5-.67 1.5-1.5s-.67-1.5-1.5-1.5-1.5.67-1.5 1.5l.02.2c.09.67.61 1.19 1.28 1.28zM14 3.5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zm-4 0c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zm11 7c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zM10 7c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm8 8c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm0-4c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm0-4c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm-4 13.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zM2.5 5.27l3.78 3.78L6 9c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1c0-.1-.03-.19-.06-.28l2.81 2.81c-.71.11-1.25.73-1.25 1.47 0 .83.67 1.5 1.5 1.5.74 0 1.36-.54 1.47-1.25l2.81 2.81c-.09-.03-.18-.06-.28-.06-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1c0-.1-.03-.19-.06-.28l3.78 3.78L20 20.23 3.77 4 2.5 5.27zM10 17c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm11-3.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zM6 13c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zM3 9.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm7 11c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zM6 17c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm-3-3.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5z"></path></g> |
| <g id="blur-on"><path d="M6 13c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0 4c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0-8c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm-3 .5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zM6 5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm15 5.5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zM14 7c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm0-3.5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zm-11 10c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm7 7c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm0-17c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zM10 7c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm0 5.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm8 .5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0 4c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0-8c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0-4c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm3 8.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zM14 17c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0 3.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm-4-12c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0 8.5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm4-4.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-4c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5z"></path></g> |
| <g id="brightness-1"><circle cx="12" cy="12" r="10"></circle></g> |
| <g id="brightness-2"><path d="M10 2c-1.82 0-3.53.5-5 1.35C7.99 5.08 10 8.3 10 12s-2.01 6.92-5 8.65C6.47 21.5 8.18 22 10 22c5.52 0 10-4.48 10-10S15.52 2 10 2z"></path></g> |
| <g id="brightness-3"><path d="M9 2c-1.05 0-2.05.16-3 .46 4.06 1.27 7 5.06 7 9.54 0 4.48-2.94 8.27-7 9.54.95.3 1.95.46 3 .46 5.52 0 10-4.48 10-10S14.52 2 9 2z"></path></g> |
| <g id="brightness-4"><path d="M20 8.69V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12 20 8.69zM12 18c-.89 0-1.74-.2-2.5-.55C11.56 16.5 13 14.42 13 12s-1.44-4.5-3.5-5.45C10.26 6.2 11.11 6 12 6c3.31 0 6 2.69 6 6s-2.69 6-6 6z"></path></g> |
| <g id="brightness-5"><path d="M20 15.31L23.31 12 20 8.69V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69zM12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6 6 2.69 6 6-2.69 6-6 6z"></path></g> |
| <g id="brightness-6"><path d="M20 15.31L23.31 12 20 8.69V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69zM12 18V6c3.31 0 6 2.69 6 6s-2.69 6-6 6z"></path></g> |
| <g id="brightness-7"><path d="M20 8.69V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12 20 8.69zM12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6 6 2.69 6 6-2.69 6-6 6zm0-10c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4z"></path></g> |
| <g id="brush"><path d="M7 14c-1.66 0-3 1.34-3 3 0 1.31-1.16 2-2 2 .92 1.22 2.49 2 4 2 2.21 0 4-1.79 4-4 0-1.66-1.34-3-3-3zm13.71-9.37l-1.34-1.34c-.39-.39-1.02-.39-1.41 0L9 12.25 11.75 15l8.96-8.96c.39-.39.39-1.02 0-1.41z"></path></g> |
| <g id="camera"><path d="M9.4 10.5l4.77-8.26C13.47 2.09 12.75 2 12 2c-2.4 0-4.6.85-6.32 2.25l3.66 6.35.06-.1zM21.54 9c-.92-2.92-3.15-5.26-6-6.34L11.88 9h9.66zm.26 1h-7.49l.29.5 4.76 8.25C21 16.97 22 14.61 22 12c0-.69-.07-1.35-.2-2zM8.54 12l-3.9-6.75C3.01 7.03 2 9.39 2 12c0 .69.07 1.35.2 2h7.49l-1.15-2zm-6.08 3c.92 2.92 3.15 5.26 6 6.34L12.12 15H2.46zm11.27 0l-3.9 6.76c.7.15 1.42.24 2.17.24 2.4 0 4.6-.85 6.32-2.25l-3.66-6.35-.93 1.6z"></path></g> |
| <g id="camera-alt"><circle cx="12" cy="12" r="3.2"></circle><path d="M9 2L7.17 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2h-3.17L15 2H9zm3 15c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z"></path></g> |
| <g id="camera-front"><path d="M10 20H5v2h5v2l3-3-3-3v2zm4 0v2h5v-2h-5zM12 8c1.1 0 2-.9 2-2s-.9-2-2-2-1.99.9-1.99 2S10.9 8 12 8zm5-8H7C5.9 0 5 .9 5 2v14c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V2c0-1.1-.9-2-2-2zM7 2h10v10.5c0-1.67-3.33-2.5-5-2.5s-5 .83-5 2.5V2z"></path></g> |
| <g id="camera-rear"><path d="M10 20H5v2h5v2l3-3-3-3v2zm4 0v2h5v-2h-5zm3-20H7C5.9 0 5 .9 5 2v14c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V2c0-1.1-.9-2-2-2zm-5 6c-1.11 0-2-.9-2-2s.89-2 1.99-2 2 .9 2 2C14 5.1 13.1 6 12 6z"></path></g> |
| <g id="camera-roll"><path d="M14 5c0-1.1-.9-2-2-2h-1V2c0-.55-.45-1-1-1H6c-.55 0-1 .45-1 1v1H4c-1.1 0-2 .9-2 2v15c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2h8V5h-8zm-2 13h-2v-2h2v2zm0-9h-2V7h2v2zm4 9h-2v-2h2v2zm0-9h-2V7h2v2zm4 9h-2v-2h2v2zm0-9h-2V7h2v2z"></path></g> |
| <g id="center-focus-strong"><path d="M12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm-7 7H3v4c0 1.1.9 2 2 2h4v-2H5v-4zM5 5h4V3H5c-1.1 0-2 .9-2 2v4h2V5zm14-2h-4v2h4v4h2V5c0-1.1-.9-2-2-2zm0 16h-4v2h4c1.1 0 2-.9 2-2v-4h-2v4z"></path></g> |
| <g id="center-focus-weak"><path d="M5 15H3v4c0 1.1.9 2 2 2h4v-2H5v-4zM5 5h4V3H5c-1.1 0-2 .9-2 2v4h2V5zm14-2h-4v2h4v4h2V5c0-1.1-.9-2-2-2zm0 16h-4v2h4c1.1 0 2-.9 2-2v-4h-2v4zM12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z"></path></g> |
| <g id="collections"><path d="M22 16V4c0-1.1-.9-2-2-2H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2zm-11-4l2.03 2.71L16 11l4 5H8l3-4zM2 6v14c0 1.1.9 2 2 2h14v-2H4V6H2z"></path></g> |
| <g id="color-lens"><path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"></path></g> |
| <g id="colorize"><path d="M20.71 5.63l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-3.12 3.12-1.93-1.91-1.41 1.41 1.42 1.42L3 16.25V21h4.75l8.92-8.92 1.42 1.42 1.41-1.41-1.92-1.92 3.12-3.12c.4-.4.4-1.03.01-1.42zM6.92 19L5 17.08l8.06-8.06 1.92 1.92L6.92 19z"></path></g> |
| <g id="compare"><path d="M10 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h5v2h2V1h-2v2zm0 15H5l5-6v6zm9-15h-5v2h5v13l-5-6v9h5c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"></path></g> |
| <g id="control-point"><path d="M13 7h-2v4H7v2h4v4h2v-4h4v-2h-4V7zm-1-5C6.49 2 2 6.49 2 12s4.49 10 10 10 10-4.49 10-10S17.51 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"></path></g> |
| <g id="control-point-duplicate"><path d="M16 8h-2v3h-3v2h3v3h2v-3h3v-2h-3zM2 12c0-2.79 1.64-5.2 4.01-6.32V3.52C2.52 4.76 0 8.09 0 12s2.52 7.24 6.01 8.48v-2.16C3.64 17.2 2 14.79 2 12zm13-9c-4.96 0-9 4.04-9 9s4.04 9 9 9 9-4.04 9-9-4.04-9-9-9zm0 16c-3.86 0-7-3.14-7-7s3.14-7 7-7 7 3.14 7 7-3.14 7-7 7z"></path></g> |
| <g id="crop"><path d="M17 15h2V7c0-1.1-.9-2-2-2H9v2h8v8zM7 17V1H5v4H1v2h4v10c0 1.1.9 2 2 2h10v4h2v-4h4v-2H7z"></path></g> |
| <g id="crop-16-9"><path d="M19 6H5c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 10H5V8h14v8z"></path></g> |
| <g id="crop-3-2"><path d="M19 4H5c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 14H5V6h14v12z"></path></g> |
| <g id="crop-5-4"><path d="M19 5H5c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 12H5V7h14v10z"></path></g> |
| <g id="crop-7-5"><path d="M19 7H5c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V9c0-1.1-.9-2-2-2zm0 8H5V9h14v6z"></path></g> |
| <g id="crop-din"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14z"></path></g> |
| <g id="crop-free"><path d="M3 5v4h2V5h4V3H5c-1.1 0-2 .9-2 2zm2 10H3v4c0 1.1.9 2 2 2h4v-2H5v-4zm14 4h-4v2h4c1.1 0 2-.9 2-2v-4h-2v4zm0-16h-4v2h4v4h2V5c0-1.1-.9-2-2-2z"></path></g> |
| <g id="crop-landscape"><path d="M19 5H5c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 12H5V7h14v10z"></path></g> |
| <g id="crop-original"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14zm-5.04-6.71l-2.75 3.54-1.96-2.36L6.5 17h11l-3.54-4.71z"></path></g> |
| <g id="crop-portrait"><path d="M17 3H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H7V5h10v14z"></path></g> |
| <g id="crop-square"><path d="M18 4H6c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 14H6V6h12v12z"></path></g> |
| <g id="dehaze"><path d="M2 15.5v2h20v-2H2zm0-5v2h20v-2H2zm0-5v2h20v-2H2z"></path></g> |
| <g id="details"><path d="M3 4l9 16 9-16H3zm3.38 2h11.25L12 16 6.38 6z"></path></g> |
| <g id="edit"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"></path></g> |
| <g id="exposure"><path d="M15 17v2h2v-2h2v-2h-2v-2h-2v2h-2v2h2zm5-15H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM5 5h6v2H5V5zm15 15H4L20 4v16z"></path></g> |
| <g id="exposure-minus-1"><path d="M4 11v2h8v-2H4zm15 7h-2V7.38L14 8.4V6.7L18.7 5h.3v13z"></path></g> |
| <g id="exposure-minus-2"><path d="M15.05 16.29l2.86-3.07c.38-.39.72-.79 1.04-1.18.32-.39.59-.78.82-1.17.23-.39.41-.78.54-1.17s.19-.79.19-1.18c0-.53-.09-1.02-.27-1.46-.18-.44-.44-.81-.78-1.11-.34-.31-.77-.54-1.26-.71-.51-.16-1.08-.24-1.72-.24-.69 0-1.31.11-1.85.32-.54.21-1 .51-1.36.88-.37.37-.65.8-.84 1.3-.18.47-.27.97-.28 1.5h2.14c.01-.31.05-.6.13-.87.09-.29.23-.54.4-.75.18-.21.41-.37.68-.49.27-.12.6-.18.96-.18.31 0 .58.05.81.15.23.1.43.25.59.43.16.18.28.4.37.65.08.25.13.52.13.81 0 .22-.03.43-.08.65-.06.22-.15.45-.29.7-.14.25-.32.53-.56.83-.23.3-.52.65-.88 1.03l-4.17 4.55V18H21v-1.71h-5.95zM2 11v2h8v-2H2z"></path></g> |
| <g id="exposure-plus-1"><path d="M10 7H8v4H4v2h4v4h2v-4h4v-2h-4V7zm10 11h-2V7.38L15 8.4V6.7L19.7 5h.3v13z"></path></g> |
| <g id="exposure-plus-2"><path d="M16.05 16.29l2.86-3.07c.38-.39.72-.79 1.04-1.18.32-.39.59-.78.82-1.17.23-.39.41-.78.54-1.17.13-.39.19-.79.19-1.18 0-.53-.09-1.02-.27-1.46-.18-.44-.44-.81-.78-1.11-.34-.31-.77-.54-1.26-.71-.51-.16-1.08-.24-1.72-.24-.69 0-1.31.11-1.85.32-.54.21-1 .51-1.36.88-.37.37-.65.8-.84 1.3-.18.47-.27.97-.28 1.5h2.14c.01-.31.05-.6.13-.87.09-.29.23-.54.4-.75.18-.21.41-.37.68-.49.27-.12.6-.18.96-.18.31 0 .58.05.81.15.23.1.43.25.59.43.16.18.28.4.37.65.08.25.13.52.13.81 0 .22-.03.43-.08.65-.06.22-.15.45-.29.7-.14.25-.32.53-.56.83-.23.3-.52.65-.88 1.03l-4.17 4.55V18H22v-1.71h-5.95zM8 7H6v4H2v2h4v4h2v-4h4v-2H8V7z"></path></g> |
| <g id="exposure-zero"><path d="M16.14 12.5c0 1-.1 1.85-.3 2.55-.2.7-.48 1.27-.83 1.7-.36.44-.79.75-1.3.95-.51.2-1.07.3-1.7.3-.62 0-1.18-.1-1.69-.3-.51-.2-.95-.51-1.31-.95-.36-.44-.65-1.01-.85-1.7-.2-.7-.3-1.55-.3-2.55v-2.04c0-1 .1-1.85.3-2.55.2-.7.48-1.26.84-1.69.36-.43.8-.74 1.31-.93C10.81 5.1 11.38 5 12 5c.63 0 1.19.1 1.7.29.51.19.95.5 1.31.93.36.43.64.99.84 1.69.2.7.3 1.54.3 2.55v2.04zm-2.11-2.36c0-.64-.05-1.18-.13-1.62-.09-.44-.22-.79-.4-1.06-.17-.27-.39-.46-.64-.58-.25-.13-.54-.19-.86-.19-.32 0-.61.06-.86.18s-.47.31-.64.58c-.17.27-.31.62-.4 1.06s-.13.98-.13 1.62v2.67c0 .64.05 1.18.14 1.62.09.45.23.81.4 1.09s.39.48.64.61.54.19.87.19c.33 0 .62-.06.87-.19s.46-.33.63-.61c.17-.28.3-.64.39-1.09.09-.45.13-.99.13-1.62v-2.66z"></path></g> |
| <g id="filter"><path d="M15.96 10.29l-2.75 3.54-1.96-2.36L8.5 15h11l-3.54-4.71zM3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm18-4H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 16H7V3h14v14z"></path></g> |
| <g id="filter-1"><path d="M3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm11 10h2V5h-4v2h2v8zm7-14H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 16H7V3h14v14z"></path></g> |
| <g id="filter-2"><path d="M3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm18-4H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 16H7V3h14v14zm-4-4h-4v-2h2c1.1 0 2-.89 2-2V7c0-1.11-.9-2-2-2h-4v2h4v2h-2c-1.1 0-2 .89-2 2v4h6v-2z"></path></g> |
| <g id="filter-3"><path d="M21 1H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 16H7V3h14v14zM3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm14 8v-1.5c0-.83-.67-1.5-1.5-1.5.83 0 1.5-.67 1.5-1.5V7c0-1.11-.9-2-2-2h-4v2h4v2h-2v2h2v2h-4v2h4c1.1 0 2-.89 2-2z"></path></g> |
| <g id="filter-4"><path d="M3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm12 10h2V5h-2v4h-2V5h-2v6h4v4zm6-14H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 16H7V3h14v14z"></path></g> |
| <g id="filter-5"><path d="M21 1H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 16H7V3h14v14zM3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm14 8v-2c0-1.11-.9-2-2-2h-2V7h4V5h-6v6h4v2h-4v2h4c1.1 0 2-.89 2-2z"></path></g> |
| <g id="filter-6"><path d="M3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm18-4H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 16H7V3h14v14zm-8-2h2c1.1 0 2-.89 2-2v-2c0-1.11-.9-2-2-2h-2V7h4V5h-4c-1.1 0-2 .89-2 2v6c0 1.11.9 2 2 2zm0-4h2v2h-2v-2z"></path></g> |
| <g id="filter-7"><path d="M3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm18-4H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 16H7V3h14v14zm-8-2l4-8V5h-6v2h4l-4 8h2z"></path></g> |
| <g id="filter-8"><path d="M3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm18-4H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 16H7V3h14v14zm-8-2h2c1.1 0 2-.89 2-2v-1.5c0-.83-.67-1.5-1.5-1.5.83 0 1.5-.67 1.5-1.5V7c0-1.11-.9-2-2-2h-2c-1.1 0-2 .89-2 2v1.5c0 .83.67 1.5 1.5 1.5-.83 0-1.5.67-1.5 1.5V13c0 1.11.9 2 2 2zm0-8h2v2h-2V7zm0 4h2v2h-2v-2z"></path></g> |
| <g id="filter-9"><path d="M3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm18-4H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 16H7V3h14v14zM15 5h-2c-1.1 0-2 .89-2 2v2c0 1.11.9 2 2 2h2v2h-4v2h4c1.1 0 2-.89 2-2V7c0-1.11-.9-2-2-2zm0 4h-2V7h2v2z"></path></g> |
| <g id="filter-9-plus"><path d="M3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm11 7V8c0-1.11-.9-2-2-2h-1c-1.1 0-2 .89-2 2v1c0 1.11.9 2 2 2h1v1H9v2h3c1.1 0 2-.89 2-2zm-3-3V8h1v1h-1zm10-8H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 8h-2V7h-2v2h-2v2h2v2h2v-2h2v6H7V3h14v6z"></path></g> |
| <g id="filter-b-and-w"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16l-7-8v8H5l7-8V5h7v14z"></path></g> |
| <g id="filter-center-focus"><path d="M5 15H3v4c0 1.1.9 2 2 2h4v-2H5v-4zM5 5h4V3H5c-1.1 0-2 .9-2 2v4h2V5zm14-2h-4v2h4v4h2V5c0-1.1-.9-2-2-2zm0 16h-4v2h4c1.1 0 2-.9 2-2v-4h-2v4zM12 9c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"></path></g> |
| <g id="filter-drama"><path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.61 5.64 5.36 8.04 2.35 8.36 0 10.9 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM19 18H6c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4h2c0-2.76-1.86-5.08-4.4-5.78C8.61 6.88 10.2 6 12 6c3.03 0 5.5 2.47 5.5 5.5v.5H19c1.65 0 3 1.35 3 3s-1.35 3-3 3z"></path></g> |
| <g id="filter-frames"><path d="M20 4h-4l-4-4-4 4H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 16H4V6h4.52l3.52-3.5L15.52 6H20v14zM18 8H6v10h12"></path></g> |
| <g id="filter-hdr"><path d="M14 6l-3.75 5 2.85 3.8-1.6 1.2C9.81 13.75 7 10 7 10l-6 8h22L14 6z"></path></g> |
| <g id="filter-none"><path d="M3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm18-4H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 16H7V3h14v14z"></path></g> |
| <g id="filter-tilt-shift"><path d="M11 4.07V2.05c-2.01.2-3.84 1-5.32 2.21L7.1 5.69c1.11-.86 2.44-1.44 3.9-1.62zm7.32.19C16.84 3.05 15.01 2.25 13 2.05v2.02c1.46.18 2.79.76 3.9 1.62l1.42-1.43zM19.93 11h2.02c-.2-2.01-1-3.84-2.21-5.32L18.31 7.1c.86 1.11 1.44 2.44 1.62 3.9zM5.69 7.1L4.26 5.68C3.05 7.16 2.25 8.99 2.05 11h2.02c.18-1.46.76-2.79 1.62-3.9zM4.07 13H2.05c.2 2.01 1 3.84 2.21 5.32l1.43-1.43c-.86-1.1-1.44-2.43-1.62-3.89zM15 12c0-1.66-1.34-3-3-3s-3 1.34-3 3 1.34 3 3 3 3-1.34 3-3zm3.31 4.9l1.43 1.43c1.21-1.48 2.01-3.32 2.21-5.32h-2.02c-.18 1.45-.76 2.78-1.62 3.89zM13 19.93v2.02c2.01-.2 3.84-1 5.32-2.21l-1.43-1.43c-1.1.86-2.43 1.44-3.89 1.62zm-7.32-.19C7.16 20.95 9 21.75 11 21.95v-2.02c-1.46-.18-2.79-.76-3.9-1.62l-1.42 1.43z"></path></g> |
| <g id="filter-vintage"><path d="M18.7 12.4c-.28-.16-.57-.29-.86-.4.29-.11.58-.24.86-.4 1.92-1.11 2.99-3.12 3-5.19-1.79-1.03-4.07-1.11-6 0-.28.16-.54.35-.78.54.05-.31.08-.63.08-.95 0-2.22-1.21-4.15-3-5.19C10.21 1.85 9 3.78 9 6c0 .32.03.64.08.95-.24-.2-.5-.39-.78-.55-1.92-1.11-4.2-1.03-6 0 0 2.07 1.07 4.08 3 5.19.28.16.57.29.86.4-.29.11-.58.24-.86.4-1.92 1.11-2.99 3.12-3 5.19 1.79 1.03 4.07 1.11 6 0 .28-.16.54-.35.78-.54-.05.32-.08.64-.08.96 0 2.22 1.21 4.15 3 5.19 1.79-1.04 3-2.97 3-5.19 0-.32-.03-.64-.08-.95.24.2.5.38.78.54 1.92 1.11 4.2 1.03 6 0-.01-2.07-1.08-4.08-3-5.19zM12 16c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4z"></path></g> |
| <g id="flare"><path d="M7 11H1v2h6v-2zm2.17-3.24L7.05 5.64 5.64 7.05l2.12 2.12 1.41-1.41zM13 1h-2v6h2V1zm5.36 6.05l-1.41-1.41-2.12 2.12 1.41 1.41 2.12-2.12zM17 11v2h6v-2h-6zm-5-2c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3zm2.83 7.24l2.12 2.12 1.41-1.41-2.12-2.12-1.41 1.41zm-9.19.71l1.41 1.41 2.12-2.12-1.41-1.41-2.12 2.12zM11 23h2v-6h-2v6z"></path></g> |
| <g id="flash-auto"><path d="M3 2v12h3v9l7-12H9l4-9H3zm16 0h-2l-3.2 9h1.9l.7-2h3.2l.7 2h1.9L19 2zm-2.15 5.65L18 4l1.15 3.65h-2.3z"></path></g> |
| <g id="flash-off"><path d="M3.27 3L2 4.27l5 5V13h3v9l3.58-6.14L17.73 20 19 18.73 3.27 3zM17 10h-4l4-8H7v2.18l8.46 8.46L17 10z"></path></g> |
| <g id="flash-on"><path d="M7 2v11h3v9l7-12h-4l4-8z"></path></g> |
| <g id="flip"><path d="M15 21h2v-2h-2v2zm4-12h2V7h-2v2zM3 5v14c0 1.1.9 2 2 2h4v-2H5V5h4V3H5c-1.1 0-2 .9-2 2zm16-2v2h2c0-1.1-.9-2-2-2zm-8 20h2V1h-2v22zm8-6h2v-2h-2v2zM15 5h2V3h-2v2zm4 8h2v-2h-2v2zm0 8c1.1 0 2-.9 2-2h-2v2z"></path></g> |
| <g id="gradient"><path d="M11 9h2v2h-2zm-2 2h2v2H9zm4 0h2v2h-2zm2-2h2v2h-2zM7 9h2v2H7zm12-6H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 18H7v-2h2v2zm4 0h-2v-2h2v2zm4 0h-2v-2h2v2zm2-7h-2v2h2v2h-2v-2h-2v2h-2v-2h-2v2H9v-2H7v2H5v-2h2v-2H5V5h14v6z"></path></g> |
| <g id="grain"><path d="M10 12c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zM6 8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12-8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm-4 8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm4-4c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-4-4c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-4-4c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"></path></g> |
| <g id="grid-off"><path d="M8 4v1.45l2 2V4h4v4h-3.45l2 2H14v1.45l2 2V10h4v4h-3.45l2 2H20v1.45l2 2V4c0-1.1-.9-2-2-2H4.55l2 2H8zm8 0h4v4h-4V4zM1.27 1.27L0 2.55l2 2V20c0 1.1.9 2 2 2h15.46l2 2 1.27-1.27L1.27 1.27zM10 12.55L11.45 14H10v-1.45zm-6-6L5.45 8H4V6.55zM8 20H4v-4h4v4zm0-6H4v-4h3.45l.55.55V14zm6 6h-4v-4h3.45l.55.54V20zm2 0v-1.46L17.46 20H16z"></path></g> |
| <g id="grid-on"><path d="M20 2H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM8 20H4v-4h4v4zm0-6H4v-4h4v4zm0-6H4V4h4v4zm6 12h-4v-4h4v4zm0-6h-4v-4h4v4zm0-6h-4V4h4v4zm6 12h-4v-4h4v4zm0-6h-4v-4h4v4zm0-6h-4V4h4v4z"></path></g> |
| <g id="hdr-off"><path d="M18 17L3.27 2.27 2 3.55l4 4V11H4V7H2v10h2v-4h2v4h2V9.55l1 1V17h4c.67 0 1.26-.33 1.62-.84l6.34 6.34 1.27-1.27L18 17zm-5-2h-2v-2.45l2 2V15zm5-2h1l.82 3.27.73.73H22l-1.19-4.17c.7-.31 1.19-1.01 1.19-1.83V9c0-1.1-.9-2-2-2h-4v5.45l2 2V13zm0-4h2v2h-2V9zm-3 2.45V9c0-1.1-.9-2-2-2h-2.45L15 11.45z"></path></g> |
| <g id="hdr-on"><path d="M6 11H4V7H2v10h2v-4h2v4h2V7H6v4zm7-4H9v10h4c1.1 0 2-.9 2-2V9c0-1.1-.9-2-2-2zm0 8h-2V9h2v6zm9-4V9c0-1.1-.9-2-2-2h-4v10h2v-4h1l1 4h2l-1.19-4.17c.7-.31 1.19-1.01 1.19-1.83zm-2 0h-2V9h2v2z"></path></g> |
| <g id="hdr-strong"><path d="M17 6c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6zM5 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z"></path></g> |
| <g id="hdr-weak"><path d="M5 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm12-2c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6zm0 10c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4z"></path></g> |
| <g id="healing"><path d="M17.73 12.02l3.98-3.98c.39-.39.39-1.02 0-1.41l-4.34-4.34c-.39-.39-1.02-.39-1.41 0l-3.98 3.98L8 2.29C7.8 2.1 7.55 2 7.29 2c-.25 0-.51.1-.7.29L2.25 6.63c-.39.39-.39 1.02 0 1.41l3.98 3.98L2.25 16c-.39.39-.39 1.02 0 1.41l4.34 4.34c.39.39 1.02.39 1.41 0l3.98-3.98 3.98 3.98c.2.2.45.29.71.29.26 0 .51-.1.71-.29l4.34-4.34c.39-.39.39-1.02 0-1.41l-3.99-3.98zM12 9c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm-4.71 1.96L3.66 7.34l3.63-3.63 3.62 3.62-3.62 3.63zM10 13c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm2 2c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm2-4c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm2.66 9.34l-3.63-3.62 3.63-3.63 3.62 3.62-3.62 3.63z"></path></g> |
| <g id="image"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"></path></g> |
| <g id="image-aspect-ratio"><path d="M16 10h-2v2h2v-2zm0 4h-2v2h2v-2zm-8-4H6v2h2v-2zm4 0h-2v2h2v-2zm8-6H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 14H4V6h16v12z"></path></g> |
| <g id="iso"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM5.5 7.5h2v-2H9v2h2V9H9v2H7.5V9h-2V7.5zM19 19H5L19 5v14zm-2-2v-1.5h-5V17h5z"></path></g> |
| <g id="landscape"><path d="M14 6l-3.75 5 2.85 3.8-1.6 1.2C9.81 13.75 7 10 7 10l-6 8h22L14 6z"></path></g> |
| <g id="leak-add"><path d="M6 3H3v3c1.66 0 3-1.34 3-3zm8 0h-2c0 4.97-4.03 9-9 9v2c6.08 0 11-4.93 11-11zm-4 0H8c0 2.76-2.24 5-5 5v2c3.87 0 7-3.13 7-7zm0 18h2c0-4.97 4.03-9 9-9v-2c-6.07 0-11 4.93-11 11zm8 0h3v-3c-1.66 0-3 1.34-3 3zm-4 0h2c0-2.76 2.24-5 5-5v-2c-3.87 0-7 3.13-7 7z"></path></g> |
| <g id="leak-remove"><path d="M10 3H8c0 .37-.04.72-.12 1.06l1.59 1.59C9.81 4.84 10 3.94 10 3zM3 4.27l2.84 2.84C5.03 7.67 4.06 8 3 8v2c1.61 0 3.09-.55 4.27-1.46L8.7 9.97C7.14 11.24 5.16 12 3 12v2c2.71 0 5.19-.99 7.11-2.62l2.5 2.5C10.99 15.81 10 18.29 10 21h2c0-2.16.76-4.14 2.03-5.69l1.43 1.43C14.55 17.91 14 19.39 14 21h2c0-1.06.33-2.03.89-2.84L19.73 21 21 19.73 4.27 3 3 4.27zM14 3h-2c0 1.5-.37 2.91-1.02 4.16l1.46 1.46C13.42 6.98 14 5.06 14 3zm5.94 13.12c.34-.08.69-.12 1.06-.12v-2c-.94 0-1.84.19-2.66.52l1.6 1.6zm-4.56-4.56l1.46 1.46C18.09 12.37 19.5 12 21 12v-2c-2.06 0-3.98.58-5.62 1.56z"></path></g> |
| <g id="lens"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z"></path></g> |
| <g id="looks"><path d="M12 10c-3.86 0-7 3.14-7 7h2c0-2.76 2.24-5 5-5s5 2.24 5 5h2c0-3.86-3.14-7-7-7zm0-4C5.93 6 1 10.93 1 17h2c0-4.96 4.04-9 9-9s9 4.04 9 9h2c0-6.07-4.93-11-11-11z"></path></g> |
| <g id="looks-3"><path d="M19.01 3h-14c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-4 7.5c0 .83-.67 1.5-1.5 1.5.83 0 1.5.67 1.5 1.5V15c0 1.11-.9 2-2 2h-4v-2h4v-2h-2v-2h2V9h-4V7h4c1.1 0 2 .89 2 2v1.5z"></path></g> |
| <g id="looks-4"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-4 14h-2v-4H9V7h2v4h2V7h2v10z"></path></g> |
| <g id="looks-5"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-4 6h-4v2h2c1.1 0 2 .89 2 2v2c0 1.11-.9 2-2 2H9v-2h4v-2H9V7h6v2z"></path></g> |
| <g id="looks-6"><path d="M11 15h2v-2h-2v2zm8-12H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-4 6h-4v2h2c1.1 0 2 .89 2 2v2c0 1.11-.9 2-2 2h-2c-1.1 0-2-.89-2-2V9c0-1.11.9-2 2-2h4v2z"></path></g> |
| <g id="looks-one"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14h-2V9h-2V7h4v10z"></path></g> |
| <g id="looks-two"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-4 8c0 1.11-.9 2-2 2h-2v2h4v2H9v-4c0-1.11.9-2 2-2h2V9H9V7h4c1.1 0 2 .89 2 2v2z"></path></g> |
| <g id="loupe"><path d="M13 7h-2v4H7v2h4v4h2v-4h4v-2h-4V7zm-1-5C6.49 2 2 6.49 2 12s4.49 10 10 10h8c1.1 0 2-.9 2-2v-8c0-5.51-4.49-10-10-10zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"></path></g> |
| <g id="movie-creation"><path d="M18 4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4h-4z"></path></g> |
| <g id="nature"><path d="M13 16.12c3.47-.41 6.17-3.36 6.17-6.95 0-3.87-3.13-7-7-7s-7 3.13-7 7c0 3.47 2.52 6.34 5.83 6.89V20H5v2h14v-2h-6v-3.88z"></path></g> |
| <g id="nature-people"><path d="M22.17 9.17c0-3.87-3.13-7-7-7s-7 3.13-7 7c0 3.47 2.52 6.34 5.83 6.89V20H6v-3h1v-4c0-.55-.45-1-1-1H3c-.55 0-1 .45-1 1v4h1v5h16v-2h-3v-3.88c3.47-.41 6.17-3.36 6.17-6.95zM4.5 11c.83 0 1.5-.67 1.5-1.5S5.33 8 4.5 8 3 8.67 3 9.5 3.67 11 4.5 11z"></path></g> |
| <g id="navigate-before"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"></path></g> |
| <g id="navigate-next"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"></path></g> |
| <g id="palette"><path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"></path></g> |
| <g id="panorama"><path d="M23 18V6c0-1.1-.9-2-2-2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2zM8.5 12.5l2.5 3.01L14.5 11l4.5 6H5l3.5-4.5z"></path></g> |
| <g id="panorama-fisheye"><path d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"></path></g> |
| <g id="panorama-horizontal"><path d="M20 6.54v10.91c-2.6-.77-5.28-1.16-8-1.16-2.72 0-5.4.39-8 1.16V6.54c2.6.77 5.28 1.16 8 1.16 2.72.01 5.4-.38 8-1.16M21.43 4c-.1 0-.2.02-.31.06C18.18 5.16 15.09 5.7 12 5.7c-3.09 0-6.18-.55-9.12-1.64-.11-.04-.22-.06-.31-.06-.34 0-.57.23-.57.63v14.75c0 .39.23.62.57.62.1 0 .2-.02.31-.06 2.94-1.1 6.03-1.64 9.12-1.64 3.09 0 6.18.55 9.12 1.64.11.04.21.06.31.06.33 0 .57-.23.57-.63V4.63c0-.4-.24-.63-.57-.63z"></path></g> |
| <g id="panorama-vertical"><path d="M19.94 21.12c-1.1-2.94-1.64-6.03-1.64-9.12 0-3.09.55-6.18 1.64-9.12.04-.11.06-.22.06-.31 0-.34-.23-.57-.63-.57H4.63c-.4 0-.63.23-.63.57 0 .1.02.2.06.31C5.16 5.82 5.71 8.91 5.71 12c0 3.09-.55 6.18-1.64 9.12-.05.11-.07.22-.07.31 0 .33.23.57.63.57h14.75c.39 0 .63-.24.63-.57-.01-.1-.03-.2-.07-.31zM6.54 20c.77-2.6 1.16-5.28 1.16-8 0-2.72-.39-5.4-1.16-8h10.91c-.77 2.6-1.16 5.28-1.16 8 0 2.72.39 5.4 1.16 8H6.54z"></path></g> |
| <g id="panorama-wide-angle"><path d="M12 6c2.45 0 4.71.2 7.29.64.47 1.78.71 3.58.71 5.36 0 1.78-.24 3.58-.71 5.36-2.58.44-4.84.64-7.29.64s-4.71-.2-7.29-.64C4.24 15.58 4 13.78 4 12c0-1.78.24-3.58.71-5.36C7.29 6.2 9.55 6 12 6m0-2c-2.73 0-5.22.24-7.95.72l-.93.16-.25.9C2.29 7.85 2 9.93 2 12s.29 4.15.87 6.22l.25.89.93.16c2.73.49 5.22.73 7.95.73s5.22-.24 7.95-.72l.93-.16.25-.89c.58-2.08.87-4.16.87-6.23s-.29-4.15-.87-6.22l-.25-.89-.93-.16C17.22 4.24 14.73 4 12 4z"></path></g> |
| <g id="photo"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"></path></g> |
| <g id="photo-album"><path d="M18 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM6 4h5v8l-2.5-1.5L6 12V4zm0 15l3-3.86 2.14 2.58 3-3.86L18 19H6z"></path></g> |
| <g id="photo-camera"><circle cx="12" cy="12" r="3.2"></circle><path d="M9 2L7.17 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2h-3.17L15 2H9zm3 15c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z"></path></g> |
| <g id="photo-library"><path d="M22 16V4c0-1.1-.9-2-2-2H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2zm-11-4l2.03 2.71L16 11l4 5H8l3-4zM2 6v14c0 1.1.9 2 2 2h14v-2H4V6H2z"></path></g> |
| <g id="portrait"><path d="M12 12.25c1.24 0 2.25-1.01 2.25-2.25S13.24 7.75 12 7.75 9.75 8.76 9.75 10s1.01 2.25 2.25 2.25zm4.5 4c0-1.5-3-2.25-4.5-2.25s-4.5.75-4.5 2.25V17h9v-.75zM19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14z"></path></g> |
| <g id="remove-red-eye"><path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"></path></g> |
| <g id="rotate-left"><path d="M7.11 8.53L5.7 7.11C4.8 8.27 4.24 9.61 4.07 11h2.02c.14-.87.49-1.72 1.02-2.47zM6.09 13H4.07c.17 1.39.72 2.73 1.62 3.89l1.41-1.42c-.52-.75-.87-1.59-1.01-2.47zm1.01 5.32c1.16.9 2.51 1.44 3.9 1.61V17.9c-.87-.15-1.71-.49-2.46-1.03L7.1 18.32zM13 4.07V1L8.45 5.55 13 10V6.09c2.84.48 5 2.94 5 5.91s-2.16 5.43-5 5.91v2.02c3.95-.49 7-3.85 7-7.93s-3.05-7.44-7-7.93z"></path></g> |
| <g id="rotate-right"><path d="M15.55 5.55L11 1v3.07C7.06 4.56 4 7.92 4 12s3.05 7.44 7 7.93v-2.02c-2.84-.48-5-2.94-5-5.91s2.16-5.43 5-5.91V10l4.55-4.45zM19.93 11c-.17-1.39-.72-2.73-1.62-3.89l-1.42 1.42c.54.75.88 1.6 1.02 2.47h2.02zM13 17.9v2.02c1.39-.17 2.74-.71 3.9-1.61l-1.44-1.44c-.75.54-1.59.89-2.46 1.03zm3.89-2.42l1.42 1.41c.9-1.16 1.45-2.5 1.62-3.89h-2.02c-.14.87-.48 1.72-1.02 2.48z"></path></g> |
| <g id="slideshow"><path d="M10 8v8l5-4-5-4zm9-5H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14z"></path></g> |
| <g id="straighten"><path d="M21 6H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 10H3V8h2v4h2V8h2v4h2V8h2v4h2V8h2v4h2V8h2v8z"></path></g> |
| <g id="style"><path d="M2.53 19.65l1.34.56v-9.03l-2.43 5.86c-.41 1.02.08 2.19 1.09 2.61zm19.5-3.7L17.07 3.98c-.31-.75-1.04-1.21-1.81-1.23-.26 0-.53.04-.79.15L7.1 5.95c-.75.31-1.21 1.03-1.23 1.8-.01.27.04.54.15.8l4.96 11.97c.31.76 1.05 1.22 1.83 1.23.26 0 .52-.05.77-.15l7.36-3.05c1.02-.42 1.51-1.59 1.09-2.6zM7.88 8.75c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-2 11c0 1.1.9 2 2 2h1.45l-3.45-8.34v6.34z"></path></g> |
| <g id="switch-camera"><path d="M20 4h-3.17L15 2H9L7.17 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-5 11.5V13H9v2.5L5.5 12 9 8.5V11h6V8.5l3.5 3.5-3.5 3.5z"></path></g> |
| <g id="switch-video"><path d="M18 9.5V6c0-.55-.45-1-1-1H3c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h14c.55 0 1-.45 1-1v-3.5l4 4v-13l-4 4zm-5 6V13H7v2.5L3.5 12 7 8.5V11h6V8.5l3.5 3.5-3.5 3.5z"></path></g> |
| <g id="tag-faces"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm3.5-9c.83 0 1.5-.67 1.5-1.5S16.33 8 15.5 8 14 8.67 14 9.5s.67 1.5 1.5 1.5zm-7 0c.83 0 1.5-.67 1.5-1.5S9.33 8 8.5 8 7 8.67 7 9.5 7.67 11 8.5 11zm3.5 6.5c2.33 0 4.31-1.46 5.11-3.5H6.89c.8 2.04 2.78 3.5 5.11 3.5z"></path></g> |
| <g id="texture"><path d="M19.51 3.08L3.08 19.51c.09.34.27.65.51.9.25.24.56.42.9.51L20.93 4.49c-.19-.69-.73-1.23-1.42-1.41zM11.88 3L3 11.88v2.83L14.71 3h-2.83zM5 3c-1.1 0-2 .9-2 2v2l4-4H5zm14 18c.55 0 1.05-.22 1.41-.59.37-.36.59-.86.59-1.41v-2l-4 4h2zm-9.71 0h2.83L21 12.12V9.29L9.29 21z"></path></g> |
| <g id="timelapse"><path d="M16.24 7.76C15.07 6.59 13.54 6 12 6v6l-4.24 4.24c2.34 2.34 6.14 2.34 8.49 0 2.34-2.34 2.34-6.14-.01-8.48zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"></path></g> |
| <g id="timer"><path d="M15 1H9v2h6V1zm-4 13h2V8h-2v6zm8.03-6.61l1.42-1.42c-.43-.51-.9-.99-1.41-1.41l-1.42 1.42C16.07 4.74 14.12 4 12 4c-4.97 0-9 4.03-9 9s4.02 9 9 9 9-4.03 9-9c0-2.12-.74-4.07-1.97-5.61zM12 20c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z"></path></g> |
| <g id="timer-10"><path d="M0 7.72V9.4l3-1V18h2V6h-.25L0 7.72zm23.78 6.65c-.14-.28-.35-.53-.63-.74-.28-.21-.61-.39-1.01-.53s-.85-.27-1.35-.38c-.35-.07-.64-.15-.87-.23-.23-.08-.41-.16-.55-.25-.14-.09-.23-.19-.28-.3-.05-.11-.08-.24-.08-.39 0-.14.03-.28.09-.41.06-.13.15-.25.27-.34.12-.1.27-.18.45-.24s.4-.09.64-.09c.25 0 .47.04.66.11.19.07.35.17.48.29.13.12.22.26.29.42.06.16.1.32.1.49h1.95c0-.39-.08-.75-.24-1.09-.16-.34-.39-.63-.69-.88-.3-.25-.66-.44-1.09-.59C21.49 9.07 21 9 20.46 9c-.51 0-.98.07-1.39.21-.41.14-.77.33-1.06.57-.29.24-.51.52-.67.84-.16.32-.23.65-.23 1.01s.08.69.23.96c.15.28.36.52.64.73.27.21.6.38.98.53.38.14.81.26 1.27.36.39.08.71.17.95.26s.43.19.57.29c.13.1.22.22.27.34.05.12.07.25.07.39 0 .32-.13.57-.4.77-.27.2-.66.29-1.17.29-.22 0-.43-.02-.64-.08-.21-.05-.4-.13-.56-.24-.17-.11-.3-.26-.41-.44-.11-.18-.17-.41-.18-.67h-1.89c0 .36.08.71.24 1.05.16.34.39.65.7.93.31.27.69.49 1.15.66.46.17.98.25 1.58.25.53 0 1.01-.06 1.44-.19.43-.13.8-.31 1.11-.54.31-.23.54-.51.71-.83.17-.32.25-.67.25-1.06-.02-.4-.09-.74-.24-1.02zm-9.96-7.32c-.34-.4-.75-.7-1.23-.88-.47-.18-1.01-.27-1.59-.27-.58 0-1.11.09-1.59.27-.48.18-.89.47-1.23.88-.34.41-.6.93-.79 1.59-.18.65-.28 1.45-.28 2.39v1.92c0 .94.09 1.74.28 2.39.19.66.45 1.19.8 1.6.34.41.75.71 1.23.89.48.18 1.01.28 1.59.28.59 0 1.12-.09 1.59-.28.48-.18.88-.48 1.22-.89.34-.41.6-.94.78-1.6.18-.65.28-1.45.28-2.39v-1.92c0-.94-.09-1.74-.28-2.39-.18-.66-.44-1.19-.78-1.59zm-.92 6.17c0 .6-.04 1.11-.12 1.53-.08.42-.2.76-.36 1.02-.16.26-.36.45-.59.57-.23.12-.51.18-.82.18-.3 0-.58-.06-.82-.18s-.44-.31-.6-.57c-.16-.26-.29-.6-.38-1.02-.09-.42-.13-.93-.13-1.53v-2.5c0-.6.04-1.11.13-1.52.09-.41.21-.74.38-1 .16-.25.36-.43.6-.55.24-.11.51-.17.81-.17.31 0 .58.06.81.17.24.11.44.29.6.55.16.25.29.58.37.99.08.41.13.92.13 1.52v2.51z"></path></g> |
| <g id="timer-3"><path d="M11.61 12.97c-.16-.24-.36-.46-.62-.65-.25-.19-.56-.35-.93-.48.3-.14.57-.3.8-.5.23-.2.42-.41.57-.64.15-.23.27-.46.34-.71.08-.24.11-.49.11-.73 0-.55-.09-1.04-.28-1.46-.18-.42-.44-.77-.78-1.06-.33-.28-.73-.5-1.2-.64-.45-.13-.97-.2-1.53-.2-.55 0-1.06.08-1.52.24-.47.17-.87.4-1.2.69-.33.29-.6.63-.78 1.03-.2.39-.29.83-.29 1.29h1.98c0-.26.05-.49.14-.69.09-.2.22-.38.38-.52.17-.14.36-.25.58-.33.22-.08.46-.12.73-.12.61 0 1.06.16 1.36.47.3.31.44.75.44 1.32 0 .27-.04.52-.12.74-.08.22-.21.41-.38.57-.17.16-.38.28-.63.37-.25.09-.55.13-.89.13H6.72v1.57H7.9c.34 0 .64.04.91.11.27.08.5.19.69.35.19.16.34.36.44.61.1.24.16.54.16.87 0 .62-.18 1.09-.53 1.42-.35.33-.84.49-1.45.49-.29 0-.56-.04-.8-.13-.24-.08-.44-.2-.61-.36-.17-.16-.3-.34-.39-.56-.09-.22-.14-.46-.14-.72H4.19c0 .55.11 1.03.32 1.45.21.42.5.77.86 1.05s.77.49 1.24.63.96.21 1.48.21c.57 0 1.09-.08 1.58-.23.49-.15.91-.38 1.26-.68.36-.3.64-.66.84-1.1.2-.43.3-.93.3-1.48 0-.29-.04-.58-.11-.86-.08-.25-.19-.51-.35-.76zm9.26 1.4c-.14-.28-.35-.53-.63-.74-.28-.21-.61-.39-1.01-.53s-.85-.27-1.35-.38c-.35-.07-.64-.15-.87-.23-.23-.08-.41-.16-.55-.25-.14-.09-.23-.19-.28-.3-.05-.11-.08-.24-.08-.39s.03-.28.09-.41c.06-.13.15-.25.27-.34.12-.1.27-.18.45-.24s.4-.09.64-.09c.25 0 .47.04.66.11.19.07.35.17.48.29.13.12.22.26.29.42.06.16.1.32.1.49h1.95c0-.39-.08-.75-.24-1.09-.16-.34-.39-.63-.69-.88-.3-.25-.66-.44-1.09-.59-.43-.15-.92-.22-1.46-.22-.51 0-.98.07-1.39.21-.41.14-.77.33-1.06.57-.29.24-.51.52-.67.84-.16.32-.23.65-.23 1.01s.08.68.23.96c.15.28.37.52.64.73.27.21.6.38.98.53.38.14.81.26 1.27.36.39.08.71.17.95.26s.43.19.57.29c.13.1.22.22.27.34.05.12.07.25.07.39 0 .32-.13.57-.4.77-.27.2-.66.29-1.17.29-.22 0-.43-.02-.64-.08-.21-.05-.4-.13-.56-.24-.17-.11-.3-.26-.41-.44-.11-.18-.17-.41-.18-.67h-1.89c0 .36.08.71.24 1.05.16.34.39.65.7.93.31.27.69.49 1.15.66.46.17.98.25 1.58.25.53 0 1.01-.06 1.44-.19.43-.13.8-.31 1.11-.54.31-.23.54-.51.71-.83.17-.32.25-.67.25-1.06-.02-.4-.09-.74-.24-1.02z"></path></g> |
| <g id="timer-auto"><path d="M12 4C9.79 4 8 5.79 8 8s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 10c-2.67 0-8 1.34-8 4v2h16v-2c0-2.67-5.33-4-8-4z"></path></g> |
| <g id="timer-off"><path d="M19.04 4.55l-1.42 1.42C16.07 4.74 14.12 4 12 4c-1.83 0-3.53.55-4.95 1.48l1.46 1.46C9.53 6.35 10.73 6 12 6c3.87 0 7 3.13 7 7 0 1.27-.35 2.47-.94 3.49l1.45 1.45C20.45 16.53 21 14.83 21 13c0-2.12-.74-4.07-1.97-5.61l1.42-1.42-1.41-1.42zM15 1H9v2h6V1zm-4 8.44l2 2V8h-2v1.44zM3.02 4L1.75 5.27 4.5 8.03C3.55 9.45 3 11.16 3 13c0 4.97 4.02 9 9 9 1.84 0 3.55-.55 4.98-1.5l2.5 2.5 1.27-1.27-7.71-7.71L3.02 4zM12 20c-3.87 0-7-3.13-7-7 0-1.28.35-2.48.95-3.52l9.56 9.56c-1.03.61-2.23.96-3.51.96z"></path></g> |
| <g id="tonality"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.94-.49-7-3.85-7-7.93s3.05-7.44 7-7.93v15.86zm2-15.86c1.03.13 2 .45 2.87.93H13v-.93zM13 7h5.24c.25.31.48.65.68 1H13V7zm0 3h6.74c.08.33.15.66.19 1H13v-1zm0 9.93V19h2.87c-.87.48-1.84.8-2.87.93zM18.24 17H13v-1h5.92c-.2.35-.43.69-.68 1zm1.5-3H13v-1h6.93c-.04.34-.11.67-.19 1z"></path></g> |
| <g id="transform"><path d="M22 18v-2H8V4h2L7 1 4 4h2v2H2v2h4v8c0 1.1.9 2 2 2h8v2h-2l3 3 3-3h-2v-2h4zM10 8h6v6h2V8c0-1.1-.9-2-2-2h-6v2z"></path></g> |
| <g id="tune"><path d="M3 17v2h6v-2H3zM3 5v2h10V5H3zm10 16v-2h8v-2h-8v-2h-2v6h2zM7 9v2H3v2h4v2h2V9H7zm14 4v-2H11v2h10zm-6-4h2V7h4V5h-4V3h-2v6z"></path></g> |
| <g id="wb-auto"><path d="M6.85 12.65h2.3L8 9l-1.15 3.65zM22 7l-1.2 6.29L19.3 7h-1.6l-1.49 6.29L15 7h-.76C12.77 5.17 10.53 4 8 4c-4.42 0-8 3.58-8 8s3.58 8 8 8c3.13 0 5.84-1.81 7.15-4.43l.1.43H17l1.5-6.1L20 16h1.75l2.05-9H22zm-11.7 9l-.7-2H6.4l-.7 2H3.8L7 7h2l3.2 9h-1.9z"></path></g> |
| <g id="wb-cloudy"><path d="M19.36 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.64-4.96z"></path></g> |
| <g id="wb-incandescent"><path d="M3.55 18.54l1.41 1.41 1.79-1.8-1.41-1.41-1.79 1.8zM11 22.45h2V19.5h-2v2.95zM4 10.5H1v2h3v-2zm11-4.19V1.5H9v4.81C7.21 7.35 6 9.28 6 11.5c0 3.31 2.69 6 6 6s6-2.69 6-6c0-2.22-1.21-4.15-3-5.19zm5 4.19v2h3v-2h-3zm-2.76 7.66l1.79 1.8 1.41-1.41-1.8-1.79-1.4 1.4z"></path></g> |
| <g id="wb-irradescent"><path d="M5 14.5h14v-6H5v6zM11 .55V3.5h2V.55h-2zm8.04 2.5l-1.79 1.79 1.41 1.41 1.8-1.79-1.42-1.41zM13 22.45V19.5h-2v2.95h2zm7.45-3.91l-1.8-1.79-1.41 1.41 1.79 1.8 1.42-1.42zM3.55 4.46l1.79 1.79 1.41-1.41-1.79-1.79-1.41 1.41zm1.41 15.49l1.79-1.8-1.41-1.41-1.79 1.79 1.41 1.42z"></path></g> |
| <g id="wb-sunny"><path d="M6.76 4.84l-1.8-1.79-1.41 1.41 1.79 1.79 1.42-1.41zM4 10.5H1v2h3v-2zm9-9.95h-2V3.5h2V.55zm7.45 3.91l-1.41-1.41-1.79 1.79 1.41 1.41 1.79-1.79zm-3.21 13.7l1.79 1.8 1.41-1.41-1.8-1.79-1.4 1.4zM20 10.5v2h3v-2h-3zm-8-5c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6zm-1 16.95h2V19.5h-2v2.95zm-7.45-3.91l1.41 1.41 1.79-1.8-1.41-1.41-1.79 1.8z"></path></g> |
| </defs></svg> |
| </core-iconset-svg> |
| |
| <core-iconset-svg id="social" iconsize="24"> |
| <svg><defs> |
| <g id="cake"><path d="M12 6c1.11 0 2-.9 2-2 0-.38-.1-.73-.29-1.03L12 0l-1.71 2.97c-.19.3-.29.65-.29 1.03 0 1.1.9 2 2 2zm4.6 9.99l-1.07-1.07-1.08 1.07c-1.3 1.3-3.58 1.31-4.89 0l-1.07-1.07-1.09 1.07C6.75 16.64 5.88 17 4.96 17c-.73 0-1.4-.23-1.96-.61V21c0 .55.45 1 1 1h16c.55 0 1-.45 1-1v-4.61c-.56.38-1.23.61-1.96.61-.92 0-1.79-.36-2.44-1.01zM18 9h-5V7h-2v2H6c-1.66 0-3 1.34-3 3v1.54c0 1.08.88 1.96 1.96 1.96.52 0 1.02-.2 1.38-.57l2.14-2.13 2.13 2.13c.74.74 2.03.74 2.77 0l2.14-2.13 2.13 2.13c.37.37.86.57 1.38.57 1.08 0 1.96-.88 1.96-1.96V12C21 10.34 19.66 9 18 9z"></path></g> |
| <g id="domain"><path d="M12 7V3H2v18h20V7H12zM6 19H4v-2h2v2zm0-4H4v-2h2v2zm0-4H4V9h2v2zm0-4H4V5h2v2zm4 12H8v-2h2v2zm0-4H8v-2h2v2zm0-4H8V9h2v2zm0-4H8V5h2v2zm10 12h-8v-2h2v-2h-2v-2h2v-2h-2V9h8v10zm-2-8h-2v2h2v-2zm0 4h-2v2h2v-2z"></path></g> |
| <g id="group"><path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"></path></g> |
| <g id="group-add"><path d="M8 10H5V7H3v3H0v2h3v3h2v-3h3v-2zm10 1c1.66 0 2.99-1.34 2.99-3S19.66 5 18 5c-.32 0-.63.05-.91.14.57.81.9 1.79.9 2.86s-.34 2.04-.9 2.86c.28.09.59.14.91.14zm-5 0c1.66 0 2.99-1.34 2.99-3S14.66 5 13 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm6.62 2.16c.83.73 1.38 1.66 1.38 2.84v2h3v-2c0-1.54-2.37-2.49-4.38-2.84zM13 13c-2 0-6 1-6 3v2h12v-2c0-2-4-3-6-3z"></path></g> |
| <g id="location-city"><path d="M15 11V5l-3-3-3 3v2H3v14h18V11h-6zm-8 8H5v-2h2v2zm0-4H5v-2h2v2zm0-4H5V9h2v2zm6 8h-2v-2h2v2zm0-4h-2v-2h2v2zm0-4h-2V9h2v2zm0-4h-2V5h2v2zm6 12h-2v-2h2v2zm0-4h-2v-2h2v2z"></path></g> |
| <g id="mood"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm3.5-9c.83 0 1.5-.67 1.5-1.5S16.33 8 15.5 8 14 8.67 14 9.5s.67 1.5 1.5 1.5zm-7 0c.83 0 1.5-.67 1.5-1.5S9.33 8 8.5 8 7 8.67 7 9.5 7.67 11 8.5 11zm3.5 6.5c2.33 0 4.31-1.46 5.11-3.5H6.89c.8 2.04 2.78 3.5 5.11 3.5z"></path></g> |
| <g id="notifications"><path d="M11.5 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zm6.5-6v-5.5c0-3.07-2.13-5.64-5-6.32V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5v.68c-2.87.68-5 3.25-5 6.32V16l-2 2v1h17v-1l-2-2z"></path></g> |
| <g id="notifications-none"><path d="M11.5 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zm6.5-6v-5.5c0-3.07-2.13-5.64-5-6.32V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5v.68c-2.87.68-5 3.25-5 6.32V16l-2 2v1h17v-1l-2-2zm-2 1H7v-6.5C7 8.01 9.01 6 11.5 6S16 8.01 16 10.5V17z"></path></g> |
| <g id="notifications-off"><path d="M11.5 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zM18 10.5c0-3.07-2.13-5.64-5-6.32V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5v.68c-.51.12-.99.32-1.45.56L18 14.18V10.5zm-.27 8.5l2 2L21 19.73 4.27 3 3 4.27l2.92 2.92C5.34 8.16 5 9.29 5 10.5V16l-2 2v1h14.73z"></path></g> |
| <g id="notifications-on"><path d="M6.58 3.58L5.15 2.15C2.76 3.97 1.18 6.8 1.03 10h2c.15-2.65 1.51-4.97 3.55-6.42zM19.97 10h2c-.15-3.2-1.73-6.03-4.13-7.85l-1.43 1.43c2.05 1.45 3.41 3.77 3.56 6.42zm-1.97.5c0-3.07-2.13-5.64-5-6.32V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5v.68c-2.87.68-5 3.25-5 6.32V16l-2 2v1h17v-1l-2-2v-5.5zM11.5 22c.14 0 .27-.01.4-.04.65-.13 1.19-.58 1.44-1.18.1-.24.16-.5.16-.78h-4c0 1.1.9 2 2 2z"></path></g> |
| <g id="notifications-paused"><path d="M11.5 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zm6.5-6v-5.5c0-3.07-2.13-5.64-5-6.32V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5v.68c-2.87.68-5 3.25-5 6.32V16l-2 2v1h17v-1l-2-2zm-4-6.2l-2.8 3.4H14V15H9v-1.8l2.8-3.4H9V8h5v1.8z"></path></g> |
| <g id="pages"><path d="M3 5v6h5L7 7l4 1V3H5c-1.1 0-2 .9-2 2zm5 8H3v6c0 1.1.9 2 2 2h6v-5l-4 1 1-4zm9 4l-4-1v5h6c1.1 0 2-.9 2-2v-6h-5l1 4zm2-14h-6v5l4-1-1 4h5V5c0-1.1-.9-2-2-2z"></path></g> |
| <g id="party-mode"><path d="M20 4h-3.17L15 2H9L7.17 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-8 3c1.63 0 3.06.79 3.98 2H12c-1.66 0-3 1.34-3 3 0 .35.07.69.18 1H7.1c-.06-.32-.1-.66-.1-1 0-2.76 2.24-5 5-5zm0 10c-1.63 0-3.06-.79-3.98-2H12c1.66 0 3-1.34 3-3 0-.35-.07-.69-.18-1h2.08c.07.32.1.66.1 1 0 2.76-2.24 5-5 5z"></path></g> |
| <g id="people"><path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"></path></g> |
| <g id="people-outline"><path d="M16.5 13c-1.2 0-3.07.34-4.5 1-1.43-.67-3.3-1-4.5-1C5.33 13 1 14.08 1 16.25V19h22v-2.75c0-2.17-4.33-3.25-6.5-3.25zm-4 4.5h-10v-1.25c0-.54 2.56-1.75 5-1.75s5 1.21 5 1.75v1.25zm9 0H14v-1.25c0-.46-.2-.86-.52-1.22.88-.3 1.96-.53 3.02-.53 2.44 0 5 1.21 5 1.75v1.25zM7.5 12c1.93 0 3.5-1.57 3.5-3.5S9.43 5 7.5 5 4 6.57 4 8.5 5.57 12 7.5 12zm0-5.5c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm9 5.5c1.93 0 3.5-1.57 3.5-3.5S18.43 5 16.5 5 13 6.57 13 8.5s1.57 3.5 3.5 3.5zm0-5.5c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2z"></path></g> |
| <g id="person"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"></path></g> |
| <g id="person-add"><path d="M15 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm-9-2V7H4v3H1v2h3v3h2v-3h3v-2H6zm9 4c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"></path></g> |
| <g id="person-outline"><path d="M12 5.9c1.16 0 2.1.94 2.1 2.1s-.94 2.1-2.1 2.1S9.9 9.16 9.9 8s.94-2.1 2.1-2.1m0 9c2.97 0 6.1 1.46 6.1 2.1v1.1H5.9V17c0-.64 3.13-2.1 6.1-2.1M12 4C9.79 4 8 5.79 8 8s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 9c-2.67 0-8 1.34-8 4v3h16v-3c0-2.66-5.33-4-8-4z"></path></g> |
| <g id="plus-one"><path d="M10 8H8v4H4v2h4v4h2v-4h4v-2h-4zm4.5-1.92V7.9l2.5-.5V18h2V5z"></path></g> |
| <g id="poll"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z"></path></g> |
| <g id="public"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"></path></g> |
| <g id="school"><path d="M5 13.18v4L12 21l7-3.82v-4L12 17l-7-3.82zM12 3L1 9l11 6 9-4.91V17h2V9L12 3z"></path></g> |
| <g id="share"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92 1.61 0 2.92-1.31 2.92-2.92s-1.31-2.92-2.92-2.92z"></path></g> |
| <g id="whatshot"><path d="M13.5.67s.74 2.65.74 4.8c0 2.06-1.35 3.73-3.41 3.73-2.07 0-3.63-1.67-3.63-3.73l.03-.36C5.21 7.51 4 10.62 4 14c0 4.42 3.58 8 8 8s8-3.58 8-8C20 8.61 17.41 3.8 13.5.67zM11.71 19c-1.78 0-3.22-1.4-3.22-3.14 0-1.62 1.05-2.76 2.81-3.12 1.77-.36 3.6-1.21 4.62-2.58.39 1.29.59 2.65.59 4.04 0 2.65-2.15 4.8-4.8 4.8z"></path></g> |
| </defs></svg> |
| </core-iconset-svg> |
| |
| <polymer-element name="core-item" attributes="label icon src" horizontal="" center="" layout="" assetpath="../bower_components/core-item/"> |
| <template> |
| <style>/* |
| Copyright (c) 2014 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 |
| */ |
| |
| :host { |
| display: block; |
| position: relative; |
| min-height: 40px; |
| white-space: nowrap; |
| } |
| |
| :host(.font-scalable) { |
| min-height: 2.5em; |
| } |
| |
| :host(.core-selected) { |
| font-weight: bold; |
| } |
| |
| #icon { |
| margin: 0 16px 0 4px; |
| } |
| |
| :host(.font-scalable) #icon { |
| margin: 0 1em 0 0.25em; |
| height: 1.5em; |
| width: 1.5em; |
| } |
| |
| polyfill-next-selector { content: ':host > a'; } |
| ::content > a { |
| position: absolute; |
| top: 0; |
| right: 0; |
| bottom: 0; |
| left: 0; |
| /* IE10 styling to ensure link is clickable. Cannot be completely |
| transparent or minifiers change it to `transparent` which does not work. */ |
| background-color: rgba(0, 0, 0, 0.000001); |
| } |
| </style> |
| <template if="{{icon || src}}"> |
| <core-icon src="{{src}}" id="icon" icon="{{icon}}" hidden?="{{!src && !icon}}"></core-icon> |
| </template> |
| <div id="label">{{label}}</div> |
| <content></content> |
| </template> |
| <script> |
| |
| Polymer('core-item', { |
| |
| /** |
| * The URL of an image for the icon. |
| * |
| * @attribute src |
| * @type string |
| * @default '' |
| */ |
| |
| /** |
| * Specifies the icon from the Polymer icon set. |
| * |
| * @attribute icon |
| * @type string |
| * @default '' |
| */ |
| |
| /** |
| * Specifies the label for the menu item. |
| * |
| * @attribute label |
| * @type string |
| * @default '' |
| */ |
| |
| }); |
| |
| </script> |
| </polymer-element> |
| |
| <polymer-element name="core-a11y-keys" assetpath="../bower_components/core-a11y-keys/"> |
| <script> |
| (function() { |
| /* |
| * Chrome uses an older version of DOM Level 3 Keyboard Events |
| * |
| * Most keys are labeled as text, but some are Unicode codepoints. |
| * Values taken from: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/keyset.html#KeySet-Set |
| */ |
| var KEY_IDENTIFIER = { |
| 'U+0009': 'tab', |
| 'U+001B': 'esc', |
| 'U+0020': 'space', |
| 'U+002A': '*', |
| 'U+0030': '0', |
| 'U+0031': '1', |
| 'U+0032': '2', |
| 'U+0033': '3', |
| 'U+0034': '4', |
| 'U+0035': '5', |
| 'U+0036': '6', |
| 'U+0037': '7', |
| 'U+0038': '8', |
| 'U+0039': '9', |
| 'U+0041': 'a', |
| 'U+0042': 'b', |
| 'U+0043': 'c', |
| 'U+0044': 'd', |
| 'U+0045': 'e', |
| 'U+0046': 'f', |
| 'U+0047': 'g', |
| 'U+0048': 'h', |
| 'U+0049': 'i', |
| 'U+004A': 'j', |
| 'U+004B': 'k', |
| 'U+004C': 'l', |
| 'U+004D': 'm', |
| 'U+004E': 'n', |
| 'U+004F': 'o', |
| 'U+0050': 'p', |
| 'U+0051': 'q', |
| 'U+0052': 'r', |
| 'U+0053': 's', |
| 'U+0054': 't', |
| 'U+0055': 'u', |
| 'U+0056': 'v', |
| 'U+0057': 'w', |
| 'U+0058': 'x', |
| 'U+0059': 'y', |
| 'U+005A': 'z', |
| 'U+007F': 'del' |
| }; |
| |
| /* |
| * Special table for KeyboardEvent.keyCode. |
| * KeyboardEvent.keyIdentifier is better, and KeyBoardEvent.key is even better than that |
| * |
| * Values from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.keyCode#Value_of_keyCode |
| */ |
| var KEY_CODE = { |
| 9: 'tab', |
| 13: 'enter', |
| 27: 'esc', |
| 33: 'pageup', |
| 34: 'pagedown', |
| 35: 'end', |
| 36: 'home', |
| 32: 'space', |
| 37: 'left', |
| 38: 'up', |
| 39: 'right', |
| 40: 'down', |
| 46: 'del', |
| 106: '*' |
| }; |
| |
| /* |
| * KeyboardEvent.key is mostly represented by printable character made by the keyboard, with unprintable keys labeled |
| * nicely. |
| * |
| * However, on OS X, Alt+char can make a Unicode character that follows an Apple-specific mapping. In this case, we |
| * fall back to .keyCode. |
| */ |
| var KEY_CHAR = /[a-z0-9*]/; |
| |
| function transformKey(key) { |
| var validKey = ''; |
| if (key) { |
| var lKey = key.toLowerCase(); |
| if (lKey.length == 1) { |
| if (KEY_CHAR.test(lKey)) { |
| validKey = lKey; |
| } |
| } else if (lKey == 'multiply') { |
| // numpad '*' can map to Multiply on IE/Windows |
| validKey = '*'; |
| } else { |
| validKey = lKey; |
| } |
| } |
| return validKey; |
| } |
| |
| var IDENT_CHAR = /U\+/; |
| function transformKeyIdentifier(keyIdent) { |
| var validKey = ''; |
| if (keyIdent) { |
| if (IDENT_CHAR.test(keyIdent)) { |
| validKey = KEY_IDENTIFIER[keyIdent]; |
| } else { |
| validKey = keyIdent.toLowerCase(); |
| } |
| } |
| return validKey; |
| } |
| |
| function transformKeyCode(keyCode) { |
| var validKey = ''; |
| if (Number(keyCode)) { |
| if (keyCode >= 65 && keyCode <= 90) { |
| // ascii a-z |
| // lowercase is 32 offset from uppercase |
| validKey = String.fromCharCode(32 + keyCode); |
| } else if (keyCode >= 112 && keyCode <= 123) { |
| // function keys f1-f12 |
| validKey = 'f' + (keyCode - 112); |
| } else if (keyCode >= 48 && keyCode <= 57) { |
| // top 0-9 keys |
| validKey = String(48 - keyCode); |
| } else if (keyCode >= 96 && keyCode <= 105) { |
| // num pad 0-9 |
| validKey = String(96 - keyCode); |
| } else { |
| validKey = KEY_CODE[keyCode]; |
| } |
| } |
| return validKey; |
| } |
| |
| function keyboardEventToKey(ev) { |
| // fall back from .key, to .keyIdentifier, to .keyCode, and then to .detail.key to support artificial keyboard events |
| var normalizedKey = transformKey(ev.key) || transformKeyIdentifier(ev.keyIdentifier) || transformKeyCode(ev.keyCode) || transformKey(ev.detail.key) || ''; |
| return { |
| shift: ev.shiftKey, |
| ctrl: ev.ctrlKey, |
| meta: ev.metaKey, |
| alt: ev.altKey, |
| key: normalizedKey |
| }; |
| } |
| |
| /* |
| * Input: ctrl+shift+f7 => {ctrl: true, shift: true, key: 'f7'} |
| * ctrl/space => {ctrl: true} || {key: space} |
| */ |
| function stringToKey(keyCombo) { |
| var keys = keyCombo.split('+'); |
| var keyObj = Object.create(null); |
| keys.forEach(function(key) { |
| if (key == 'shift') { |
| keyObj.shift = true; |
| } else if (key == 'ctrl') { |
| keyObj.ctrl = true; |
| } else if (key == 'alt') { |
| keyObj.alt = true; |
| } else { |
| keyObj.key = key; |
| } |
| }); |
| return keyObj; |
| } |
| |
| function keyMatches(a, b) { |
| return Boolean(a.alt) == Boolean(b.alt) && Boolean(a.ctrl) == Boolean(b.ctrl) && Boolean(a.shift) == Boolean(b.shift) && a.key === b.key; |
| } |
| |
| function processKeys(ev) { |
| var current = keyboardEventToKey(ev); |
| for (var i = 0, dk; i < this._desiredKeys.length; i++) { |
| dk = this._desiredKeys[i]; |
| if (keyMatches(dk, current)) { |
| ev.preventDefault(); |
| ev.stopPropagation(); |
| this.fire('keys-pressed', current, this, false); |
| break; |
| } |
| } |
| } |
| |
| function listen(node, handler) { |
| if (node && node.addEventListener) { |
| node.addEventListener('keydown', handler); |
| } |
| } |
| |
| function unlisten(node, handler) { |
| if (node && node.removeEventListener) { |
| node.removeEventListener('keydown', handler); |
| } |
| } |
| |
| Polymer('core-a11y-keys', { |
| created: function() { |
| this._keyHandler = processKeys.bind(this); |
| }, |
| attached: function() { |
| if (!this.target) { |
| this.target = this.parentNode; |
| } |
| listen(this.target, this._keyHandler); |
| }, |
| detached: function() { |
| unlisten(this.target, this._keyHandler); |
| }, |
| publish: { |
| /** |
| * The set of key combinations that will be matched (in keys syntax). |
| * |
| * @attribute keys |
| * @type string |
| * @default '' |
| */ |
| keys: '', |
| /** |
| * The node that will fire keyboard events. |
| * Default to this element's parentNode unless one is assigned |
| * |
| * @attribute target |
| * @type Node |
| * @default this.parentNode |
| */ |
| target: null |
| }, |
| keysChanged: function() { |
| // * can have multiple mappings: shift+8, * on numpad or Multiply on numpad |
| var normalized = this.keys.replace('*', '* shift+*'); |
| this._desiredKeys = normalized.toLowerCase().split(' ').map(stringToKey); |
| }, |
| targetChanged: function(oldTarget) { |
| unlisten(oldTarget, this._keyHandler); |
| listen(this.target, this._keyHandler); |
| } |
| }); |
| })(); |
| </script> |
| </polymer-element> |
| <polymer-element name="core-menu" extends="core-selector" assetpath="../bower_components/core-menu/"> |
| <template> |
| |
| <style>/* |
| Copyright (c) 2014 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 |
| */ |
| |
| :host { |
| display: block; |
| margin: 12px; |
| } |
| |
| polyfill-next-selector { content: ':host > core-item'; } |
| ::content > core-item { |
| cursor: default; |
| } |
| </style> |
| |
| <core-a11y-keys target="{{}}" keys="up" on-keys-pressed="{{ selectPrevious }}"></core-a11y-keys> |
| <core-a11y-keys target="{{}}" keys="down" on-keys-pressed="{{ selectNext }}"></core-a11y-keys> |
| <core-a11y-keys target="{{}}" keys="enter" on-keys-pressed="{{ validateSelected }}"></core-a11y-keys> |
| |
| <shadow></shadow> |
| |
| </template> |
| <script>Polymer('core-menu');</script></polymer-element> |
| |
| <polymer-element name="core-toolbar" attributes="justify middleJustify bottomJustify" assetpath="../bower_components/core-toolbar/"> |
| <template> |
| |
| <style>/* |
| Copyright (c) 2014 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 |
| */ |
| |
| :host { |
| /* technical */ |
| display: block; |
| position: relative; |
| box-sizing: border-box; |
| -moz-box-sizing: border-box; |
| /* size */ |
| height: 64px; |
| /* typography */ |
| font-size: 1.3em; |
| /* background */ |
| background-color: #CFD8DC; |
| } |
| |
| :host(.animate) { |
| /* transition */ |
| transition: height 0.18s ease-in; |
| } |
| |
| :host(.medium-tall) { |
| height: 128px; |
| } |
| |
| :host(.tall) { |
| height: 192px; |
| } |
| |
| .toolbar-tools { |
| position: relative; |
| height: 64px; |
| padding: 0 8px; |
| pointer-events: none; |
| } |
| |
| /* narrow layout */ |
| :host(.core-narrow), |
| :host-context(.core-narrow) { |
| height: 56px; |
| } |
| |
| polyfill-next-selector { content: ':host.core-narrow.medium-tall, .core-narrow :host.medium-tall'; } |
| :host(.core-narrow.medium-tall), |
| :host-context(.core-narrow):host(.medium-tall) { |
| height: 112px; |
| } |
| |
| polyfill-next-selector { content: ':host.core-narrow.tall, .core-narrow :host.tall'; } |
| :host(.core-narrow.tall), |
| :host-context(.core-narrow):host(.tall) { |
| height: 168px; |
| } |
| |
| polyfill-next-selector { content: ':host.core-narrow .toolbar-tools, .core-narrow :host .toolbar-tools'; } |
| :host(.core-narrow) .toolbar-tools, |
| :host-context(.core-narrow) .toolbar-tools { |
| height: 56px; |
| padding: 0; |
| } |
| |
| /* middle bar */ |
| #middleBar { |
| position: absolute; |
| top: 0; |
| right: 0; |
| left: 0; |
| } |
| |
| :host(.tall, .medium-tall) #middleBar { |
| -webkit-transform: translateY(100%); |
| transform: translateY(100%); |
| } |
| |
| /* bottom bar */ |
| #bottomBar { |
| position: absolute; |
| right: 0; |
| bottom: 0; |
| left: 0; |
| } |
| |
| /* make elements (e.g. buttons) respond to mouse/touch events */ |
| polyfill-next-selector { content: '.toolbar-tools > *:not([disabled])'; } |
| ::content > *:not([disabled]) { |
| pointer-events: auto; |
| } |
| |
| /* elements spacing */ |
| polyfill-next-selector { content: '.toolbar-tools > *'; } |
| ::content > * { |
| margin: 0 8px; |
| } |
| |
| /* misc helpers */ |
| polyfill-next-selector { content: '.toolbar-tools > .fit'; } |
| ::content > .fit { |
| position: absolute; |
| top: auto; |
| right: 0; |
| bottom: 0; |
| left: 0; |
| width: auto; |
| margin: 0; |
| } |
| |
| polyfill-next-selector { content: ':host .indent'; } |
| ::content > .indent { |
| margin-left: 60px; |
| } |
| </style> |
| |
| <div id="bottomBar" class="toolbar-tools" center="" horizontal="" layout=""> |
| <content select=".bottom"></content> |
| </div> |
| |
| <div id="middleBar" class="toolbar-tools" center="" horizontal="" layout=""> |
| <content select=".middle"></content> |
| </div> |
| |
| <div id="topBar" class="toolbar-tools" center="" horizontal="" layout=""> |
| <content></content> |
| </div> |
| |
| </template> |
| <script> |
| |
| (function() { |
| |
| Polymer('core-toolbar', { |
| |
| /** |
| * Controls how the items are aligned horizontally. |
| * Options are `start`, `center`, `end`, `between` and `around`. |
| * |
| * @attribute justify |
| * @type string |
| * @default '' |
| */ |
| justify: '', |
| |
| /** |
| * Controls how the items are aligned horizontally when they are placed |
| * in the middle. |
| * Options are `start`, `center`, `end`, `between` and `around`. |
| * |
| * @attribute middleJustify |
| * @type string |
| * @default '' |
| */ |
| middleJustify: '', |
| |
| /** |
| * Controls how the items are aligned horizontally when they are placed |
| * at the bottom. |
| * Options are `start`, `center`, `end`, `between` and `around`. |
| * |
| * @attribute bottomJustify |
| * @type string |
| * @default '' |
| */ |
| bottomJustify: '', |
| |
| justifyChanged: function(old) { |
| this.updateBarJustify(this.$.topBar, this.justify, old); |
| }, |
| |
| middleJustifyChanged: function(old) { |
| this.updateBarJustify(this.$.middleBar, this.middleJustify, old); |
| }, |
| |
| bottomJustifyChanged: function(old) { |
| this.updateBarJustify(this.$.bottomBar, this.bottomJustify, old); |
| }, |
| |
| updateBarJustify: function(bar, justify, old) { |
| if (old) { |
| bar.removeAttribute(this.toLayoutAttrName(old)); |
| } |
| if (justify) { |
| bar.setAttribute(this.toLayoutAttrName(justify), ''); |
| } |
| }, |
| |
| toLayoutAttrName: function(value) { |
| return value === 'between' ? 'justified' : value + '-justified'; |
| } |
| |
| }); |
| |
| })(); |
| |
| </script> |
| </polymer-element> |
| |
| <polymer-element name="core-tooltip" attributes="noarrow position label show tipAttribute" role="tooltip" tabindex="0" assetpath="../bower_components/core-tooltip/"> |
| <template> |
| <style>/* Copyright (c) 2014 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 */ |
| |
| :host { |
| box-sizing: border-box; |
| position: relative; |
| display: inline-block; |
| outline: none; |
| } |
| |
| :host(:hover:not([disabled])) .core-tooltip { |
| visibility: visible !important; |
| } |
| |
| :host([focused]) .core-tooltip { |
| visibility: visible !important; |
| } |
| |
| .core-tooltip:not(.show) { |
| visibility: hidden; |
| } |
| |
| .core-tooltip { |
| position: absolute; |
| font-size: 10px; |
| font-weight: 500; |
| padding: 8px; |
| color: white; |
| background-color: rgba(0, 0, 0, 0.9); |
| box-sizing: border-box; |
| border-radius: 3px; /* TODO: not in spec. */ |
| white-space: nowrap; |
| line-height: 6px; |
| z-index: 1002; /* TODO: this is brittle. */ |
| -webkit-user-select: none; |
| user-select: none; |
| } |
| |
| :host([large]) .core-tooltip { |
| line-height: 14px; |
| font-size: 14px; |
| padding: 16px; |
| } |
| |
| .core-tooltip.noarrow::after { |
| display: none; |
| } |
| |
| .core-tooltip::after { |
| position: absolute; |
| border: solid transparent; |
| content: ''; |
| height: 0; |
| width: 0; |
| border-width: 4px; |
| } |
| |
| .top { |
| margin-bottom: 10px; /* TODO: not specified in spec */ |
| bottom: 100%; |
| } |
| |
| .right { |
| margin-left: 10px; /* TODO: not specified in spec */ |
| left: 100%; |
| } |
| |
| .bottom { |
| top: 100%; |
| margin-top: 10px; /* TODO: not specified in spec */ |
| } |
| |
| .left { |
| margin-right: 10px; /* TODO: not specified in spec */ |
| right: 100%; |
| } |
| |
| .core-tooltip.bottom::after { |
| bottom: 100%; |
| left: calc(50% - 4px); |
| border-bottom-color: rgba(0,0,0,0.8); |
| } |
| |
| .core-tooltip.left::after { |
| left: 100%; |
| top: calc(50% - 4px); |
| border-left-color: rgba(0,0,0,0.8); |
| } |
| |
| .core-tooltip.top::after { |
| top: 100%; |
| left: calc(50% - 4px); |
| border-top-color: rgba(0,0,0,0.8); |
| } |
| |
| .core-tooltip.right::after { |
| right: 100%; |
| top: calc(50% - 4px); |
| border-right-color: rgba(0,0,0,0.8); |
| } |
| </style> |
| |
| <div id="tooltip" hidden?="{{!hasTooltipContent}}" class="core-tooltip {{position}} {{ {noarrow: noarrow, show: show && !disabled} | tokenList}}"> |
| <content id="c" select="[{{tipAttribute}}]">{{label}}</content> |
| </div> |
| |
| <content></content> |
| |
| </template> |
| <script> |
| (function() { |
| |
| var proto = { |
| |
| /** |
| * A simple string label for the tooltip to display. To display a rich |
| * HTML tooltip instead, omit `label` and include the `tip` attribute |
| * on a child node of `core-tooltip`. |
| * |
| * @attribute label |
| * @type string |
| * @default null |
| */ |
| label: null, |
| |
| eventDelegates: { |
| 'core-resize': 'positionChanged' |
| }, |
| |
| computed: { |
| // Indicates whether the tooltip has a set label propety or |
| // an element with the `tip` attribute. |
| hasTooltipContent: 'label || !!tipElement' |
| }, |
| |
| publish: { |
| /** |
| * Forces the tooltip to display. If `disabled` is set, this property is ignored. |
| * |
| * @attribute show |
| * @type boolean |
| * @default false |
| */ |
| show: {value: false, reflect: true}, |
| |
| /** |
| * Positions the tooltip to the top, right, bottom, left of its content. |
| * |
| * @attribute position |
| * @type string |
| * @default 'bottom' |
| */ |
| position: {value: 'bottom', reflect: true}, |
| |
| /** |
| * If true, the tooltip an arrow pointing towards the content. |
| * |
| * @attribute noarrow |
| * @type boolean |
| * @default false |
| */ |
| noarrow: {value: false, reflect: true} |
| }, |
| |
| /** |
| * Customizes the attribute used to specify which content |
| * is the rich HTML tooltip. |
| * |
| * @attribute tipAttribute |
| * @type string |
| * @default 'tip' |
| */ |
| tipAttribute: 'tip', |
| |
| attached: function() { |
| this.updatedChildren(); |
| this.resizableAttachedHandler(); |
| }, |
| |
| detached: function() { |
| this.resizableDetachedHandler(); |
| }, |
| |
| updatedChildren: function () { |
| this.tipElement = null; |
| |
| for (var i = 0, el; el = this.$.c.getDistributedNodes()[i]; ++i) { |
| if (el.hasAttribute && el.hasAttribute(this.tipAttribute)) { |
| this.tipElement = el; |
| break; |
| } |
| } |
| |
| // Job ensures we're not double calling setPosition() on DOM attach. |
| this.job('positionJob', this.setPosition); |
| |
| // Monitor children to re-position tooltip when light dom changes. |
| this.onMutation(this, this.updatedChildren); |
| }, |
| |
| labelChanged: function(oldVal, newVal) { |
| this.job('positionJob', this.setPosition); |
| }, |
| |
| positionChanged: function(oldVal, newVal) { |
| this.job('positionJob', this.setPosition); |
| }, |
| |
| setPosition: function() { |
| var controlWidth = this.clientWidth; |
| var controlHeight = this.clientHeight; |
| var toolTipWidth = this.$.tooltip.clientWidth; |
| var toolTipHeight = this.$.tooltip.clientHeight; |
| |
| switch (this.position) { |
| case 'top': |
| case 'bottom': |
| this.$.tooltip.style.left = (controlWidth - toolTipWidth) / 2 + 'px'; |
| this.$.tooltip.style.top = null; |
| break; |
| case 'left': |
| case 'right': |
| this.$.tooltip.style.left = null; |
| this.$.tooltip.style.top = (controlHeight - toolTipHeight) / 2 + 'px'; |
| break; |
| } |
| } |
| |
| }; |
| |
| Polymer.mixin2(proto, Polymer.CoreFocusable); |
| Polymer.mixin(proto, Polymer.CoreResizable); |
| Polymer('core-tooltip',proto); |
| })(); |
| |
| </script> |
| </polymer-element> |
| |
| <polymer-element name="paper-ripple" attributes="initialOpacity opacityDecayVelocity" assetpath="../bower_components/paper-ripple/"> |
| <template> |
| |
| <style> |
| |
| :host { |
| display: block; |
| position: relative; |
| border-radius: inherit; |
| overflow: hidden; |
| } |
| |
| :host-context([noink]) { |
| pointer-events: none; |
| } |
| |
| #bg, #waves, .wave-container, .wave { |
| pointer-events: none; |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| } |
| |
| #bg, .wave { |
| opacity: 0; |
| } |
| |
| #waves, .wave { |
| overflow: hidden; |
| } |
| |
| .wave-container, .wave { |
| border-radius: 50%; |
| } |
| |
| :host(.circle) #bg, |
| :host(.circle) #waves { |
| border-radius: 50%; |
| } |
| |
| :host(.circle) .wave-container { |
| overflow: hidden; |
| } |
| |
| </style> |
| |
| <div id="bg"></div> |
| <div id="waves"> |
| </div> |
| |
| </template> |
| <script> |
| |
| (function() { |
| |
| var waveMaxRadius = 150; |
| // |
| // INK EQUATIONS |
| // |
| function waveRadiusFn(touchDownMs, touchUpMs, anim) { |
| // Convert from ms to s |
| var touchDown = touchDownMs / 1000; |
| var touchUp = touchUpMs / 1000; |
| var totalElapsed = touchDown + touchUp; |
| var ww = anim.width, hh = anim.height; |
| // use diagonal size of container to avoid floating point math sadness |
| var waveRadius = Math.min(Math.sqrt(ww * ww + hh * hh), waveMaxRadius) * 1.1 + 5; |
| var duration = 1.1 - .2 * (waveRadius / waveMaxRadius); |
| var tt = (totalElapsed / duration); |
| |
| var size = waveRadius * (1 - Math.pow(80, -tt)); |
| return Math.abs(size); |
| } |
| |
| function waveOpacityFn(td, tu, anim) { |
| // Convert from ms to s. |
| var touchDown = td / 1000; |
| var touchUp = tu / 1000; |
| var totalElapsed = touchDown + touchUp; |
| |
| if (tu <= 0) { // before touch up |
| return anim.initialOpacity; |
| } |
| return Math.max(0, anim.initialOpacity - touchUp * anim.opacityDecayVelocity); |
| } |
| |
| function waveOuterOpacityFn(td, tu, anim) { |
| // Convert from ms to s. |
| var touchDown = td / 1000; |
| var touchUp = tu / 1000; |
| |
| // Linear increase in background opacity, capped at the opacity |
| // of the wavefront (waveOpacity). |
| var outerOpacity = touchDown * 0.3; |
| var waveOpacity = waveOpacityFn(td, tu, anim); |
| return Math.max(0, Math.min(outerOpacity, waveOpacity)); |
| } |
| |
| // Determines whether the wave should be completely removed. |
| function waveDidFinish(wave, radius, anim) { |
| var waveOpacity = waveOpacityFn(wave.tDown, wave.tUp, anim); |
| |
| // If the wave opacity is 0 and the radius exceeds the bounds |
| // of the element, then this is finished. |
| return waveOpacity < 0.01 && radius >= Math.min(wave.maxRadius, waveMaxRadius); |
| }; |
| |
| function waveAtMaximum(wave, radius, anim) { |
| var waveOpacity = waveOpacityFn(wave.tDown, wave.tUp, anim); |
| |
| return waveOpacity >= anim.initialOpacity && radius >= Math.min(wave.maxRadius, waveMaxRadius); |
| } |
| |
| // |
| // DRAWING |
| // |
| function drawRipple(ctx, x, y, radius, innerAlpha, outerAlpha) { |
| // Only animate opacity and transform |
| if (outerAlpha !== undefined) { |
| ctx.bg.style.opacity = outerAlpha; |
| } |
| ctx.wave.style.opacity = innerAlpha; |
| |
| var s = radius / (ctx.containerSize / 2); |
| var dx = x - (ctx.containerWidth / 2); |
| var dy = y - (ctx.containerHeight / 2); |
| |
| ctx.wc.style.webkitTransform = 'translate3d(' + dx + 'px,' + dy + 'px,0)'; |
| ctx.wc.style.transform = 'translate3d(' + dx + 'px,' + dy + 'px,0)'; |
| |
| // 2d transform for safari because of border-radius and overflow:hidden clipping bug. |
| // https://bugs.webkit.org/show_bug.cgi?id=98538 |
| ctx.wave.style.webkitTransform = 'scale(' + s + ',' + s + ')'; |
| ctx.wave.style.transform = 'scale3d(' + s + ',' + s + ',1)'; |
| } |
| |
| // |
| // SETUP |
| // |
| function createWave(elem) { |
| var elementStyle = window.getComputedStyle(elem); |
| var fgColor = elementStyle.color; |
| |
| var inner = document.createElement('div'); |
| inner.style.backgroundColor = fgColor; |
| inner.classList.add('wave'); |
| |
| var outer = document.createElement('div'); |
| outer.classList.add('wave-container'); |
| outer.appendChild(inner); |
| |
| var container = elem.$.waves; |
| container.appendChild(outer); |
| |
| elem.$.bg.style.backgroundColor = fgColor; |
| |
| var wave = { |
| bg: elem.$.bg, |
| wc: outer, |
| wave: inner, |
| waveColor: fgColor, |
| maxRadius: 0, |
| isMouseDown: false, |
| mouseDownStart: 0.0, |
| mouseUpStart: 0.0, |
| tDown: 0, |
| tUp: 0 |
| }; |
| return wave; |
| } |
| |
| function removeWaveFromScope(scope, wave) { |
| if (scope.waves) { |
| var pos = scope.waves.indexOf(wave); |
| scope.waves.splice(pos, 1); |
| // FIXME cache nodes |
| wave.wc.remove(); |
| } |
| }; |
| |
| // Shortcuts. |
| var pow = Math.pow; |
| var now = Date.now; |
| if (window.performance && performance.now) { |
| now = performance.now.bind(performance); |
| } |
| |
| function cssColorWithAlpha(cssColor, alpha) { |
| var parts = cssColor.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/); |
| if (typeof alpha == 'undefined') { |
| alpha = 1; |
| } |
| if (!parts) { |
| return 'rgba(255, 255, 255, ' + alpha + ')'; |
| } |
| return 'rgba(' + parts[1] + ', ' + parts[2] + ', ' + parts[3] + ', ' + alpha + ')'; |
| } |
| |
| function dist(p1, p2) { |
| return Math.sqrt(pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2)); |
| } |
| |
| function distanceFromPointToFurthestCorner(point, size) { |
| var tl_d = dist(point, {x: 0, y: 0}); |
| var tr_d = dist(point, {x: size.w, y: 0}); |
| var bl_d = dist(point, {x: 0, y: size.h}); |
| var br_d = dist(point, {x: size.w, y: size.h}); |
| return Math.max(tl_d, tr_d, bl_d, br_d); |
| } |
| |
| Polymer('paper-ripple', { |
| |
| /** |
| * The initial opacity set on the wave. |
| * |
| * @attribute initialOpacity |
| * @type number |
| * @default 0.25 |
| */ |
| initialOpacity: 0.25, |
| |
| /** |
| * How fast (opacity per second) the wave fades out. |
| * |
| * @attribute opacityDecayVelocity |
| * @type number |
| * @default 0.8 |
| */ |
| opacityDecayVelocity: 0.8, |
| |
| backgroundFill: true, |
| pixelDensity: 2, |
| |
| eventDelegates: { |
| down: 'downAction', |
| up: 'upAction' |
| }, |
| |
| ready: function() { |
| this.waves = []; |
| }, |
| |
| downAction: function(e) { |
| var wave = createWave(this); |
| |
| this.cancelled = false; |
| wave.isMouseDown = true; |
| wave.tDown = 0.0; |
| wave.tUp = 0.0; |
| wave.mouseUpStart = 0.0; |
| wave.mouseDownStart = now(); |
| |
| var rect = this.getBoundingClientRect(); |
| var width = rect.width; |
| var height = rect.height; |
| var touchX = e.x - rect.left; |
| var touchY = e.y - rect.top; |
| |
| wave.startPosition = {x:touchX, y:touchY}; |
| |
| if (this.classList.contains("recenteringTouch")) { |
| wave.endPosition = {x: width / 2, y: height / 2}; |
| wave.slideDistance = dist(wave.startPosition, wave.endPosition); |
| } |
| wave.containerSize = Math.max(width, height); |
| wave.containerWidth = width; |
| wave.containerHeight = height; |
| wave.maxRadius = distanceFromPointToFurthestCorner(wave.startPosition, {w: width, h: height}); |
| |
| // The wave is circular so constrain its container to 1:1 |
| wave.wc.style.top = (wave.containerHeight - wave.containerSize) / 2 + 'px'; |
| wave.wc.style.left = (wave.containerWidth - wave.containerSize) / 2 + 'px'; |
| wave.wc.style.width = wave.containerSize + 'px'; |
| wave.wc.style.height = wave.containerSize + 'px'; |
| |
| this.waves.push(wave); |
| |
| if (!this._loop) { |
| this._loop = this.animate.bind(this, { |
| width: width, |
| height: height |
| }); |
| requestAnimationFrame(this._loop); |
| } |
| // else there is already a rAF |
| }, |
| |
| upAction: function() { |
| for (var i = 0; i < this.waves.length; i++) { |
| // Declare the next wave that has mouse down to be mouse'ed up. |
| var wave = this.waves[i]; |
| if (wave.isMouseDown) { |
| wave.isMouseDown = false; |
| wave.mouseUpStart = now(); |
| wave.mouseDownStart = 0; |
| wave.tUp = 0.0; |
| break; |
| } |
| } |
| this._loop && requestAnimationFrame(this._loop); |
| }, |
| |
| cancel: function() { |
| this.cancelled = true; |
| }, |
| |
| animate: function(ctx) { |
| var shouldRenderNextFrame = false; |
| |
| var deleteTheseWaves = []; |
| // The oldest wave's touch down duration |
| var longestTouchDownDuration = 0; |
| var longestTouchUpDuration = 0; |
| // Save the last known wave color |
| var lastWaveColor = null; |
| // wave animation values |
| var anim = { |
| initialOpacity: this.initialOpacity, |
| opacityDecayVelocity: this.opacityDecayVelocity, |
| height: ctx.height, |
| width: ctx.width |
| } |
| |
| for (var i = 0; i < this.waves.length; i++) { |
| var wave = this.waves[i]; |
| |
| if (wave.mouseDownStart > 0) { |
| wave.tDown = now() - wave.mouseDownStart; |
| } |
| if (wave.mouseUpStart > 0) { |
| wave.tUp = now() - wave.mouseUpStart; |
| } |
| |
| // Determine how long the touch has been up or down. |
| var tUp = wave.tUp; |
| var tDown = wave.tDown; |
| longestTouchDownDuration = Math.max(longestTouchDownDuration, tDown); |
| longestTouchUpDuration = Math.max(longestTouchUpDuration, tUp); |
| |
| // Obtain the instantenous size and alpha of the ripple. |
| var radius = waveRadiusFn(tDown, tUp, anim); |
| var waveAlpha = waveOpacityFn(tDown, tUp, anim); |
| var waveColor = cssColorWithAlpha(wave.waveColor, waveAlpha); |
| lastWaveColor = wave.waveColor; |
| |
| // Position of the ripple. |
| var x = wave.startPosition.x; |
| var y = wave.startPosition.y; |
| |
| // Ripple gravitational pull to the center of the canvas. |
| if (wave.endPosition) { |
| |
| // This translates from the origin to the center of the view based on the max dimension of |
| var translateFraction = Math.min(1, radius / wave.containerSize * 2 / Math.sqrt(2) ); |
| |
| x += translateFraction * (wave.endPosition.x - wave.startPosition.x); |
| y += translateFraction * (wave.endPosition.y - wave.startPosition.y); |
| } |
| |
| // If we do a background fill fade too, work out the correct color. |
| var bgFillColor = null; |
| if (this.backgroundFill) { |
| var bgFillAlpha = waveOuterOpacityFn(tDown, tUp, anim); |
| bgFillColor = cssColorWithAlpha(wave.waveColor, bgFillAlpha); |
| } |
| |
| // Draw the ripple. |
| drawRipple(wave, x, y, radius, waveAlpha, bgFillAlpha); |
| |
| // Determine whether there is any more rendering to be done. |
| var maximumWave = waveAtMaximum(wave, radius, anim); |
| var waveDissipated = waveDidFinish(wave, radius, anim); |
| var shouldKeepWave = !waveDissipated || maximumWave; |
| // keep rendering dissipating wave when at maximum radius on upAction |
| var shouldRenderWaveAgain = wave.mouseUpStart ? !waveDissipated : !maximumWave; |
| shouldRenderNextFrame = shouldRenderNextFrame || shouldRenderWaveAgain; |
| if (!shouldKeepWave || this.cancelled) { |
| deleteTheseWaves.push(wave); |
| } |
| } |
| |
| if (shouldRenderNextFrame) { |
| requestAnimationFrame(this._loop); |
| } |
| |
| for (var i = 0; i < deleteTheseWaves.length; ++i) { |
| var wave = deleteTheseWaves[i]; |
| removeWaveFromScope(this, wave); |
| } |
| |
| if (!this.waves.length && this._loop) { |
| // clear the background color |
| this.$.bg.style.backgroundColor = null; |
| this._loop = null; |
| this.fire('core-transitionend'); |
| } |
| } |
| |
| }); |
| |
| })(); |
| |
| </script> |
| </polymer-element> |
| <polymer-element name="paper-button-base" tabindex="0" assetpath="../bower_components/paper-button/"> |
| |
| <script> |
| |
| (function() { |
| |
| var p = { |
| |
| eventDelegates: { |
| down: 'downAction', |
| up: 'upAction' |
| }, |
| |
| toggleBackground: function() { |
| if (this.active) { |
| |
| if (!this.$.bg) { |
| var bg = document.createElement('div'); |
| bg.setAttribute('id', 'bg'); |
| bg.setAttribute('fit', ''); |
| bg.style.opacity = 0.25; |
| this.$.bg = bg; |
| this.shadowRoot.insertBefore(bg, this.shadowRoot.firstChild); |
| } |
| this.$.bg.style.backgroundColor = getComputedStyle(this).color; |
| |
| } else { |
| |
| if (this.$.bg) { |
| this.$.bg.style.backgroundColor = ''; |
| } |
| } |
| }, |
| |
| activeChanged: function() { |
| this.super(); |
| |
| if (this.toggle && (!this.lastEvent || this.matches(':host-context([noink])'))) { |
| this.toggleBackground(); |
| } |
| }, |
| |
| pressedChanged: function() { |
| this.super(); |
| |
| if (!this.lastEvent) { |
| return; |
| } |
| |
| if (this.$.ripple && !this.hasAttribute('noink')) { |
| if (this.pressed) { |
| this.$.ripple.downAction(this.lastEvent); |
| } else { |
| this.$.ripple.upAction(); |
| } |
| } |
| |
| this.adjustZ(); |
| }, |
| |
| focusedChanged: function() { |
| this.adjustZ(); |
| }, |
| |
| disabledChanged: function() { |
| this._disabledChanged(); |
| this.adjustZ(); |
| }, |
| |
| recenteringTouchChanged: function() { |
| if (this.$.ripple) { |
| this.$.ripple.classList.toggle('recenteringTouch', this.recenteringTouch); |
| } |
| }, |
| |
| fillChanged: function() { |
| if (this.$.ripple) { |
| this.$.ripple.classList.toggle('fill', this.fill); |
| } |
| }, |
| |
| adjustZ: function() { |
| if (!this.$.shadow) { |
| return; |
| } |
| if (this.active) { |
| this.$.shadow.setZ(2); |
| } else if (this.disabled) { |
| this.$.shadow.setZ(0); |
| } else if (this.focused) { |
| this.$.shadow.setZ(3); |
| } else { |
| this.$.shadow.setZ(1); |
| } |
| }, |
| |
| downAction: function(e) { |
| this._downAction(); |
| |
| if (this.hasAttribute('noink')) { |
| return; |
| } |
| |
| this.lastEvent = e; |
| if (!this.$.ripple) { |
| var ripple = document.createElement('paper-ripple'); |
| ripple.setAttribute('id', 'ripple'); |
| ripple.setAttribute('fit', ''); |
| if (this.recenteringTouch) { |
| ripple.classList.add('recenteringTouch'); |
| } |
| if (!this.fill) { |
| ripple.classList.add('circle'); |
| } |
| this.$.ripple = ripple; |
| this.shadowRoot.insertBefore(ripple, this.shadowRoot.firstChild); |
| // No need to forward the event to the ripple because the ripple |
| // is triggered in activeChanged |
| } |
| }, |
| |
| upAction: function() { |
| this._upAction(); |
| |
| if (this.toggle) { |
| this.toggleBackground(); |
| if (this.$.ripple) { |
| this.$.ripple.cancel(); |
| } |
| } |
| } |
| |
| }; |
| |
| Polymer.mixin2(p, Polymer.CoreFocusable); |
| Polymer('paper-button-base',p); |
| |
| })(); |
| |
| </script> |
| </polymer-element> |
| <polymer-element name="paper-button" extends="paper-button-base" attributes="raised recenteringTouch fill" role="button" assetpath="../bower_components/paper-button/"> |
| |
| <template> |
| |
| <style> |
| |
| :host { |
| display: inline-block; |
| position: relative; |
| box-sizing: border-box; |
| min-width: 5.14em; |
| margin: 0 0.29em; |
| background: transparent; |
| text-align: center; |
| font: inherit; |
| text-transform: uppercase; |
| outline: none; |
| border-radius: 3px; |
| -moz-user-select: none; |
| -ms-user-select: none; |
| -webkit-user-select: none; |
| user-select: none; |
| cursor: pointer; |
| z-index: 0; |
| } |
| |
| :host([disabled]) { |
| background: #eaeaea; |
| color: #a8a8a8; |
| cursor: auto; |
| pointer-events: none; |
| } |
| |
| ::content * { |
| text-transform: inherit; |
| } |
| |
| #bg, #shadow { |
| border-radius: inherit; |
| } |
| |
| #ripple { |
| pointer-events: none; |
| z-index: -1; |
| } |
| |
| .button-content { |
| padding: 0.7em 0.57em |
| } |
| |
| polyfill-next-selector { content: '.button-content > a'; } |
| ::content > a { |
| height: 100%; |
| padding: 0.7em 0.57em; |
| margin: -0.7em -0.57em; |
| /* flex */ |
| -ms-flex: 1 1 0.000000001px; |
| -webkit-flex: 1; |
| flex: 1; |
| -webkit-flex-basis: 0.000000001px; |
| flex-basis: 0.000000001px; |
| } |
| |
| </style> |
| |
| <template if="{{raised}}"> |
| <paper-shadow id="shadow" fit="" animated=""></paper-shadow> |
| </template> |
| |
| <!-- this div is needed to position the ripple behind text content --> |
| <div class="button-content" relative="" layout="" horizontal="" center-center=""> |
| <content></content> |
| </div> |
| |
| <core-a11y-keys keys="space enter" target="{{}}" on-keys-pressed="{{_activate}}"></core-a11y-keys> |
| |
| </template> |
| |
| <script> |
| Polymer('paper-button',{ |
| |
| publish: { |
| |
| /** |
| * If true, the button will be styled with a shadow. |
| * |
| * @attribute raised |
| * @type boolean |
| * @default false |
| */ |
| raised: false, |
| |
| /** |
| * By default the ripple emanates from where the user touched the button. |
| * Set this to true to always center the ripple. |
| * |
| * @attribute recenteringTouch |
| * @type boolean |
| * @default false |
| */ |
| recenteringTouch: false, |
| |
| /** |
| * By default the ripple expands to fill the button. Set this to true to |
| * constrain the ripple to a circle within the button. |
| * |
| * @attribute fill |
| * @type boolean |
| * @default true |
| */ |
| fill: true |
| |
| }, |
| |
| _activate: function() { |
| this.click(); |
| this.fire('tap'); |
| if (!this.pressed) { |
| var bcr = this.getBoundingClientRect(); |
| var x = bcr.left + (bcr.width / 2); |
| var y = bcr.top + (bcr.height / 2); |
| this.downAction({x: x, y: y}); |
| var fn = function fn() { |
| this.upAction(); |
| this.removeEventListener('keyup', fn); |
| }.bind(this); |
| this.addEventListener('keyup', fn); |
| } |
| } |
| |
| }); |
| </script> |
| </polymer-element> |
| |
| <polymer-element name="paper-icon-button" extends="paper-button-base" attributes="src icon" role="button" assetpath="../bower_components/paper-icon-button/"> |
| |
| <template> |
| |
| <style> |
| :host { |
| display: inline-block; |
| position: relative; |
| padding: 8px; |
| outline: none; |
| -webkit-user-select: none; |
| -moz-user-select: none; |
| -ms-user-select: none; |
| user-select: none; |
| cursor: pointer; |
| z-index: 0; |
| } |
| |
| :host([disabled]) { |
| color: #c9c9c9; |
| pointer-events: none; |
| cursor: auto; |
| } |
| |
| #ripple { |
| pointer-events: none; |
| z-index: -1; |
| } |
| |
| #icon { |
| display: block; |
| pointer-events: none; |
| } |
| </style> |
| |
| <!-- to position to ripple behind the icon --> |
| <core-icon relative="" id="icon" src="{{src}}" icon="{{icon}}"></core-icon> |
| |
| </template> |
| |
| <script> |
| Polymer('paper-icon-button',{ |
| |
| publish: { |
| |
| /** |
| * The URL of an image for the icon. If the src property is specified, |
| * the icon property should not be. |
| * |
| * @attribute src |
| * @type string |
| * @default '' |
| */ |
| src: '', |
| |
| /** |
| * Specifies the icon name or index in the set of icons available in |
| * the icon's icon set. If the icon property is specified, |
| * the src property should not be. |
| * |
| * @attribute icon |
| * @type string |
| * @default '' |
| */ |
| icon: '', |
| |
| recenteringTouch: true, |
| fill: false |
| |
| }, |
| |
| iconChanged: function(oldIcon) { |
| var label = this.getAttribute('aria-label'); |
| if (!label || label === oldIcon) { |
| this.setAttribute('aria-label', this.icon); |
| } |
| } |
| |
| }); |
| |
| </script> |
| |
| </polymer-element> |
| |
| <polymer-element name="core-input" extends="input" assetpath="../bower_components/core-input/"> |
| |
| <script> |
| |
| Polymer('core-input', { |
| |
| publish: { |
| |
| /** |
| * The "committed" value of the input, ie. the input value when the user |
| * hits the "enter" key or blurs the input after changing the value. You |
| * can bind to this value instead of listening for the "change" event. |
| * Setting this property has no effect on the input value. |
| * |
| * @attribute committedValue |
| * @type string |
| * @default '' |
| */ |
| committedValue: '', |
| |
| /** |
| * Set to true to prevent invalid input from being entered. |
| * |
| * @attribute preventInvalidInput |
| * @type boolean |
| * @default false |
| */ |
| preventInvalidInput: false |
| |
| }, |
| |
| previousValidInput: '', |
| |
| eventDelegates: { |
| input: 'inputAction', |
| change: 'changeAction' |
| }, |
| |
| ready: function() { |
| /* set ARIA attributes */ |
| this.disabledHandler(); |
| this.placeholderHandler(); |
| }, |
| |
| attributeChanged: function(attr, old) { |
| if (this[attr + 'Handler']) { |
| this[attr + 'Handler'](old); |
| } |
| }, |
| |
| disabledHandler: function() { |
| if (this.disabled) { |
| this.setAttribute('aria-disabled', ''); |
| } else { |
| this.removeAttribute('aria-disabled'); |
| } |
| }, |
| |
| placeholderHandler: function() { |
| if (this.placeholder) { |
| this.setAttribute('aria-label', this.placeholder); |
| } else { |
| this.removeAttribute('aria-label'); |
| } |
| }, |
| |
| /** |
| * Commits the `value` to `committedValue` |
| * |
| * @method commit |
| */ |
| commit: function() { |
| this.committedValue = this.value; |
| }, |
| |
| changeAction: function() { |
| this.commit(); |
| }, |
| |
| inputAction: function(e) { |
| if (this.preventInvalidInput) { |
| if (!e.target.validity.valid) { |
| e.target.value = this.previousValidInput; |
| } else { |
| this.previousValidInput = e.target.value; |
| } |
| } |
| } |
| |
| }); |
| |
| </script> |
| |
| </polymer-element> |
| |
| <polymer-element name="core-style" hidden="" assetpath="../bower_components/core-style/"> |
| <script> |
| (function() { |
| |
| window.CoreStyle = window.CoreStyle || { |
| g: {}, |
| list: {}, |
| refMap: {} |
| }; |
| |
| Polymer('core-style', { |
| /** |
| * The `id` property should be set if the `core-style` is a producer |
| * of styles. In this case, the `core-style` should have text content |
| * that is cssText. |
| * |
| * @attribute id |
| * @type string |
| * @default '' |
| */ |
| |
| |
| publish: { |
| /** |
| * The `ref` property should be set if the `core-style` element is a |
| * consumer of styles. Set it to the `id` of the desired `core-style` |
| * element. |
| * |
| * @attribute ref |
| * @type string |
| * @default '' |
| */ |
| ref: '' |
| }, |
| |
| // static |
| g: CoreStyle.g, |
| refMap: CoreStyle.refMap, |
| |
| /** |
| * The `list` is a map of all `core-style` producers stored by `id`. It |
| * should be considered readonly. It's useful for nesting one `core-style` |
| * inside another. |
| * |
| * @attribute list |
| * @type object |
| * @default {map of all `core-style` producers} |
| */ |
| list: CoreStyle.list, |
| |
| // if we have an id, we provide style |
| // if we have a ref, we consume/require style |
| ready: function() { |
| if (this.id) { |
| this.provide(); |
| } else { |
| this.registerRef(this.ref); |
| if (!window.ShadowDOMPolyfill) { |
| this.require(); |
| } |
| } |
| }, |
| |
| // can't shim until attached if using SD polyfill because need to find host |
| attached: function() { |
| if (!this.id && window.ShadowDOMPolyfill) { |
| this.require(); |
| } |
| }, |
| |
| /****** producer stuff *******/ |
| |
| provide: function() { |
| this.register(); |
| // we want to do this asap, especially so we can do so before definitions |
| // that use this core-style are registered. |
| if (this.textContent) { |
| this._completeProvide(); |
| } else { |
| this.async(this._completeProvide); |
| } |
| }, |
| |
| register: function() { |
| var i = this.list[this.id]; |
| if (i) { |
| if (!Array.isArray(i)) { |
| this.list[this.id] = [i]; |
| } |
| this.list[this.id].push(this); |
| } else { |
| this.list[this.id] = this; |
| } |
| }, |
| |
| // stamp into a shadowRoot so we can monitor dom of the bound output |
| _completeProvide: function() { |
| this.createShadowRoot(); |
| this.domObserver = new MutationObserver(this.domModified.bind(this)) |
| .observe(this.shadowRoot, {subtree: true, |
| characterData: true, childList: true}); |
| this.provideContent(); |
| }, |
| |
| provideContent: function() { |
| this.ensureTemplate(); |
| this.shadowRoot.textContent = ''; |
| this.shadowRoot.appendChild(this.instanceTemplate(this.template)); |
| this.cssText = this.shadowRoot.textContent; |
| }, |
| |
| ensureTemplate: function() { |
| if (!this.template) { |
| this.template = this.querySelector('template:not([repeat]):not([bind])'); |
| // move content into the template |
| if (!this.template) { |
| this.template = document.createElement('template'); |
| var n = this.firstChild; |
| while (n) { |
| this.template.content.appendChild(n.cloneNode(true)); |
| n = n.nextSibling; |
| } |
| } |
| } |
| }, |
| |
| domModified: function() { |
| this.cssText = this.shadowRoot.textContent; |
| this.notify(); |
| }, |
| |
| // notify instances that reference this element |
| notify: function() { |
| var s$ = this.refMap[this.id]; |
| if (s$) { |
| for (var i=0, s; (s=s$[i]); i++) { |
| s.require(); |
| } |
| } |
| }, |
| |
| /****** consumer stuff *******/ |
| |
| registerRef: function(ref) { |
| //console.log('register', ref); |
| this.refMap[this.ref] = this.refMap[this.ref] || []; |
| this.refMap[this.ref].push(this); |
| }, |
| |
| applyRef: function(ref) { |
| this.ref = ref; |
| this.registerRef(this.ref); |
| this.require(); |
| }, |
| |
| require: function() { |
| var cssText = this.cssTextForRef(this.ref); |
| //console.log('require', this.ref, cssText); |
| if (cssText) { |
| this.ensureStyleElement(); |
| // do nothing if cssText has not changed |
| if (this.styleElement._cssText === cssText) { |
| return; |
| } |
| this.styleElement._cssText = cssText; |
| if (window.ShadowDOMPolyfill) { |
| this.styleElement.textContent = cssText; |
| cssText = WebComponents.ShadowCSS.shimStyle(this.styleElement, |
| this.getScopeSelector()); |
| } |
| this.styleElement.textContent = cssText; |
| } |
| }, |
| |
| cssTextForRef: function(ref) { |
| var s$ = this.byId(ref); |
| var cssText = ''; |
| if (s$) { |
| if (Array.isArray(s$)) { |
| var p = []; |
| for (var i=0, l=s$.length, s; (i<l) && (s=s$[i]); i++) { |
| p.push(s.cssText); |
| } |
| cssText = p.join('\n\n'); |
| } else { |
| cssText = s$.cssText; |
| } |
| } |
| if (s$ && !cssText) { |
| console.warn('No styles provided for ref:', ref); |
| } |
| return cssText; |
| }, |
| |
| byId: function(id) { |
| return this.list[id]; |
| }, |
| |
| ensureStyleElement: function() { |
| if (!this.styleElement) { |
| this.styleElement = window.ShadowDOMPolyfill ? |
| this.makeShimStyle() : |
| this.makeRootStyle(); |
| } |
| if (!this.styleElement) { |
| console.warn(this.localName, 'could not setup style.'); |
| } |
| }, |
| |
| makeRootStyle: function() { |
| var style = document.createElement('style'); |
| this.appendChild(style); |
| return style; |
| }, |
| |
| makeShimStyle: function() { |
| var host = this.findHost(this); |
| if (host) { |
| var name = host.localName; |
| var style = document.querySelector('style[' + name + '=' + this.ref +']'); |
| if (!style) { |
| style = document.createElement('style'); |
| style.setAttribute(name, this.ref); |
| document.head.appendChild(style); |
| } |
| return style; |
| } |
| }, |
| |
| getScopeSelector: function() { |
| if (!this._scopeSelector) { |
| var selector = '', host = this.findHost(this); |
| if (host) { |
| var typeExtension = host.hasAttribute('is'); |
| var name = typeExtension ? host.getAttribute('is') : host.localName; |
| selector = WebComponents.ShadowCSS.makeScopeSelector(name, |
| typeExtension); |
| } |
| this._scopeSelector = selector; |
| } |
| return this._scopeSelector; |
| }, |
| |
| findHost: function(node) { |
| while (node.parentNode) { |
| node = node.parentNode; |
| } |
| return node.host || wrap(document.documentElement); |
| }, |
| |
| /* filters! */ |
| // TODO(dfreedm): add more filters! |
| |
| cycle: function(rgb, amount) { |
| if (rgb.match('#')) { |
| var o = this.hexToRgb(rgb); |
| if (!o) { |
| return rgb; |
| } |
| rgb = 'rgb(' + o.r + ',' + o.b + ',' + o.g + ')'; |
| } |
| |
| function cycleChannel(v) { |
| return Math.abs((Number(v) - amount) % 255); |
| } |
| |
| return rgb.replace(/rgb\(([^,]*),([^,]*),([^,]*)\)/, function(m, a, b, c) { |
| return 'rgb(' + cycleChannel(a) + ',' + cycleChannel(b) + ', ' |
| + cycleChannel(c) + ')'; |
| }); |
| }, |
| |
| hexToRgb: function(hex) { |
| var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); |
| return result ? { |
| r: parseInt(result[1], 16), |
| g: parseInt(result[2], 16), |
| b: parseInt(result[3], 16) |
| } : null; |
| } |
| |
| }); |
| |
| |
| })(); |
| </script> |
| </polymer-element> |
| <core-style id="paper-input-decorator"> |
| |
| .label-text, |
| .error { |
| color: {{g.paperInput.labelColor}}; |
| } |
| |
| ::-webkit-input-placeholder { |
| color: {{g.paperInput.labelColor}}; |
| } |
| |
| ::-moz-placeholder { |
| color: {{g.paperInput.labelColor}}; |
| } |
| |
| :-ms-input-placeholder { |
| color: {{g.paperInput.labelColor}}; |
| } |
| |
| .unfocused-underline { |
| background-color: {{g.paperInput.labelColor}}; |
| } |
| |
| :host([focused]) .floated-label .label-text { |
| color: {{g.paperInput.focusedColor}}; |
| } |
| |
| .focused-underline { |
| background-color: {{g.paperInput.focusedColor}}; |
| } |
| |
| :host(.invalid) .floated-label .label-text, |
| .error { |
| color: {{g.paperInput.invalidColor}}; |
| } |
| |
| :host(.invalid) .unfocused-underline, |
| :host(.invalid) .focused-underline { |
| background-color: {{g.paperInput.invalidColor}}; |
| } |
| |
| </core-style> |
| |
| <polymer-element name="paper-input-decorator" layout="" vertical="" on-transitionend="{{transitionEndAction}}" on-webkittransitionend="{{transitionEndAction}}" on-input="{{inputAction}}" on-down="{{downAction}}" on-tap="{{tapAction}}" on-char-counter-error="{{charCounterErrorAction}}" assetpath="../bower_components/paper-input/"> |
| |
| <template> |
| |
| <style>/* |
| * @license |
| * Copyright (c) 2014 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 |
| */ |
| |
| :host { |
| display: inline-block; |
| outline: none; |
| text-align: inherit; |
| padding: 0.75em 0; |
| } |
| |
| polyfill-next-selector { |
| content: '.input-body > :not(.label)'; |
| } |
| ::content > *, |
| ::content > input[is="core-input"] { |
| padding: 0; |
| margin: 0.5em 0 0.25em; |
| width: 100%; |
| } |
| |
| polyfill-next-selector { |
| content: 'input, textarea'; |
| } |
| ::content input, |
| ::content input[is=core-input], |
| ::content textarea { |
| font: inherit; |
| color: inherit; |
| background-color: transparent; |
| border: none; |
| outline: none; |
| } |
| |
| polyfill-next-selector { |
| content: ':invalid'; |
| } |
| ::content input:invalid, |
| ::content textarea:invalid { |
| box-shadow: none; |
| } |
| |
| polyfill-next-selector { |
| content: 'textarea'; |
| } |
| ::content textarea { |
| resize: none; |
| } |
| |
| [invisible] { |
| visibility: hidden; |
| } |
| |
| [animated] { |
| visibility: visible !important; |
| -webkit-transition: -webkit-transform 0.2s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1); |
| transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1); |
| } |
| |
| .floated-label { |
| font-size: 0.75em; |
| background: transparent; |
| white-space: nowrap; |
| } |
| |
| .mirror-text { |
| padding: 0.5em 0 0.25em; |
| max-width: 100%; |
| white-space: nowrap; |
| } |
| |
| :host([multiline]) .mirror-text { |
| white-space: pre-wrap; |
| word-wrap: break-word; |
| } |
| |
| .label { |
| padding: 0.5em 0 0.25em; |
| background: transparent; |
| pointer-events: none; |
| } |
| |
| .label-text { |
| overflow: hidden; |
| text-overflow: ellipsis; |
| white-space: nowrap; |
| display: inline-block; |
| max-width: 100%; |
| -moz-transform-origin: 0% 0%; |
| -webkit-transform-origin: 0% 0%; |
| transform-origin: 0% 0%; |
| } |
| |
| .underline { |
| height: 0px; |
| overflow: visible; |
| } |
| |
| :host([disabled]) .underline { |
| border-bottom: 1px dashed #757575; |
| } |
| |
| .unfocused-underline { |
| height: 1px; |
| } |
| |
| .focused-underline { |
| height: 2px; |
| -webkit-transform: none; |
| transform: none; |
| } |
| |
| .focused-underline[invisible] { |
| -webkit-transform: scale3d(0,1,1); |
| transform: scale3d(0,1,1); |
| } |
| |
| .error-text { |
| font-size: 0.75em; |
| padding: 0.5em 0; |
| } |
| |
| .error-icon { |
| height: 20px; |
| width: 20px; |
| } |
| </style> |
| <core-style ref="paper-input-decorator"></core-style> |
| |
| <div class="floated-label" aria-hidden="true" hidden?="{{!floatingLabel}}" invisible?="{{!floatingLabelVisible || labelAnimated}}"> |
| <!-- needed for floating label animation measurement --> |
| <span id="floatedLabelText" class="label-text">{{label}}</span> |
| </div> |
| |
| <div class="input-body" flex="" auto="" relative=""> |
| |
| <div class="label" fit="" invisible="" aria-hidden="true"> |
| <!-- needed for floating label animation measurement --> |
| <span id="labelText" class="label-text" invisible?="{{!_labelVisible}}" animated?="{{labelAnimated}}">{{label}}</span> |
| </div> |
| |
| <content select="*:not(.counter)"></content> |
| |
| </div> |
| |
| <div id="underline" class="underline" relative=""> |
| <div class="unfocused-underline" fit="" invisible?="{{disabled}}"></div> |
| <div id="focusedUnderline" class="focused-underline" fit="" invisible?="{{!underlineVisible}}" animated?="{{underlineAnimated}}"></div> |
| </div> |
| |
| <div class="footer" layout="" horizontal="" end-justified=""> |
| <div class="error" flex="" layout="" horizontal="" center="" hidden?="{{!isInvalid}}"> |
| <div class="error-text" flex="" auto="" role="alert" aria-hidden="{{!isInvalid}}">{{error}}</div> |
| <core-icon id="errorIcon" class="error-icon" icon="warning"></core-icon> |
| </div> |
| <div aria-hidden="true"> |
| <content select=".counter"></content> |
| </div> |
| </div> |
| |
| </template> |
| |
| <script> |
| |
| (function() { |
| |
| var paperInput = CoreStyle.g.paperInput = CoreStyle.g.paperInput || {}; |
| |
| paperInput.labelColor = '#757575'; |
| paperInput.focusedColor = '#4059a9'; |
| paperInput.invalidColor = '#d34336'; |
| |
| Polymer('paper-input-decorator',{ |
| |
| publish: { |
| |
| /** |
| * The label for this input. It normally appears as grey text inside |
| * the text input and disappears once the user enters text. |
| * |
| * @attribute label |
| * @type string |
| * @default '' |
| */ |
| label: '', |
| |
| /** |
| * If true, the label will "float" above the text input once the |
| * user enters text instead of disappearing. |
| * |
| * @attribute floatingLabel |
| * @type boolean |
| * @default false |
| */ |
| floatingLabel: false, |
| |
| /** |
| * Set to true to style the element as disabled. |
| * |
| * @attribute disabled |
| * @type boolean |
| * @default false |
| */ |
| disabled: {value: false, reflect: true}, |
| |
| /** |
| * Use this property to override the automatic label visibility. |
| * If this property is set to `true` or `false`, the label visibility |
| * will respect this value instead of be based on whether there is |
| * a non-null value in the input. |
| * |
| * @attribute labelVisible |
| * @type boolean |
| * @default false |
| */ |
| labelVisible: null, |
| |
| /** |
| * Set this property to true to show the error message. |
| * |
| * @attribute isInvalid |
| * @type boolean |
| * @default false |
| */ |
| isInvalid: false, |
| |
| /** |
| * Set this property to true to validate the input as the user types. |
| * This will not validate when changing the input programmatically; call |
| * `validate()` instead. |
| * |
| * @attribute autoValidate |
| * @type boolean |
| * @default false |
| */ |
| autoValidate: false, |
| |
| /** |
| * The message to display if the input value fails validation. If this |
| * is unset or the empty string, a default message is displayed depending |
| * on the type of validation error. |
| * |
| * @attribute error |
| * @type string |
| */ |
| error: '', |
| |
| focused: {value: false, reflect: true} |
| |
| }, |
| |
| computed: { |
| floatingLabelVisible: 'floatingLabel && !_labelVisible', |
| _labelVisible: '(labelVisible === true || labelVisible === false) ? labelVisible : _autoLabelVisible' |
| }, |
| |
| ready: function() { |
| // Delegate focus/blur events |
| Polymer.addEventListener(this, 'focus', this.focusAction.bind(this), true); |
| Polymer.addEventListener(this, 'blur', this.blurAction.bind(this), true); |
| }, |
| |
| attached: function() { |
| this.input = this.querySelector('input,textarea'); |
| |
| this.mo = new MutationObserver(function() { |
| this.input = this.querySelector('input,textarea'); |
| }.bind(this)); |
| this.mo.observe(this, {childList: true}); |
| }, |
| |
| detached: function() { |
| this.mo.disconnect(); |
| this.mo = null; |
| }, |
| |
| prepareLabelTransform: function() { |
| var toRect = this.$.floatedLabelText.getBoundingClientRect(); |
| var fromRect = this.$.labelText.getBoundingClientRect(); |
| if (toRect.width !== 0) { |
| var sy = toRect.height / fromRect.height; |
| this.$.labelText.cachedTransform = |
| 'scale3d(' + (toRect.width / fromRect.width) + ',' + sy + ',1) ' + |
| 'translate3d(0,' + (toRect.top - fromRect.top) / sy + 'px,0)'; |
| } |
| }, |
| |
| animateFloatingLabel: function() { |
| if (!this.floatingLabel || this.labelAnimated) { |
| return false; |
| } |
| |
| if (!this.$.labelText.cachedTransform) { |
| this.prepareLabelTransform(); |
| } |
| |
| // If there's still no cached transform, the input is invisible so don't |
| // do the animation. |
| if (!this.$.labelText.cachedTransform) { |
| return false; |
| } |
| |
| this.labelAnimated = true; |
| // Handle interrupted animation |
| this.async(function() { |
| this.transitionEndAction(); |
| }, null, 250); |
| |
| if (this._labelVisible) { |
| // Handle if the label started out floating |
| if (!this.$.labelText.style.webkitTransform && !this.$.labelText.style.transform) { |
| this.$.labelText.style.webkitTransform = this.$.labelText.cachedTransform; |
| this.$.labelText.style.transform = this.$.labelText.cachedTransform; |
| this.$.labelText.offsetTop; |
| } |
| this.$.labelText.style.webkitTransform = ''; |
| this.$.labelText.style.transform = ''; |
| } else { |
| this.$.labelText.style.webkitTransform = this.$.labelText.cachedTransform; |
| this.$.labelText.style.transform = this.$.labelText.cachedTransform; |
| this.input.placeholder = ''; |
| } |
| |
| return true; |
| }, |
| |
| animateUnderline: function(e) { |
| if (this.focused) { |
| var rect = this.$.underline.getBoundingClientRect(); |
| var right = e.x - rect.left; |
| this.$.focusedUnderline.style.mozTransformOrigin = right + 'px'; |
| this.$.focusedUnderline.style.webkitTransformOrigin = right + 'px '; |
| this.$.focusedUnderline.style.transformOriginX = right + 'px'; |
| |
| // Animations only run when the user interacts with the input |
| this.underlineAnimated = true; |
| } |
| }, |
| |
| /** |
| * Validate the input using HTML5 Constraints. |
| * |
| * @method validate |
| * @return {boolean} True if the input is valid. |
| */ |
| validate: function() { |
| if (!this.input) { |
| return true; |
| } |
| |
| this.isInvalid = !this.input.validity.valid; |
| return this.input.validity.valid; |
| }, |
| |
| _labelVisibleChanged: function(old) { |
| // do not do the animation on first render |
| if (old !== undefined) { |
| if (!this.animateFloatingLabel()) { |
| this.updateInputLabel(this.input, this.label); |
| } |
| } |
| }, |
| |
| labelVisibleChanged: function() { |
| if (this.labelVisible === 'true') { |
| this.labelVisible = true; |
| } else if (this.labelVisible === 'false') { |
| this.labelVisible = false; |
| } |
| }, |
| |
| labelChanged: function() { |
| if (this.input) { |
| this.updateInputLabel(this.input, this.label); |
| } |
| }, |
| |
| isInvalidChanged: function() { |
| this.classList.toggle('invalid', this.isInvalid); |
| }, |
| |
| focusedChanged: function() { |
| this.updateLabelVisibility(this.input && this.input.value); |
| if (this.lastEvent) { |
| this.animateUnderline(this.lastEvent); |
| this.lastEvent = null; |
| } |
| this.underlineVisible = this.focused; |
| }, |
| |
| inputChanged: function(old) { |
| if (this.input) { |
| this.updateLabelVisibility(this.input.value); |
| this.updateInputLabel(this.input, this.label); |
| |
| if (this.autoValidate) { |
| this.validate(); |
| } |
| } |
| if (old) { |
| this.updateInputLabel(old, ''); |
| } |
| }, |
| |
| focusAction: function() { |
| this.focused = true; |
| }, |
| |
| blurAction: function() { |
| this.focused = false; |
| }, |
| |
| /** |
| * Updates the label visibility based on a value. This is handled automatically |
| * if the user is typing, but if you imperatively set the input value you need |
| * to call this function. |
| * |
| * @method updateLabelVisibility |
| * @param {string} value |
| */ |
| updateLabelVisibility: function(value) { |
| var v = (value !== null && value !== undefined) ? String(value) : value; |
| this._autoLabelVisible = (!this.focused && !v) || (!this.floatingLabel && !v); |
| }, |
| |
| updateInputLabel: function(input, label) { |
| if (this._labelVisible) { |
| this.input.placeholder = this.label; |
| } else { |
| this.input.placeholder = ''; |
| } |
| if (label) { |
| input.setAttribute('aria-label', label); |
| } else { |
| input.removeAttribute('aria-label'); |
| } |
| }, |
| |
| inputAction: function() { |
| this.updateLabelVisibility(this.input.value); |
| if (this.autoValidate) { |
| this.validate(); |
| } |
| }, |
| |
| downAction: function(e) { |
| // eat the event and do nothing if already focused |
| if (e.target !== this.input && this.focused) { |
| e.preventDefault(); |
| return; |
| } |
| // cache the event here because "down" fires before "focus" when tapping on |
| // the input and the underline animation runs on focus change |
| this.lastEvent = e; |
| }, |
| |
| tapAction: function(e) { |
| if (this.disabled) { |
| return; |
| } |
| |
| if (this.focused) { |
| return; |
| } |
| |
| if (this.input) { |
| this.input.focus(); |
| e.preventDefault(); |
| } |
| }, |
| |
| transitionEndAction: function() { |
| this.underlineAnimated = false; |
| this.labelAnimated = false; |
| if (this._labelVisible) { |
| this.input.placeholder = this.label; |
| } |
| }, |
| |
| charCounterErrorAction: function(e) { |
| this.isInvalid = e.detail.hasError; |
| |
| // If the allowed characters have been exceeded, show either the error |
| // icon, or the character counter, but not both. |
| this.$.errorIcon.hidden = e.detail.hideErrorIcon; |
| } |
| |
| }); |
| |
| }()); |
| |
| </script> |
| |
| </polymer-element> |
| <polymer-element name="paper-input" assetpath="../bower_components/paper-input/"> |
| |
| <template> |
| |
| <style> |
| :host { |
| display: inline-block; |
| } |
| </style> |
| |
| <paper-input-decorator id="decorator" label="{{label}}" floatinglabel="{{floatingLabel}}" value="{{value}}" disabled?="{{disabled}}"> |
| <input is="core-input" id="input" value="{{value}}" committedvalue="{{committedValue}}" on-change="{{changeAction}}" disabled?="{{disabled}}"> |
| </paper-input-decorator> |
| |
| </template> |
| |
| <script> |
| |
| Polymer('paper-input', { |
| |
| publish: { |
| /** |
| * The label for this input. It normally appears as grey text inside |
| * the text input and disappears once the user enters text. |
| * |
| * @attribute label |
| * @type string |
| * @default '' |
| */ |
| label: '', |
| |
| /** |
| * If true, the label will "float" above the text input once the |
| * user enters text instead of disappearing. |
| * |
| * @attribute floatingLabel |
| * @type boolean |
| * @default false |
| */ |
| floatingLabel: false, |
| |
| /** |
| * Set to true to style the element as disabled. |
| * |
| * @attribute disabled |
| * @type boolean |
| * @default false |
| */ |
| disabled: {value: false, reflect: true}, |
| |
| /** |
| * The current value of the input. |
| * |
| * @attribute value |
| * @type String |
| * @default '' |
| */ |
| value: '', |
| |
| /** |
| * The most recently committed value of the input. |
| * |
| * @attribute committedValue |
| * @type String |
| * @default '' |
| */ |
| committedValue: '' |
| |
| }, |
| |
| /** |
| * Focuses the `input`. |
| * |
| * @method focus |
| */ |
| focus: function() { |
| this.$.input.focus(); |
| }, |
| |
| valueChanged: function() { |
| this.$.decorator.updateLabelVisibility(this.value); |
| }, |
| |
| changeAction: function(e) { |
| // re-fire event that does not bubble across shadow roots |
| this.fire('change', null, this); |
| } |
| |
| }); |
| |
| </script> |
| |
| </polymer-element> |
| |
| <polymer-element name="paper-item" extends="paper-button-base" assetpath="../bower_components/paper-item/"> |
| |
| <template> |
| |
| <style> |
| |
| :host { |
| display: block; |
| position: relative; |
| font-size: 16px; |
| box-sizing: border-box; |
| min-width: 7em; |
| outline: none; |
| -moz-user-select: none; |
| -ms-user-select: none; |
| -webkit-user-select: none; |
| user-select: none; |
| cursor: pointer; |
| z-index: 0; |
| } |
| |
| :host([disabled]) { |
| color: #a8a8a8; |
| cursor: auto; |
| pointer-events: none; |
| } |
| |
| :host(.core-selected) { |
| background-color: #eaeaea; |
| } |
| |
| #ripple { |
| pointer-events: none; |
| z-index: -1; |
| } |
| |
| .button-content { |
| padding: 0.9em 1em; |
| } |
| |
| polyfill-next-selector { content: '.button-content > a'; } |
| ::content > a { |
| height: 100%; |
| /* flex */ |
| -ms-flex: 1 1 0.000000001px; |
| -webkit-flex: 1; |
| flex: 1; |
| -webkit-flex-basis: 0.000000001px; |
| flex-basis: 0.000000001px; |
| } |
| |
| </style> |
| |
| <!-- this div is needed to position the ripple behind text content --> |
| <div class="button-content" relative="" layout="" horizontal="" center=""> |
| <content></content> |
| </div> |
| |
| </template> |
| |
| <script> |
| Polymer('paper-item',{ |
| |
| publish: { |
| |
| /** |
| * If true, the button will be styled with a shadow. |
| * |
| * @attribute raised |
| * @type boolean |
| * @default false |
| */ |
| raised: false, |
| |
| /** |
| * By default the ripple emanates from where the user touched the button. |
| * Set this to true to always center the ripple. |
| * |
| * @attribute recenteringTouch |
| * @type boolean |
| * @default false |
| */ |
| recenteringTouch: false, |
| |
| /** |
| * By default the ripple expands to fill the button. Set this to false to |
| * constrain the ripple to a circle within the button. |
| * |
| * @attribute fill |
| * @type boolean |
| * @default true |
| */ |
| fill: true |
| |
| } |
| |
| }); |
| </script> |
| </polymer-element> |
| |
| <polymer-element name="core-range" attributes="value min max step ratio" assetpath="../bower_components/core-range/"> |
| <script> |
| |
| Polymer('core-range', { |
| |
| /** |
| * The number that represents the current value. |
| * |
| * @attribute value |
| * @type number |
| * @default 0 |
| */ |
| value: 0, |
| |
| /** |
| * The number that indicates the minimum value of the range. |
| * |
| * @attribute min |
| * @type number |
| * @default 0 |
| */ |
| min: 0, |
| |
| /** |
| * The number that indicates the maximum value of the range. |
| * |
| * @attribute max |
| * @type number |
| * @default 100 |
| */ |
| max: 100, |
| |
| /** |
| * Specifies the value granularity of the range's value. |
| * |
| * @attribute step |
| * @type number |
| * @default 1 |
| */ |
| step: 1, |
| |
| /** |
| * Returns the ratio of the value. |
| * |
| * @attribute ratio |
| * @type number |
| * @default 0 |
| */ |
| ratio: 0, |
| |
| observe: { |
| 'value min max step': 'update' |
| }, |
| |
| calcRatio: function(value) { |
| return (this.clampValue(value) - this.min) / (this.max - this.min); |
| }, |
| |
| clampValue: function(value) { |
| return Math.min(this.max, Math.max(this.min, this.calcStep(value))); |
| }, |
| |
| calcStep: function(value) { |
| return this.step ? (Math.round(value / this.step) / (1 / this.step)) : value; |
| }, |
| |
| validateValue: function() { |
| var v = this.clampValue(this.value); |
| this.value = this.oldValue = isNaN(v) ? this.oldValue : v; |
| return this.value !== v; |
| }, |
| |
| update: function() { |
| this.validateValue(); |
| this.ratio = this.calcRatio(this.value) * 100; |
| } |
| |
| }); |
| |
| </script> |
| </polymer-element> |
| <polymer-element name="paper-progress" extends="core-range" attributes="secondaryProgress indeterminate" assetpath="../bower_components/paper-progress/"> |
| |
| <template> |
| |
| <style>/* |
| Copyright (c) 2014 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 |
| */ |
| |
| :host { |
| display: inline-block; |
| width: 200px; |
| height: 4px; |
| } |
| |
| #progressContainer { |
| position: relative; |
| height: 100%; |
| background-color: #c8c8c8; |
| overflow: hidden; |
| } |
| |
| #activeProgress, |
| #secondaryProgress { |
| -webkit-transform-origin: left center; |
| transform-origin: left center; |
| -webkit-transform: scaleX(0); |
| transform: scaleX(0); |
| } |
| |
| #activeProgress { |
| background-color: #0f9d58; |
| } |
| |
| #secondaryProgress { |
| background-color: #87ceac; |
| } |
| |
| #activeProgress.indeterminate { |
| -webkit-transform-origin: center center; |
| transform-origin: center center; |
| -webkit-animation: indeterminate-bar 1s linear infinite; |
| animation: indeterminate-bar 1s linear infinite; |
| } |
| |
| @-webkit-keyframes indeterminate-bar { |
| 0% { |
| -webkit-transform: translate(-50%) scaleX(0); |
| } |
| 50% { |
| -webkit-transform: translate(0%) scaleX(0.3); |
| } |
| 100% { |
| -webkit-transform: translate(50%) scaleX(0); |
| } |
| } |
| |
| @keyframes indeterminate-bar { |
| 0% { |
| transform: translate(-50%) scaleX(0); |
| } |
| 50% { |
| transform: translate(0%) scaleX(0.3); |
| } |
| 100% { |
| transform: translate(50%) scaleX(0); |
| } |
| } |
| </style> |
| |
| <div id="progressContainer" role="progressbar" aria-valuenow="{{value}}" aria-valuemin="{{min}}" aria-valuemax="{{max}}"> |
| |
| <div id="secondaryProgress" fit=""></div> |
| <div id="activeProgress" fit=""></div> |
| |
| </div> |
| |
| </template> |
| |
| <script> |
| |
| Polymer('paper-progress', { |
| |
| /** |
| * The number that represents the current secondary progress. |
| * |
| * @attribute secondaryProgress |
| * @type number |
| * @default 0 |
| */ |
| secondaryProgress: 0, |
| |
| /** |
| * Use an indeterminate progress indicator. |
| * |
| * @attribute indeterminate |
| * @type boolean |
| * @default false |
| */ |
| indeterminate: false, |
| |
| step: 0, |
| |
| observe: { |
| 'value secondaryProgress min max indeterminate': 'update' |
| }, |
| |
| update: function() { |
| this.super(); |
| this.secondaryProgress = this.clampValue(this.secondaryProgress); |
| this.secondaryRatio = this.calcRatio(this.secondaryProgress) * 100; |
| |
| // If we use attribute/class binding, the animation sometimes doesn't translate properly |
| // on Safari 7.1. So instead, we toggle the class here in the update method. |
| this.$.activeProgress.classList.toggle('indeterminate', this.indeterminate); |
| }, |
| |
| transformProgress: function(progress, ratio) { |
| var transform = 'scaleX(' + (ratio / 100) + ')'; |
| progress.style.transform = progress.style.webkitTransform = transform; |
| }, |
| |
| ratioChanged: function() { |
| this.transformProgress(this.$.activeProgress, this.ratio); |
| }, |
| |
| secondaryRatioChanged: function() { |
| this.transformProgress(this.$.secondaryProgress, this.secondaryRatio); |
| } |
| |
| }); |
| |
| </script> |
| |
| </polymer-element> |
| |
| <polymer-element name="paper-spinner" attributes="active alt" role="progressbar" assetpath="../bower_components/paper-spinner/"> |
| <template> |
| <style>/* |
| @license |
| Copyright (c) 2014 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 |
| */ |
| |
| /**************************/ |
| /* STYLES FOR THE SPINNER */ |
| /**************************/ |
| |
| /* |
| * Constants: |
| * STROKEWIDTH = 3px |
| * ARCSIZE = 270 degrees (amount of circle the arc takes up) |
| * ARCTIME = 1333ms (time it takes to expand and contract arc) |
| * ARCSTARTROT = 216 degrees (how much the start location of the arc |
| * should rotate each time, 216 gives us a |
| * 5 pointed star shape (it's 360/5 * 3). |
| * For a 7 pointed star, we might do |
| * 360/7 * 3 = 154.286) |
| * CONTAINERWIDTH = 28px |
| * SHRINK_TIME = 400ms |
| */ |
| |
| :host { |
| display: inline-block; |
| position: relative; |
| width: 28px; /* CONTAINERWIDTH */ |
| height: 28px; /* CONTAINERWIDTH */ |
| } |
| |
| #spinnerContainer { |
| width: 100%; |
| height: 100%; |
| } |
| |
| #spinnerContainer.active { |
| /* duration: 360 * ARCTIME / (ARCSTARTROT + (360-ARCSIZE)) */ |
| -webkit-animation: container-rotate 1568ms linear infinite; |
| animation: container-rotate 1568ms linear infinite; |
| } |
| |
| @-webkit-keyframes container-rotate { |
| to { -webkit-transform: rotate(360deg) } |
| } |
| |
| @keyframes container-rotate { |
| to { transform: rotate(360deg) } |
| } |
| |
| .spinner-layer { |
| position: absolute; |
| width: 100%; |
| height: 100%; |
| opacity: 0; |
| } |
| |
| .blue { |
| border-color: #4285f4; |
| } |
| |
| .red { |
| border-color: #db4437; |
| } |
| |
| .yellow { |
| border-color: #f4b400; |
| } |
| |
| .green { |
| border-color: #0f9d58; |
| } |
| |
| /** |
| * IMPORTANT NOTE ABOUT CSS ANIMATION PROPERTIES (keanulee): |
| * |
| * iOS Safari (tested on iOS 8.1) does not handle animation-delay very well - it doesn't |
| * guarantee that the animation will start _exactly_ after that value. So we avoid using |
| * animation-delay and instead set custom keyframes for each color (as redundant as it |
| * seems). |
| * |
| * We write out each animation in full (instead of separating animation-name, |
| * animation-duration, etc.) because under the polyfill, Safari does not recognize those |
| * specific properties properly, treats them as -webkit-animation, and overrides the |
| * other animation rules. See https://github.com/Polymer/platform/issues/53. |
| */ |
| .active .spinner-layer.blue { |
| /* durations: 4 * ARCTIME */ |
| -webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, blue-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; |
| animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, blue-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; |
| } |
| |
| .active .spinner-layer.red { |
| /* durations: 4 * ARCTIME */ |
| -webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, red-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; |
| animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, red-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; |
| } |
| |
| .active .spinner-layer.yellow { |
| /* durations: 4 * ARCTIME */ |
| -webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, yellow-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; |
| animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, yellow-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; |
| } |
| |
| .active .spinner-layer.green { |
| /* durations: 4 * ARCTIME */ |
| -webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, green-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; |
| animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, green-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; |
| } |
| |
| @-webkit-keyframes fill-unfill-rotate { |
| 12.5% { -webkit-transform: rotate(135deg); } /* 0.5 * ARCSIZE */ |
| 25% { -webkit-transform: rotate(270deg); } /* 1 * ARCSIZE */ |
| 37.5% { -webkit-transform: rotate(405deg); } /* 1.5 * ARCSIZE */ |
| 50% { -webkit-transform: rotate(540deg); } /* 2 * ARCSIZE */ |
| 62.5% { -webkit-transform: rotate(675deg); } /* 2.5 * ARCSIZE */ |
| 75% { -webkit-transform: rotate(810deg); } /* 3 * ARCSIZE */ |
| 87.5% { -webkit-transform: rotate(945deg); } /* 3.5 * ARCSIZE */ |
| to { -webkit-transform: rotate(1080deg); } /* 4 * ARCSIZE */ |
| } |
| |
| @keyframes fill-unfill-rotate { |
| 12.5% { transform: rotate(135deg); } /* 0.5 * ARCSIZE */ |
| 25% { transform: rotate(270deg); } /* 1 * ARCSIZE */ |
| 37.5% { transform: rotate(405deg); } /* 1.5 * ARCSIZE */ |
| 50% { transform: rotate(540deg); } /* 2 * ARCSIZE */ |
| 62.5% { transform: rotate(675deg); } /* 2.5 * ARCSIZE */ |
| 75% { transform: rotate(810deg); } /* 3 * ARCSIZE */ |
| 87.5% { transform: rotate(945deg); } /* 3.5 * ARCSIZE */ |
| to { transform: rotate(1080deg); } /* 4 * ARCSIZE */ |
| } |
| |
| /** |
| * HACK: Even though the intention is to have the current .spinner-layer at |
| * `opacity: 1`, we set it to `opacity: 0.99` instead since this forces Chrome |
| * to do proper subpixel rendering for the elements being animated. This is |
| * especially visible in Chrome 39 on Ubuntu 14.04. See: |
| * |
| * - https://github.com/Polymer/paper-spinner/issues/9 |
| * - https://code.google.com/p/chromium/issues/detail?id=436255 |
| */ |
| @-webkit-keyframes blue-fade-in-out { |
| from { opacity: 0.99; } |
| 25% { opacity: 0.99; } |
| 26% { opacity: 0; } |
| 89% { opacity: 0; } |
| 90% { opacity: 0.99; } |
| 100% { opacity: 0.99; } |
| } |
| |
| @keyframes blue-fade-in-out { |
| from { opacity: 0.99; } |
| 25% { opacity: 0.99; } |
| 26% { opacity: 0; } |
| 89% { opacity: 0; } |
| 90% { opacity: 0.99; } |
| 100% { opacity: 0.99; } |
| } |
| |
| @-webkit-keyframes red-fade-in-out { |
| from { opacity: 0; } |
| 15% { opacity: 0; } |
| 25% { opacity: 0.99; } |
| 50% { opacity: 0.99; } |
| 51% { opacity: 0; } |
| } |
| |
| @keyframes red-fade-in-out { |
| from { opacity: 0; } |
| 15% { opacity: 0; } |
| 25% { opacity: 0.99; } |
| 50% { opacity: 0.99; } |
| 51% { opacity: 0; } |
| } |
| |
| @-webkit-keyframes yellow-fade-in-out { |
| from { opacity: 0; } |
| 40% { opacity: 0; } |
| 50% { opacity: 0.99; } |
| 75% { opacity: 0.99; } |
| 76% { opacity: 0; } |
| } |
| |
| @keyframes yellow-fade-in-out { |
| from { opacity: 0; } |
| 40% { opacity: 0; } |
| 50% { opacity: 0.99; } |
| 75% { opacity: 0.99; } |
| 76% { opacity: 0; } |
| } |
| |
| @-webkit-keyframes green-fade-in-out { |
| from { opacity: 0; } |
| 65% { opacity: 0; } |
| 75% { opacity: 0.99; } |
| 90% { opacity: 0.99; } |
| 100% { opacity: 0; } |
| } |
| |
| @keyframes green-fade-in-out { |
| from { opacity: 0; } |
| 65% { opacity: 0; } |
| 75% { opacity: 0.99; } |
| 90% { opacity: 0.99; } |
| 100% { opacity: 0; } |
| } |
| |
| /** |
| * Patch the gap that appear between the two adjacent div.circle-clipper while the |
| * spinner is rotating (appears on Chrome 38, Safari 7.1, and IE 11). |
| * |
| * Update: the gap no longer appears on Chrome when .spinner-layer's opacity is 0.99, |
| * but still does on Safari and IE. |
| */ |
| .gap-patch { |
| position: absolute; |
| box-sizing: border-box; |
| top: 0; |
| left: 45%; |
| width: 10%; |
| height: 100%; |
| overflow: hidden; |
| border-color: inherit; |
| } |
| |
| .gap-patch .circle { |
| width: 1000%; |
| left: -450%; |
| } |
| |
| .circle-clipper { |
| display: inline-block; |
| position: relative; |
| width: 50%; |
| height: 100%; |
| overflow: hidden; |
| border-color: inherit; |
| } |
| |
| .circle-clipper .circle { |
| width: 200%; |
| } |
| |
| .circle { |
| box-sizing: border-box; |
| height: 100%; |
| border-width: 3px; /* STROKEWIDTH */ |
| border-style: solid; |
| border-color: inherit; |
| border-bottom-color: transparent !important; |
| border-radius: 50%; |
| -webkit-animation: none; |
| animation: none; |
| } |
| |
| .circle-clipper.left .circle { |
| border-right-color: transparent !important; |
| -webkit-transform: rotate(129deg); |
| transform: rotate(129deg); |
| } |
| |
| .circle-clipper.right .circle { |
| left: -100%; |
| border-left-color: transparent !important; |
| -webkit-transform: rotate(-129deg); |
| transform: rotate(-129deg); |
| } |
| |
| .active .circle-clipper.left .circle { |
| /* duration: ARCTIME */ |
| -webkit-animation: left-spin 1333ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; |
| animation: left-spin 1333ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; |
| } |
| |
| .active .circle-clipper.right .circle { |
| /* duration: ARCTIME */ |
| -webkit-animation: right-spin 1333ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; |
| animation: right-spin 1333ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; |
| } |
| |
| @-webkit-keyframes left-spin { |
| from { -webkit-transform: rotate(130deg); } |
| 50% { -webkit-transform: rotate(-5deg); } |
| to { -webkit-transform: rotate(130deg); } |
| } |
| |
| @keyframes left-spin { |
| from { transform: rotate(130deg); } |
| 50% { transform: rotate(-5deg); } |
| to { transform: rotate(130deg); } |
| } |
| |
| @-webkit-keyframes right-spin { |
| from { -webkit-transform: rotate(-130deg); } |
| 50% { -webkit-transform: rotate(5deg); } |
| to { -webkit-transform: rotate(-130deg); } |
| } |
| |
| @keyframes right-spin { |
| from { transform: rotate(-130deg); } |
| 50% { transform: rotate(5deg); } |
| to { transform: rotate(-130deg); } |
| } |
| |
| #spinnerContainer.cooldown { |
| /* duration: SHRINK_TIME */ |
| -webkit-animation: container-rotate 1568ms linear infinite, fade-out 400ms cubic-bezier(0.4, 0.0, 0.2, 1); |
| animation: container-rotate 1568ms linear infinite, fade-out 400ms cubic-bezier(0.4, 0.0, 0.2, 1); |
| } |
| |
| @-webkit-keyframes fade-out { |
| from { opacity: 0.99; } |
| to { opacity: 0; } |
| } |
| |
| @keyframes fade-out { |
| from { opacity: 0.99; } |
| to { opacity: 0; } |
| } |
| </style> |
| |
| <div id="spinnerContainer"> |
| <div class="spinner-layer blue"> |
| <div class="circle-clipper left"> |
| <div class="circle" fit=""></div> |
| </div><div class="gap-patch"> |
| <div class="circle" fit=""></div> |
| </div><div class="circle-clipper right"> |
| <div class="circle" fit=""></div> |
| </div> |
| </div> |
| |
| <div class="spinner-layer red"> |
| <div class="circle-clipper left"> |
| <div class="circle" fit=""></div> |
| </div><div class="gap-patch"> |
| <div class="circle" fit=""></div> |
| </div><div class="circle-clipper right"> |
| <div class="circle" fit=""></div> |
| </div> |
| </div> |
| |
| <div class="spinner-layer yellow"> |
| <div class="circle-clipper left"> |
| <div class="circle" fit=""></div> |
| </div><div class="gap-patch"> |
| <div class="circle" fit=""></div> |
| </div><div class="circle-clipper right"> |
| <div class="circle" fit=""></div> |
| </div> |
| </div> |
| |
| <div class="spinner-layer green"> |
| <div class="circle-clipper left"> |
| <div class="circle" fit=""></div> |
| </div><div class="gap-patch"> |
| <div class="circle" fit=""></div> |
| </div><div class="circle-clipper right"> |
| <div class="circle" fit=""></div> |
| </div> |
| </div> |
| </div> |
| </template> |
| |
| <script> |
| Polymer('paper-spinner',{ |
| eventDelegates: { |
| 'animationend': 'reset', |
| 'webkitAnimationEnd': 'reset' |
| }, |
| publish: { |
| /** |
| * Displays the spinner. |
| * |
| * @attribute active |
| * @type boolean |
| * @default false |
| */ |
| active: {value: false, reflect: true}, |
| |
| /** |
| * Alternative text content for accessibility support. |
| * If alt is present, it will add an aria-label whose content matches alt when active. |
| * If alt is not present, it will default to 'loading' as the alt value. |
| * @attribute alt |
| * @type string |
| * @default 'loading' |
| */ |
| alt: {value: 'loading', reflect: true} |
| }, |
| |
| ready: function() { |
| // Allow user-provided `aria-label` take preference to any other text alternative. |
| if (this.hasAttribute('aria-label')) { |
| this.alt = this.getAttribute('aria-label'); |
| } else { |
| this.setAttribute('aria-label', this.alt); |
| } |
| if (!this.active) { |
| this.setAttribute('aria-hidden', 'true'); |
| } |
| }, |
| |
| activeChanged: function() { |
| if (this.active) { |
| this.$.spinnerContainer.classList.remove('cooldown'); |
| this.$.spinnerContainer.classList.add('active'); |
| this.removeAttribute('aria-hidden'); |
| } else { |
| this.$.spinnerContainer.classList.add('cooldown'); |
| this.setAttribute('aria-hidden', 'true'); |
| } |
| }, |
| |
| altChanged: function() { |
| if (this.alt === '') { |
| this.setAttribute('aria-hidden', 'true'); |
| } else { |
| this.removeAttribute('aria-hidden'); |
| } |
| this.setAttribute('aria-label', this.alt); |
| }, |
| |
| reset: function() { |
| this.$.spinnerContainer.classList.remove('active', 'cooldown'); |
| } |
| }); |
| </script> |
| </polymer-element> |
| |
| <polymer-element name="paper-tab" attributes="noink" role="tab" inline="" flex="" center-center="" horizontal="" layout="" assetpath="../bower_components/paper-tabs/"> |
| <template> |
| |
| <style>/* |
| Copyright (c) 2014 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 |
| */ |
| |
| :host { |
| position: relative; |
| overflow: hidden; |
| cursor: default; |
| } |
| |
| .tab-content { |
| transition: opacity .1s cubic-bezier(0.4, 0.0, 1, 1), color .1s cubic-bezier(0.4, 0.0, 1, 1); |
| height: 100%; |
| margin: 0 12px; |
| } |
| |
| :host-context(#tabsContainer.scrollable) .tab-content { |
| margin: 0 24px; |
| } |
| |
| :host-context(paper-tabs[link]) .tab-content { |
| margin: 0 !important; |
| } |
| |
| polyfill-next-selector { content: '.core-narrow #tabsContainer.scrollable :host .tab-content'; } |
| :host-context(.core-narrow):host-context(#tabsContainer.scrollable) .tab-content { |
| margin: 0 12px; |
| } |
| |
| :host(:not(.core-selected)) .tab-content { |
| opacity: 0.6; |
| } |
| |
| #ink { |
| position: absolute; |
| top: 0; |
| right: 0; |
| bottom: 0; |
| left: 0; |
| color: #ffff8d; |
| pointer-events: none; |
| } |
| |
| polyfill-next-selector { content: '.tab-content > a'; } |
| ::content > a { |
| height: 100%; |
| padding: 0 12px; |
| /* flex */ |
| -ms-flex: 1 1 0.000000001px; |
| -webkit-flex: 1; |
| flex: 1; |
| -webkit-flex-basis: 0.000000001px; |
| flex-basis: 0.000000001px; |
| } |
| </style> |
| |
| <div class="tab-content" flex="" auto="" center-center="" horizontal="" layout=""><content></content></div> |
| <paper-ripple id="ink" initialopacity="0.95" opacitydecayvelocity="0.98"></paper-ripple> |
| |
| </template> |
| <script> |
| |
| Polymer('paper-tab', { |
| |
| /** |
| * If true, ink ripple effect is disabled. |
| * |
| * @attribute noink |
| * @type boolean |
| * @default false |
| */ |
| noink: false, |
| |
| eventDelegates: { |
| down: 'downAction', |
| up: 'upAction' |
| }, |
| |
| downAction: function(e) { |
| if (this.noink || (this.parentElement && this.parentElement.noink)) { |
| return; |
| } |
| this.$.ink.downAction(e); |
| }, |
| |
| upAction: function() { |
| this.$.ink.upAction(); |
| }, |
| |
| cancelRipple: function() { |
| this.$.ink.upAction(); |
| } |
| |
| }); |
| |
| </script> |
| </polymer-element> |
| <polymer-element name="paper-tabs" extends="core-selector" attributes="noink nobar noslide scrollable hideScrollButton alignBottom" role="tablist" horizontal="" center="" layout="" assetpath="../bower_components/paper-tabs/"> |
| <template> |
| |
| <style>/* |
| Copyright (c) 2014 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 |
| */ |
| |
| :host { |
| display: block; |
| font-size: 14px; |
| font-weight: 500; |
| height: 48px; |
| overflow: hidden; |
| -webkit-user-select: none; |
| -moz-user-select: none; |
| -ms-user-select: none; |
| user-select: none; |
| -webkit-tap-highlight-color: rgba(0,0,0,0); |
| } |
| |
| #tabsContainer { |
| position: relative; |
| height: 100%; |
| white-space: nowrap; |
| overflow: hidden; |
| } |
| |
| #tabsContent { |
| height: 100%; |
| } |
| |
| #tabsContainer.scrollable > #tabsContent { |
| position: absolute; |
| white-space: nowrap; |
| } |
| |
| .scroll-button { |
| width: 40px; |
| padding: 0 12px; |
| } |
| |
| .scroll-button > paper-icon-button { |
| transition: opacity 0.18s; |
| } |
| |
| .scroll-button > .hidden { |
| opacity: 0; |
| } |
| |
| #selectionBar { |
| position: absolute; |
| height: 2px; |
| bottom: 0; |
| left: 0; |
| width: 0; |
| background-color: #ffff8d; |
| transition: width, left; |
| } |
| |
| #selectionBar.alignBottom { |
| top: 0; |
| bottom: auto; |
| } |
| |
| #selectionBar.expand { |
| transition-duration: 0.15s; |
| transition-timing-function: cubic-bezier(0.4, 0.0, 1, 1); |
| } |
| |
| #selectionBar.contract { |
| transition-duration: 0.18s; |
| transition-timing-function: cubic-bezier(0.0, 0.0, 0.2, 1); |
| } |
| |
| polyfill-next-selector { content: '#tabsContent > *:not(#selectionBar)'; } |
| ::content > * { |
| height: 100%; |
| } |
| </style> |
| |
| <div class="scroll-button" hidden?="{{!scrollable || hideScrollButton}}"> |
| <paper-icon-button icon="chevron-left" class="{{ {hidden: leftHidden} | tokenList }}" on-down="{{holdLeft}}" on-up="{{releaseHold}}"></paper-icon-button> |
| </div> |
| |
| <div id="tabsContainer" class="{{ {scrollable: scrollable} | tokenList }}" flex="" on-scroll="{{scroll}}" on-trackstart="{{trackStart}}"> |
| |
| <div id="tabsContent" horizontal="" layout?="{{!scrollable}}"> |
| <shadow></shadow> |
| <div id="selectionBar" hidden?="{{nobar}}" class="{{ {alignBottom: alignBottom} | tokenList }}" on-transitionend="{{barTransitionEnd}}"></div> |
| </div> |
| |
| </div> |
| |
| <div class="scroll-button" hidden?="{{!scrollable || hideScrollButton}}"> |
| <paper-icon-button icon="chevron-right" class="{{ {hidden: rightHidden} | tokenList }}" on-down="{{holdRight}}" on-up="{{releaseHold}}"></paper-icon-button> |
| </div> |
| |
| </template> |
| <script> |
| |
| Polymer('paper-tabs',Polymer.mixin({ |
| |
| /** |
| * If true, ink ripple effect is disabled. |
| * |
| * @attribute noink |
| * @type boolean |
| * @default false |
| */ |
| noink: false, |
| |
| /** |
| * If true, the bottom bar to indicate the selected tab will not be shown. |
| * |
| * @attribute nobar |
| * @type boolean |
| * @default false |
| */ |
| nobar: false, |
| |
| /** |
| * If true, the slide effect for the bottom bar is disabled. |
| * |
| * @attribute noslide |
| * @type boolean |
| * @default false |
| */ |
| noslide: false, |
| |
| /** |
| * If true, tabs are scrollable and the tab width is based on the label width. |
| * |
| * @attribute scrollable |
| * @type boolean |
| * @default false |
| */ |
| scrollable: false, |
| |
| /** |
| * If true, dragging on the tabs to scroll is disabled. |
| * |
| * @attribute disableDrag |
| * @type boolean |
| * @default false |
| */ |
| disableDrag: false, |
| |
| /** |
| * If true, scroll buttons (left/right arrow) will be hidden for scrollable tabs. |
| * |
| * @attribute hideScrollButton |
| * @type boolean |
| * @default false |
| */ |
| hideScrollButton: false, |
| |
| /** |
| * If true, the tabs are aligned to bottom (the selection bar appears at the top). |
| * |
| @attribute alignBottom |
| @type boolean |
| @default false |
| */ |
| alignBottom: false, |
| |
| eventDelegates: { |
| 'core-resize': 'resizeHandler' |
| }, |
| |
| activateEvent: 'tap', |
| |
| step: 10, |
| |
| holdDelay: 10, |
| |
| ready: function() { |
| this.super(); |
| this._trackxHandler = this.trackx.bind(this); |
| Polymer.addEventListener(this.$.tabsContainer, 'trackx', this._trackxHandler); |
| this._tabsObserver = new MutationObserver(this.updateBar.bind(this)); |
| }, |
| |
| domReady: function() { |
| this.async('resizeHandler'); |
| this._tabsObserver.observe(this, {childList: true, subtree: true, characterData: true}); |
| }, |
| |
| attached: function() { |
| this.resizableAttachedHandler(); |
| }, |
| |
| detached: function() { |
| Polymer.removeEventListener(this.$.tabsContainer, 'trackx', this._trackxHandler); |
| this._tabsObserver.disconnect(); |
| this.resizableDetachedHandler(); |
| }, |
| |
| trackStart: function(e) { |
| if (!this.scrollable || this.disableDrag) { |
| return; |
| } |
| var t = e.target; |
| if (t && t.cancelRipple) { |
| t.cancelRipple(); |
| } |
| this._startx = this.$.tabsContainer.scrollLeft; |
| e.preventTap(); |
| }, |
| |
| trackx: function(e) { |
| if (!this.scrollable || this.disableDrag) { |
| return; |
| } |
| this.$.tabsContainer.scrollLeft = this._startx - e.dx; |
| }, |
| |
| resizeHandler: function() { |
| this.scroll(); |
| this.updateBar(); |
| }, |
| |
| scroll: function() { |
| if (!this.scrollable) { |
| return; |
| } |
| var tc = this.$.tabsContainer; |
| var l = tc.scrollLeft; |
| this.leftHidden = l === 0; |
| this.rightHidden = l === Math.max(0, (tc.scrollWidth - tc.clientWidth)); |
| }, |
| |
| holdLeft: function() { |
| this.holdJob = setInterval(this.scrollToLeft.bind(this), this.holdDelay); |
| }, |
| |
| holdRight: function() { |
| this.holdJob = setInterval(this.scrollToRight.bind(this), this.holdDelay); |
| }, |
| |
| releaseHold: function() { |
| clearInterval(this.holdJob); |
| this.holdJob = null; |
| }, |
| |
| scrollToLeft: function() { |
| this.$.tabsContainer.scrollLeft -= this.step; |
| }, |
| |
| scrollToRight: function() { |
| this.$.tabsContainer.scrollLeft += this.step; |
| }, |
| |
| /** |
| * Invoke this to update the size and position of the bottom bar. Usually |
| * you only need to call this if the `paper-tabs` is initially hidden and |
| * later becomes visible. |
| * |
| * @method updateBar |
| */ |
| updateBar: function() { |
| this.async('selectedItemChanged'); |
| }, |
| |
| selectedItemChanged: function(old) { |
| var oldIndex = this.selectedIndex; |
| this.super(arguments); |
| var s = this.$.selectionBar.style; |
| |
| if (!this.selectedItem) { |
| s.width = 0; |
| s.left = 0; |
| return; |
| } |
| |
| var r = this.$.tabsContent.getBoundingClientRect(); |
| this._w = r.width; |
| this._l = r.left; |
| |
| r = this.selectedItem.getBoundingClientRect(); |
| this._sw = r.width; |
| this._sl = r.left; |
| this._sOffsetLeft = this._sl - this._l; |
| |
| if (this.noslide || old == null) { |
| this.positionBarForSelected(); |
| return; |
| } |
| |
| var oldRect = old.getBoundingClientRect(); |
| |
| var m = 5; |
| this.$.selectionBar.classList.add('expand'); |
| if (oldIndex < this.selectedIndex) { |
| s.width = this.calcPercent(this._sl + this._sw - oldRect.left) - m + '%'; |
| this._transitionCounter = 1; |
| } else { |
| s.width = this.calcPercent(oldRect.left + oldRect.width - this._sl) - m + '%'; |
| s.left = this.calcPercent(this._sOffsetLeft) + m + '%'; |
| this._transitionCounter = 2; |
| } |
| if (this.scrollable) { |
| this.scrollToSelectedIfNeeded(); |
| } |
| }, |
| |
| scrollToSelectedIfNeeded: function() { |
| var scrollLeft = this.$.tabsContainer.scrollLeft; |
| // scroll to selected if needed |
| if (this._sOffsetLeft + this._sw < scrollLeft || |
| this._sOffsetLeft - scrollLeft > this.$.tabsContainer.offsetWidth) { |
| this.$.tabsContainer.scrollLeft = this._sOffsetLeft; |
| } |
| }, |
| |
| positionBarForSelected: function() { |
| var s = this.$.selectionBar.style; |
| s.width = this.calcPercent(this._sw) + '%'; |
| s.left = this.calcPercent(this._sOffsetLeft) + '%'; |
| }, |
| |
| calcPercent: function(w) { |
| return 100 * w / this._w; |
| }, |
| |
| barTransitionEnd: function(e) { |
| this._transitionCounter--; |
| var cl = this.$.selectionBar.classList; |
| if (cl.contains('expand') && !this._transitionCounter) { |
| cl.remove('expand'); |
| cl.add('contract'); |
| this.positionBarForSelected(); |
| } else if (cl.contains('contract')) { |
| cl.remove('contract'); |
| } |
| } |
| |
| }, Polymer.CoreResizable)); |
| |
| </script> |
| </polymer-element> |
| |
| <polymer-element name="paper-toast" attributes="text duration opened responsiveWidth swipeDisabled autoCloseDisabled" role="status" assetpath="../bower_components/paper-toast/"> |
| |
| <template> |
| |
| <style>/* |
| Copyright (c) 2014 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 |
| */ |
| |
| :host { |
| display: inline-block; |
| background: #323232; |
| color: #f1f1f1; |
| min-height: 48px; |
| min-width: 288px; |
| padding: 16px 24px 12px; |
| box-sizing: border-box; |
| -moz-box-sizing: border-box; |
| box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26); |
| border-radius: 2px; |
| bottom: 12px; |
| left: 12px; |
| font-size: 14px; |
| cursor: default; |
| } |
| |
| :host(.capsule) { |
| border-radius: 24px; |
| } |
| |
| :host(.fit-bottom) { |
| bottom: 0; |
| left: 0; |
| width: 100%; |
| min-width: 0; |
| border-radius: 0; |
| } |
| |
| :host(.core-transition.dragging) { |
| transition: none; |
| } |
| |
| :host(.core-transition.fade-out-down), |
| :host(.core-transition.fade-out-up), |
| :host(.core-transition.fade-out-right), |
| :host(.core-transition.fade-out-left) { |
| opacity: 0; |
| transition: -webkit-transform 0.08s ease-in-out, opacity 0.08s ease-in-out; |
| transition: transform 0.08s ease-in-out, opacity 0.08s ease-in-out; |
| } |
| |
| :host(.core-transition.fade-out-down) { |
| -webkit-transform: translate(0, 100%); |
| transform: translate(0, 100%); |
| } |
| |
| :host(.core-transition.fade-out-up) { |
| -webkit-transform: translate(0, -100%); |
| transform: translate(0, -100%); |
| } |
| |
| :host(.core-transition.fade-out-right) { |
| -webkit-transform: translate(100%, 0); |
| transform: translate(100%, 0); |
| } |
| |
| :host(.core-transition.fade-out-left) { |
| -webkit-transform: translate(-100%, 0); |
| transform: translate(-100%, 0); |
| } |
| |
| .toast-container { |
| overflow: hidden; |
| } |
| |
| .toast-action { |
| padding-left: 24px; |
| cursor: pointer; |
| text-transform: uppercase; |
| } |
| </style> |
| |
| <core-overlay id="overlay" autofocusdisabled="" autoclosedisabled="{{autoCloseDisabled}}" opened="{{opened}}" target="{{}}" transition="core-transition-bottom"></core-overlay> |
| |
| <div class="toast-container" horizontal="" layout=""> |
| |
| <div class="toast-text" flex="">{{text}}</div> |
| |
| <div class="toast-text toast-action" on-tap="{{dismiss}}"> |
| <content></content> |
| </div> |
| |
| </div> |
| |
| <core-media-query query="max-width: {{responsiveWidth}}" querymatches="{{narrowMode}}"></core-media-query> |
| |
| </template> |
| <script> |
| |
| (function() { |
| |
| var currentToast; |
| |
| Polymer('paper-toast', { |
| |
| /** |
| * The text shows in a toast. |
| * |
| * @attribute text |
| * @type string |
| * @default '' |
| */ |
| text: '', |
| |
| /** |
| * The duration in milliseconds to show the toast. |
| * |
| * @attribute duration |
| * @type number |
| * @default 3000 |
| */ |
| duration: 3000, |
| |
| /** |
| * Set opened to true to show the toast and to false to hide it. |
| * |
| * @attribute opened |
| * @type boolean |
| * @default false |
| */ |
| opened: false, |
| |
| /** |
| * Min-width when the toast changes to narrow layout. In narrow layout, |
| * the toast fits at the bottom of the screen when opened. |
| * |
| * @attribute responsiveWidth |
| * @type string |
| * @default '480px' |
| */ |
| responsiveWidth: '480px', |
| |
| /** |
| * If true, the toast can't be swiped. |
| * |
| * @attribute swipeDisabled |
| * @type boolean |
| * @default false |
| */ |
| swipeDisabled: false, |
| |
| /** |
| * By default, the toast will close automatically if the user taps |
| * outside it or presses the escape key. Disable this behavior by setting |
| * the `autoCloseDisabled` property to true. |
| * |
| * @attribute autoCloseDisabled |
| * @type boolean |
| * @default false |
| */ |
| autoCloseDisabled: false, |
| |
| narrowMode: false, |
| |
| eventDelegates: { |
| trackstart: 'trackStart', |
| track: 'track', |
| trackend: 'trackEnd', |
| transitionend: 'transitionEnd' |
| }, |
| |
| narrowModeChanged: function() { |
| this.classList.toggle('fit-bottom', this.narrowMode); |
| if (this.opened) { |
| this.$.overlay.resizeHandler(); |
| } |
| }, |
| |
| openedChanged: function() { |
| if (this.opened) { |
| this.dismissJob = this.job(this.dismissJob, this.dismiss, this.duration); |
| } else { |
| this.dismissJob && this.dismissJob.stop(); |
| this.dismiss(); |
| } |
| }, |
| |
| /** |
| * Toggle the opened state of the toast. |
| * @method toggle |
| */ |
| toggle: function() { |
| this.opened = !this.opened; |
| }, |
| |
| /** |
| * Show the toast for the specified duration |
| * @method show |
| */ |
| show: function() { |
| if (currentToast) { |
| currentToast.dismiss(); |
| } |
| currentToast = this; |
| this.opened = true; |
| }, |
| |
| /** |
| * Dismiss the toast and hide it. |
| * @method dismiss |
| */ |
| dismiss: function() { |
| if (this.dragging) { |
| this.shouldDismiss = true; |
| } else { |
| this.opened = false; |
| if (currentToast === this) { |
| currentToast = null; |
| } |
| } |
| }, |
| |
| trackStart: function(e) { |
| if (!this.swipeDisabled) { |
| e.preventTap(); |
| this.vertical = e.yDirection; |
| this.w = this.offsetWidth; |
| this.h = this.offsetHeight; |
| this.dragging = true; |
| this.classList.add('dragging'); |
| } |
| }, |
| |
| track: function(e) { |
| if (this.dragging) { |
| var s = this.style; |
| if (this.vertical) { |
| var y = e.dy; |
| s.opacity = (this.h - Math.abs(y)) / this.h; |
| s.transform = s.webkitTransform = 'translate3d(0, ' + y + 'px, 0)'; |
| } else { |
| var x = e.dx; |
| s.opacity = (this.w - Math.abs(x)) / this.w; |
| s.transform = s.webkitTransform = 'translate3d(' + x + 'px, 0, 0)'; |
| } |
| } |
| }, |
| |
| trackEnd: function(e) { |
| if (this.dragging) { |
| this.classList.remove('dragging'); |
| this.style.opacity = ''; |
| this.style.transform = this.style.webkitTransform = ''; |
| var cl = this.classList; |
| if (this.vertical) { |
| cl.toggle('fade-out-down', e.yDirection === 1 && e.dy > 0); |
| cl.toggle('fade-out-up', e.yDirection === -1 && e.dy < 0); |
| } else { |
| cl.toggle('fade-out-right', e.xDirection === 1 && e.dx > 0); |
| cl.toggle('fade-out-left', e.xDirection === -1 && e.dx < 0); |
| } |
| this.dragging = false; |
| } |
| }, |
| |
| transitionEnd: function() { |
| var cl = this.classList; |
| if (cl.contains('fade-out-right') || cl.contains('fade-out-left') || |
| cl.contains('fade-out-down') || cl.contains('fade-out-up')) { |
| this.dismiss(); |
| cl.remove('fade-out-right', 'fade-out-left', |
| 'fade-out-down', 'fade-out-up'); |
| } else if (this.shouldDismiss) { |
| this.dismiss(); |
| } |
| this.shouldDismiss = false; |
| } |
| |
| }); |
| |
| })(); |
| |
| </script> |
| </polymer-element> |
| |
| <polymer-element name="paper-fab" extends="paper-button-base" attributes="src icon mini" role="button" assetpath="../bower_components/paper-fab/"> |
| |
| <template> |
| |
| <style> |
| :host { |
| display: inline-block; |
| position: relative; |
| outline: none; |
| -webkit-user-select: none; |
| user-select: none; |
| cursor: pointer; |
| z-index: 0; |
| |
| box-sizing: border-box; |
| width: 56px; |
| height: 56px; |
| background: #d23f31; |
| color: #fff; |
| border-radius: 50%; |
| padding: 16px; |
| } |
| |
| :host([mini]) { |
| width: 40px; |
| height: 40px; |
| padding: 8px; |
| } |
| |
| :host([disabled]) { |
| color: #c9c9c9; |
| pointer-events: none; |
| cursor: auto; |
| } |
| |
| #ripple { |
| pointer-events: none; |
| z-index: -1; |
| } |
| |
| #shadow { |
| border-radius: inherit; |
| pointer-events: none; |
| } |
| |
| #icon { |
| display: block; |
| pointer-events: none; |
| } |
| </style> |
| |
| <template if="{{raised}}"> |
| <paper-shadow id="shadow" fit="" animated=""></paper-shadow> |
| </template> |
| |
| <!-- to position to ripple behind the icon --> |
| <core-icon relative="" id="icon" src="{{src}}" icon="{{icon}}"></core-icon> |
| |
| </template> |
| |
| <script> |
| Polymer('paper-fab',{ |
| |
| publish: { |
| |
| /** |
| * The URL of an image for the icon. If the src property is specified, |
| * the icon property should not be. |
| * |
| * @attribute src |
| * @type string |
| * @default '' |
| */ |
| src: '', |
| |
| /** |
| * Specifies the icon name or index in the set of icons available in |
| * the icon's icon set. If the icon property is specified, |
| * the src property should not be. |
| * |
| * @attribute icon |
| * @type string |
| * @default '' |
| */ |
| icon: '', |
| |
| /** |
| * Set this to true to style this is a "mini" FAB. |
| * |
| * @attribute mini |
| * @type boolean |
| * @default false |
| */ |
| mini: false, |
| |
| raised: true, |
| recenteringTouch: true, |
| fill: false |
| |
| }, |
| |
| iconChanged: function(oldIcon) { |
| var label = this.getAttribute('aria-label'); |
| if (!label || label === oldIcon) { |
| this.setAttribute('aria-label', this.icon); |
| } |
| } |
| |
| }); |
| |
| </script> |
| </polymer-element> |
| |
| <polymer-element name="paper-autocomplete" extends="paper-input" attributes="maxItems caseSensitive delimiter allowWhitespace" assetpath="../src/lib/web-components/paper-autocomplete/"> |
| <template> |
| <style>/* Copyright 2015 The Vanadium Authors. All rights reserved. */ |
| /* Use of this source code is governed by a BSD-style */ |
| /* license that can be found in the LICENSE file. */ |
| |
| /* Style the children core-item nodes */ |
| ::content .highlighted { |
| background-color: #99ccff; |
| } |
| ::content .core-selected { |
| font-weight: normal; |
| } |
| /* Note: Rules were split to boost CSS specificity */ |
| ::content paper-item:hover, |
| ::content core-item:hover { |
| background-color: #6699cc; |
| } |
| |
| /* core-item looks better with side padding */ |
| ::content core-item { |
| padding-left: 0.5em; |
| padding-right: 0.5em; |
| } |
| /* Pick default text color, which affects the paper-ripple color */ |
| ::content core-item, ::content paper-item { |
| color: #003366; |
| overflow: hidden; |
| } |
| |
| #frame, #top-input { |
| width: inherit; |
| } |
| #bottom-suggest { |
| position: relative; |
| width: inherit; |
| } |
| #suggest-box { |
| max-height: inherit; |
| position: absolute; |
| top: 0px; |
| left: 0px; |
| z-index: 10000; |
| width: inherit; |
| |
| /* The following style may be overridden */ |
| background-color: #ffffff; |
| border: solid 1px #000000; |
| box-shadow: 5px 5px 2px #999999; |
| } |
| |
| .hidden { |
| display: none; |
| }</style> |
| <div id="frame"> |
| <div id="top-input"> |
| <shadow></shadow> |
| </div> |
| <div id="bottom-suggest"> |
| <core-selector id="suggest-box" class="suggest-box-style hidden" on-core-activate="{{selectAction}}"> |
| <content id="items" select="core-item,paper-item"></content> |
| </core-selector> |
| </div> |
| </div> |
| </template> |
| <script> |
| Polymer('paper-autocomplete',{ |
| /** |
| * Maximum number of items to show in the suggestion box. |
| * A null value indicates that there is no limit. |
| * |
| * @attribute: maxItems |
| * @type: integer | null |
| */ |
| maxItems: null, |
| |
| /** |
| * Flag that indicates the case-sensitivity of prefix-matching. |
| * |
| * @attribute: caseSensitive |
| * @type: boolean |
| */ |
| caseSensitive: false, |
| |
| /** |
| * If null, this element acts like a standard autocomplete unit. |
| * If given a value, then instead of replacing the full text string, |
| * this element will replace the substring after the last occurrence of |
| * the delimiter. In effect, making this like a list selector. |
| * |
| * Example: delimiter === '' (Append-only) |
| * When the text box has 'abcd', selecting 'ef' makes this 'abcdef' |
| * This choice of delimiter prevents suggestions from being filtered. |
| * |
| * Example: delimiter === ',' (CSV-style list) |
| * If the text box has 'red,orange,g', and 'green' is selected, |
| * then the text box will have 'red,orange,green'. |
| * |
| * Some delimiters work well with dynamic child node suggestions. |
| * |
| * Example: delimiter === '/' (Directory path) |
| * If the text box has 'drive/google/photos/' and 'May2012' is selected, |
| * then the text box will have 'drive/google/photos/May2012'. |
| * |
| * @attribute: delimiter |
| * @type: string | null |
| */ |
| delimiter: null, |
| |
| /** |
| * Set to true to tolerate whitespace when filtering suggestions. |
| * Whitespace will also be maintained when a suggestion is selected. |
| * Note: This attribute is only useful when there is a delimiter. |
| * |
| * Example: delimiter === ',' and allowWhitespace === true |
| * If the text box has 'red, orange, g' and 'green' is selected, |
| * then the text box will have 'red, orange, green'. |
| * |
| * @attribute: allowWhitespace |
| * @type: boolean |
| */ |
| allowWhitespace: false, |
| |
| /** |
| * Whether this element is currently being edited or not. Becomes true |
| * when the internal paper-input gains focus or receives input. |
| * |
| * @type: boolean |
| */ |
| editing: false, |
| |
| /** |
| * A special flag indicating that the suggestion box is going to close. |
| */ |
| closing: false, |
| |
| /** |
| * List of indexes of children that can be shown. The children are |
| * filtered by the current value. |
| * |
| * @type: Array<integer> |
| */ |
| showing: [], |
| |
| /** |
| * Tracks the highlight position in the showing array. The corresponding |
| * child node is highlighted with the 'highlighted' class. |
| * |
| * @type: int | null |
| */ |
| highlightIndex: null, |
| |
| /** |
| * Tracks the highlighted item's label. Used to update the highlight index |
| * when the user types new characters. |
| * |
| * @type: string | null |
| */ |
| highlightLabel: null, |
| |
| /** |
| * Called when this node is added to the DOM. Sets up event listeners |
| * through the shadow DOM to its internal paper-input. Additionally, |
| * watches for mutations in its light DOM children. |
| */ |
| ready: function() { |
| this.super(); |
| |
| // Add listener to the suggestBox to cancel the suggestion box from |
| // closing if the user starts to click on it. |
| var suggestBox = this.$['suggest-box']; |
| suggestBox.addEventListener( |
| 'mousedown', |
| this.cancelClosing.bind(this) |
| ); |
| |
| // Listen to inputs to determine editing status. |
| this.addEventListener('input', this.inputAction.bind(this)); |
| this.addEventListener('keydown', this.keydownAction.bind(this)); |
| this.addEventListener('focus', this.inputFocusAction.bind(this)); |
| this.addEventListener('blur', this.inputBlurAction.bind(this)); |
| |
| // Capture changes to children |
| this.watchMutation(); |
| }, |
| |
| /** |
| * Whenever the light DOM children change, then rerender this element. |
| */ |
| watchMutation: function() { |
| var change = function() { |
| this.filterSuggestions(); |
| // Note: onMutation unbinds after each use, so we need to rebind. |
| this.onMutation(this, change.bind(this)); |
| } |
| this.onMutation(this, change.bind(this)); |
| }, |
| |
| /** |
| * Set the editing flag to true upon gaining focus. |
| */ |
| inputFocusAction: function(e) { |
| this.super(e); |
| if (!this.closing) { |
| this.editing = true; |
| } |
| this.closing = false; |
| this.filterSuggestions(); |
| }, |
| |
| /** |
| * When blurring, potentially set editing to false. |
| * Has a delay before the suggestion box is closed in case the user starts |
| * to click on the suggestion box. |
| */ |
| inputBlurAction: function(e) { |
| this.super(e); |
| |
| this.closing = true; |
| this.async(function() { |
| if (this.closing) { |
| this.showSuggestions(false); |
| this.editing = false; |
| } |
| }, null, 100); |
| }, |
| |
| /** |
| * If the mouse is pressed down on the suggestion box, set the closing |
| * flag to false. |
| */ |
| cancelClosing: function(e) { |
| this.async(function() { |
| if (this.closing) { |
| this.closing = false; |
| } |
| }, null, 0); |
| }, |
| |
| /** |
| * On the hidden input's 'input' event, the fact that data changed means |
| * that the user is editing. Re-render the suggestions. |
| */ |
| inputAction: function(e) { |
| this.closing = false; |
| this.editing = true; |
| this.filterSuggestions(); |
| }, |
| |
| /** |
| * Filters the potential list of suggestions drawn from child node labels. |
| * Use a prefixMatch to decide which child nodes to show/hide. |
| * Finally, show the suggestion box if the conditions are right. |
| */ |
| filterSuggestions: function() { |
| var showing = []; |
| var childNodes = this.$.items.getDistributedNodes(); |
| for (var i = 0; i < childNodes.length; i++) { |
| var item = childNodes[i]; |
| |
| // Show nodes with a prefix match, as long |
| // as the maxItems limit has not been reached. |
| if ((this.maxItems === null || showing.length < this.maxItems) && |
| this.prefixMatch(this.getItemLabel(item))) { |
| |
| showing.push(i); |
| item.style.display = ''; |
| } else { |
| item.style.display = 'none'; |
| } |
| } |
| this.showing = showing; |
| |
| this.highlightSeekIndex(); |
| this.showSuggestions(this.shouldShowSuggestions()); |
| }, |
| |
| /** |
| * Update the highlight index, using the highlight label and the shown |
| * items. If there are no matches, reset the highlight variables. |
| * Note: There may be false updates if the suggestion list contains |
| * >1 of the same label. |
| */ |
| highlightSeekIndex: function() { |
| var found = false; |
| var childNodes = this.$.items.getDistributedNodes(); |
| for (var i = 0; i < this.showing.length; i++) { |
| var itemIndex = this.showing[i]; |
| var item = childNodes[itemIndex]; |
| |
| if (!found && this.getItemLabel(item) === this.highlightLabel) { |
| // There was a match; the new highlightIndex is the showing index. |
| this.highlightIndex = i; |
| found = true; |
| item.classList.toggle('highlighted', true); |
| } else { |
| // This isn't a match, so reset highlighting, just in case. |
| item.classList.toggle('highlighted', false); |
| } |
| } |
| |
| // If not found, then nothing should be highlighted. |
| // Note: Cannot use highlightReset since the item may not exist at the |
| // current highlightIndex. |
| if (!found) { |
| this.highlightIndex = null; |
| this.highlightLabel = null; |
| } |
| }, |
| |
| /** |
| * Depending on the caseSensitive attribute, returns whether or not the |
| * given word has a prefix match with the current value of this element. |
| */ |
| prefixMatch: function(word) { |
| var curValue = this.getInputValueSuffix(); |
| if (!this.caseSensitive) { |
| return word.toLowerCase().indexOf(curValue.toLowerCase()) === 0; |
| } |
| return word.indexOf(curValue) === 0; |
| }, |
| |
| /** |
| * Get the suffix of the current input value. If there is no delimiter, |
| * then the full input value is used instead. |
| */ |
| getInputValueSuffix: function() { |
| var value = this.value || ''; |
| if (this.delimiter === '') { |
| return ''; |
| } else if (!this.delimiter) { |
| return value; |
| } |
| |
| // Find the suffix. |
| var parts = value.split(this.delimiter); |
| var suffix = parts[parts.length - 1]; |
| |
| // Handle excess whitespace. |
| if (this.allowWhitespace) { |
| return suffix.trimLeft(); |
| } |
| return suffix; |
| }, |
| |
| /** |
| * Gets the label out of the item for prefix-matching and replacement. |
| * Note: paper-item and core-item mismatch; label is not available on the |
| * former anymore. |
| */ |
| getItemLabel: function(item) { |
| return item.textContent || item.label; |
| }, |
| |
| /** |
| * Determine whether or not the suggestBox should be shown. |
| * Show if this node is being edited and there are valid suggestions. |
| */ |
| shouldShowSuggestions: function() { |
| return this.editing && this.showing.length > 0; |
| }, |
| |
| /** |
| * The suggestion box is shown/hidden. If hidden, then additional internal |
| * values are reset. |
| */ |
| showSuggestions: function(shouldShow) { |
| this.$['suggest-box'].classList.toggle('hidden', !shouldShow); |
| if (!shouldShow) { |
| this.highlightReset(); |
| this.closing = false; |
| } |
| }, |
| |
| /** |
| * Process the 'core-activate' event when a suggestion was tapped. |
| */ |
| selectAction: function(e) { |
| this.selectItem(e, e.detail.item); |
| }, |
| |
| /** |
| * When an item in the suggestion list is clicked or 'enter' is pressed |
| * while it is being highlighted, then replace this node's value. |
| * |
| * This node is no longer being edited, so re-render will hide it. |
| * Note: Fires this element's 'input' and 'change' events. |
| * Special: Click loses the element's focus, but pressing enter does not. |
| */ |
| selectItem: function(e, item) { |
| this.highlightReset(); |
| |
| this.replaceValue(this.getItemLabel(item)); |
| |
| this.fireInput(); |
| this.changeAction(e); |
| |
| this.showSuggestions(false); |
| this.editing = false; |
| }, |
| |
| /** |
| * Replace the current value and inputValue of this element. |
| * If there is a delimiter, replace only the suffix. |
| */ |
| replaceValue: function(newValue) { |
| if (this.delimiter === '') { |
| this.value += newValue; |
| } else if (!this.delimiter) { |
| this.value = newValue; |
| } else { |
| // Find the suffix. |
| var parts = this.value.split(this.delimiter); |
| var suffix = parts[parts.length - 1]; |
| |
| // Compute the replacement, taking into account whitespace. |
| var keepLength = 0; |
| if (this.allowWhitespace) { |
| keepLength = suffix.length - suffix.trimLeft().length |
| } |
| parts[parts.length - 1] = suffix.substring(0, keepLength) + newValue; |
| |
| // Replace the input value. |
| this.value = parts.join(this.delimiter); |
| } |
| }, |
| |
| /** |
| * Fire the input event. (The invocation matches core-input's.) |
| */ |
| fireInput: function() { |
| this.fire('input', null, this); |
| }, |
| |
| /** |
| * Fire the delete-item event with the corresponding child item. |
| */ |
| fireDeleteItem: function(item) { |
| this.fire('delete-item', null, item); |
| }, |
| |
| /** |
| * Obtain the child item that is currently highlighted. |
| */ |
| getHighlightedItem: function() { |
| var selected = this.showing[this.highlightIndex]; |
| return this.$.items.getDistributedNodes()[selected]; |
| }, |
| |
| /** |
| * Remove highlight off the current item and stop tracking it. |
| */ |
| highlightReset: function() { |
| this.highlightItem(false); |
| this.highlightIndex = null; |
| }, |
| |
| /** |
| * Add/remove the highlighted class off the currently highlighted item. |
| */ |
| highlightItem: function(shouldHighlight) { |
| if (this.highlightIndex !== null) { |
| var item = this.getHighlightedItem(); |
| item.classList.toggle('highlighted', shouldHighlight); |
| this.highlightLabel = this.getItemLabel(item); |
| } else { |
| this.highlightLabel = null; |
| } |
| }, |
| |
| /** |
| * Pressing up and down change the currently highlighted item. |
| * Pressing enter will select that item. |
| * Pressing escape will close the suggestBox. |
| * Pressing any other key will reset highlight. |
| */ |
| keydownAction: function(e) { |
| var KEY_DELETE = 46; |
| var KEY_UP = 38; |
| var KEY_DOWN = 40; |
| var KEY_ENTER = 13; |
| var KEY_ESC = 27; |
| |
| // The suggest box is not open. |
| if (!this.shouldShowSuggestions()) { |
| // If up or down was hit, potentially open the suggest box. |
| if (e.keyCode === KEY_UP || e.keyCode === KEY_DOWN) { |
| this.editing = true; |
| this.showSuggestions(this.shouldShowSuggestions()); |
| } |
| return; |
| } |
| |
| // The suggest box is open. Handle the keypresses accordingly. |
| switch(e.keyCode) { |
| case KEY_DELETE: |
| // Shift+Delete => fire a 'delete-item' event |
| if (e.shiftKey === true && this.highlightIndex !== null) { |
| this.fireDeleteItem(this.getHighlightedItem()); |
| } |
| break; |
| case KEY_UP: // Highlight the item above the current one. |
| e.preventDefault(); |
| |
| // unhighlight, change selection, highlight |
| this.highlightItem(false); |
| if (this.highlightIndex === null || this.highlightIndex === 0) { |
| this.highlightIndex = this.showing.length - 1; |
| } else { |
| this.highlightIndex--; |
| } |
| this.highlightItem(true); |
| break; |
| case KEY_DOWN: // Highlight the item below the current one. |
| e.preventDefault(); |
| |
| // unhighlight, change selection, highlight |
| this.highlightItem(false); |
| if (this.highlightIndex === null || |
| this.highlightIndex === this.showing.length - 1) { |
| |
| this.highlightIndex = 0; |
| } else { |
| this.highlightIndex++; |
| } |
| this.highlightItem(true); |
| break; |
| case KEY_ENTER: // Activate the highlighted node, if any. |
| if (this.highlightIndex !== null) { |
| e.preventDefault(); // do not commit a change |
| |
| this.selectItem(e, this.getHighlightedItem()); |
| } else { |
| this.showSuggestions(false); |
| this.editing = false; |
| } |
| break; |
| case KEY_ESC: // Hide the suggestions and reset the current highlight. |
| this.showSuggestions(false); |
| this.editing = false; |
| break; |
| } |
| } |
| }); |
| </script> |
| </polymer-element> |
| <polymer-element name="tree-node" attributes="label icon emptytext" assetpath="../src/lib/web-components/tree-node/"> |
| <template> |
| <style>/* Copyright 2015 The Vanadium Authors. All rights reserved. */ |
| /* Use of this source code is governed by a BSD-style */ |
| /* license that can be found in the LICENSE file. */ |
| |
| .indent { |
| padding: 0; |
| margin: 3px 0 0 20px; |
| white-space: nowrap; |
| } |
| #expander, .item, .itemtext, .itemicon { |
| display: inline-block; |
| vertical-align: middle; |
| } |
| #expander.notParent { |
| visibility: hidden; |
| /* example of alternative treatment of inactive widget |
| opacity: 0.4; |
| color: grey; */ |
| } |
| .item { |
| box-sizing: border-box; |
| color: rgba(0, 0, 0, 0.870588); |
| box-sizing: border-box; |
| background-color: #fafafa; |
| border-radius: 3px; |
| overflow: hidden; |
| box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.2); |
| border: solid 1px rgba(0, 0, 0, 0.12); |
| padding: 1px 5px 2px 3px; |
| cursor: pointer; |
| } |
| .item:hover .itemcontent, .item:focus .itemcontent, |
| .expander-anchor:hover, .expander-anchor:focus { |
| opacity: 0.7; |
| } |
| .item.highlight { |
| background-color: rgb(255, 171, 64); |
| color: rgb(250, 250, 250); |
| } |
| .itemicon { |
| padding-right: 3px; |
| } |
| paper-spinner { |
| pointer-events: none; |
| position: absolute; |
| width: 24px; |
| height: 24px; |
| margin-left: -28px; |
| margin-top: 4px; |
| } |
| |
| .empty { |
| font-size: 0.9em; |
| color: rgba(0,0,0, 0.56); |
| margin-left: 50px; |
| padding: 3px 0 3px 0; |
| } |
| |
| .expander-anchor, .item { |
| display: inline-block; |
| text-decoration: none; |
| color: initial; |
| outline: none; |
| } |
| |
| /* how to add content to item on highlight |
| span.item.highlight::after { |
| content: " \0025B6"; |
| } */ |
| </style> |
| <div class="indent" aria-expanded="{{open}}" aria-owns="{{isParent && open?'children':''}}"> |
| <div class="row"> |
| <template if="{{showEmptyIcon}}"> |
| <core-icon id="expander" icon="remove" alt="{{emptytext}}" title="{{emptytext}}"> |
| </core-icon> |
| </template> |
| <template if="{{!showEmptyIcon}}"> |
| <a class="expander-anchor" href="javascript:void(0);" on-click="{{openme}}" tabindex="{{isParent?'0':'-1'}}"> |
| <core-icon id="expander" class="{{isParent?'':'notParent'}}" icon="{{open?'expand-more':'chevron-right'}}" alt="{{open?'Collapse':'Expand'}}" title="{{open?'Collapse':'Expand'}}"> |
| </core-icon> |
| </a> |
| </template> |
| <paper-spinner active="{{loading}}" aria-label="loading"> |
| </paper-spinner> |
| <a href="javascript:void(0);" on-click="{{activate}}" role="treeitem" aria-selected="{{highlight}}" class="item{{highlight?' highlight':''}}" title="{{itemtitle}}"> |
| <span class="itemcontent"> |
| <template if="{{icon}}"> |
| <core-icon class="itemicon" icon="{{icon}}" alt="{{iconalt}}"> |
| </core-icon> |
| </template> |
| <span class="itemtext"> |
| {{label}} |
| </span> |
| </span> |
| </a> |
| </div> |
| <template if="{{isParent && open}}"> |
| <div id="children" role="group"> |
| <content></content> |
| </div> |
| </template> |
| </div> |
| </template> |
| |
| <script> |
| Polymer('tree-node', { |
| publish: { |
| emptytext: 'No children', |
| iconalt: 'item icon', |
| itemtitle: 'item', |
| open: false, |
| highlight: false, |
| loading: false, |
| isExpandable: false |
| }, |
| isParent: false, |
| showEmptyIcon: false, |
| ready: function() { |
| this.isExpandableChanged(); |
| this.onMutation(this, this.childrenUpdated); |
| }, |
| childrenUpdated: function(observer, mutations) { |
| this.isExpandableChanged(); |
| this.onMutation(this, this.childrenUpdated); |
| this.updateShowEmptyIcon(); |
| }, |
| isExpandableChanged: function() { |
| this.isParent = !!this.firstElementChild || this.isExpandable; |
| }, |
| openChanged: function() { |
| this.updateShowEmptyIcon(); |
| }, |
| loadingChanged: function() { |
| this.updateShowEmptyIcon(); |
| }, |
| updateShowEmptyIcon: function() { |
| var me = this; |
| setTimeout(function() { |
| me.showEmptyIcon = ( |
| !me.firstElementChild && |
| !me.loading && |
| me.isExpandable && |
| me.open |
| ); |
| }, 30); // delay showing the empty icon by a bit so it is less jarring |
| }, |
| openme: function(e, d, sender) { |
| this.open = !this.open; |
| this.fire('openchange', { node: this }); |
| }, |
| activate: function(event, detail, sender) { |
| this.fire('activate', { node: this, |
| shiftKey: event.shiftKey, altKey: event.altKey, |
| ctrlKey: event.ctrlKey, metaKey: event.metaKey }); |
| } |
| }); |
| </script> |
| </polymer-element> |
| |
| <core-iconset-svg id="vanadium"> |
| <svg> |
| <defs> |
| <g id="service" transform="scale(0.23)"> |
| <path d="M50,0C22.385,0,0,22.385,0,50c0,27.615,22.385,50,50,50c27.614,0,50-22.385,50-50C100,22.385,77.615,0,50,0z M50,91.789 |
| C26.958,91.789,8.212,73.042,8.212,50C8.212,26.958,26.958,8.212,50,8.212c23.042,0,41.788,18.747,41.788,41.789 |
| C91.788,73.042,73.042,91.789,50,91.789z"></path> |
| </g> |
| <g id="mountpoint" transform="scale(1.3)"> |
| <path d="M16,2v14h-14v-14h14m0,-2h-14c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2v-14c0,-1.1 -0.9,-2 -2,-2z"></path> |
| </g> |
| |
| <g id="both" transform="scale(0.19)"> |
| <path d="M0,0H120V120H0Z M60,10c-27.615,0 -50,22.385 -50,50c0,27.615 22.385,50 50,50c27.614,0 50,-22.385 50,-50c0,-27.615 -22.385,-50 -50,-50z"></path> |
| </g> |
| </defs> |
| </svg> |
| </core-iconset-svg> |
| |
| <polymer-element name="nsb-plugins-log-viewer" assetpath="../src/item-plugins/system/log-viewer/"> |
| <template> |
| <h3>Logs for {{vname}}</h3> |
| </template> |
| </polymer-element></div></body></html> |