diff --git a/.gitignore b/.gitignore index af2fb1769b10821f92b984d20656498f3f4883b9..ec8fbb301aea718c2edff9ede8c0a3dbc61d5035 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ config/* !config/home-assistant.conf.default homeassistant/components/http/www_static/polymer/bower_components/* -homeassistant/components/http/www_static/polymer/build.htm # There is not a better solution afaik.. !config/custom_components diff --git a/README.md b/README.md index f97bbd462fd6288e243178373f1412dd7dc2d3ce..b0fc49566e57b6c2b31f693bfb19f40420844f6f 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ The system is built modular so support for other devices or actions can be imple ## Installation instructions / Quick-start guide -Running Home Assistant requires that node.js and python3 are installed. (Node.js is required for installing dependencies and concatenating the frontend) +Running Home Assistant requires that python3 is installed. Run the following code to get up and running with the minimum setup: @@ -36,8 +36,6 @@ Run the following code to get up and running with the minimum setup: git clone --recursive https://github.com/balloob/home-assistant.git cd home-assistant pip3 install -r requirements.txt -npm install bower vulcanize -./build_polymer python3 start.py ``` diff --git a/build_polymer b/build_polymer index 42eeae21143b6f45f2b3fd534f30f13d5bbe36e1..3c865987c6a1501fa1698ebb99641725e3309480 100755 --- a/build_polymer +++ b/build_polymer @@ -1,3 +1,5 @@ +# To build the frontend, you need node, bower and vulcanize +# npm install -g bower vulcanize cd homeassistant/components/http/www_static/polymer bower install -vulcanize -o build.htm home-assistant-main.html +vulcanize -o ../frontend.html home-assistant-main.html diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 50741e7f3b6f1f2a8b52f4f596f82a253bb3bcf0..7c951be02298787a61640b7966811b7f2e4cbb6a 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -119,6 +119,7 @@ DOMAIN_ICONS = { CONF_API_PASSWORD = "api_password" CONF_SERVER_HOST = "server_host" CONF_SERVER_PORT = "server_port" +CONF_DEVELOPMENT = "development" def _get_domain_icon(domain): @@ -141,8 +142,11 @@ def setup(hass, config): server_port = config[DOMAIN].get(CONF_SERVER_PORT, rem.SERVER_PORT) + development = config[DOMAIN].get(CONF_DEVELOPMENT, "") == "1" + server = HomeAssistantHTTPServer((server_host, server_port), - RequestHandler, hass, api_password) + RequestHandler, hass, api_password, + development) hass.listen_once_event( ha.EVENT_HOMEASSISTANT_START, @@ -161,12 +165,13 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer): """ Handle HTTP requests in a threaded fashion. """ def __init__(self, server_address, RequestHandlerClass, - hass, api_password): + hass, api_password, development=False): super().__init__(server_address, RequestHandlerClass) + self.server_address = server_address self.hass = hass self.api_password = api_password - self.server_address = server_address + self.development = development self.logger = logging.getLogger(__name__) # To store flash messages between sessions @@ -175,6 +180,10 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer): # We will lazy init this one if needed self.event_forwarder = None + if development: + self.logger.info("running frontend in development mode") + + def start(self): """ Starts the server. """ self.logger.info( @@ -378,8 +387,10 @@ class RequestHandler(BaseHTTPRequestHandler): self.send_header('Content-type', 'text/html; charset=utf-8') self.end_headers() - # TODO let's be able to switch this based on env - app_url = "build.htm" if False else "home-assistant-main.html" + if self.server.development: + app_url = "polymer/home-assistant-main.html" + else: + app_url = "frontend.html" write(("<html>" "<head><title>Home Assistant</title>" @@ -390,7 +401,7 @@ class RequestHandler(BaseHTTPRequestHandler): "<script" " src='/static/polymer/bower_components/" "platform/platform.js'></script>" - "<link rel='import' href='/static/polymer/{}' />" + "<link rel='import' href='/static/{}' />" "<meta name='viewport' content='width=device-width, " " user-scalable=no, initial-scale=1.0, " " minimum-scale=1.0, maximum-scale=1.0' />" diff --git a/homeassistant/components/http/www_static/frontend.html b/homeassistant/components/http/www_static/frontend.html new file mode 100644 index 0000000000000000000000000000000000000000..271466d3823e8b2726a0f55ecf2de994b4233c3a --- /dev/null +++ b/homeassistant/components/http/www_static/frontend.html @@ -0,0 +1,9976 @@ +<div hidden><!-- +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 +--> +<link href="//fonts.googleapis.com/css?family=RobotoDraft:regular,bold,italic,thin,light,bolditalic,black,medium&lang=en" rel="stylesheet" type="text/css"> +</div> +<div hidden><!-- +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 +--> + +<!-- +`core-header-panel` contains a header section and a content panel section. + +__Important:__ The `core-header-panel` will not display if its parent does not have a height. + +Using [layout attributes](http://www.polymer-project.org/docs/polymer/layout-attrs.html), you can easily make the `core-header-panel` fill the screen + + <body fullbleed layout vertical> + <core-header-panel flex> + <core-toolbar> + <div>Hello World!</div> + </core-toolbar> + </core-header-panel> + </body> + +or, if you would prefer to do it in CSS, just give `html`, `body`, and `core-header-panel` a height of 100%: + + html, body { + height: 100%; + margin: 0; + } + core-header-panel { + height: 100%; + } + +Special support is provided for scrolling modes when one uses a core-toolbar or equivalent +for the header section. + +Example: + + <core-header-panel> + <core-toolbar>Header</core-toolbar> + <div>Content goes here...</div> + </core-header-panel> + +If you want to use other than `core-toolbar` for the header, add +`core-header` class to that element. + +Example: + + <core-header-panel> + <div class="core-header">Header</div> + <div>Content goes here...</div> + </core-header-panel> + +To have the content fits to the main area, use `fit` attribute. + + <core-header-panel> + <div class="core-header">standard</div> + <div class="content" fit>content fits 100% below the header</div> + </core-header-panel> + +Use `mode` to control the header and scrolling behavior. + +@group Polymer Core Elements +@element core-header-panel +@homepage github.io +--> + +<!-- +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 +--> + +<!-- +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 +--> +<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 src="polymer/bower_components/polymer/polymer.js"></script> +<!--<link rel="import" href="../polymer-dev/polymer.html">--> + +<polymer-element name="core-header-panel" assetpath="polymer/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; +} + +#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-appbar class="tall"> + * <core-icon-button icon="menu"></core-icon-button> + * </core-appbar> + * <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 header = this.header; + if (header) { + var configs = this.modeConfigs; + // 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.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> +</div> +<div hidden><!-- +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 +--> + +<!-- +`core-toolbar` is a horizontal bar containing elements that can be used for +label, navigation, search and actions. + + <core-toolbar> + <core-icon-button icon="menu" on-tap="{{menuAction}}"></core-icon-button> + <div flex>Title</div> + <core-icon-button icon="more" on-tap="{{moreAction}}"></core-icon-button> + </core-toolbar> + +`core-toolbar` has a standard height, but can made be taller by setting `tall` +class on the `core-toolbar`. This will make the toolbar 3x the normal height. + + <core-toolbar class="tall"> + <core-icon-button icon="menu"></core-icon-button> + </core-toolbar> + +Apply `medium-tall` class to make the toolbar medium tall. This will make the +toolbar 2x the normal height. + + <core-toolbar class="medium-tall"> + <core-icon-button icon="menu"></core-icon-button> + </core-toolbar> + +When taller, elements can pin to either the top (default), middle or bottom. + + <core-toolbar class="tall"> + <core-icon-button icon="menu"></core-icon-button> + <div class="middle indent">Middle Title</div> + <div class="bottom indent">Bottom Title</div> + </core-toolbar> + +To make an element completely fit at the bottom of the toolbar, use `fit` along +with `bottom`. + + <core-toolbar class="tall"> + <div id="progressBar" class="bottom fit"></div> + </core-toolbar> + +`core-toolbar` adapts to mobile/narrow layout when there is a `core-narrow` class set +on itself or any of its ancestors. + +@group Polymer Core Elements +@element core-toolbar +@homepage github.io +--> + + + +<polymer-element name="core-toolbar" assetpath="polymer/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 > *'; } +::content > * { + 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>Polymer('core-toolbar');</script></polymer-element> +</div> +<div hidden><!-- +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 +--> + +<!-- +`core-icon-button` is an icon with button behaviors. + + <core-icon-button src="star.png"></core-icon-button> + +`core-icon-button` includes a default icon set. Use `icon` to specify +which icon from the icon set to use. + + <core-icon-button icon="menu"></core-icon-button> + +See [`core-iconset`](#core-iconset) for more information about +how to use a custom icon set. + +@group Polymer Core Elements +@element core-icon-button +@homepage github.io +--> + +<!-- +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 +--> +<!-- + +The `core-icon` element displays an icon. By default an icon renders as a 24px square. + +Example using src: + + <core-icon src="star.png"></core-icon> + +Example setting size to 32px x 32px: + + <core-icon class="big" src="big_star.png"></core-icon> + + <style> + .big { + height: 32px; + width: 32px; + } + </style> + +The core elements include several sets of icons. +To use the default set of icons, import `core-icons.html` and use the `icon` attribute to specify an icon: + + <!-- import default iconset and core-icon --> + <link rel="import" href="/components/core-icons/core-icons.html"> + + <core-icon icon="menu"></core-icon> + +To use a different built-in set of icons, import `core-icons/<iconset>-icons.html`, and +specify the icon as `<iconset>:<icon>`. For example: + + <!-- import communication iconset and core-icon --> + <link rel="import" href="/components/core-icons/communication-icons.html"> + + <core-icon icon="communication:email"></core-icon> + +You can also create custom icon sets of bitmap or SVG icons. + +Example of using an icon named `cherry` from a custom iconset with the ID `fruit`: + + <core-icon icon="fruit:cherry"></core-icon> + +See [core-iconset](#core-iconset) and [core-iconset-svg](#core-iconset-svg) for more information about +how to create a custom iconset. + +See [core-icons](http://www.polymer-project.org/components/core-icons/demo.html) for the default set of icons. + +@group Polymer Core Elements +@element core-icon +@homepage polymer.github.io +--> +<!-- +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 +--> + +<!-- +/** + * @group Polymer Core Elements + * + * The `core-iconset` element allows users to define their own icon sets. + * The `src` property specifies the url of the icon image. Multiple icons may + * be included in this image and they may be organized into rows. + * The `icons` property is a space separated list of names corresponding to the + * icons. The names must be ordered as the icons are ordered in the icon image. + * Icons are expected to be square and are the size specified by the `iconSize` + * property. The `width` property corresponds to the width of the icon image + * and must be specified if icons are arranged into multiple rows in the image. + * + * All `core-iconset` elements are available for use by other `core-iconset` + * elements via a database keyed by id. Typically, an element author that wants + * to support a set of custom icons uses a `core-iconset` to retrieve + * and use another, user-defined iconset. + * + * Example: + * + * <core-iconset id="my-icons" src="my-icons.png" width="96" iconSize="24" + * icons="location place starta stopb bus car train walk"> + * </core-iconset> + * + * This will automatically register the icon set "my-icons" to the iconset + * database. To use these icons from within another element, make a + * `core-iconset` element and call the `byId` method to retrieve a + * given iconset. To apply a particular icon to an element, use the + * `applyIcon` method. For example: + * + * iconset.applyIcon(iconNode, 'car'); + * + * Themed icon sets are also supported. The `core-iconset` can contain child + * `property` elements that specify a theme with an offsetX and offsetY of the + * theme within the icon resource. For example. + * + * <core-iconset id="my-icons" src="my-icons.png" width="96" iconSize="24" + * icons="location place starta stopb bus car train walk"> + * <property theme="special" offsetX="256" offsetY="24"></property> + * </core-iconset> + * + * Then a themed icon can be applied like this: + * + * iconset.applyIcon(iconNode, 'car', 'special'); + * + * @element core-iconset + * @extends core-meta + * @homepage github.io + */ +--> + +<!-- +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 +--> + +<!-- +`core-meta` provides a method of constructing a self-organizing database. +It is useful to collate element meta-data for things like catalogs and for +designer. + +Example, an element folder has a `metadata.html` file in it, that contains a +`core-meta`, something like this: + + <core-meta id="my-element" label="My Element"> + <property name="color" value="blue"></property> + </core-meta> + +An application can import as many of these files as it wants, and then use +`core-meta` again to access the collected data. + + <script> + var meta = document.createElement('core-meta'); + console.log(meta.list); // dump a list of all meta-data elements that have been created + </script> + +Use `byId(id)` to retrive a specific core-meta. + + <script> + var meta = document.createElement('core-meta'); + console.log(meta.byId('my-element')); + </script> + +By default all meta-data are stored in a single databse. If your meta-data +have different types and want them to be stored separately, use `type` to +differentiate them. + +Example: + + <core-meta id="x-foo" type="xElt"></core-meta> + <core-meta id="x-bar" type="xElt"></core-meta> + <core-meta id="y-bar" type="yElt"></core-meta> + + <script> + var meta = document.createElement('core-meta'); + meta.type = 'xElt'; + console.log(meta.list); + </script> + +@group Polymer Core Elements +@element core-meta +@homepage github.io +--> + + + +<polymer-element name="core-meta" attributes="label type" hidden assetpath="polymer/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-iconset" extends="core-meta" attributes="src width icons iconSize" assetpath="polymer/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> + + +<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> + +<polymer-element name="core-icon" attributes="src icon alt" assetpath="polymer/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> + +<!-- +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 +--> + + +<!-- +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 +--> + +<!-- +/** + * @group Polymer Core Elements + * + * The `core-iconset-svg` element allows users to define their own icon sets + * that contain svg icons. The svg icon elements should be children of the + * `core-iconset-svg` element. Multiple icons should be given distinct id's. + * + * Using svg elements to create icons has a few advantages over traditional + * bitmap graphics like jpg or png. Icons that use svg are vector based so they + * are resolution independent and should look good on any device. They are + * stylable via css. Icons can be themed, colorized, and even animated. + * + * Example: + * + * <core-iconset-svg id="my-svg-icons" iconSize="24"> + * <svg> + * <defs> + * <g id="shape"> + * <rect x="50" y="50" width="50" height="50" /> + * <circle cx="50" cy="50" r="50" /> + * </g> + * </defs> + * </svg> + * </core-iconset-svg> + * + * This will automatically register the icon set "my-svg-icons" to the iconset + * database. To use these icons from within another element, make a + * `core-iconset` element and call the `byId` method + * to retrieve a given iconset. To apply a particular icon inside an + * element use the `applyIcon` method. For example: + * + * iconset.applyIcon(iconNode, 'car'); + * + * @element core-iconset-svg + * @extends core-meta + * @homepage github.io + */ +--> + + + +<polymer-element name="core-iconset-svg" extends="core-meta" attributes="iconSize" assetpath="polymer/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)); + }, + + 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="icons" iconsize="24"> +<svg><defs> +<g id="accessibility"><path d="M12,2c1.1,0,2,0.9,2,2s-0.9,2-2,2s-2-0.9-2-2S10.9,2,12,2z M21,9h-6v13h-2v-6h-2v6H9V9H3V7h18V9z"/></g> +<g id="account-balance"><path d="M4,10v7h3v-7H4z M10,10v7h3v-7H10z M2,22h19v-3H2V22z M16,10v7h3v-7H16z M11.5,1L2,6v2h19V6L11.5,1z"/></g> +<g id="account-box"><path d="M3,5l0,14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5c0-1.1-0.9-2-2-2H5C3.9,3,3,3.9,3,5z M15,9c0,1.7-1.3,3-3,3c-1.7,0-3-1.3-3-3c0-1.7,1.3-3,3-3C13.7,6,15,7.3,15,9z M6,17c0-2,4-3.1,6-3.1s6,1.1,6,3.1v1H6V17z"/></g> +<g id="account-child"><path d="M16.5,12c1.4,0,2.5-1.1,2.5-2.5C19,8.1,17.9,7,16.5,7C15.1,7,14,8.1,14,9.5C14,10.9,15.1,12,16.5,12z M9,11c1.7,0,3-1.3,3-3s-1.3-3-3-3C7.3,5,6,6.3,6,8S7.3,11,9,11z M16.5,14c-1.8,0-5.5,0.9-5.5,2.7V19h11v-2.2C22,14.9,18.3,14,16.5,14z M9,13c-2.3,0-7,1.2-7,3.5V19h7v-2.2c0-0.8,0.3-2.3,2.4-3.5C10.5,13.1,9.7,13,9,13z"/></g> +<g id="account-circle"><path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10c5.5,0,10-4.5,10-10S17.5,2,12,2z M12,5c1.7,0,3,1.3,3,3c0,1.7-1.3,3-3,3c-1.7,0-3-1.3-3-3C9,6.3,10.3,5,12,5z M12,19.2c-2.5,0-4.7-1.3-6-3.2c0-2,4-3.1,6-3.1c2,0,6,1.1,6,3.1C16.7,17.9,14.5,19.2,12,19.2z"/></g> +<g id="add"><path d="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6V13z"/></g> +<g id="add-box"><path d="M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M17,13h-4v4h-2v-4H7v-2h4V7h2v4h4V13z"/></g> +<g id="add-circle"><path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10c5.5,0,10-4.5,10-10S17.5,2,12,2z M17,13h-4v4h-2v-4H7v-2h4V7h2v4h4V13z"/></g> +<g id="add-circle-outline"><path d="M13,7h-2v4H7v2h4v4h2v-4h4v-2h-4V7z M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10c5.5,0,10-4.5,10-10S17.5,2,12,2z M12,20c-4.4,0-8-3.6-8-8s3.6-8,8-8c4.4,0,8,3.6,8,8S16.4,20,12,20z"/></g> +<g id="add-shopping-cart"><polygon points="18.3,6 18.3,6 15.6,11 "/></g> +<g id="alarm"><path d="M22,5.7l-4.6-3.9l-1.3,1.5l4.6,3.9L22,5.7z M7.9,3.4L6.6,1.9L2,5.7l1.3,1.5L7.9,3.4z M12.5,8H11v6l4.7,2.9l0.8-1.2l-4-2.4V8z M12,4c-5,0-9,4-9,9c0,5,4,9,9,9c5,0,9-4,9-9C21,8,17,4,12,4z M12,20c-3.9,0-7-3.1-7-7c0-3.9,3.1-7,7-7c3.9,0,7,3.1,7,7C19,16.9,15.9,20,12,20z"/></g> +<g id="alarm-add"><path d="M7.9,3.4L6.6,1.9L2,5.7l1.3,1.5L7.9,3.4z M22,5.7l-4.6-3.9l-1.3,1.5l4.6,3.9L22,5.7z M12,4c-5,0-9,4-9,9c0,5,4,9,9,9c5,0,9-4,9-9C21,8,17,4,12,4z M12,20c-3.9,0-7-3.1-7-7c0-3.9,3.1-7,7-7c3.9,0,7,3.1,7,7C19,16.9,15.9,20,12,20z M13,9h-2v3H8v2h3v3h2v-3h3v-2h-3V9z"/></g> +<g id="alarm-off"><path d="M12,6c3.9,0,7,3.1,7,7c0,0.8-0.2,1.6-0.4,2.4l1.5,1.5c0.6-1.2,0.9-2.5,0.9-3.9c0-5-4-9-9-9c-1.4,0-2.7,0.3-3.9,0.9l1.5,1.5C10.4,6.2,11.2,6,12,6z M22,5.7l-4.6-3.9l-1.3,1.5l4.6,3.9L22,5.7z M2.9,2.3L1.6,3.6L3,4.9L1.9,5.8l1.4,1.4l1.1-0.9l0.8,0.8C3.8,8.7,3,10.7,3,13c0,5,4,9,9,9c2.3,0,4.3-0.8,5.9-2.2l2.2,2.2l1.3-1.3L3.9,3.3L2.9,2.3z M16.5,18.4c-1.2,1-2.8,1.6-4.5,1.6c-3.9,0-7-3.1-7-7c0-1.7,0.6-3.3,1.6-4.5L16.5,18.4z M8,3.3L6.6,1.9L5.7,2.6L7.2,4L8,3.3z"/></g> +<g id="alarm-on"><path d="M22,5.7l-4.6-3.9l-1.3,1.5l4.6,3.9L22,5.7z M7.9,3.4L6.6,1.9L2,5.7l1.3,1.5L7.9,3.4z M12,4c-5,0-9,4-9,9c0,5,4,9,9,9c5,0,9-4,9-9C21,8,17,4,12,4z M12,20c-3.9,0-7-3.1-7-7c0-3.9,3.1-7,7-7s7,3.1,7,7C19,16.9,15.9,20,12,20z M10.5,14.5l-2.1-2.1l-1.1,1.1l3.2,3.2l6-6l-1.1-1.1L10.5,14.5z"/></g> +<g id="android"><path d="M6,18c0,0.6,0.4,1,1,1h1v3.5C8,23.3,8.7,24,9.5,24c0.8,0,1.5-0.7,1.5-1.5V19h2v3.5c0,0.8,0.7,1.5,1.5,1.5c0.8,0,1.5-0.7,1.5-1.5V19h1c0.6,0,1-0.4,1-1V8H6V18z M3.5,8C2.7,8,2,8.7,2,9.5v7C2,17.3,2.7,18,3.5,18C4.3,18,5,17.3,5,16.5v-7C5,8.7,4.3,8,3.5,8z M20.5,8C19.7,8,19,8.7,19,9.5v7c0,0.8,0.7,1.5,1.5,1.5c0.8,0,1.5-0.7,1.5-1.5v-7C22,8.7,21.3,8,20.5,8z M15.5,2.2l1.3-1.3c0.2-0.2,0.2-0.5,0-0.7c-0.2-0.2-0.5-0.2-0.7,0l-1.5,1.5C13.9,1.2,13,1,12,1c-1,0-1.9,0.2-2.7,0.6L7.9,0.1C7.7,0,7.3,0,7.1,0.1C7,0.3,7,0.7,7.1,0.9l1.3,1.3C7,3.3,6,5,6,7h12C18,5,17,3.2,15.5,2.2z M10,5H9V4h1V5z M15,5h-1V4h1V5z"/></g> +<g id="announcement"><path d="M20,2H4C2.9,2,2,2.9,2,4l0,18l4-4h14c1.1,0,2-0.9,2-2V4C22,2.9,21.1,2,20,2z M13,11h-2V5h2V11z M13,15h-2v-2h2V15z"/></g> +<g id="apps"><path d="M4,8h4V4H4V8z M10,20h4v-4h-4V20z M4,20h4v-4H4V20z M4,14h4v-4H4V14z M10,14h4v-4h-4V14z M16,4v4h4V4H16z M10,8h4V4h-4V8z M16,14h4v-4h-4V14z M16,20h4v-4h-4V20z"/></g> +<g id="archive"><path d="M20.5,5.2l-1.4-1.7C18.9,3.2,18.5,3,18,3H6C5.5,3,5.1,3.2,4.8,3.5L3.5,5.2C3.2,5.6,3,6,3,6.5V19c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V6.5C21,6,20.8,5.6,20.5,5.2z M12,17.5L6.5,12H10v-2h4v2h3.5L12,17.5z M5.1,5l0.8-1h12l0.9,1H5.1z"/></g> +<g id="arrow-back"><path d="M20,11H7.8l5.6-5.6L12,4l-8,8l8,8l1.4-1.4L7.8,13H20V11z"/></g> +<g id="arrow-drop-down"><polygon points="7,10 12,15 17,10 "/></g> +<g id="arrow-drop-down-circle"><path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10c5.5,0,10-4.5,10-10S17.5,2,12,2z M12,14l-4-4h8L12,14z"/></g> +<g id="arrow-drop-up"><polygon points="7,14 12,9 17,14 "/></g> +<g id="arrow-forward"><polygon points="12,4 10.6,5.4 16.2,11 4,11 4,13 16.2,13 10.6,18.6 12,20 20,12 "/></g> +<g id="aspect-ratio"><path d="M19,12h-2v3h-3v2h5V12z M7,9h3V7H5v5h2V9z M21,3H3C1.9,3,1,3.9,1,5v14c0,1.1,0.9,2,2,2h18c1.1,0,2-0.9,2-2V5C23,3.9,22.1,3,21,3z M21,19H3V5h18V19z"/></g> +<g id="assessment"><path d="M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M9,17H7v-7h2V17z M13,17h-2V7h2V17z M17,17h-2v-4h2V17z"/></g> +<g id="assignment"><path d="M19,3h-4.2c-0.4-1.2-1.5-2-2.8-2c-1.3,0-2.4,0.8-2.8,2H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M12,3c0.6,0,1,0.4,1,1s-0.4,1-1,1s-1-0.4-1-1S11.4,3,12,3z M14,17H7v-2h7V17z M17,13H7v-2h10V13z M17,9H7V7h10V9z"/></g> +<g id="assignment-ind"><path d="M19,3h-4.2c-0.4-1.2-1.5-2-2.8-2c-1.3,0-2.4,0.8-2.8,2H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M12,3c0.6,0,1,0.4,1,1s-0.4,1-1,1s-1-0.4-1-1S11.4,3,12,3z M12,7c1.7,0,3,1.3,3,3c0,1.7-1.3,3-3,3c-1.7,0-3-1.3-3-3C9,8.3,10.3,7,12,7z M18,19H6v-1.4c0-2,4-3.1,6-3.1s6,1.1,6,3.1V19z"/></g> +<g id="assignment-late"><path d="M19,3h-4.2c-0.4-1.2-1.5-2-2.8-2c-1.3,0-2.4,0.8-2.8,2H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M12,3c0.6,0,1,0.4,1,1s-0.4,1-1,1s-1-0.4-1-1S11.4,3,12,3z M17,15.6L15.6,17L12,13.4L8.4,17L7,15.6l3.6-3.6L7,8.4L8.4,7l3.6,3.6L15.6,7L17,8.4L13.4,12L17,15.6z"/></g> +<g id="assignment-return"><path d="M19,3h-4.2c-0.4-1.2-1.5-2-2.8-2c-1.3,0-2.4,0.8-2.8,2H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M12,3c0.6,0,1,0.4,1,1s-0.4,1-1,1c-0.6,0-1-0.4-1-1S11.4,3,12,3z M16,15h-4v3l-5-5l5-5v3h4V15z"/></g> +<g id="assignment-returned"><path d="M19,3h-4.2c-0.4-1.2-1.5-2-2.8-2c-1.3,0-2.4,0.8-2.8,2H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M12,3c0.6,0,1,0.4,1,1s-0.4,1-1,1s-1-0.4-1-1S11.4,3,12,3z M12,18l-5-5h3V9h4v4h3L12,18z"/></g> +<g id="attachment"><path d="M7.5,18c-3,0-5.5-2.5-5.5-5.5S4.5,7,7.5,7H18c2.2,0,4,1.8,4,4s-1.8,4-4,4H9.5C8.1,15,7,13.9,7,12.5S8.1,10,9.5,10H17v1.5H9.5c-0.6,0-1,0.4-1,1s0.4,1,1,1H18c1.4,0,2.5-1.1,2.5-2.5S19.4,8.5,18,8.5H7.5c-2.2,0-4,1.8-4,4s1.8,4,4,4H17V18H7.5z"/></g> +<g id="backspace"><path d="M22,3H7C6.3,3,5.8,3.3,5.4,3.9L0,12l5.4,8.1C5.8,20.6,6.3,21,7,21h15c1.1,0,2-0.9,2-2V5C24,3.9,23.1,3,22,3z M19,15.6L17.6,17L14,13.4L10.4,17L9,15.6l3.6-3.6L9,8.4L10.4,7l3.6,3.6L17.6,7L19,8.4L15.4,12L19,15.6z"/></g> +<g id="backup"><path d="M19.4,10c-0.7-3.4-3.7-6-7.4-6C9.1,4,6.6,5.6,5.4,8C2.3,8.4,0,10.9,0,14c0,3.3,2.7,6,6,6h13c2.8,0,5-2.2,5-5C24,12.4,21.9,10.2,19.4,10z M14,13v4h-4v-4H7l5-5l5,5H14z"/></g> +<g id="block"><path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10c5.5,0,10-4.5,10-10S17.5,2,12,2z M4,12c0-4.4,3.6-8,8-8c1.8,0,3.5,0.6,4.9,1.7L5.7,16.9C4.6,15.5,4,13.8,4,12z M12,20c-1.8,0-3.5-0.6-4.9-1.7L18.3,7.1C19.4,8.5,20,10.2,20,12C20,16.4,16.4,20,12,20z"/></g> +<g id="book"><path d="M18,2H6C4.9,2,4,2.9,4,4v16c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2V4C20,2.9,19.1,2,18,2z M6,4h5v8l-2.5-1.5L6,12V4z"/></g> +<g id="bookmark"><path d="M17,3H7C5.9,3,5,3.9,5,5l0,16l7-3l7,3V5C19,3.9,18.1,3,17,3z"/></g> +<g id="bookmark-outline"><path d="M17,3H7C5.9,3,5,3.9,5,5l0,16l7-3l7,3V5C19,3.9,18.1,3,17,3z M17,18l-5-2.2L7,18V5h10V18z"/></g> +<g id="bug-report"><path d="M20,8h-2.8c-0.5-0.8-1.1-1.5-1.8-2L17,4.4L15.6,3l-2.2,2.2C13,5.1,12.5,5,12,5s-1,0.1-1.4,0.2L8.4,3L7,4.4L8.6,6C7.9,6.5,7.3,7.2,6.8,8H4v2h2.1C6,10.3,6,10.7,6,11v1H4v2h2v1c0,0.3,0,0.7,0.1,1H4v2h2.8c1,1.8,3,3,5.2,3s4.2-1.2,5.2-3H20v-2h-2.1c0.1-0.3,0.1-0.7,0.1-1v-1h2v-2h-2v-1c0-0.3,0-0.7-0.1-1H20V8z M14,16h-4v-2h4V16z M14,12h-4v-2h4V12z"/></g> +<g id="cached"><path d="M19,8l-4,4h3c0,3.3-2.7,6-6,6c-1,0-2-0.3-2.8-0.7l-1.5,1.5C9,19.5,10.4,20,12,20c4.4,0,8-3.6,8-8h3L19,8z M6,12c0-3.3,2.7-6,6-6c1,0,2,0.3,2.8,0.7l1.5-1.5C15,4.5,13.6,4,12,4c-4.4,0-8,3.6-8,8H1l4,4l4-4H6z"/></g> +<g id="cancel"><path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10c5.5,0,10-4.5,10-10S17.5,2,12,2z M17,15.6L15.6,17L12,13.4L8.4,17L7,15.6l3.6-3.6L7,8.4L8.4,7l3.6,3.6L15.6,7L17,8.4L13.4,12L17,15.6z"/></g> +<g id="check"><polygon points="9,16.2 4.8,12 3.4,13.4 9,19 21,7 19.6,5.6 "/></g> +<g id="check-box"><path d="M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M10,17l-5-5l1.4-1.4l3.6,3.6l7.6-7.6L19,8L10,17z"/></g> +<g id="check-box-blank"><path d="M19,3H5C3.9,3,3,3.9,3,5l0,14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z"/></g> +<g id="check-box-outline"><path d="M7.9,10.1l-1.4,1.4L11,16L21,6l-1.4-1.4L11,13.2L7.9,10.1z M19,19L5,19V5h10V3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2v-8h-2V19z"/></g> +<g id="check-box-outline-blank"><path d="M19,5v14L5,19V5H19 M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3L19,3z"/></g> +<g id="check-circle"><path d="M12,2C6.5,2,2,6.5,2,12c0,5.5,4.5,10,10,10c5.5,0,10-4.5,10-10C22,6.5,17.5,2,12,2z M10,17l-5-5l1.4-1.4l3.6,3.6l7.6-7.6L19,8L10,17z"/></g> +<g id="check-circle-blank"><path d="M12,2C6.5,2,2,6.5,2,12c0,5.5,4.5,10,10,10c5.5,0,10-4.5,10-10C22,6.5,17.5,2,12,2z"/></g> +<g id="check-circle-outline"><path d="M7.9,10.1l-1.4,1.4L11,16L21,6l-1.4-1.4L11,13.2L7.9,10.1z M20,12c0,4.4-3.6,8-8,8s-8-3.6-8-8s3.6-8,8-8c0.8,0,1.5,0.1,2.2,0.3l1.6-1.6C14.6,2.3,13.3,2,12,2C6.5,2,2,6.5,2,12s4.5,10,10,10s10-4.5,10-10H20z"/></g> +<g id="check-circle-outline-blank"><path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10c5.5,0,10-4.5,10-10S17.5,2,12,2z M12,20c-4.4,0-8-3.6-8-8s3.6-8,8-8c4.4,0,8,3.6,8,8S16.4,20,12,20z"/></g> +<g id="chevron-left"><polygon points="15.4,7.4 14,6 8,12 14,18 15.4,16.6 10.8,12 "/></g> +<g id="chevron-right"><polygon points="10,6 8.6,7.4 13.2,12 8.6,16.6 10,18 16,12 "/></g> +<g id="class"><path d="M18,2H6C4.9,2,4,2.9,4,4v16c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2V4C20,2.9,19.1,2,18,2z M6,4h5v8l-2.5-1.5L6,12V4z"/></g> +<g id="clear"><polygon points="19,6.4 17.6,5 12,10.6 6.4,5 5,6.4 10.6,12 5,17.6 6.4,19 12,13.4 17.6,19 19,17.6 13.4,12 "/></g> +<g id="close"><polygon points="19,6.4 17.6,5 12,10.6 6.4,5 5,6.4 10.6,12 5,17.6 6.4,19 12,13.4 17.6,19 19,17.6 13.4,12 "/></g> +<g id="cloud"><path d="M19.4,10c-0.7-3.4-3.7-6-7.4-6C9.1,4,6.6,5.6,5.4,8C2.3,8.4,0,10.9,0,14c0,3.3,2.7,6,6,6h13c2.8,0,5-2.2,5-5C24,12.4,21.9,10.2,19.4,10z"/></g> +<g id="cloud-circle"><path d="M12,2C6.5,2,2,6.5,2,12c0,5.5,4.5,10,10,10c5.5,0,10-4.5,10-10C22,6.5,17.5,2,12,2z M16.5,16c0,0-8.5,0-8.5,0c-1.7,0-3-1.3-3-3s1.3-3,3-3c0,0,0.1,0,0.1,0c0.4-1.7,2-3,3.9-3c2.2,0,4,1.8,4,4h0.5c1.4,0,2.5,1.1,2.5,2.5C19,14.9,17.9,16,16.5,16z"/></g> +<g id="cloud-done"><path d="M19.4,10c-0.7-3.4-3.7-6-7.4-6C9.1,4,6.6,5.6,5.4,8C2.3,8.4,0,10.9,0,14c0,3.3,2.7,6,6,6h13c2.8,0,5-2.2,5-5C24,12.4,21.9,10.2,19.4,10z M10,17l-3.5-3.5l1.4-1.4l2.1,2.1L15.2,9l1.4,1.4L10,17z"/></g> +<g id="cloud-download"><path d="M19.4,10c-0.7-3.4-3.7-6-7.4-6C9.1,4,6.6,5.6,5.4,8C2.3,8.4,0,10.9,0,14c0,3.3,2.7,6,6,6h13c2.8,0,5-2.2,5-5C24,12.4,21.9,10.2,19.4,10z M17,13l-5,5l-5-5h3V9h4v4H17z"/></g> +<g id="cloud-off"><path d="M19.4,10c-0.7-3.4-3.7-6-7.4-6c-1.5,0-2.9,0.4-4,1.2l1.5,1.5C10.2,6.2,11.1,6,12,6c3,0,5.5,2.5,5.5,5.5V12H19c1.7,0,3,1.3,3,3c0,1.1-0.6,2.1-1.6,2.6l1.5,1.5c1.3-0.9,2.1-2.4,2.1-4.1C24,12.4,21.9,10.2,19.4,10z M3,5.3L5.8,8C2.6,8.2,0,10.8,0,14c0,3.3,2.7,6,6,6h11.7l2,2l1.3-1.3L4.3,4L3,5.3z M7.7,10l8,8H6c-2.2,0-4-1.8-4-4c0-2.2,1.8-4,4-4H7.7z"/></g> +<g id="cloud-queue"><path d="M19.4,10c-0.7-3.4-3.7-6-7.4-6C9.1,4,6.6,5.6,5.4,8C2.3,8.4,0,10.9,0,14c0,3.3,2.7,6,6,6h13c2.8,0,5-2.2,5-5C24,12.4,21.9,10.2,19.4,10z M19,18H6c-2.2,0-4-1.8-4-4c0-2.2,1.8-4,4-4h0.7C7.4,7.7,9.5,6,12,6c3,0,5.5,2.5,5.5,5.5V12H19c1.7,0,3,1.3,3,3S20.7,18,19,18z"/></g> +<g id="cloud-upload"><path d="M19.4,10c-0.7-3.4-3.7-6-7.4-6C9.1,4,6.6,5.6,5.4,8C2.3,8.4,0,10.9,0,14c0,3.3,2.7,6,6,6h13c2.8,0,5-2.2,5-5C24,12.4,21.9,10.2,19.4,10z M14,13v4h-4v-4H7l5-5l5,5H14z"/></g> +<g id="content-copy"><path d="M16,1H4C2.9,1,2,1.9,2,3v14h2V3h12V1z M19,5H8C6.9,5,6,5.9,6,7v14c0,1.1,0.9,2,2,2h11c1.1,0,2-0.9,2-2V7C21,5.9,20.1,5,19,5z M19,21H8V7h11V21z"/></g> +<g id="content-cut"><path d="M10,6c0-2.2-1.8-4-4-4S2,3.8,2,6c0,2.2,1.8,4,4,4c0.6,0,1.1-0.1,1.6-0.4L10,12l-2.4,2.4C7.1,14.1,6.6,14,6,14c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4s4-1.8,4-4c0-0.6-0.1-1.1-0.4-1.6L12,14l7,7h4L9.6,7.6C9.9,7.1,10,6.6,10,6z M6,8C4.9,8,4,7.1,4,6s0.9-2,2-2c1.1,0,2,0.9,2,2S7.1,8,6,8z M6,20c-1.1,0-2-0.9-2-2s0.9-2,2-2c1.1,0,2,0.9,2,2S7.1,20,6,20z M12,11.5c0.3,0,0.5,0.2,0.5,0.5c0,0.3-0.2,0.5-0.5,0.5c-0.3,0-0.5-0.2-0.5-0.5C11.5,11.7,11.7,11.5,12,11.5z M23,3h-4l-6,6l2,2L23,3z"/></g> +<g id="content-paste"><path d="M19,2h-4.2c-0.4-1.2-1.5-2-2.8-2c-1.3,0-2.4,0.8-2.8,2H5C3.9,2,3,2.9,3,4v16c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V4C21,2.9,20.1,2,19,2z M12,2c0.6,0,1,0.4,1,1s-0.4,1-1,1c-0.6,0-1-0.4-1-1S11.4,2,12,2z M19,20H5V4h2v3h10V4h2V20z"/></g> +<g id="create"><path d="M3,17.2V21h3.8L17.8,9.9l-3.8-3.8L3,17.2z M20.7,7c0.4-0.4,0.4-1,0-1.4l-2.3-2.3c-0.4-0.4-1-0.4-1.4,0l-1.8,1.8l3.8,3.8L20.7,7z"/></g> +<g id="credit-card"><path d="M20,4H4C2.9,4,2,4.9,2,6v12c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V6C22,4.9,21.1,4,20,4z M20,18H4v-6h16V18z M20,8H4V6h16V8z"/></g> +<g id="delete"><path d="M6,19c0,1.1,0.9,2,2,2h8c1.1,0,2-0.9,2-2V7H6V19z M19,4h-3.5l-1-1h-5l-1,1H5v2h14V4z"/></g> +<g id="description"><path d="M14,2H6C4.9,2,4,2.9,4,4l0,16c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2V8L14,2z M16,18H8v-2h8V18z M16,14H8v-2h8V14z M13,9V3.5L18.5,9H13z"/></g> +<g id="developer-mode-tv"><path d="M4,5h16v2h2l0-2c0-1.1-0.9-2-2-2H4C2.9,3,2,3.9,2,5v2h2V5z M7.6,13.8L4.7,11l2.8-2.8L6.1,6.8L1.9,11l4.2,4.2L7.6,13.8z M20,17H4v-2H2v2c0,1.1,0.9,2,2,2h4v2h8v-2h4c1.1,0,2-0.9,2-2l0-2h-2V17z M22,11l-4.2-4.2l-1.4,1.4l2.8,2.8l-2.8,2.8l1.4,1.4L22,11L22,11L22,11L22,11L22,11z"/></g> +<g id="done"><polygon points="9,16.2 4.8,12 3.4,13.4 9,19 21,7 19.6,5.6 "/></g> +<g id="done-all"><path d="M18,7l-1.4-1.4l-6.3,6.3l1.4,1.4L18,7z M22.2,5.6L11.7,16.2L7.5,12l-1.4,1.4l5.6,5.6l12-12L22.2,5.6z M0.4,13.4L6,19l1.4-1.4L1.8,12L0.4,13.4z"/></g> +<g id="drafts"><path d="M22,8c0-0.7-0.4-1.3-0.9-1.7L12,1L2.9,6.3C2.4,6.7,2,7.3,2,8v10c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2L22,8z M12,13L3.7,7.8L12,3l8.3,4.8L12,13z"/></g> +<g id="drawer"><path d="M12,8c1.1,0,2-0.9,2-2s-0.9-2-2-2c-1.1,0-2,0.9-2,2S10.9,8,12,8z M12,10c-1.1,0-2,0.9-2,2s0.9,2,2,2c1.1,0,2-0.9,2-2S13.1,10,12,10z M12,16c-1.1,0-2,0.9-2,2s0.9,2,2,2c1.1,0,2-0.9,2-2S13.1,16,12,16z"/></g> +<g id="drive"><path d="M22.3,14L15.4,2H8.6l0,0l6.9,12H22.3z M9.7,15l-3.4,6h13.1l3.4-6H9.7z M7.7,3.5L1.2,15l3.4,6l6.6-11.5L7.7,3.5z"/></g> +<g id="drive-archive"><path d="M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M12,18l-4-4h8L12,18z M16,12H8v-2h8V12z M16,8H8V6h8V8z"/></g> +<g id="drive-audio"><path d="M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M7.2,18C6.5,18,6,17.5,6,16.8v-3.6V12c0-3.3,2.7-6,6-6s6,2.7,6,6v1.2v3.6c0,0.7-0.5,1.2-1.2,1.2H14v-4h2v-2c0-2.2-1.8-4-4-4s-4,1.8-4,4v2h2v4H7.2z"/></g> +<g id="drive-chart"><path d="M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M9,17H7v-7h2V17z M13,17h-2V7h2V17z M17,17h-2v-4h2V17z"/></g> +<g id="drive-document"><path d="M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M17,9H7V7h10V9z M17,13H7v-2h10V13z M14,17H7v-2h7V17z"/></g> +<g id="drive-drawing"><path d="M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M18,18h-6v-5.8c-0.7,0.6-1.5,1-2.5,1c-2,0-3.7-1.7-3.7-3.7s1.7-3.7,3.7-3.7c2,0,3.7,1.7,3.7,3.7c0,1-0.4,1.8-1,2.5H18V18z"/></g> +<g id="drive-file"><path d="M6,2C4.9,2,4,2.9,4,4l0,16c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2V8l-6-6H6z M13,9V3.5L18.5,9H13z"/></g> +<g id="drive-file-move"><path d="M20,6h-8l-2-2H4C2.9,4,2,4.9,2,6v12c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V8C22,6.9,21.1,6,20,6z M9,18v-3H5v-4h4V8l5,5L9,18z"/></g> +<g id="drive-file-rename"><path d="M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M6,17v-2.5l7.9-7.9c0.2-0.2,0.5-0.2,0.7,0l1.8,1.8c0.2,0.2,0.2,0.5,0,0.7L8.5,17H6z M18,17h-7.5l2-2H18V17z"/></g> +<g id="drive-form"><path d="M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M9,17H7v-2h2V17z M9,13H7v-2h2V13z M9,9H7V7h2V9z M17,17h-7v-2h7V17z M17,13h-7v-2h7V13z M17,9h-7V7h7V9z"/></g> +<g id="drive-fusiontable"><path d="M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M19,10.2L13,17l-4-4l-4,4v-3l4-4l4,4l6-6.8V10.2z"/></g> +<g id="drive-image"><path d="M21,19V5c0-1.1-0.9-2-2-2H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14C20.1,21,21,20.1,21,19z M8.5,13.5l2.5,3l3.5-4.5l4.5,6H5L8.5,13.5z"/></g> +<g id="drive-keep"><path d="M9,21c0,0.6,0.4,1,1,1h4c0.6,0,1-0.4,1-1v-1H9V21z M12,2C8.1,2,5,5.1,5,9c0,2.4,1.2,4.5,3,5.7V17c0,0.6,0.4,1,1,1h6c0.6,0,1-0.4,1-1v-2.3c1.8-1.3,3-3.4,3-5.7C19,5.1,15.9,2,12,2z"/></g> +<g id="drive-ms-excel"><path d="M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M16.2,17h-2L12,13.2L9.8,17h-2l3.2-5L7.8,7h2l2.2,3.8L14.2,7h2L13,12L16.2,17z"/></g> +<g id="drive-ms-powerpoint"><path d="M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M9.8,13.4V17H8V7h4.3c1.5,0,2.2,0.3,2.8,0.9c0.7,0.6,0.9,1.4,0.9,2.3c0,1-0.3,1.8-0.9,2.3c-0.6,0.5-1.3,0.8-2.8,0.8H9.8z"/><path d="M9.8,12V8.4h2.3c0.7,0,1.2,0.2,1.5,0.6c0.3,0.4,0.5,0.7,0.5,1.2c0,0.6-0.2,0.9-0.5,1.3c-0.3,0.3-0.7,0.5-1.4,0.5H9.8z"/></g> +<g id="drive-ms-word"><path d="M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M15.5,17H14l-2-7.5L10,17H8.5L6.1,7h1.7l1.5,7.5l2-7.5h1.4l2,7.5L16.2,7h1.7L15.5,17z"/></g> +<g id="drive-pdf"><path d="M11.3,8.6L11.3,8.6C11.4,8.6,11.4,8.6,11.3,8.6c0.1-0.4,0.2-0.6,0.2-0.9l0-0.2c0.1-0.5,0.1-0.9,0-1c0,0,0,0,0-0.1l-0.1-0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0.1-0.1,0.1C11.1,7,11.1,7.7,11.3,8.6C11.3,8.6,11.3,8.6,11.3,8.6z M8.3,15.5c-0.2,0.1-0.4,0.2-0.5,0.3c-0.7,0.6-1.2,1.3-1.3,1.6c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0C7.1,17.3,7.7,16.7,8.3,15.5C8.4,15.5,8.4,15.5,8.3,15.5C8.4,15.5,8.3,15.5,8.3,15.5z M17.5,14c-0.1-0.1-0.5-0.4-1.9-0.4c-0.1,0-0.1,0-0.2,0c0,0,0,0,0,0c0,0,0,0,0,0.1c0.7,0.3,1.4,0.5,1.9,0.5c0.1,0,0.1,0,0.2,0l0,0c0,0,0.1,0,0.1,0c0,0,0,0,0-0.1c0,0,0,0,0,0C17.6,14.1,17.5,14.1,17.5,14z M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M17.9,14.8C17.7,14.9,17.4,15,17,15c-0.8,0-2-0.2-3-0.7c-1.7,0.2-3,0.4-4,0.8c-0.1,0-0.1,0-0.2,0.1c-1.2,2.1-2.2,3.1-3,3.1c-0.2,0-0.3,0-0.4-0.1l-0.5-0.3l0-0.1c-0.1-0.2-0.1-0.3-0.1-0.5c0.1-0.5,0.7-1.4,1.9-2.1c0.2-0.1,0.5-0.3,0.9-0.5c0.3-0.5,0.6-1.1,1-1.8c0.5-1,0.8-2,1.1-2.9l0,0c-0.4-1.2-0.6-1.9-0.2-3.3c0.1-0.4,0.4-0.8,0.8-0.8l0.2,0c0.2,0,0.4,0.1,0.6,0.2c0.7,0.7,0.4,2.3,0,3.6c0,0.1,0,0.1,0,0.1c0.4,1.1,1,2,1.6,2.6c0.3,0.2,0.5,0.4,0.9,0.6c0.5,0,0.9-0.1,1.3-0.1c1.2,0,2,0.2,2.3,0.7c0.1,0.2,0.1,0.4,0.1,0.6C18.2,14.3,18.1,14.6,17.9,14.8z M11.4,10.9c-0.2,0.7-0.6,1.5-1,2.4c-0.2,0.4-0.4,0.7-0.6,1.1c0,0,0.1,0,0.1,0l0.1,0v0c1.3-0.5,2.5-0.8,3.3-0.9c-0.2-0.1-0.3-0.2-0.4-0.3C12.4,12.6,11.8,11.8,11.4,10.9z"/></g> +<g id="drive-presentation"><path d="M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M19,16H5V8h14V16z"/></g> +<g id="drive-script"><path d="M19,3H5C3.9,3,3,3.9,3,5l0,4h0v6h0l0,4c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M11,17v-3H5v-4h6V7l5,5L11,17z"/></g> +<g id="drive-site"><path d="M19,4H5C3.9,4,3,4.9,3,6l0,12c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V6C21,4.9,20.1,4,19,4z M14,18H5v-4h9V18z M14,13H5V9h9V13z M19,18h-4V9h4V18z"/></g> +<g id="drive-spreadsheet"><path d="M19,3H5C3.9,3,3,3.9,3,5l0,3h0v11c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M19,11h-8v8H9v-8H5V9h4V5h2v4h8V11z"/></g> +<g id="drive-text"><path d="M14,2H6C4.9,2,4,2.9,4,4l0,16c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2V8L14,2z M16,18H8v-2h8V18z M16,14H8v-2h8V14z M13,9V3.5L18.5,9H13z"/></g> +<g id="drive-video"><path d="M18,4l2,4h-3l-2-4h-2l2,4h-3l-2-4H8l2,4H7L5,4H4C2.9,4,2,4.9,2,6l0,12c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V4H18z"/></g> +<g id="drive-zip"><path d="M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M14,9h-2v2h2v2h-2v-2h-2V9h2V7h-2V5h2v2h2V9z M14,17h-2v-2h-2v-2h2v2h2V17z"/></g> +<g id="due-date"><path d="M17,12h-5v5h5V12z M16,1v2H8V1H6v2H5C3.9,3,3,3.9,3,5l0,14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5c0-1.1-0.9-2-2-2h-1V1H16z M19,19H5V8h14V19z"/></g> +<g id="error"><path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10c5.5,0,10-4.5,10-10S17.5,2,12,2z M13,17h-2v-2h2V17z M13,13h-2V7h2V13z"/></g> +<g id="event"><path d="M17,12h-5v5h5V12z M16,1v2H8V1H6v2H5C3.9,3,3,3.9,3,5l0,14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5c0-1.1-0.9-2-2-2h-1V1H16z M19,19H5V8h14V19z"/></g> +<g id="exit-to-app"><path d="M10.1,15.6l1.4,1.4l5-5l-5-5l-1.4,1.4l2.6,2.6H3v2h9.7L10.1,15.6z M19,3H5C3.9,3,3,3.9,3,5v4h2V5h14v14H5v-4H3v4c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z"/></g> +<g id="expand-less"><polygon points="12,8 6,14 7.4,15.4 12,10.8 16.6,15.4 18,14 "/></g> +<g id="expand-more"><polygon points="16.6,8.6 12,13.2 7.4,8.6 6,10 12,16 18,10 "/></g> +<g id="explore"><path d="M12,10.9c-0.6,0-1.1,0.5-1.1,1.1s0.5,1.1,1.1,1.1c0.6,0,1.1-0.5,1.1-1.1S12.6,10.9,12,10.9z M12,2C6.5,2,2,6.5,2,12c0,5.5,4.5,10,10,10c5.5,0,10-4.5,10-10C22,6.5,17.5,2,12,2z M14.2,14.2L6,18l3.8-8.2L18,6L14.2,14.2z"/></g> +<g id="extension"><path d="M20.5,11H19V7c0-1.1-0.9-2-2-2h-4V3.5C13,2.1,11.9,1,10.5,1C9.1,1,8,2.1,8,3.5V5H4C2.9,5,2,5.9,2,7l0,3.8h1.5c1.5,0,2.7,1.2,2.7,2.7S5,16.2,3.5,16.2H2L2,20c0,1.1,0.9,2,2,2h3.8v-1.5c0-1.5,1.2-2.7,2.7-2.7c1.5,0,2.7,1.2,2.7,2.7V22H17c1.1,0,2-0.9,2-2v-4h1.5c1.4,0,2.5-1.1,2.5-2.5S21.9,11,20.5,11z"/></g> +<g id="favorite"><path d="M12,21.4L10.6,20C5.4,15.4,2,12.3,2,8.5C2,5.4,4.4,3,7.5,3c1.7,0,3.4,0.8,4.5,2.1C13.1,3.8,14.8,3,16.5,3C19.6,3,22,5.4,22,8.5c0,3.8-3.4,6.9-8.6,11.5L12,21.4z"/></g> +<g id="favorite-outline"><path d="M16.5,3c-1.7,0-3.4,0.8-4.5,2.1C10.9,3.8,9.2,3,7.5,3C4.4,3,2,5.4,2,8.5c0,3.8,3.4,6.9,8.6,11.5l1.4,1.3l1.4-1.3c5.1-4.7,8.6-7.8,8.6-11.5C22,5.4,19.6,3,16.5,3z M12.1,18.6L12,18.6l-0.1-0.1C7.1,14.2,4,11.4,4,8.5C4,6.5,5.5,5,7.5,5c1.5,0,3,1,3.6,2.4h1.9C13.5,6,15,5,16.5,5c2,0,3.5,1.5,3.5,3.5C20,11.4,16.9,14.2,12.1,18.6z"/></g> +<g id="file-download"><path d="M19,9h-4V3H9v6H5l7,7L19,9z M5,18v2h14v-2H5z"/></g> +<g id="file-map"><path d="M12,6.5c-0.8,0-1.5,0.7-1.5,1.5s0.7,1.5,1.5,1.5c0.8,0,1.5-0.7,1.5-1.5S12.8,6.5,12,6.5z M19,1H5C3.9,1,3,1.9,3,3v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V3C21,1.9,20.1,1,19,1z M12.5,17h-1c-1-4.1-4-5.8-4-9c0-2.5,2-4.5,4.5-4.5c2.5,0,4.5,2,4.5,4.5C16.5,11.2,13.5,12.9,12.5,17z"/></g> +<g id="file-upload"><polygon points="9,16 15,16 15,10 19,10 12,3 5,10 9,10 "/></g> +<g id="filter"><path d="M10,18h4v-2h-4V18z M3,6v2h18V6H3z M6,13h12v-2H6V13z"/></g> +<g id="flag"><polygon points="14.4,6 14,4 5,4 5,21 7,21 7,14 12.6,14 13,16 20,16 20,6 "/></g> +<g id="flip-to-back"><path d="M9,7H7l0,2h2V7z M9,11H7v2h2V11z M9,3C7.9,3,7,3.9,7,5h2V3z M13,15h-2v2h2V15z M19,3v2h2C21,3.9,20.1,3,19,3z M13,3h-2v2h2V3z M9,17v-2H7C7,16.1,7.9,17,9,17z M19,13h2v-2h-2V13z M19,9h2V7h-2V9z M19,17c1.1,0,2-0.9,2-2h-2V17z M5,7H3v2h0l0,10c0,1.1,0.9,2,2,2h12v-2H5V7z M15,5h2V3h-2V5z M15,17h2v-2h-2V17z"/></g> +<g id="flip-to-front"><path d="M3,13h2v-2H3L3,13z M3,17h2v-2H3V17z M5,21v-2H3C3,20.1,3.9,21,5,21z M3,9h2V7H3V9z M15,21h2v-2h-2V21z M19,3H9C7.9,3,7,3.9,7,5v2h0v2v6c0,1.1,0.9,2,2,2h5h4h1c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M19,15H9V5h10V15z M11,21h2v-2h-2V21z M7,21h2v-2H7V21z"/></g> +<g id="folder"><path d="M10,4H4C2.9,4,2,4.9,2,6l0,12c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V8c0-1.1-0.9-2-2-2h-8L10,4z"/></g> +<g id="folder-mydrive"><path d="M20,6h-8l-2-2H4C2.9,4,2,4.9,2,6l0,12c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V8C22,6.9,21.1,6,20,6z M11.5,17l-1.1-2.1l2.8-5l1.5,2.7L12.3,17H11.5z M18.3,17h-5.5l1.4-2.5h5.1l0.3,0.5L18.3,17z M13.8,9h2.4l2.8,5H16l-2.6-4.5L13.8,9z"/></g> +<g id="folder-open"><path d="M20,6h-8l-2-2H4C2.9,4,2,4.9,2,6l0,12c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V8C22,6.9,21.1,6,20,6z M20,18H4V8h16V18z"/></g> +<g id="folder-shared"><path d="M20,6h-8l-2-2H4C2.9,4,2,4.9,2,6l0,12c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V8C22,6.9,21.1,6,20,6z M15,9c1.1,0,2,0.9,2,2c0,1.1-0.9,2-2,2c-1.1,0-2-0.9-2-2C13,9.9,13.9,9,15,9z M19,17h-8v-1c0-1.3,2.7-2,4-2c1.3,0,4,0.7,4,2V17z"/></g> +<g id="forward"><polygon points="12,8 12,4 20,12 12,20 12,16 4,16 4,8 "/></g> +<g id="fullscreen"><path d="M7,14H5v5h5v-2H7V14z M5,10h2V7h3V5H5V10z M17,17h-3v2h5v-5h-2V17z M14,5v2h3v3h2V5H14z"/></g> +<g id="fullscreen-exit"><path d="M5,16h3v3h2v-5H5V16z M8,8H5v2h5V5H8V8z M14,19h2v-3h3v-2h-5V19z M16,8V5h-2v5h5V8H16z"/></g> +<g id="gesture"><path d="M4.6,6.9C5.3,6.2,6,5.5,6.3,5.7c0.5,0.2,0,1-0.3,1.5c-0.3,0.4-2.9,3.9-2.9,6.3c0,1.3,0.5,2.3,1.3,3c0.8,0.6,1.7,0.7,2.6,0.5c1.1-0.3,1.9-1.4,3.1-2.8c1.2-1.5,2.8-3.4,4.1-3.4c1.6,0,1.6,1,1.8,1.8c-3.8,0.6-5.4,3.7-5.4,5.4c0,1.7,1.4,3.1,3.2,3.1c1.6,0,4.3-1.3,4.7-6.1H21v-2.5h-2.5c-0.2-1.6-1.1-4.2-4-4.2c-2.2,0-4.2,1.9-4.9,2.8c-0.6,0.7-2.1,2.5-2.3,2.7c-0.3,0.3-0.7,0.8-1.1,0.8c-0.4,0-0.7-0.8-0.4-1.9c0.4-1.1,1.4-2.9,1.9-3.5C8.4,8,8.9,7.2,8.9,5.9C8.9,3.7,7.3,3,6.4,3C5.1,3,4,4,3.7,4.3C3.4,4.6,3.1,4.9,2.8,5.2L4.6,6.9z M13.9,18.6c-0.3,0-0.7-0.3-0.7-0.7c0-0.6,0.7-2.2,2.9-2.8C15.7,17.8,14.6,18.6,13.9,18.6z"/></g> +<g id="get-app"><path d="M19,9h-4V3H9v6H5l7,7L19,9z M5,18v2h14v-2H5z"/></g> +<g id="google"><path d="M16.3,13.4l-1.1-0.8c-0.4-0.3-0.8-0.7-0.8-1.4c0-0.7,0.5-1.3,1-1.6c1.3-1,2.6-2.1,2.6-4.3c0-2.1-1.3-3.3-2-3.9h1.7L18.9,0h-6.2C8.3,0,6.1,2.8,6.1,5.8c0,2.3,1.8,4.8,5,4.8h0.8c-0.1,0.3-0.4,0.8-0.4,1.3c0,1,0.4,1.4,0.9,2c-1.4,0.1-4,0.4-5.9,1.6c-1.8,1.1-2.3,2.6-2.3,3.7c0,2.3,2.1,4.5,6.6,4.5c5.4,0,8-3,8-5.9C18.8,15.7,17.7,14.6,16.3,13.4z M8.7,4.3c0-2.2,1.3-3.2,2.7-3.2c2.6,0,4,3.5,4,5.5c0,2.6-2.1,3.1-2.9,3.1C10,9.7,8.7,6.6,8.7,4.3z M12.3,22.3c-3.3,0-5.4-1.5-5.4-3.7c0-2.2,2-2.9,2.6-3.2c1.3-0.4,3-0.5,3.3-0.5c0.3,0,0.5,0,0.7,0c2.4,1.7,3.4,2.4,3.4,4C16.9,20.8,15,22.3,12.3,22.3z"/></g> +<g id="google-plus"><path d="M21,10V7h-2v3h-3v2h3v3h2v-3h3v-2H21z M13.3,13.4l-1.1-0.8c-0.4-0.3-0.8-0.7-0.8-1.4c0-0.7,0.5-1.3,1-1.6c1.3-1,2.6-2.1,2.6-4.3c0-2.1-1.3-3.3-2-3.9h1.7L15.9,0H9.7C5.3,0,3.1,2.8,3.1,5.8c0,2.3,1.8,4.8,5,4.8h0.8c-0.1,0.3-0.4,0.8-0.4,1.3c0,1,0.4,1.4,0.9,2c-1.4,0.1-4,0.4-5.9,1.6c-1.8,1.1-2.3,2.6-2.3,3.7c0,2.3,2.1,4.5,6.6,4.5c5.4,0,8-3,8-5.9C15.8,15.7,14.7,14.6,13.3,13.4z M5.7,4.3c0-2.2,1.3-3.2,2.7-3.2c2.6,0,4,3.5,4,5.5c0,2.6-2.1,3.1-2.9,3.1C7,9.7,5.7,6.6,5.7,4.3z M9.3,22.3c-3.3,0-5.4-1.5-5.4-3.7c0-2.2,2-2.9,2.6-3.2c1.3-0.4,3-0.5,3.3-0.5c0.3,0,0.5,0,0.7,0c2.4,1.7,3.4,2.4,3.4,4C13.9,20.8,12,22.3,9.3,22.3z"/></g> +<g id="grade"><polygon points="12,17.3 18.2,21 16.5,14 22,9.2 14.8,8.6 12,2 9.2,8.6 2,9.2 7.5,14 5.8,21 "/></g> +<g id="group-work"><path d="M12,2C6.5,2,2,6.5,2,12c0,5.5,4.5,10,10,10c5.5,0,10-4.5,10-10C22,6.5,17.5,2,12,2z M8,17.5c-1.4,0-2.5-1.1-2.5-2.5s1.1-2.5,2.5-2.5s2.5,1.1,2.5,2.5S9.4,17.5,8,17.5z M9.5,8c0-1.4,1.1-2.5,2.5-2.5s2.5,1.1,2.5,2.5s-1.1,2.5-2.5,2.5S9.5,9.4,9.5,8z M16,17.5c-1.4,0-2.5-1.1-2.5-2.5s1.1-2.5,2.5-2.5s2.5,1.1,2.5,2.5S17.4,17.5,16,17.5z"/></g> +<g id="help"><path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10c5.5,0,10-4.5,10-10S17.5,2,12,2z M13,19h-2v-2h2V19z M15.1,11.3l-0.9,0.9C13.4,12.9,13,13.5,13,15h-2v-0.5c0-1.1,0.4-2.1,1.2-2.8l1.2-1.3C13.8,10.1,14,9.6,14,9c0-1.1-0.9-2-2-2c-1.1,0-2,0.9-2,2H8c0-2.2,1.8-4,4-4c2.2,0,4,1.8,4,4C16,9.9,15.6,10.7,15.1,11.3z"/></g> +<g id="highlight-remove"><path d="M14.6,8L12,10.6L9.4,8L8,9.4l2.6,2.6L8,14.6L9.4,16l2.6-2.6l2.6,2.6l1.4-1.4L13.4,12L16,9.4L14.6,8z M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10c5.5,0,10-4.5,10-10S17.5,2,12,2z M12,20c-4.4,0-8-3.6-8-8s3.6-8,8-8s8,3.6,8,8S16.4,20,12,20z"/></g> +<g id="history"><path opacity="0.9" d="M13,3c-5,0-9,4-9,9H1l3.9,3.9c0,0,0,0.1,0.1,0.1l4-4H6c0-3.9,3.1-7,7-7c3.9,0,7,3.1,7,7s-3.1,7-7,7c-1.9,0-3.7-0.8-4.9-2.1l-1.4,1.4C8.3,20,10.5,21,13,21c5,0,9-4,9-9S18,3,13,3z M12,8v5l4.3,2.5l0.7-1.2l-3.5-2.1V8H12z"/></g> +<g id="home"><polygon points="10,20 10,14 14,14 14,20 19,20 19,12 22,12 12,3 2,12 5,12 5,20 "/></g> +<g id="https"><path d="M18,8h-1V6c0-2.8-2.2-5-5-5C9.2,1,7,3.2,7,6v2H6c-1.1,0-2,0.9-2,2v10c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2V10C20,8.9,19.1,8,18,8z M12,17c-1.1,0-2-0.9-2-2s0.9-2,2-2c1.1,0,2,0.9,2,2S13.1,17,12,17z M15.1,8H8.9V6c0-1.7,1.4-3.1,3.1-3.1c1.7,0,3.1,1.4,3.1,3.1V8z"/></g> +<g id="inbox"><path d="M19,3H5C3.9,3,3,3.9,3,5l0,14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M19,15h-4c0,1.7-1.3,3-3,3c-1.7,0-3-1.3-3-3H5V5h14V15z M16,10h-2V7h-4v3H8l4,4L16,10z"/></g> +<g id="info"><path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10c5.5,0,10-4.5,10-10S17.5,2,12,2z M13,17h-2v-6h2V17z M13,9h-2V7h2V9z"/></g> +<g id="info-outline"><path d="M11,17h2v-6h-2V17z M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10c5.5,0,10-4.5,10-10S17.5,2,12,2z M12,20c-4.4,0-8-3.6-8-8s3.6-8,8-8c4.4,0,8,3.6,8,8S16.4,20,12,20z M11,9h2V7h-2V9z"/></g> +<g id="input"><path d="M21,3H3C1.9,3,1,3.9,1,5v4h2V5h18v14H3v-4H1v4c0,1.1,0.9,2,2,2h18c1.1,0,2-0.9,2-2V5C23,3.9,22.1,3,21,3z M11,16l4-4l-4-4v3H1v2h10V16z"/></g> +<g id="invert-colors"><path d="M17.7,7.9L12,2.3l0,0v0L6.3,7.9c-3.1,3.1-3.1,8.2,0,11.3c1.6,1.6,3.6,2.3,5.7,2.3c2,0,4.1-0.8,5.7-2.3C20.8,16.1,20.8,11.1,17.7,7.9z M12,19.6L12,19.6c-1.6,0-3.1-0.6-4.2-1.8C6.6,16.7,6,15.2,6,13.6c0-1.6,0.6-3.1,1.8-4.2L12,5.1L12,19.6z"/></g> +<g id="keep"><path d="M16,12V4h1V2H7v2h1v8l-2,2v2h5.2v6h1.6v-6H18v-2L16,12z"/></g> +<g id="label"><path d="M17.6,5.8C17.3,5.3,16.7,5,16,5L5,5C3.9,5,3,5.9,3,7v10c0,1.1,0.9,2,2,2l11,0c0.7,0,1.3-0.3,1.6-0.8L22,12L17.6,5.8z"/></g> +<g id="label-outline"><path d="M17.6,5.8C17.3,5.3,16.7,5,16,5L5,5C3.9,5,3,5.9,3,7v10c0,1.1,0.9,2,2,2l11,0c0.7,0,1.3-0.3,1.6-0.8L22,12L17.6,5.8z M16,17H5V7h11l3.5,5L16,17z"/></g> +<g id="language"><path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10c5.5,0,10-4.5,10-10S17.5,2,12,2z M18.9,8H16c-0.3-1.3-0.8-2.4-1.4-3.6C16.4,5.1,18,6.3,18.9,8z M12,4c0.8,1.2,1.5,2.5,1.9,4h-3.8C10.5,6.6,11.2,5.2,12,4z M4.3,14C4.1,13.4,4,12.7,4,12s0.1-1.4,0.3-2h3.4c-0.1,0.7-0.1,1.3-0.1,2s0.1,1.3,0.1,2H4.3z M5.1,16H8c0.3,1.3,0.8,2.4,1.4,3.6C7.6,18.9,6,17.7,5.1,16z M8,8H5.1c1-1.7,2.5-2.9,4.3-3.6C8.8,5.6,8.3,6.7,8,8z M12,20c-0.8-1.2-1.5-2.5-1.9-4h3.8C13.5,17.4,12.8,18.8,12,20z M14.3,14H9.7c-0.1-0.7-0.2-1.3-0.2-2s0.1-1.3,0.2-2h4.7c0.1,0.7,0.2,1.3,0.2,2S14.4,13.3,14.3,14z M14.6,19.6c0.6-1.1,1.1-2.3,1.4-3.6h2.9C18,17.7,16.4,18.9,14.6,19.6z M16.4,14c0.1-0.7,0.1-1.3,0.1-2s-0.1-1.3-0.1-2h3.4c0.2,0.6,0.3,1.3,0.3,2s-0.1,1.4-0.3,2H16.4z"/></g> +<g id="launch"><path d="M19,19H5V5h7V3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2v-7h-2V19z M14,3v2h3.6l-9.8,9.8l1.4,1.4L19,6.4V10h2V3H14z"/></g> +<g id="link"><path d="M3.9,12c0-1.7,1.4-3.1,3.1-3.1h4V7H7c-2.8,0-5,2.2-5,5s2.2,5,5,5h4v-1.9H7C5.3,15.1,3.9,13.7,3.9,12z M8,13h8v-2H8V13z M17,7h-4v1.9h4c1.7,0,3.1,1.4,3.1,3.1s-1.4,3.1-3.1,3.1h-4V17h4c2.8,0,5-2.2,5-5S19.8,7,17,7z"/></g> +<g id="list"><path d="M3,13h2v-2H3V13z M3,17h2v-2H3V17z M3,9h2V7H3V9z M7,13h14v-2H7V13z M7,17h14v-2H7V17z M7,7v2h14V7H7z"/></g> +<g id="lock"><path d="M18,8h-1V6c0-2.8-2.2-5-5-5C9.2,1,7,3.2,7,6v2H6c-1.1,0-2,0.9-2,2v10c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2V10C20,8.9,19.1,8,18,8z M12,17c-1.1,0-2-0.9-2-2s0.9-2,2-2c1.1,0,2,0.9,2,2S13.1,17,12,17z M15.1,8H8.9V6c0-1.7,1.4-3.1,3.1-3.1c1.7,0,3.1,1.4,3.1,3.1V8z"/></g> +<g id="lock-open"><path d="M12,17c1.1,0,2-0.9,2-2s-0.9-2-2-2c-1.1,0-2,0.9-2,2S10.9,17,12,17z M18,8h-1V6c0-2.8-2.2-5-5-5C9.2,1,7,3.2,7,6h1.9c0-1.7,1.4-3.1,3.1-3.1c1.7,0,3.1,1.4,3.1,3.1v2H6c-1.1,0-2,0.9-2,2v10c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2V10C20,8.9,19.1,8,18,8z M18,20H6V10h12V20z"/></g> +<g id="lock-outline"><path d="M18,8h-1V6c0-2.8-2.2-5-5-5C9.2,1,7,3.2,7,6v2H6c-1.1,0-2,0.9-2,2v10c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2V10C20,8.9,19.1,8,18,8z M12,2.9c1.7,0,3.1,1.4,3.1,3.1v2H9V6H8.9C8.9,4.3,10.3,2.9,12,2.9z M18,20H6V10h12V20z M12,17c1.1,0,2-0.9,2-2s-0.9-2-2-2c-1.1,0-2,0.9-2,2S10.9,17,12,17z"/></g> +<g id="loyalty"><path d="M21.4,11.6l-9-9C12.1,2.2,11.6,2,11,2H4C2.9,2,2,2.9,2,4v7c0,0.6,0.2,1.1,0.6,1.4l9,9c0.4,0.4,0.9,0.6,1.4,0.6c0.6,0,1.1-0.2,1.4-0.6l7-7c0.4-0.4,0.6-0.9,0.6-1.4C22,12.4,21.8,11.9,21.4,11.6z M5.5,7C4.7,7,4,6.3,4,5.5S4.7,4,5.5,4S7,4.7,7,5.5S6.3,7,5.5,7z M17.3,15.3L13,19.5l-4.3-4.3l0,0C8.3,14.8,8,14.2,8,13.5c0-1.4,1.1-2.5,2.5-2.5c0.7,0,1.3,0.3,1.8,0.7l0.7,0.7l0.7-0.7c0.5-0.5,1.1-0.7,1.8-0.7c1.4,0,2.5,1.1,2.5,2.5C18,14.2,17.7,14.8,17.3,15.3L17.3,15.3z"/></g> +<g id="mail"><path d="M20,4H4C2.9,4,2,4.9,2,6l0,12c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V6C22,4.9,21.1,4,20,4z M20,8l-8,5L4,8V6l8,5l8-5V8z"/></g> +<g id="markunread"><path d="M20,6H10v6H8V4h6V0H6v6H4C2.9,6,2,6.9,2,8l0,12c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V8C22,6.9,21.1,6,20,6z"/></g> +<g id="markunread"><path d="M22,6l2-2l-2-2l-2,2l-2-2l-2,2l-2-2l-2,2l-2-2L8,4L6,2L4,4L2,2L0,4l2,2L0,8l2,2l-2,2l2,2l-2,2l2,2l-2,2l2,2l2-2l2,2l2-2l2,2l2-2l2,2l2-2l2,2l2-2l2,2l2-2l-2-2l2-2l-2-2l2-2l-2-2l2-2L22,6z M20,8l-8,5L4,8V6l8,5l8-5V8z"/></g> +<g id="menu"><path d="M3,18h18v-2H3V18z M3,13h18v-2H3V13z M3,6v2h18V6H3z"/></g> +<g id="more-horiz"><path d="M6,10c-1.1,0-2,0.9-2,2s0.9,2,2,2c1.1,0,2-0.9,2-2S7.1,10,6,10z M18,10c-1.1,0-2,0.9-2,2s0.9,2,2,2c1.1,0,2-0.9,2-2S19.1,10,18,10z M12,10c-1.1,0-2,0.9-2,2s0.9,2,2,2c1.1,0,2-0.9,2-2S13.1,10,12,10z"/></g> +<g id="more-vert"><path d="M12,8c1.1,0,2-0.9,2-2s-0.9-2-2-2c-1.1,0-2,0.9-2,2S10.9,8,12,8z M12,10c-1.1,0-2,0.9-2,2s0.9,2,2,2c1.1,0,2-0.9,2-2S13.1,10,12,10z M12,16c-1.1,0-2,0.9-2,2s0.9,2,2,2c1.1,0,2-0.9,2-2S13.1,16,12,16z"/></g> +<g id="note-add"><path d="M14,2H6C4.9,2,4,2.9,4,4l0,16c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2V8L14,2z M16,16h-3v3h-2v-3H8v-2h3v-3h2v3h3V16z M13,9V3.5L18.5,9H13z"/></g> +<g id="open-in-browser"><path d="M19,4H5C3.9,4,3,4.9,3,6v12c0,1.1,0.9,2,2,2h3v-2H5V8h14v10h-3v2h3c1.1,0,2-0.9,2-2V6C21,4.9,20.1,4,19,4z M12,10l-5,5h3v5h4v-5h3L12,10z"/></g> +<g id="open-in-new"><path d="M19,19H5V5h7V3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2v-7h-2V19z M14,3v2h3.6l-9.8,9.8l1.4,1.4L19,6.4V10h2V3H14z"/></g> +<g id="open-with"><path d="M10,9h4V6h3l-5-5L7,6h3V9z M9,10H6V7l-5,5l5,5v-3h3V10z M23,12l-5-5v3h-3v4h3v3L23,12z M14,15h-4v3H7l5,5l5-5h-3V15z"/></g> +<g id="payment"><path d="M20,4H4C2.9,4,2,4.9,2,6l0,12c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V6C22,4.9,21.1,4,20,4z M20,18H4v-6h16V18z M20,8H4V6h16V8z"/></g> +<g id="perm-camera-mic"><path d="M20,5h-3.2L15,3H9L7.2,5H4C2.9,5,2,5.9,2,7v12c0,1.1,0.9,2,2,2h7v-2.1C8.2,18.4,6,16,6,13h2c0,2.2,1.8,4,4,4s4-1.8,4-4h2c0,3-2.2,5.4-5,5.9V21h7c1.1,0,2-0.9,2-2V7C22,5.9,21.1,5,20,5z M14,13c0,1.1-0.9,2-2,2s-2-0.9-2-2V9c0-1.1,0.9-2,2-2s2,0.9,2,2V13z"/></g> +<g id="perm-contact-cal"><path d="M19,3h-1V1h-2v2H8V1H6v2H5C3.9,3,3,3.9,3,5l0,14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M12,6c1.7,0,3,1.3,3,3c0,1.7-1.3,3-3,3s-3-1.3-3-3C9,7.3,10.3,6,12,6z M18,18H6v-1c0-2,4-3.1,6-3.1s6,1.1,6,3.1V18z"/></g> +<g id="perm-data-setting"><path d="M19,11.5c0.3,0,0.7,0,1,0.1V0L0,20h11.6c0-0.3-0.1-0.7-0.1-1C11.5,14.9,14.9,11.5,19,11.5z M22.7,19.5c0-0.2,0-0.3,0-0.5c0-0.2,0-0.3,0-0.5l1.1-0.8c0.1-0.1,0.1-0.2,0.1-0.3l-1-1.7c-0.1-0.1-0.2-0.2-0.3-0.1L21.3,16c-0.3-0.2-0.5-0.4-0.8-0.5l-0.2-1.3c0-0.1-0.1-0.2-0.2-0.2h-2c-0.1,0-0.2,0.1-0.2,0.2l-0.2,1.3c-0.3,0.1-0.6,0.3-0.8,0.5l-1.2-0.5c-0.1,0-0.2,0-0.3,0.1l-1,1.7c-0.1,0.1,0,0.2,0.1,0.3l1.1,0.8c0,0.2,0,0.3,0,0.5c0,0.2,0,0.3,0,0.5l-1.1,0.8c-0.1,0.1-0.1,0.2-0.1,0.3l1,1.7c0.1,0.1,0.2,0.2,0.3,0.1l1.2-0.5c0.3,0.2,0.5,0.4,0.8,0.5l0.2,1.3c0,0.1,0.1,0.2,0.2,0.2h2c0.1,0,0.2-0.1,0.2-0.2l0.2-1.3c0.3-0.1,0.6-0.3,0.8-0.5l1.2,0.5c0.1,0,0.2,0,0.3-0.1l1-1.7c0.1-0.1,0-0.2-0.1-0.3L22.7,19.5z M19,20.5c-0.8,0-1.5-0.7-1.5-1.5s0.7-1.5,1.5-1.5s1.5,0.7,1.5,1.5S19.8,20.5,19,20.5z"/></g> +<g id="perm-device-info"><path d="M13,7h-2v2h2V7z M13,11h-2v6h2V11z M17,1L7,1C5.9,1,5,1.9,5,3v18c0,1.1,0.9,2,2,2h10c1.1,0,2-0.9,2-2V3C19,1.9,18.1,1,17,1z M17,19H7V5h10V19z"/></g> +<g id="perm-identity"><path d="M12,5.9c1.2,0,2.1,0.9,2.1,2.1s-0.9,2.1-2.1,2.1S9.9,9.2,9.9,8S10.8,5.9,12,5.9 M12,14.9c3,0,6.1,1.5,6.1,2.1v1.1H5.9V17C5.9,16.4,9,14.9,12,14.9 M12,4C9.8,4,8,5.8,8,8c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,5.8,14.2,4,12,4L12,4z M12,13c-2.7,0-8,1.3-8,4v3h16v-3C20,14.3,14.7,13,12,13L12,13z"/></g> +<g id="perm-media"><path d="M2,6H0v5h0l0,9c0,1.1,0.9,2,2,2h18v-2H2V6z M22,4h-8l-2-2H6C4.9,2,4,2.9,4,4l0,12c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V6C24,4.9,23.1,4,22,4z M7,15l4.5-6l3.5,4.5l2.5-3L21,15H7z"/></g> +<g id="perm-phone-msg"><path d="M20,15.5c-1.2,0-2.4-0.2-3.6-0.6c-0.3-0.1-0.7,0-1,0.2l-2.2,2.2c-2.8-1.4-5.1-3.8-6.6-6.6l2.2-2.2c0.3-0.3,0.4-0.7,0.2-1C8.7,6.4,8.5,5.2,8.5,4c0-0.6-0.4-1-1-1H4C3.5,3,3,3.4,3,4c0,9.4,7.6,17,17,17c0.6,0,1-0.4,1-1v-3.5C21,15.9,20.6,15.5,20,15.5z M12,3v10l3-3h6V3H12z"/></g> +<g id="perm-scan-wifi"><path d="M12,3C7,3,3.2,4.9,0,7.2L12,22L24,7.3C20.9,4.9,17.1,3,12,3z M13,16h-2v-6h2V16z M11,8V6h2v2H11z"/></g> +<g id="picture-in-picture"><path d="M19,7h-8v6h8V7z M21,3H3C1.9,3,1,3.9,1,5v14c0,1.1,0.9,2,2,2h18c1.1,0,2-0.9,2-2V5C23,3.9,22.1,3,21,3z M21,19H3V5h18V19z"/></g> +<g id="polymer"><polygon points="19,4 15,4 7.1,16.6 4.5,12 9,4 5,4 0.5,12 5,20 9,20 16.9,7.4 19.5,12 15,20 19,20 23.5,12 "/></g> +<g id="print"><path d="M19,8H5c-1.7,0-3,1.3-3,3v6h4v4h12v-4h4v-6C22,9.3,20.7,8,19,8z M16,19H8v-5h8V19z M19,12c-0.6,0-1-0.4-1-1s0.4-1,1-1c0.6,0,1,0.4,1,1S19.6,12,19,12z M18,3H6v4h12V3z"/></g> +<g id="query-builder"><path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10c5.5,0,10-4.5,10-10S17.5,2,12,2z M12,20c-4.4,0-8-3.6-8-8s3.6-8,8-8c4.4,0,8,3.6,8,8S16.4,20,12,20z"/><polygon points="12.5,7 11,7 11,13 16.2,16.2 17,14.9 12.5,12.3 "/></g> +<g id="question-answer"><path d="M21,6h-2v9H6v2c0,0.6,0.4,1,1,1h11l4,4V7C22,6.4,21.6,6,21,6z M17,12V3c0-0.6-0.4-1-1-1H3C2.4,2,2,2.4,2,3v14l4-4h10C16.6,13,17,12.6,17,12z"/></g> +<g id="radio-button-off"><path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10c5.5,0,10-4.5,10-10S17.5,2,12,2z M12,20c-4.4,0-8-3.6-8-8s3.6-8,8-8c4.4,0,8,3.6,8,8S16.4,20,12,20z"/></g> +<g id="radio-button-on"><path d="M12,7c-2.8,0-5,2.2-5,5s2.2,5,5,5c2.8,0,5-2.2,5-5S14.8,7,12,7z M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10c5.5,0,10-4.5,10-10S17.5,2,12,2z M12,20c-4.4,0-8-3.6-8-8s3.6-8,8-8c4.4,0,8,3.6,8,8S16.4,20,12,20z"/></g> +<g id="receipt"><path d="M18,17H6v-2h12V17z M18,13H6v-2h12V13z M18,9H6V7h12V9z M3,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,2L7.5,3.5L6,2L4.5,3.5L3,2V22z"/></g> +<g id="redeem"><path d="M20,6h-2.2C17.9,5.7,18,5.4,18,5c0-1.7-1.3-3-3-3c-1,0-2,0.5-2.5,1.3l0,0L12,4l-0.5-0.7l0,0C11,2.5,10.1,2,9,2C7.4,2,6,3.3,6,5c0,0.4,0.1,0.7,0.2,1H4C2.9,6,2,6.9,2,8l0,11c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V8C22,6.9,21.1,6,20,6z M15,4c0.6,0,1,0.4,1,1s-0.4,1-1,1s-1-0.4-1-1S14.5,4,15,4z M9,4c0.6,0,1,0.4,1,1S9.6,6,9,6S8,5.6,8,5S8.5,4,9,4z M20,19H4v-2h16V19z M20,14H4V8h5.1L7,10.8L8.6,12L11,8.8l1-1.4l1,1.4l2.4,3.2l1.6-1.2L14.9,8H20V14z"/></g> +<g id="refresh"><path d="M17.6,6.4C16.2,4.9,14.2,4,12,4c-4.4,0-8,3.6-8,8s3.6,8,8,8c3.7,0,6.8-2.6,7.7-6h-2.1c-0.8,2.3-3,4-5.6,4c-3.3,0-6-2.7-6-6s2.7-6,6-6c1.7,0,3.1,0.7,4.2,1.8L13,11h7V4L17.6,6.4z"/></g> +<g id="reminder"><path d="M16.9,13c1.3-1.3,2.1-3,2.1-5c0-3.9-3.1-7-7-7C8.1,1,5,4.1,5,8c0,2,0.8,3.7,2.1,5l0,0l3.5,3.5L6,21.1l1.4,1.4L16.9,13z M15.5,11.5L15.5,11.5L12,15.1l-3.5-3.5l0,0l0,0C7.6,10.6,7,9.4,7,8c0-2.8,2.2-5,5-5c2.8,0,5,2.2,5,5C17,9.4,16.4,10.6,15.5,11.5L15.5,11.5z M13.4,19.3l3.2,3.2l1.4-1.4l-3.2-3.2L13.4,19.3z"/></g> +<g id="remove"><path d="M19,13H5v-2h14V13z"/></g> +<g id="remove-circle"><path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10c5.5,0,10-4.5,10-10S17.5,2,12,2z M17,13H7v-2h10V13z"/></g> +<g id="remove-circle-outline"><path d="M7,11v2h10v-2H7z M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10c5.5,0,10-4.5,10-10S17.5,2,12,2z M12,20c-4.4,0-8-3.6-8-8s3.6-8,8-8c4.4,0,8,3.6,8,8S16.4,20,12,20z"/></g> +<g id="reorder"><path d="M4,16h16v-2H4V16z M4,9v2h16V9H4z"/></g> +<g id="reply"><path d="M10,9V5l-7,7l7,7v-4.1c5,0,8.5,1.6,11,5.1C20,15,17,10,10,9z"/></g> +<g id="reply-all"><path d="M7,8V5l-7,7l7,7v-3l-4-4L7,8z M13,9V5l-7,7l7,7v-4.1c5,0,8.5,1.6,11,5.1C23,15,20,10,13,9z"/></g> +<g id="report"><path d="M15.7,3H8.3L3,8.3v7.5L8.3,21h7.5l5.3-5.3V8.3L15.7,3z M12,17.3c-0.7,0-1.3-0.6-1.3-1.3c0-0.7,0.6-1.3,1.3-1.3c0.7,0,1.3,0.6,1.3,1.3C13.3,16.7,12.7,17.3,12,17.3z M13,13h-2V7h2V13z"/></g> +<g id="report-problem"><path d="M1,21h22L12,2L1,21z M13,18h-2v-2h2V18z M13,14h-2v-4h2V14z"/></g> +<g id="restore"><path d="M13,3c-5,0-9,4-9,9H1l3.9,3.9c0,0,0,0.1,0.1,0.1l4-4H6c0-3.9,3.1-7,7-7c3.9,0,7,3.1,7,7s-3.1,7-7,7c-1.9,0-3.7-0.8-4.9-2.1l-1.4,1.4C8.3,20,10.5,21,13,21c5,0,9-4,9-9S18,3,13,3z M12,8v5l4.3,2.5l0.7-1.2l-3.5-2.1V8H12z"/></g> +<g id="room"><path d="M12,2C8.1,2,5,5.1,5,9c0,5.2,7,13,7,13s7-7.8,7-13C19,5.1,15.9,2,12,2z M12,11.5c-1.4,0-2.5-1.1-2.5-2.5s1.1-2.5,2.5-2.5c1.4,0,2.5,1.1,2.5,2.5S13.4,11.5,12,11.5z"/></g> +<g id="rotate-left"><path d="M7.1,8.5L5.7,7.1C4.8,8.3,4.2,9.6,4.1,11h2C6.2,10.1,6.6,9.3,7.1,8.5z M6.1,13h-2c0.2,1.4,0.7,2.7,1.6,3.9l1.4-1.4C6.6,14.7,6.2,13.9,6.1,13z M7.1,18.3c1.2,0.9,2.5,1.4,3.9,1.6v-2c-0.9-0.1-1.7-0.5-2.5-1L7.1,18.3z M13,4.1V1L8.5,5.5L13,10V6.1c2.8,0.5,5,2.9,5,5.9s-2.2,5.4-5,5.9v2c3.9-0.5,7-3.9,7-7.9S16.9,4.6,13,4.1z"/></g> +<g id="rotate-right"><path d="M15.5,5.5L11,1v3.1C7.1,4.6,4,7.9,4,12s3.1,7.4,7,7.9v-2C8.2,17.4,6,15,6,12s2.2-5.4,5-5.9V10L15.5,5.5z M19.9,11c-0.2-1.4-0.7-2.7-1.6-3.9l-1.4,1.4c0.5,0.8,0.9,1.6,1,2.5H19.9z M13,17.9v2c1.4-0.2,2.7-0.7,3.9-1.6l-1.4-1.4C14.7,17.4,13.9,17.8,13,17.9z M16.9,15.5l1.4,1.4c0.9-1.2,1.5-2.5,1.6-3.9h-2C17.8,13.9,17.4,14.7,16.9,15.5z"/></g> +<g id="rotation-3d"><path d="M11,14v-1c0-0.6-0.4-1-1-1c0.6,0,1-0.4,1-1v-1c0-1.1-0.9-2-2-2H6v2h3v1H7v2h2v1l0,0l0,0v0h0H6v2h3C10.1,16,11,15.1,11,14z M15,8h-3v8h3c1.7,0,3-1.3,3-3v-2C18,9.3,16.7,8,15,8z M16,13c0,0.6-0.4,1-1,1h-1v-4h1c0.6,0,1,0.4,1,1V13z M12,0c-0.2,0-0.4,0-0.7,0l3.8,3.8l1.3-1.3c3.3,1.5,5.6,4.7,6,8.5h1.5C23.4,4.8,18.3,0,12,0z M7.5,21.5c-3.3-1.5-5.6-4.7-6-8.5H0.1C0.6,19.2,5.7,24,12,24c0.2,0,0.4,0,0.7,0l-3.8-3.8L7.5,21.5z"/></g> +<g id="save"><path d="M17,3H5C3.9,3,3,3.9,3,5l0,14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V7L17,3z M12,19c-1.7,0-3-1.3-3-3s1.3-3,3-3c1.7,0,3,1.3,3,3S13.7,19,12,19z M15,9H5V5h10V9z"/></g> +<g id="schedule"><path fill-opacity="0.9" d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10c5.5,0,10-4.5,10-10S17.5,2,12,2z M12,20c-4.4,0-8-3.6-8-8s3.6-8,8-8c4.4,0,8,3.6,8,8S16.4,20,12,20z"/><polygon fill-opacity="0.9" points="12.5,7 11,7 11,13 16.2,16.2 17,14.9 12.5,12.2 "/></g> +<g id="search"><path d="M15.5,14h-0.8l-0.3-0.3c1-1.1,1.6-2.6,1.6-4.2C16,5.9,13.1,3,9.5,3C5.9,3,3,5.9,3,9.5S5.9,16,9.5,16c1.6,0,3.1-0.6,4.2-1.6l0.3,0.3v0.8l5,5l1.5-1.5L15.5,14z M9.5,14C7,14,5,12,5,9.5S7,5,9.5,5C12,5,14,7,14,9.5S12,14,9.5,14z"/></g> +<g id="select-all"><path d="M3,5h2V3C3.9,3,3,3.9,3,5z M3,13h2v-2H3V13z M7,21h2v-2H7V21z M3,9h2V7H3V9z M13,3h-2v2h2V3z M19,3v2h2C21,3.9,20.1,3,19,3z M5,21v-2H3C3,20.1,3.9,21,5,21z M3,17h2v-2H3V17z M9,3H7v2h2V3z M11,21h2v-2h-2V21z M19,13h2v-2h-2V13z M19,21c1.1,0,2-0.9,2-2h-2V21z M19,9h2V7h-2V9z M19,17h2v-2h-2V17z M15,21h2v-2h-2V21z M15,5h2V3h-2V5z M7,17h10V7H7V17z M9,9h6v6H9V9z"/></g> +<g id="send"><polygon points="2,21 23,12 2,3 2,10 17,12 2,14 "/></g> +<g id="send-money"><path d="M2,12c0-2.6,1.7-4.8,4-5.7V4.3c-3.4,0.9-6,4-6,7.7s2.6,6.8,6,7.7v-2.1C3.7,16.8,2,14.6,2,12z M24,12l-4-4v3h-7v2h7v3L24,12z M14,18c-3.3,0-6-2.7-6-6s2.7-6,6-6c1.7,0,3.2,0.7,4.2,1.8l1.4-1.4C18.2,4.9,16.2,4,14,4c-4.4,0-8,3.6-8,8s3.6,8,8,8c2.2,0,4.2-0.9,5.7-2.3l-1.4-1.4C17.2,17.3,15.7,18,14,18z"/></g> +<g id="settings"><path d="M19.4,13c0-0.3,0.1-0.6,0.1-1s0-0.7-0.1-1l2.1-1.7c0.2-0.2,0.2-0.4,0.1-0.6l-2-3.5C19.5,5.1,19.3,5,19,5.1l-2.5,1c-0.5-0.4-1.1-0.7-1.7-1l-0.4-2.6C14.5,2.2,14.2,2,14,2h-4C9.8,2,9.5,2.2,9.5,2.4L9.1,5.1C8.5,5.3,8,5.7,7.4,6.1L5,5.1C4.7,5,4.5,5.1,4.3,5.3l-2,3.5C2.2,8.9,2.3,9.2,2.5,9.4L4.6,11c0,0.3-0.1,0.6-0.1,1s0,0.7,0.1,1l-2.1,1.7c-0.2,0.2-0.2,0.4-0.1,0.6l2,3.5C4.5,18.9,4.7,19,5,18.9l2.5-1c0.5,0.4,1.1,0.7,1.7,1l0.4,2.6c0,0.2,0.2,0.4,0.5,0.4h4c0.2,0,0.5-0.2,0.5-0.4l0.4-2.6c0.6-0.3,1.2-0.6,1.7-1l2.5,1c0.2,0.1,0.5,0,0.6-0.2l2-3.5c0.1-0.2,0.1-0.5-0.1-0.6L19.4,13z M12,15.5c-1.9,0-3.5-1.6-3.5-3.5s1.6-3.5,3.5-3.5s3.5,1.6,3.5,3.5S13.9,15.5,12,15.5z"/></g> +<g id="settings-applications"><path d="M12,10c-1.1,0-2,0.9-2,2s0.9,2,2,2s2-0.9,2-2S13.1,10,12,10z M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M17.2,12c0,0.2,0,0.5,0,0.7l1.5,1.2c0.1,0.1,0.2,0.3,0.1,0.4l-1.4,2.4c-0.1,0.2-0.3,0.2-0.4,0.2l-1.7-0.7c-0.4,0.3-0.8,0.5-1.2,0.7l-0.3,1.9c0,0.2-0.2,0.3-0.3,0.3h-2.8c-0.2,0-0.3-0.1-0.3-0.3L10,16.9c-0.4-0.2-0.8-0.4-1.2-0.7l-1.7,0.7c-0.2,0.1-0.3,0-0.4-0.2l-1.4-2.4c-0.1-0.2,0-0.3,0.1-0.4l1.5-1.2c0-0.2,0-0.5,0-0.7s0-0.5,0-0.7l-1.5-1.2c-0.1-0.1-0.2-0.3-0.1-0.4l1.4-2.4c0.1-0.2,0.3-0.2,0.4-0.2l1.7,0.7C9.2,7.6,9.6,7.3,10,7.1l0.3-1.9c0-0.2,0.2-0.3,0.3-0.3h2.8c0.2,0,0.3,0.1,0.3,0.3L14,7.1c0.4,0.2,0.8,0.4,1.2,0.7l1.7-0.7c0.2-0.1,0.3,0,0.4,0.2l1.4,2.4c0.1,0.2,0,0.3-0.1,0.4l-1.5,1.2C17.2,11.5,17.2,11.8,17.2,12z"/></g> +<g id="settings-backup-restore"><path d="M14,12c0-1.1-0.9-2-2-2s-2,0.9-2,2s0.9,2,2,2S14,13.1,14,12z M12,3c-5,0-9,4-9,9H0l4,4l4-4H5c0-3.9,3.1-7,7-7s7,3.1,7,7s-3.1,7-7,7c-1.5,0-2.9-0.5-4.1-1.3l-1.4,1.4C8,20.3,9.9,21,12,21c5,0,9-4,9-9S17,3,12,3z"/></g> +<g id="settings-bluetooth"><path d="M11,24h2v-2h-2V24z M7,24h2v-2H7V24z M15,24h2v-2h-2V24z M17.7,5.7L12,0h-1v7.6L6.4,3L5,4.4l5.6,5.6L5,15.6L6.4,17l4.6-4.6V20h1l5.7-5.7L13.4,10L17.7,5.7z M13,3.8l1.9,1.9L13,7.6V3.8z M14.9,14.3L13,16.2v-3.8L14.9,14.3z"/></g> +<g id="settings-cell"><path d="M7,24h2v-2H7V24z M11,24h2v-2h-2V24z M15,24h2v-2h-2V24z M16,0L8,0C6.9,0,6,0.9,6,2v16c0,1.1,0.9,2,2,2h8c1.1,0,2-0.9,2-2V2C18,0.9,17.1,0,16,0z M16,16H8V4h8V16z"/></g> +<g id="settings-display"><path d="M21,19H3V5h18V19z M21,3H3C1.9,3,1,3.9,1,5v14c0,1.1,0.9,2,2,2h18c1.1,0,2-0.9,2-2V5C23,3.9,22.1,3,21,3L21,3z"/><path d="M10,12c0-1.1,0.9-2,2-2V8c0.3,0,0.7,0.1,1,0.1V6h-2v2.1c-0.4,0.1-0.7,0.2-1,0.4L8.5,7.1L7.1,8.5L8.6,10c-0.2,0.3-0.3,0.7-0.4,1H6v2h2.1c0.1,0.4,0.2,0.7,0.4,1l-1.5,1.5l1.4,1.4l1.5-1.5c0.3,0.2,0.7,0.3,1,0.4V18h2v-2.1c-0.3,0.1-0.7,0.1-1,0.1v-2C10.9,14,10,13.1,10,12z M15.4,10l1.5-1.5l-1.4-1.4L14,8.6C14.6,8.9,15.1,9.4,15.4,10z M14,15.4l1.5,1.5l1.4-1.4L15.4,14C15.1,14.6,14.6,15.1,14,15.4z M12,10v4c1.1,0,2-0.9,2-2C14,10.9,13.1,10,12,10z M15.9,11c0.1,0.3,0.1,0.7,0.1,1s-0.1,0.7-0.1,1H18v-2H15.9z"/></g> +<g id="settings-ethernet"><path d="M7.8,6.8L6.2,5.5L0.8,12l5.4,6.5l1.5-1.3L3.4,12L7.8,6.8z M7,13h2v-2H7V13z M17,11h-2v2h2V11z M11,13h2v-2h-2V13z M17.8,5.5l-1.5,1.3l4.3,5.2l-4.3,5.2l1.5,1.3l5.4-6.5L17.8,5.5z"/></g> +<g id="settings-input-antenna"><path d="M12,5c-3.9,0-7,3.1-7,7h2c0-2.8,2.2-5,5-5s5,2.2,5,5h2C19,8.1,15.9,5,12,5z M13,14.3c0.9-0.4,1.5-1.3,1.5-2.3c0-1.4-1.1-2.5-2.5-2.5S9.5,10.6,9.5,12c0,1,0.6,1.9,1.5,2.3v3.3L7.6,21L9,22.4l3-3l3,3l1.4-1.4L13,17.6V14.3z M12,1C5.9,1,1,5.9,1,12h2c0-5,4-9,9-9s9,4,9,9h2C23,5.9,18.1,1,12,1z"/></g> +<g id="settings-input-component"><path d="M5,2c0-0.6-0.4-1-1-1S3,1.4,3,2v4H1v6h6V6H5V2z M9,16c0,1.3,0.8,2.4,2,2.8V23h2v-4.2c1.2-0.4,2-1.5,2-2.8v-2H9V16z M1,16c0,1.3,0.8,2.4,2,2.8V23h2v-4.2c1.2-0.4,2-1.5,2-2.8v-2H1V16z M21,6V2c0-0.6-0.4-1-1-1s-1,0.4-1,1v4h-2v6h6V6H21z M13,2c0-0.6-0.4-1-1-1s-1,0.4-1,1v4H9v6h6V6h-2V2z M17,16c0,1.3,0.8,2.4,2,2.8V23h2v-4.2c1.2-0.4,2-1.5,2-2.8v-2h-6V16z"/></g> +<g id="settings-input-composite"><path d="M5,2c0-0.6-0.4-1-1-1S3,1.4,3,2v4H1v6h6V6H5V2z M9,16c0,1.3,0.8,2.4,2,2.8V23h2v-4.2c1.2-0.4,2-1.5,2-2.8v-2H9V16z M1,16c0,1.3,0.8,2.4,2,2.8V23h2v-4.2c1.2-0.4,2-1.5,2-2.8v-2H1V16z M21,6V2c0-0.6-0.4-1-1-1s-1,0.4-1,1v4h-2v6h6V6H21z M13,2c0-0.6-0.4-1-1-1s-1,0.4-1,1v4H9v6h6V6h-2V2z M17,16c0,1.3,0.8,2.4,2,2.8V23h2v-4.2c1.2-0.4,2-1.5,2-2.8v-2h-6V16z"/></g> +<g id="settings-input-hdmi"><path d="M18,7V4c0-1.1-0.9-2-2-2H8C6.9,2,6,2.9,6,4v3H5v6l3,6v3h8v-3l3-6V7H18z M8,4h8v3h-2V5h-1v2h-2V5h-1v2H8V4z"/></g> +<g id="settings-input-svideo"><path d="M8,11.5C8,10.7,7.3,10,6.5,10S5,10.7,5,11.5S5.7,13,6.5,13S8,12.3,8,11.5z M15,6.5C15,5.7,14.3,5,13.5,5h-3C9.7,5,9,5.7,9,6.5S9.7,8,10.5,8h3C14.3,8,15,7.3,15,6.5z M8.5,15C7.7,15,7,15.7,7,16.5S7.7,18,8.5,18s1.5-0.7,1.5-1.5S9.3,15,8.5,15z M12,1C5.9,1,1,5.9,1,12s4.9,11,11,11s11-4.9,11-11S18.1,1,12,1z M12,21c-5,0-9-4-9-9s4-9,9-9s9,4,9,9S17,21,12,21z M17.5,10c-0.8,0-1.5,0.7-1.5,1.5s0.7,1.5,1.5,1.5s1.5-0.7,1.5-1.5S18.3,10,17.5,10z M15.5,15c-0.8,0-1.5,0.7-1.5,1.5s0.7,1.5,1.5,1.5s1.5-0.7,1.5-1.5S16.3,15,15.5,15z"/></g> +<g id="settings-overscan"><path d="M12,5.5L10,8h4L12,5.5z M18,10v4l2.5-2L18,10z M6,10l-2.5,2L6,14V10z M14,16h-4l2,2.5L14,16z M21,3H3C1.9,3,1,3.9,1,5v14c0,1.1,0.9,2,2,2h18c1.1,0,2-0.9,2-2V5C23,3.9,22.1,3,21,3z M21,19H3V5h18V19z"/></g> +<g id="settings-phone"><path d="M13,9h-2v2h2V9z M17,9h-2v2h2V9z M20,15.5c-1.2,0-2.4-0.2-3.6-0.6c-0.3-0.1-0.7,0-1,0.2l-2.2,2.2c-2.8-1.4-5.1-3.8-6.6-6.6l2.2-2.2c0.3-0.3,0.4-0.7,0.2-1C8.7,6.4,8.5,5.2,8.5,4c0-0.6-0.4-1-1-1H4C3.4,3,3,3.4,3,4c0,9.4,7.6,17,17,17c0.6,0,1-0.4,1-1v-3.5C21,15.9,20.6,15.5,20,15.5z M19,9v2h2V9H19z"/></g> +<g id="settings-power"><path d="M7,24h2v-2H7V24z M11,24h2v-2h-2V24z M13,2h-2v10h2V2z M16.6,4.4l-1.4,1.4C16.8,6.9,18,8.8,18,11c0,3.3-2.7,6-6,6c-3.3,0-6-2.7-6-6c0-2.2,1.2-4.1,2.9-5.1L7.4,4.4C5.4,5.9,4,8.3,4,11c0,4.4,3.6,8,8,8c4.4,0,8-3.6,8-8C20,8.3,18.6,5.9,16.6,4.4z M15,24h2v-2h-2V24z"/></g> +<g id="settings-remote"><path d="M15,9H9c-0.6,0-1,0.4-1,1v12c0,0.6,0.4,1,1,1h6c0.6,0,1-0.4,1-1V10C16,9.4,15.6,9,15,9z M12,15c-1.1,0-2-0.9-2-2s0.9-2,2-2s2,0.9,2,2S13.1,15,12,15z M7.1,6.1l1.4,1.4C9.4,6.6,10.6,6,12,6s2.6,0.6,3.5,1.5l1.4-1.4C15.7,4.8,13.9,4,12,4S8.3,4.8,7.1,6.1z M12,0C9,0,6.2,1.2,4.2,3.2l1.4,1.4C7.3,3,9.5,2,12,2s4.7,1,6.4,2.6l1.4-1.4C17.8,1.2,15,0,12,0z"/></g> +<g id="settings-voice"><path d="M7,24h2v-2H7V24z M12,13c1.7,0,3-1.3,3-3l0-6c0-1.7-1.3-3-3-3c-1.7,0-3,1.3-3,3v6C9,11.7,10.3,13,12,13z M11,24h2v-2h-2V24z M15,24h2v-2h-2V24z M19,10h-1.7c0,3-2.5,5.1-5.3,5.1c-2.8,0-5.3-2.1-5.3-5.1H5c0,3.4,2.7,6.2,6,6.7V20h2v-3.3C16.3,16.2,19,13.4,19,10z"/></g> +<g id="shop"><path d="M16,6V4l-2-2h-4L8,4v2H2v13c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V6H16z M10,4h4v2h-4V4z M9,18V9l7.5,4L9,18z"/></g> +<g id="shop-two"><path d="M18,5V3l-2-2h-4l-2,2v2H5v11c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5H18z M12,3h4v2h-4V3z M12,15V8l5.5,3L12,15z M3,9H1v11c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2H3V9z"/></g> +<g id="shopping-basket"><path d="M17.2,9l-4.4-6.6C12.6,2.2,12.3,2,12,2c-0.3,0-0.6,0.1-0.8,0.4L6.8,9H2c-0.6,0-1,0.4-1,1c0,0.1,0,0.2,0,0.3l2.5,9.3c0.2,0.8,1,1.5,1.9,1.5h13c0.9,0,1.7-0.6,1.9-1.5l2.5-9.3c0-0.1,0-0.2,0-0.3c0-0.6-0.4-1-1-1H17.2z M9,9l3-4.4L15,9H9z M12,17c-1.1,0-2-0.9-2-2s0.9-2,2-2c1.1,0,2,0.9,2,2S13.1,17,12,17z"/></g> +<g id="shopping-cart"><path d="M7,18c-1.1,0-2,0.9-2,2s0.9,2,2,2c1.1,0,2-0.9,2-2S8.1,18,7,18z M1,2v2h2l3.6,7.6L5.2,14C5.1,14.3,5,14.7,5,15c0,1.1,0.9,2,2,2h12v-2H7.4c-0.1,0-0.2-0.1-0.2-0.2c0,0,0-0.1,0-0.1L8.1,13h7.4c0.8,0,1.4-0.4,1.7-1l3.6-6.5C21,5.3,21,5.2,21,5c0-0.6-0.4-1-1-1H5.2L4.3,2H1z M17,18c-1.1,0-2,0.9-2,2s0.9,2,2,2c1.1,0,2-0.9,2-2S18.1,18,17,18z"/></g> +<g id="sort"><path d="M3,18h6v-2H3V18z M3,6v2h18V6H3z M3,13h12v-2H3V13z"/></g> +<g id="star"><polygon points="12,17.273 18.18,21 16.545,13.971 22,9.244 14.809,8.627 12,2 9.191,8.627 2,9.244 7.455,13.971 5.82,21 "/></g> +<g id="star-half"><path d="M22,9.744l-7.191-0.617L12,2.5L9.191,9.127L2,9.744v0l0,0l5.455,4.727L5.82,21.5L12,17.772l0,0l6.18,3.727l-1.635-7.029L22,9.744z M12,15.896V6.595l1.71,4.036l4.38,0.376l-3.322,2.878l0.996,4.281L12,15.896z"/></g> +<g id="star-outline"><path d="M22,9.244l-7.191-0.617L12,2L9.191,8.627L2,9.244l5.455,4.727L5.82,21L12,17.272L18.18,21l-1.635-7.029L22,9.244z M12,15.396l-3.763,2.27l0.996-4.281L5.91,10.507l4.38-0.376L12,6.095l1.71,4.036l4.38,0.376l-3.322,2.878l0.996,4.281L12,15.396z"/></g> +<g id="star-rate"><polygon points="12,14.3 15.7,17 14.3,12.6 18,10 13.5,10 12,5.5 10.5,10 6,10 9.7,12.6 8.3,17 "/></g> +<g id="stars"><path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10c5.5,0,10-4.5,10-10S17.5,2,12,2z M16.2,18L12,15.4L7.8,18l1.1-4.8L5.2,10l4.9-0.4L12,5l1.9,4.5l4.9,0.4l-3.7,3.2L16.2,18z"/></g> +<g id="store"><path d="M20,4H4v2h16V4z M21,14v-2l-1-5H4l-1,5v2h1v6h10v-6h4v6h2v-6H21z M12,18H6v-4h6V18z"/></g> +<g id="subject"><path d="M14,17H4v2h10V17z M20,9H4v2h16V9z M4,15h16v-2H4V15z M4,5v2h16V5H4z"/></g> +<g id="swap-driving-apps"><circle cx="6.5" cy="15.5" r="1.5"/><circle cx="17.5" cy="15.5" r="1.5"/><path d="M18.9,7c-0.2-0.6-0.8-1-1.4-1H16H6V4L3,7l2,2l1,1V8h11.7l1.3,4H3v9c0,0.6,0.4,1,1,1h1c0.6,0,1-0.4,1-1v-1h12v1c0,0.6,0.4,1,1,1h1c0.6,0,1-0.4,1-1v-8L18.9,7z M6.5,17C5.7,17,5,16.3,5,15.5S5.7,14,6.5,14C7.3,14,8,14.7,8,15.5S7.3,17,6.5,17z M17.5,17c-0.8,0-1.5-0.7-1.5-1.5s0.7-1.5,1.5-1.5c0.8,0,1.5,0.7,1.5,1.5S18.3,17,17.5,17z M16,0v2H8v2h8v2l3-3L16,0z"/></g> +<g id="swap-driving-apps-wheel"><path d="M14.4,6.1c-0.5-0.2-1.1,0-1.3,0.6L11.7,10c-1,0.1-1.7,1-1.7,2c0,1.1,0.9,2,2,2s2-0.9,2-2c0-0.5-0.2-0.9-0.4-1.2l1.4-3.4C15.1,6.9,14.9,6.3,14.4,6.1z M7,9c-0.6,0-1,0.4-1,1c0,0.6,0.4,1,1,1s1-0.4,1-1C8,9.4,7.6,9,7,9z M11,7c0-0.6-0.4-1-1-1S9,6.4,9,7c0,0.6,0.4,1,1,1S11,7.6,11,7z M17,9c-0.6,0-1,0.4-1,1c0,0.6,0.4,1,1,1s1-0.4,1-1C18,9.4,17.6,9,17,9z M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10s10-4.5,10-10S17.5,2,12,2z M17.3,18c-1.4-1.2-3.2-2-5.3-2s-3.9,0.8-5.3,2C5.1,16.5,4,14.4,4,12c0-4.4,3.6-8,8-8s8,3.6,8,8C20,14.4,18.9,16.5,17.3,18z"/></g> +<g id="swap-horiz"><path d="M7,11l-4,4l4,4v-3h7v-2H7V11z M21,9l-4-4v3h-7v2h7v3L21,9z"/></g> +<g id="swap-vert"><path d="M16,17v-7h-2v7h-3l4,4l4-4H16z M9,3L5,7h3v7h2V7h3L9,3z"/></g> +<g id="swap-vert-circle"><path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10c5.5,0,10-4.5,10-10S17.5,2,12,2z M6.5,9L10,5.5L13.5,9H11v4H9V9H6.5z M17.5,15L14,18.5L10.5,15H13v-4h2v4H17.5z"/></g> +<g id="system-update-tv"><path d="M12,15l4-4h-3V3h-2v8H8L12,15z M20,3h-5v2h5v12H4V5h5V3H4C2.9,3,2,3.9,2,5v12c0,1.1,0.9,2,2,2h4v2h8v-2h4c1.1,0,2-0.9,2-2l0-12C22,3.9,21.1,3,20,3z"/></g> +<g id="tab"><path d="M21,3H3C1.9,3,1,3.9,1,5v14c0,1.1,0.9,2,2,2h18c1.1,0,2-0.9,2-2V5C23,3.9,22.1,3,21,3z M21,19L3,19V5h10v4h8V19z"/></g> +<g id="tab-unselected"><path d="M1,9h2V7H1V9z M1,13h2v-2H1V13z M1,5h2V3C1.9,3,1,3.9,1,5z M9,21h2v-2l-2,0V21z M1,17h2v-2H1V17z M3,21v-2H1C1,20.1,1.9,21,3,21z M21,3h-8v6h10V5C23,3.9,22.1,3,21,3z M21,17h2v-2h-2V17z M9,5h2V3H9V5z M5,21h2v-2l-2,0V21z M5,5h2V3H5V5z M21,21c1.1,0,2-0.9,2-2h-2V21z M21,13h2v-2h-2V13z M13,21h2v-2l-2,0V21z M17,21h2v-2l-2,0V21z"/></g> +<g id="text-format"><path d="M5,17v2h14v-2H5z M9.5,12.8h5l0.9,2.2h2.1L12.8,4h-1.5L6.5,15h2.1L9.5,12.8z M12,6l1.9,5h-3.7L12,6z"/></g> +<g id="theaters"><path d="M18,3v2h-2V3H8v2H6V3H4v18h2v-2h2v2h8v-2h2v2h2V3H18z M8,17H6v-2h2V17z M8,13H6v-2h2V13z M8,9H6V7h2V9z M18,17h-2v-2h2V17z M18,13h-2v-2h2V13z M18,9h-2V7h2V9z"/></g> +<g id="thumb-down"><path d="M15,3H6C5.2,3,4.5,3.5,4.2,4.2l-3,7.1C1.1,11.5,1,11.7,1,12v1.9l0,0c0,0,0,0.1,0,0.1c0,1.1,0.9,2,2,2h6.3l-1,4.6c0,0.1,0,0.2,0,0.3c0,0.4,0.2,0.8,0.4,1.1L9.8,23l6.6-6.6c0.4-0.4,0.6-0.9,0.6-1.4V5C17,3.9,16.1,3,15,3z M19,3v12h4V3H19z"/></g> +<g id="thumb-up"><path d="M1,21h4V9H1V21z M23,10c0-1.1-0.9-2-2-2h-6.3l1-4.6c0-0.1,0-0.2,0-0.3c0-0.4-0.2-0.8-0.4-1.1L14.2,1L7.6,7.6C7.2,7.9,7,8.4,7,9v10c0,1.1,0.9,2,2,2h9c0.8,0,1.5-0.5,1.8-1.2l3-7.1c0.1-0.2,0.1-0.5,0.1-0.7V10L23,10C23,10.1,23,10,23,10z"/></g> +<g id="toc"><path d="M3,9h14V7H3V9z M3,13h14v-2H3V13z M3,17h14v-2H3V17z M19,17h2v-2h-2V17z M19,7v2h2V7H19z M19,13h2v-2h-2V13z"/></g> +<g id="today"><path d="M19,3h-1V1h-2v2H8V1H6v2H5C3.9,3,3,3.9,3,5l0,14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M19,19H5V8h14V19z"/><rect x="7" y="10" width="5" height="5"/></g> +<g id="translate"><path d="M12.9,15.1l-2.5-2.5l0,0c1.7-1.9,3-4.2,3.7-6.5H17V4h-7V2H8v2H1v2l11.2,0c-0.7,1.9-1.7,3.8-3.2,5.4c-0.9-1-1.7-2.2-2.3-3.4h-2c0.7,1.6,1.7,3.2,3,4.6l-5.1,5L4,19l5-5l3.1,3.1L12.9,15.1z M18.5,10h-2L12,22h2l1.1-3h4.8l1.1,3h2L18.5,10z M15.9,17l1.6-4.3l1.6,4.3H15.9z"/></g> +<g id="trending-down"><polygon points="16,18 18.3,15.7 13.4,10.8 9.4,14.8 2,7.4 3.4,6 9.4,12 13.4,8 19.7,14.3 22,12 22,18 "/></g> +<g id="trending-neutral"><polygon points="22,12 18,8 18,11 3,11 3,13 18,13 18,16 "/></g> +<g id="trending-up"><polygon points="16,6 18.3,8.3 13.4,13.2 9.4,9.2 2,16.6 3.4,18 9.4,12 13.4,16 19.7,9.7 22,12 22,6 "/></g> +<g id="turned-in"><path d="M17,3H7C5.9,3,5,3.9,5,5l0,16l7-3l7,3V5C19,3.9,18.1,3,17,3z"/></g> +<g id="turned-in-not"><path d="M17,3H7C5.9,3,5,3.9,5,5l0,16l7-3l7,3V5C19,3.9,18.1,3,17,3z M17,18l-5-2.2L7,18V5h10V18z"/></g> +<g id="undo"><path d="M12,5V1.5l-5,5l5,5V7c3.3,0,6,2.7,6,6s-2.7,6-6,6c-3.3,0-6-2.7-6-6H4c0,4.4,3.6,8,8,8c4.4,0,8-3.6,8-8S16.4,5,12,5z"/></g> +<g id="unfold-less"><path d="M7.4,18.6L8.8,20l3.2-3.2l3.2,3.2l1.4-1.4L12,14L7.4,18.6z M16.6,5.4L15.2,4L12,7.2L8.8,4L7.4,5.4L12,10L16.6,5.4z"/></g> +<g id="unfold-more"><path d="M12,5.8L15.2,9l1.4-1.4L12,3L7.4,7.6L8.8,9L12,5.8z M12,18.2L8.8,15l-1.4,1.4L12,21l4.6-4.6L15.2,15L12,18.2z"/></g> +<g id="view-array"><path d="M4,18h3V5H4V18z M18,5v13h3V5H18z M8,18h9V5H8V18z"/></g> +<g id="view-column"><path d="M10,18h5V5h-5V18z M4,18h5V5H4V18z M16,5v13h5V5H16z"/></g> +<g id="view-headline"><path d="M4,15h17v-2H4V15z M4,19h17v-2H4V19z M4,11h17V9H4V11z M4,5v2h17V5H4z"/></g> +<g id="view-list"><path d="M4,14h4v-4H4V14z M4,19h4v-4H4V19z M4,9h4V5H4V9z M9,14h12v-4H9V14z M9,19h12v-4H9V19z M9,5v4h12V5H9z"/></g> +<g id="view-module"><path d="M4,11h5V5H4V11z M4,18h5v-6H4V18z M10,18h5v-6h-5V18z M16,18h5v-6h-5V18z M10,11h5V5h-5V11z M16,5v6h5V5H16z"/></g> +<g id="view-quilt"><path d="M10,18h5v-6h-5V18z M4,18h5V5H4V18z M16,18h5v-6h-5V18z M10,5v6h11V5H10z"/></g> +<g id="view-stream"><path d="M4,18h17v-6H4V18z M4,5v6h17V5H4z"/></g> +<g id="visibility"><path d="M12,4.5C7,4.5,2.7,7.6,1,12c1.7,4.4,6,7.5,11,7.5c5,0,9.3-3.1,11-7.5C21.3,7.6,17,4.5,12,4.5z M12,17c-2.8,0-5-2.2-5-5s2.2-5,5-5c2.8,0,5,2.2,5,5S14.8,17,12,17z M12,9c-1.7,0-3,1.3-3,3s1.3,3,3,3c1.7,0,3-1.3,3-3S13.7,9,12,9z"/></g> +<g id="visibility-off"><path d="M12,7c2.8,0,5,2.2,5,5c0,0.6-0.1,1.3-0.4,1.8l2.9,2.9c1.5-1.3,2.7-2.9,3.4-4.7c-1.7-4.4-6-7.5-11-7.5c-1.4,0-2.7,0.3-4,0.7l2.2,2.2C10.7,7.1,11.4,7,12,7z M2,4.3l2.3,2.3L4.7,7c-1.7,1.3-3,3-3.7,5c1.7,4.4,6,7.5,11,7.5c1.5,0,3-0.3,4.4-0.8l0.4,0.4l2.9,2.9l1.3-1.3L3.3,3L2,4.3z M7.5,9.8l1.5,1.5C9,11.6,9,11.8,9,12c0,1.7,1.3,3,3,3c0.2,0,0.4,0,0.7-0.1l1.5,1.5C13.5,16.8,12.8,17,12,17c-2.8,0-5-2.2-5-5C7,11.2,7.2,10.5,7.5,9.8z M11.8,9l3.1,3.1c0-0.1,0-0.1,0-0.2c0-1.7-1.3-3-3-3C11.9,9,11.9,9,11.8,9z"/></g> +<g id="wallet-giftcard"><path d="M20,6h-2.2C17.9,5.7,18,5.4,18,5c0-1.7-1.3-3-3-3c-1,0-2,0.5-2.5,1.3l0,0L12,4l-0.5-0.7l0,0C11,2.5,10,2,9,2C7.3,2,6,3.3,6,5c0,0.4,0.1,0.7,0.2,1H4C2.9,6,2,6.9,2,8l0,11c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V8C22,6.9,21.1,6,20,6z M15,4c0.6,0,1,0.4,1,1s-0.4,1-1,1s-1-0.4-1-1S14.4,4,15,4z M9,4c0.6,0,1,0.4,1,1S9.6,6,9,6S8,5.6,8,5S8.4,4,9,4z M20,19H4v-2h16V19z M20,14H4V8h5.1L7,10.8L8.6,12L11,8.8l1-1.4l1,1.4l2.4,3.2l1.6-1.2L14.9,8H20V14z"/></g> +<g id="wallet-membership"><path d="M20,2H4C2.9,2,2,2.9,2,4v11c0,1.1,0.9,2,2,2h4v5l4-2l4,2v-5h4c1.1,0,2-0.9,2-2V4C22,2.9,21.1,2,20,2z M20,15H4v-2h16V15z M20,10H4V4h16V10z"/></g> +<g id="wallet-travel"><path d="M20,6h-3V4l-2-2H9L7,4v2H4C2.9,6,2,6.9,2,8v11c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V8C22,6.9,21.1,6,20,6z M9,4h6v2H9V4z M20,19H4v-2h16V19z M20,14H4V8h3v4h2V8h6v4h2V8h3V14z"/></g> +<g id="warning"><path d="M1,21h22L12,2L1,21z M13,18h-2v-2h2V18z M13,14h-2v-4h2V14z"/></g> +<g id="work"><path d="M20,6h-4V4l-2-2h-4L8,4v2H4C2.9,6,2,6.9,2,8l0,11c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V8C22,6.9,21.1,6,20,6z M14,6h-4V4h4V6z"/></g> +</defs></svg> +</core-iconset-svg> + + +<polymer-element name="core-icon-button" attributes="src icon active" assetpath="polymer/bower_components/core-icon-button/"> + + <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 +The complete set of authors may be found at http://polymer.github.io/AUTHORS +The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS +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 +*/ + +:host { + display: inline-block; + box-sizing: border-box; + -moz-box-sizing: border-box; + user-select: none; + -moz-user-select: none; + -webkit-user-select: none; + border-radius: 2px; + padding: 7px; + margin: 2px; + vertical-align: middle; + font-size: 1rem; + cursor: pointer; +} + +:host([disabled]) { + opacity: 0.6; + pointer-events: none; +} + +:host(.outline) { + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1); +} + +:host(:hover:not([disabled])) { + box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.12), 0 0 0 1px rgba(0, 0, 0, 0.1); +} + +:host(.selected:not([disabled])) { + background-color: rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 0 0 rgba(0, 0, 0, 0.05), 0 0 0 1px rgba(0, 0, 0, 0.12); +} + +:host(:active:not([disabled]), .selected:active:not([disabled])) { + background-color: rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 0 0 rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.12); +} + +:host(.core-dark-theme.outline) { + background-color: rgba(200, 200, 200, 0.05); + box-shadow: 0 0 0 1px rgba(200, 200, 200, 0.1); +} + +:host(.core-dark-theme:hover) { + background-color: rgba(200, 200, 200, 0.05); + box-shadow: 0 1px 0 0 rgba(200, 200, 200, 0.12), 0 0 0 1px rgba(200, 200, 200, 0.1); +} + +:host(.core-dark-theme.selected) { + background-color: rgba(220, 220, 220, 0.05); + box-shadow: inset 0 1px 0 0 rgba(200, 200, 200, 0.05), 0 0 0 1px rgba(200, 200, 200, 0.12); +} + +:host(.core-dark-theme:active, .core-dark-theme.selected:active) { + background-color: rgba(200, 200, 200, 0.05); + box-shadow: inset 0 1px 0 0 rgba(200, 200, 200, 0.1), 0 0 0 1px rgba(200, 200, 200, 0.12); +} + +core-icon { + pointer-events: none; +} + +/* note: this is a polyfill aware selector */ +:host ::content > :not(core-icon) { + margin-left: 4px; +} +</style> + <core-icon src="{{src}}" icon="{{icon}}"></core-icon><content></content> + </template> + + <script> + + Polymer('core-icon-button', { + + /** + * The URL of an image for the icon. Should not use `icon` property + * if you are using this property. + * + * @attribute src + * @type string + * @default '' + */ + src: '', + + /** + * If true, border is placed around the button to indicate it's + * active state. + * + * @attribute active + * @type boolean + * @default false + */ + active: false, + + /** + * Specifies the icon name or index in the set of icons available in + * the icon set. Should not use `src` property if you are using this + * property. + * + * @attribute icon + * @type string + * @default '' + */ + icon: '', + + activeChanged: function() { + this.classList.toggle('selected', this.active); + } + + }); + + </script> + +</polymer-element> +</div> +<div hidden><!-- +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 +--> + +<!-- +@group Paper Elements + +Material Design: <a href="https://spec.googleplex.com/quantum/components/buttons.html">Button</a> + +`paper-fab` is a floating action button. It contains an image placed in the center and +comes in two sizes: regular size and a smaller size by applying the class `mini`. When +the user touches the button, a ripple effect emanates from the center of the button. + +You may import `core-icons` to use with this element, or provide an URL to a custom icon. +See `core-iconset` for more information about how to use a custom icon set. + +Example: + + <link href="path/to/core-icons/core-icons.html" rel="import"> + + <paper-fab icon="add"></paper-fab> + <paper-fab mini icon="favorite"></paper-fab> + <paper-fab src="star.png"></paper-fab> + +Styling +------- + +Style the button with CSS as you would a normal DOM element. If you are using the icons +provided by `core-icons`, the icon will inherit the foreground color of the button. + + /* make a blue "cloud" button */ + <paper-fab icon="cloud" style="color: blue;"></paper-fab> + +By default, the ripple is the same color as the foreground at 25% opacity. You may +customize the color using this selector: + + /* make #my-button use a blue ripple instead of foreground color */ + #my-button::shadow #ripple { + color: blue; + } + +The opacity of the ripple is not customizable via CSS. + +Accessibility +------------- + +The button is accessible by default if you use the `icon` property. By default, the +`aria-label` attribute will be set to the `icon` property. If you use a custom icon, +you should ensure that the `aria-label` attribute is set. + + <paper-fab src="star.png" aria-label="star"></paper-fab> + +@element paper-fab +@extends paper-button-base +@status unstable +--> + + +<!-- +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 +--> + +<!-- +@group Paper Elements + +`paper-button-base` is the base class for button-like elements with ripple and optional shadow. + +@element paper-button-base +@extends paper-focusable +@status unstable +--> + + +<!-- +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 +--> + +<!-- +/** + * @group Paper Elements + * + * paper-focusable is a base class for paper elements that can be focused. + * + * @element paper-focusable + * @status beta + * @homepage github.io + */ +--> + + + +<polymer-element name="paper-focusable" attributes="active focused disabled isToggle" tabindex="0" on-down="{{downAction}}" on-up="{{upAction}}" on-focus="{{focusAction}}" on-blur="{{blurAction}}" on-contextmenu="{{contextMenuAction}}" assetpath="polymer/bower_components/paper-focusable/"> + + <template> + <style> + :host([disabled]) { + pointer-events: none; + } + </style> + <content></content> + </template> + + <script> + Polymer('paper-focusable', { + + publish: { + + /** + * If true, the button is currently active either because the + * user is holding down the button, 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 isToggle + * @type boolean + * @default false + */ + isToggle: {value: false, reflect: false} + + }, + + disabledChanged: function() { + if (this.disabled) { + this.removeAttribute('tabindex'); + } else { + this.setAttribute('tabindex', 0); + } + }, + + downAction: function() { + this.pressed = true; + + if (this.isToggle) { + 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.isToggle) { + 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> +</polymer-element> + + +<polymer-element name="paper-button-base" extends="paper-focusable" assetpath="polymer/bower_components/paper-button/"> + + <script> + Polymer('paper-button-base',{ + + z: 1, + + activeChanged: function() { + this.super(); + + if (this.active) { + // FIXME: remove when paper-ripple can have a default 'down' state. + if (!this.lastEvent) { + var rect = this.getBoundingClientRect(); + this.lastEvent = { + x: rect.left + rect.width / 2, + y: rect.top + rect.height / 2 + } + } + this.$.ripple.downAction(this.lastEvent); + } else { + this.$.ripple.upAction(); + } + this.adjustZ(); + }, + + disabledChanged: function() { + this.super(); + if (this.disabled) { + this.setAttribute('aria-disabled', ''); + } else { + this.removeAttribute('aria-disabled'); + } + 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.active) { + this.z = 2; + } else if (this.disabled) { + this.z = 0; + } else { + this.z = 1; + } + }, + + downAction: function(e) { + this.super(e); + 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 + } + } + + }); + </script> +</polymer-element> + +<!-- +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 +--> + +<!-- +`paper-ripple` provides a visual effect that other paper elements can +use to simulate a rippling effect emanating from the point of contact. The +effect can be visualized as a concentric circle with motion. + +Example: + + <paper-ripple></paper-ripple> + +`paper-ripple` listens to "down" and "up" events so it would display ripple +effect when touches on it. You can also defeat the default behavior and +manually route the down and up actions to the ripple element. Note that it is +important if you call downAction() you will have to make sure to call upAction() +so that `paper-ripple` would end the animation loop. + +Example: + + <paper-ripple id="ripple" style="pointer-events: none;"></paper-ripple> + ... + downAction: function(e) { + this.$.ripple.downAction({x: e.x, y: e.y}); + }, + upAction: function(e) { + this.$.ripple.upAction(); + } + +Styling ripple effect: + + Use CSS color property to style the ripple: + + paper-ripple { + color: #4285f4; + } + + Note that CSS color property is inherited so it is not required to set it on + the `paper-ripple` element directly. + +Apply `recenteringTouch` class to make the recentering rippling effect. + + <paper-ripple class="recenteringTouch"></paper-ripple> + +Apply `circle` class to make the rippling effect within a circle. + + <paper-ripple class="circle"></paper-ripple> + +@group Paper Elements +@element paper-ripple +@homepage github.io +--> + + + +<polymer-element name="paper-ripple" attributes="initialOpacity opacityDecayVelocity" assetpath="polymer/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> + +<!-- +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 +--> + +<!-- +The `paper-shadow` element is a helper to add shadows to elements. +Paper shadows are composed of two shadows on top of each other. We +mimic this effect by using two elements on top of each other, each with a +different drop shadow. You can apply the shadow to an element by assigning +it as the target. If you do not specify a target, the shadow is applied to +the `paper-shadow` element's parent element or shadow host element if its +parent is a shadow root. Alternatively, you can use the CSS classes included +by this element directly. + +Example: + + <div id="myCard" class="card"></div> + <paper-shadow id="myShadow" z="1"></div> + + // Assign a target explicitly + myShadow.target = document.getElementById('myCard'); + + // Auto-assign the target. + <div class="card"> + <paper-shadow z="1"></paper-shadow> + </div> + + // Use the classes directly + <div class="card paper-shadow-top paper-shadow-top-z-1"> + <div class="card-inner paper-shadow-bottom paper-shadow-bottom-z-1"></div> + </div> + +If you assign a target to a `paper-shadow` element, it creates two nodes and inserts +them as the first children of the target, or the first children of the target's shadow +root if there is one. This implies: + + 1. If the primary node that drops the shadow has styling that affects its shape, + the same styling must be applied to elements with class `paper-shadow`. + `border-radius` is a very common property and is inherited automatically. + + 2. The target's overflow property will be set to `overflow: visible` because the + shadow is rendered beyond the bounds of its container. Position the shadow as a + separate layer and use a different child element for clipping if needed. + +@group Paper Elements +@class paper-shadow +--> + + + +<polymer-element name="paper-shadow" assetpath="polymer/bower_components/paper-shadow/"> + + <template> + + <style no-shim="">/* + * @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 + */ + +.paper-shadow { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + border-radius: inherit; + pointer-events: none; +} + +.paper-shadow-animated.paper-shadow { + transition: box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1); +} + +.paper-shadow-top-z-1 { + box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.16); +} + +.paper-shadow-bottom-z-1 { + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26); +} + +.paper-shadow-top-z-2 { + box-shadow: 0 6px 20px 0 rgba(0, 0, 0, 0.19); +} + +.paper-shadow-bottom-z-2 { + box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2); +} + +.paper-shadow-top-z-3 { + box-shadow: 0 17px 50px 0 rgba(0, 0, 0, 0.19); +} + +.paper-shadow-bottom-z-3 { + box-shadow: 0 12px 15px 0 rgba(0, 0, 0, 0.24); +} + +.paper-shadow-top-z-4 { + box-shadow: 0 25px 55px 0 rgba(0, 0, 0, 0.21); +} + +.paper-shadow-bottom-z-4 { + box-shadow: 0 16px 28px 0 rgba(0, 0, 0, 0.22); +} + +.paper-shadow-top-z-5 { + box-shadow: 0 40px 77px 0 rgba(0, 0, 0, 0.22); +} + +.paper-shadow-bottom-z-5 { + box-shadow: 0 27px 24px 0 rgba(0, 0, 0, 0.2); +} + +.paper-shadow-animate-z-1-z-2.paper-shadow-top { + -webkit-transition: none; + -webkit-animation: animate-shadow-top-z-1-z-2 0.7s infinite alternate; +} + +.paper-shadow-animate-z-1-z-2 .paper-shadow-bottom { + -webkit-transition: none; + -webkit-animation: animate-shadow-bottom-z-1-z-2 0.7s infinite alternate; +} + +@-webkit-keyframes animate-shadow-top-z-1-z-2 { + 0% { + box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.16); + } + 100% { + box-shadow: 0 6px 20px 0 rgba(0, 0, 0, 0.19); + } +} + +@-webkit-keyframes animate-shadow-bottom-z-1-z-2 { + 0% { + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26); + } + 100% { + box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2); + } +}</style> + + </template> + + <script> + Polymer('paper-shadow', { + + publish: { + /** + * If set, the shadow is applied to this node. + * + * @attribute target + * @type Element + * @default null + */ + target: {value: null, reflect: true}, + + /** + * The z-depth of this shadow, from 0-5. + * + * @attribute z + * @type number + * @default 1 + */ + z: {value: 1, reflect: true}, + + /** + * If true, the shadow animates between z-depth changes. + * + * @attribute animated + * @type boolean + * @default false + */ + animated: {value: false, reflect: true}, + + /** + * Workaround: getComputedStyle is wrong sometimes so `paper-shadow` + * may overwrite the `position` CSS property. Set this property to + * true to prevent this. + * + * @attribute hasPosition + * @type boolean + * @default false + */ + hasPosition: false + }, + + // NOTE: include template so that styles are loaded, but remove + // so that we can decide dynamically what part to include + registerCallback: function(polymerElement) { + var template = polymerElement.querySelector('template'); + this._style = template.content.querySelector('style'); + this._style.removeAttribute('no-shim'); + }, + + fetchTemplate: function() { + return null; + }, + + attached: function() { + // If no target is bound at attach, default the target to the parent + // element or shadow host. + if (!this.target) { + if (!this.parentElement && this.parentNode.host) { + this.target = this.parentNode.host; + } else if (this.parentElement && (window.ShadowDOMPolyfill ? this.parentElement !== wrap(document.body) : this.parentElement !== document.body)) { + this.target = this.parentElement; + } + } + }, + + targetChanged: function(old) { + if (old) { + this.removeShadow(old); + } + if (this.target) { + this.addShadow(this.target); + } + }, + + zChanged: function(old) { + if (this.target && this.target._paperShadow) { + var shadow = this.target._paperShadow; + ['top', 'bottom'].forEach(function(s) { + shadow[s].classList.remove('paper-shadow-' + s + '-z-' + old); + shadow[s].classList.add('paper-shadow-' + s + '-z-' + this.z); + }.bind(this)); + } + }, + + animatedChanged: function() { + if (this.target && this.target._paperShadow) { + var shadow = this.target._paperShadow; + ['top', 'bottom'].forEach(function(s) { + if (this.animated) { + shadow[s].classList.add('paper-shadow-animated'); + } else { + shadow[s].classList.remove('paper-shadow-animated'); + } + }.bind(this)); + } + }, + + addShadow: function(node) { + if (node._paperShadow) { + return; + } + + if (!node._hasShadowStyle) { + if (!node.shadowRoot) { + node.createShadowRoot().innerHTML = '<content></content>'; + } + this.installScopeStyle(this._style, 'shadow', node.shadowRoot); + node._hasShadowStyle = true; + } + + var computed = getComputedStyle(node); + if (!this.hasPosition && computed.position === 'static') { + node.style.position = 'relative'; + } + node.style.overflow = 'visible'; + + // Both the top and bottom shadows are children of the target, so + // it does not affect the classes and CSS properties of the target. + ['top', 'bottom'].forEach(function(s) { + var inner = (node._paperShadow && node._paperShadow[s]) || document.createElement('div'); + inner.classList.add('paper-shadow'); + inner.classList.add('paper-shadow-' + s + '-z-' + this.z); + if (this.animated) { + inner.classList.add('paper-shadow-animated'); + } + + if (node.shadowRoot) { + node.shadowRoot.insertBefore(inner, node.shadowRoot.firstChild); + } else { + node.insertBefore(inner, node.firstChild); + } + + node._paperShadow = node._paperShadow || {}; + node._paperShadow[s] = inner; + }.bind(this)); + + }, + + removeShadow: function(node) { + if (!node._paperShadow) { + return; + } + + ['top', 'bottom'].forEach(function(s) { + node._paperShadow[s].remove(); + }); + node._paperShadow = null; + + node.style.position = null; + } + + }); + </script> +</polymer-element> + + +<polymer-element name="paper-fab" extends="paper-button-base" attributes="src icon mini" role="button" assetpath="polymer/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; + } + </style> + + <template if="{{raised}}"> + <paper-shadow id="shadow" z="{{z}}" 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: false, + fill: true + + }, + + iconChanged: function(oldIcon) { + this.setAttribute('aria-label', this.icon); + } + + }); + + </script> +</polymer-element> +</div> + +<div hidden> +<!-- +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 +--> + +<!-- +@group Polymer Core Elements + +The `core-ajax` element exposes `XMLHttpRequest` functionality. + + <core-ajax + auto + url="http://gdata.youtube.com/feeds/api/videos/" + params='{"alt":"json", "q":"chrome"}' + handleAs="json" + on-core-response="{{handleResponse}}"></core-ajax> + +With `auto` set to `true`, the element performs a request whenever +its `url` or `params` properties are changed. + +Note: The `params` attribute must be double quoted JSON. + +You can trigger a request explicitly by calling `go` on the +element. + +@element core-ajax +@status beta +@homepage github.io +--> +<!-- +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 +--> +<!-- +/** + * @group Polymer Core Elements + * + * core-xhr can be used to perform XMLHttpRequests. + * + * <core-xhr id="xhr"></core-xhr> + * ... + * this.$.xhr.request({url: url, params: params, callback: callback}); + * + * @element core-xhr + */ +--> + + + +<polymer-element name="core-xhr" hidden assetpath="polymer/bower_components/core-ajax/"> + + <script> + + Polymer('core-xhr', { + + /** + * Sends a HTTP request to the server and returns the XHR object. + * + * @method request + * @param {Object} inOptions + * @param {String} inOptions.url The url to which the request is sent. + * @param {String} inOptions.method The HTTP method to use, default is GET. + * @param {boolean} inOptions.sync By default, all requests are sent asynchronously. To send synchronous requests, set to true. + * @param {Object} inOptions.params Data to be sent to the server. + * @param {Object} inOptions.body The content for the request body for POST method. + * @param {Object} inOptions.headers HTTP request headers. + * @param {String} inOptions.responseType The response type. Default is 'text'. + * @param {boolean} inOptions.withCredentials Whether or not to send credentials on the request. Default is false. + * @param {Object} inOptions.callback Called when request is completed. + * @returns {Object} XHR object. + */ + request: function(options) { + var xhr = new XMLHttpRequest(); + var url = options.url; + var method = options.method || 'GET'; + var async = !options.sync; + // + var params = this.toQueryString(options.params); + if (params && method == 'GET') { + url += (url.indexOf('?') > 0 ? '&' : '?') + params; + } + var xhrParams = this.isBodyMethod(method) ? (options.body || params) : null; + // + xhr.open(method, url, async); + if (options.responseType) { + xhr.responseType = options.responseType; + } + if (options.withCredentials) { + xhr.withCredentials = true; + } + this.makeReadyStateHandler(xhr, options.callback); + this.setRequestHeaders(xhr, options.headers); + xhr.send(xhrParams); + if (!async) { + xhr.onreadystatechange(xhr); + } + return xhr; + }, + + toQueryString: function(params) { + var r = []; + for (var n in params) { + var v = params[n]; + n = encodeURIComponent(n); + r.push(v == null ? n : (n + '=' + encodeURIComponent(v))); + } + return r.join('&'); + }, + + isBodyMethod: function(method) { + return this.bodyMethods[(method || '').toUpperCase()]; + }, + + bodyMethods: { + POST: 1, + PUT: 1, + DELETE: 1 + }, + + makeReadyStateHandler: function(xhr, callback) { + xhr.onreadystatechange = function() { + if (xhr.readyState == 4) { + callback && callback.call(null, xhr.response, xhr); + } + }; + }, + + setRequestHeaders: function(xhr, headers) { + if (headers) { + for (var name in headers) { + xhr.setRequestHeader(name, headers[name]); + } + } + } + + }); + + </script> + +</polymer-element> + +<polymer-element name="core-ajax" hidden attributes="url handleAs auto params response error method headers body contentType withCredentials" assetpath="polymer/bower_components/core-ajax/"> +<script> + + Polymer('core-ajax', { + /** + * Fired when a response is received. + * + * @event core-response + */ + + /** + * Fired when an error is received. + * + * @event core-error + */ + + /** + * Fired whenever a response or an error is received. + * + * @event core-complete + */ + + /** + * The URL target of the request. + * + * @attribute url + * @type string + * @default '' + */ + url: '', + + /** + * Specifies what data to store in the `response` property, and + * to deliver as `event.response` in `response` events. + * + * One of: + * + * `text`: uses `XHR.responseText`. + * + * `xml`: uses `XHR.responseXML`. + * + * `json`: uses `XHR.responseText` parsed as JSON. + * + * `arraybuffer`: uses `XHR.response`. + * + * `blob`: uses `XHR.response`. + * + * `document`: uses `XHR.response`. + * + * @attribute handleAs + * @type string + * @default 'text' + */ + handleAs: '', + + /** + * If true, automatically performs an Ajax request when either `url` or `params` changes. + * + * @attribute auto + * @type boolean + * @default false + */ + auto: false, + + /** + * Parameters to send to the specified URL, as JSON. + * + * @attribute params + * @type string (JSON) + * @default '' + */ + params: '', + + /** + * The response for the most recently made request, or null if it hasn't + * completed yet or the request resulted in error. + * + * @attribute response + * @type Object + * @default null + */ + response: null, + + /** + * The error for the most recently made request, or null if it hasn't + * completed yet or the request resulted in success. + * + * @attribute error + * @type Object + * @default null + */ + error: null, + + /** + * The HTTP method to use such as 'GET', 'POST', 'PUT', or 'DELETE'. + * Default is 'GET'. + * + * @attribute method + * @type string + * @default '' + */ + method: '', + + /** + * HTTP request headers to send. + * + * Example: + * + * <core-ajax + * auto + * url="http://somesite.com" + * headers='{"X-Requested-With": "XMLHttpRequest"}' + * handleAs="json" + * on-core-response="{{handleResponse}}"></core-ajax> + * + * @attribute headers + * @type Object + * @default null + */ + headers: null, + + /** + * Optional raw body content to send when method === "POST". + * + * Example: + * + * <core-ajax method="POST" auto url="http://somesite.com" + * body='{"foo":1, "bar":2}'> + * </core-ajax> + * + * @attribute body + * @type Object + * @default null + */ + body: null, + + /** + * Content type to use when sending data. + * + * @attribute contentType + * @type string + * @default 'application/x-www-form-urlencoded' + */ + contentType: 'application/x-www-form-urlencoded', + + /** + * Set the withCredentials flag on the request. + * + * @attribute withCredentials + * @type boolean + * @default false + */ + withCredentials: false, + + /** + * Additional properties to send to core-xhr. + * + * Can be set to an object containing default properties + * to send as arguments to the `core-xhr.request()` method + * which implements the low-level communication. + * + * @property xhrArgs + * @type Object + * @default null + */ + xhrArgs: null, + + ready: function() { + this.xhr = document.createElement('core-xhr'); + }, + + receive: function(response, xhr) { + if (this.isSuccess(xhr)) { + this.processResponse(xhr); + } else { + this.processError(xhr); + } + this.complete(xhr); + }, + + isSuccess: function(xhr) { + var status = xhr.status || 0; + return !status || (status >= 200 && status < 300); + }, + + processResponse: function(xhr) { + var response = this.evalResponse(xhr); + if (xhr === this.activeRequest) { + this.response = response; + } + this.fire('core-response', {response: response, xhr: xhr}); + }, + + processError: function(xhr) { + var response = xhr.status + ': ' + xhr.responseText; + if (xhr === this.activeRequest) { + this.error = response; + } + this.fire('core-error', {response: response, xhr: xhr}); + }, + + complete: function(xhr) { + this.fire('core-complete', {response: xhr.status, xhr: xhr}); + }, + + evalResponse: function(xhr) { + return this[(this.handleAs || 'text') + 'Handler'](xhr); + }, + + xmlHandler: function(xhr) { + return xhr.responseXML; + }, + + textHandler: function(xhr) { + return xhr.responseText; + }, + + jsonHandler: function(xhr) { + var r = xhr.responseText; + try { + return JSON.parse(r); + } catch (x) { + console.warn('core-ajax caught an exception trying to parse response as JSON:'); + console.warn('url:', this.url); + console.warn(x); + return r; + } + }, + + documentHandler: function(xhr) { + return xhr.response; + }, + + blobHandler: function(xhr) { + return xhr.response; + }, + + arraybufferHandler: function(xhr) { + return xhr.response; + }, + + urlChanged: function() { + if (!this.handleAs) { + var ext = String(this.url).split('.').pop(); + switch (ext) { + case 'json': + this.handleAs = 'json'; + break; + } + } + this.autoGo(); + }, + + paramsChanged: function() { + this.autoGo(); + }, + + autoChanged: function() { + this.autoGo(); + }, + + // TODO(sorvell): multiple side-effects could call autoGo + // during one micro-task, use a job to have only one action + // occur + autoGo: function() { + if (this.auto) { + this.goJob = this.job(this.goJob, this.go, 0); + } + }, + + /** + * Performs an Ajax request to the specified URL. + * + * @method go + */ + go: function() { + var args = this.xhrArgs || {}; + // TODO(sjmiles): we may want XHR to default to POST if body is set + args.body = this.body || args.body; + args.params = this.params || args.params; + if (args.params && typeof(args.params) == 'string') { + args.params = JSON.parse(args.params); + } + args.headers = this.headers || args.headers || {}; + if (args.headers && typeof(args.headers) == 'string') { + args.headers = JSON.parse(args.headers); + } + var hasContentType = Object.keys(args.headers).some(function (header) { + return header.toLowerCase() === 'content-type'; + }); + if (!hasContentType && this.contentType) { + args.headers['Content-Type'] = this.contentType; + } + if (this.handleAs === 'arraybuffer' || this.handleAs === 'blob' || + this.handleAs === 'document') { + args.responseType = this.handleAs; + } + args.withCredentials = this.withCredentials; + args.callback = this.receive.bind(this); + args.url = this.url; + args.method = this.method; + + this.response = this.error = null; + this.activeRequest = args.url && this.xhr.request(args); + return this.activeRequest; + } + + }); + +</script> +</polymer-element> + +<!-- +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 +--> + +<!-- +`paper-toast` provides lightweight feedback about an operation in a small popup +at the base of the screen on mobile and at the lower left on desktop. Toasts are +above all other elements on screen, including the FAB. + +Toasts automatically disappear after a timeout or after user interaction +elsewhere on the screen, whichever comes first. Toasts can be swiped off +screen. There can be only one on the screen at a time. + +Example: + + <paper-toast text="Your draft has been discarded." onclick="discardDraft(el)"></paper-toast> + + <script> + function discardDraft(el) { + el.show(); + } + </script> + +An action button can be presented in the toast. + +Example (using Polymer's data-binding features): + + <paper-toast id="toast2" text="Connection timed out. Showing limited messages."> + <div style="color: blue;" on-tap="{{retry}}">Retry</div> + </paper-toast> + +Positioning toast: + +A standard toast appears near the lower left of the screen. You can change the +position by overriding bottom and left positions. + + paper-toast { + bottom: 40px; + left: 10px; + } + +To make it fit at the bottom of the screen: + + paper-toast { + bottom: 0; + left: 0; + width: 100%; + } + +When the screen size is smaller than the `responsiveWidth` (default to 480px), +the toast will automatically fits at the bottom of the screen. + +@group Paper Elements +@element paper-toast +@homepage github.io +--> + +<!-- +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 +--> + + +<!-- +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 +--> + +<!-- + +`<core-transition>` is an abstraction of an animation. It is used to implement pluggable +transitions, for example in `<core-overlay>`. You can extend this class to create a custom +animation, instantiate it, and import it where you need the animation. + +All instances of `<core-transition>` are stored in a single database with `type=transition`. +For more about the database, please see the documentation for `<core-meta>`. + +Each instance of `<core-transition>` objects are shared across all the clients, so you should +not store state information specific to the animated element in the transition. Rather, store +it on the element. + +Example: + +my-transition.html: + + <polymer-element name="my-transition" extends="core-transition"> + <script> + go: function(node) { + node.style.transition = 'opacity 1s ease-out'; + node.style.opacity = 0; + } + </script> + </polymer-element> + + <my-transition id="my-fade-out"></my-transition> + +my-transition-demo.html: + + <link href="components/core-meta/core-meta.html" rel="import"> + <link href="my-transition.html" rel="import"> + + <div id="animate-me"></div> + + <script> + // Get the core-transition + var meta = document.createElement('core-meta'); + meta.type = 'transition'; + var transition = meta.byId('my-fade-out'); + + // Run the animation + var animated = document.getElementById('animate-me'); + transition.go(animated); + </script> + +@group Polymer Core Elements +@element core-transition +@extends core-meta +@status beta +@homepage github.io +--> +<!-- +Fired when the animation finishes. + +@event core-transitionend +@param {Object} detail +@param {Object} detail.node The animated node +--> + + + +<polymer-element name="core-transition" extends="core-meta" assetpath="polymer/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> +<!-- +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 +--> + + + +<polymer-element name="core-key-helper" assetpath="polymer/bower_components/core-overlay/"> + <script> + Polymer('core-key-helper', { + ENTER_KEY: 13, + ESCAPE_KEY: 27 + }); + </script> +</polymer-element> + +<!-- +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 +--> + +<polymer-element name="core-overlay-layer" assetpath="polymer/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> + + +<!-- +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 +@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> + +<polymer-element name="core-overlay" assetpath="polymer/bower_components/core-overlay/"> +<script> +(function() { + + Polymer('core-overlay', { + + 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' + }, + + 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 + Platform.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() { + 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.size.v) { + this.sizingTarget.style.maxHeight = ''; + } + if (!this.dimensions.size.h) { + this.sizingTarget.style.maxWidth = ''; + } + 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]; + }, + }); + + // 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> + +<!-- +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 +--> + +<!-- + +`<core-transition-css>` implements CSS transitions as `<core-transition>` objects so they can be +reused in a pluggable transition system such as in `<core-overlay>`. Currently this class has +some specific support to animate an element from and to the viewport such as a dialog, but you +can override it for different effects. + +Example: + +my-css-transition.html: + + <polymer-element name="my-css-transition" extends="core-transition-css"> + <template> + <style> + :host(.my-transition) { + opacity: 0; + transition: transform 1s ease-out, opacity 1s ease-out; + } + :host(.my-transition.my-opened) { + opacity: 1; + transform: none; + } + :host(.my-transition-top) { + transform: translateY(-100vh); + } + :host(.my-transition-bottom) { + transform: translateY(100vh); + } + </style> + </template> + <script> + Polymer({ + baseClass: 'my-transition', + openedClass: 'my-opened' + }); + </script> + </polymer-element> + + <my-css-transition id="my-transition-top" transitionType="top"></my-css-transition> + <my-css-transition id="my-transition-bottom" transitionType="bottom"></my-css-transition> + +my-css-transition-demo.html + + <link href="components/core-meta/core-meta.html" rel="import"> + <link href="my-css-transition.html"> + + <div id="animate-me"></div> + + <script> + // Get the core-transition + var meta = document.createElement('core-meta'); + meta.type = 'transition'; + var transition1 = meta.byId('my-transition-top'); + + // Set up the animation + var animated = document.getElementById('animate-me'); + transition1.setup(animated); + transition1.go(animated, {opened: true}); + </script> + +The first element in the template of a `<core-transition-css>` object should be a stylesheet. It +will be injected to the scope of the animated node in the `setup` function. The node is initially +invisible with `opacity: 0`, and you can transition it to an "opened" state by passing +`{opened: true}` to the `go` function. + +All nodes being animated will get the class `my-transition` added in the `setup` function. +Additionally, the class `my-transition-<transitionType>` will be applied. You can use the +`transitionType` attribute to implement several different behaviors with the same +`<core-transition-css>` object. In the above example, `<my-css-transition>` implements both +sliding the node from the top of the viewport and from the bottom of the viewport. + +Available transitions +--------------------- + +`<core-transition-css>` includes several commonly used transitions. + +`core-transition-fade`: Animates from `opacity: 0` to `opacity: 1` when it opens. + +`core-transition-center`: Zooms the node into the final size. + +`core-transition-top`: Slides the node into the final position from the top. + +`core-transition-bottom`: Slides the node into the final position from the bottom. + +`core-transition-left`: Slides the node into the final position from the left. + +`core-transition-right`: Slides the node into the final position from the right. + +@group Polymer Core Elements +@element core-transition-css +@extends core-transition +@status beta +@homepage github.io +--> + + + +<polymer-element name="core-transition-css" extends="core-transition" attributes="transitionType" assetpath="polymer/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; + transition: transform 0.2s ease-in-out, opacity 0.2s ease-in; + -webkit-transition: -webkit-transform 0.2s ease-in-out, opacity 0.2s ease-in; +} + + +:host(.core-transition.core-opened) { + opacity: 1; + transform: translateZ(0); + -webkit-transform: translateZ(0); +} + +:host(.core-transition-center) { + transform: scale(0.5); + -webkit-transform: scale(0.5); +} + +:host(.core-transition-top) { + transform: translateY(-200%); + -webkit-transform: translateY(-200%); +} + +:host(.core-transition-bottom) { + transform: translateY(200%); + -webkit-transform: translateY(200%); +} + +:host(.core-transition-left) { + transform: translateX(-200%); + -webkit-transform: translateX(-200%); +} + +:host(.core-transition-right) { + transform: translateX(200%); + -webkit-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() { + node.classList.toggle(this.revealedClass, opened); + 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> + +<!-- +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 +--> +<!-- +/** + * @group Polymer Core Elements + * @element core-media-query + * @status beta + * @homepage github.io + * + * core-media-query can be used to data bind to a CSS media query. + * The "query" property is a bare CSS media query. + * The "queryMatches" property will be a boolean representing if the page matches that media query. + * + * core-media-query uses media query listeners to dynamically update the "queryMatches" property. + * A "core-media-change" event also fires when queryMatches changes. + * + * Example: + * + * <core-media-query query="max-width: 640px" queryMatches="{{phoneScreen}}"></core-media-query> + * + */ + + /** + * Fired when the media query state changes + * + * @event core-media-change + */ +--> + + +<polymer-element name="core-media-query" attributes="query queryMatches" assetpath="polymer/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="paper-toast" attributes="text duration opened responsiveWidth swipeDisabled" role="status" assetpath="polymer/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 autofocusdisabled="" opened="{{opened}}" target="{{}}" sizingtarget="{{$.container}}" 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, + + eventDelegates: { + trackstart: 'trackStart', + track: 'track', + trackend: 'trackEnd', + transitionend: 'transitionEnd' + }, + + narrowModeChanged: function() { + this.classList.toggle('fit-bottom', this.narrowMode); + }, + + 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.webkitTransform = s.transform = 'translate3d(0, ' + y + 'px, 0)'; + } else { + var x = e.dx; + s.opacity = (this.w - Math.abs(x)) / this.w; + s.webkitTransform = s.transform = 'translate3d(' + x + 'px, 0, 0)'; + } + } + }, + + trackEnd: function(e) { + if (this.dragging) { + this.classList.remove('dragging'); + this.style.opacity = null; + this.style.webkitTransform = this.style.transform = null; + 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> + + + +<!-- +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 +--> + +<!-- +Provides a dialog overlay. + +Child elements that include a `dismissive` attribute are positioned in the lower left corner of the dialog. Elements that use the `affirmative` attribute are positioned in the lower right corner. + +Child elements that include the `dismissive` or `affirmative` attribute will automatically toggle the dialog when clicked. + +One child element should have the `autofocus` attribute so that the Enter key will automatically take action. This is +especially important for screen reader environments. + +Example: + + <paper-dialog heading="Title for dialog"> + <p>Lorem ipsum ....</p> + <p>Id qui scripta ...</p> + <paper-button label="More Info..." dismissive></paper-button> + <paper-button label="Decline" affirmative></paper-button> + <paper-button label="Accept" affirmative autofocus></paper-button> + </paper-dialog> + +#### Transitions + +`<paper-dialog>` can be used with `<paper-transition>` to transition the overlay open and close. + +To use a transition, import `paper-dialog-transition.html` alongside paper-dialog: + + <link rel="import" href="paper-dialog/paper-dialog-transition.html"> + +Then set the `transition` attribute: + + <paper-dialog heading="Title for dialog" transition="paper-dialog-transition-center"> + + <paper-dialog heading="Title for dialog" transition="paper-dialog-transition-bottom"> + +@group Paper Elements +@element paper-dialog +@homepage github.io +--> +<!-- +Fired when the dialog's `opened` property changes. + +@event core-overlay-open +@param {Object} detail +@param {Object} detail.opened the opened state +--> + + + + +<polymer-element name="paper-dialog" attributes="opened heading transition autoCloseDisabled backdrop layered closeSelector" role="dialog" assetpath="polymer/bower_components/paper-dialog/"> + + <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 { + background: white; + color: rgba(0, 0, 0, 0.87); +} + +#shadow { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + z-index: -1; +} + +#container { + overflow: hidden; +} + +#main { + height: auto; + -webkit-order: 1; + -ms-flex-order: 1; + order: 1; + padding: 24px; + overflow: auto; +} + +h1 { + margin: 0; +} + +#actions { + -webkit-order: 2; + -ms-flex-order: 2; + order: 2; + padding: 4px 16px 20px; +} + +polyfill-next-selector { content: ':host > *'; } +::content > * { + font: inherit; +} +</style> + + <div id="shadow"> + <paper-shadow z="3" hasposition=""></paper-shadow> + </div> + + <core-overlay id="overlay" opened="{{opened}}" autoclosedisabled?="{{autoCloseDisabled}}" backdrop?="{{backdrop}}" layered?="{{layered}}" target="{{}}" sizingtarget="{{$.container}}" closeselector="{{closeSelector}}" transition="{{transition}}" margin="20"></core-overlay> + + <div id="container" layout="" vertical=""> + + <div id="actions" layout="" horizontal=""> + <content select="[dismissive]"></content> + <div flex="" auto=""></div> + <content select="[affirmative]"></content> + </div> + + <div id="main" flex="" auto=""> + <h1>{{heading}}</h1> + <content></content> + </div> + + </div> + + </template> + + <script> + + Polymer('paper-dialog', { + + /** + * Set opened to true to show the dialog and to false to hide it. + * A dialog may be made intially opened by setting its opened attribute. + + * @attribute opened + * @type boolean + * @default false + */ + opened: false, + + /** + * If true, the dialog 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 dialog is guaranteed to display above page content. + * + * @attribute layered + * @type boolean + * @default false + */ + layered: false, + + /** + * By default a dialog 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, + + /** + * This property specifies a selector matching elements that should + * close the dialog on tap. + * + * @attribute closeSelector + * @type string + * @default "" + */ + closeSelector: '[dismissive],[affirmative]', + + /** + * @attribute heading + * @type string + * @default '' + */ + heading: '', + + /** + * Set this property to the id of a `core-transition` element to specify + * the transition to use when opening/closing this dialog. + * + * @attribute transition + * @type string + * @default '' + */ + transition: '', + + /** + * Toggle the dialog's opened state. + * @method toggle + */ + toggle: function() { + this.$.overlay.toggle(); + }, + + headingChanged: function() { + this.setAttribute('aria-label', this.heading); + } + + }); + + </script> + +</polymer-element> + +<!-- +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 +--> + + + +<polymer-element name="paper-dialog-transition" extends="core-transition-css" assetpath="polymer/bower_components/paper-dialog/"> + +<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(.paper-dialog-transition) { + outline: none; + opacity: 0; + transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1); + -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); +} + +:host(.paper-dialog-transition.core-opened) { + opacity: 1; + transform: none; + -webkit-transform: none; +} + +:host(.paper-dialog-transition-bottom) { + transform: scale(0.9) translateY(200%); + -webkit-transform: scale(0.9) translateY(200%); +} + +:host(.paper-dialog-transition-center.core-opened) { + animation: paper-dialog-transition-center-keyframes 0.2s cubic-bezier(0.4, 0, 0.2, 1); + -webkit-animation: paper-dialog-transition-center-keyframes 0.2s cubic-bezier(0.4, 0, 0.2, 1); +} + +@keyframes paper-dialog-transition-center-keyframes { + 0% { + transform: scale(0.5) translateY(0); + -webkit-transform: scale(0.5) translateY(0); + } + 90% { + transform: scale(1) translateY(-10px); + -webkit-transform: scale(1) translateY(-10px); + } + 100% { + transform: scale(1) translateY(0); + -webkit-transform: scale(1) translateY(0); + } +} + +@-webkit-keyframes paper-dialog-transition-center-keyframes { + 0% { + transform: scale(0.5) translateY(0); + -webkit-transform: scale(0.5) translateY(0); + } + 90% { + transform: scale(1) translateY(-10px); + -webkit-transform: scale(1) translateY(-10px); + } + 100% { + transform: scale(1) translateY(0); + -webkit-transform: scale(1) translateY(0); + } +} +</style> +</template> + +<script> + Polymer('paper-dialog-transition',{ + baseClass: 'paper-dialog-transition' + }); +</script> + +</polymer-element> + +<paper-dialog-transition id="paper-dialog-transition-bottom" transitiontype="bottom"></paper-dialog-transition> +<paper-dialog-transition id="paper-dialog-transition-center" transitiontype="center"></paper-dialog-transition> +<!-- +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 +--> + +<!-- +@group Paper Elements + +Material Design: <a href="http://www.google.com/design/spec/components/buttons.html">Buttons</a> + +`paper-button` is a button. When the user touches the button, a ripple effect emanates +from the point of contact. It may be flat or raised. A raised button is styled with a +shadow. + +Example: + + <paper-button>flat button</paper-button> + <paper-button raised>raised button</paper-button> + +You may use custom DOM in the button body to create a variety of buttons. For example, to +create a button with an icon and some text: + + <paper-button> + <core-icon icon="favorite"> + custom button content + </paper-button> + +Styling +------- + +Style the button with CSS as you would a normal DOM element. + + /* make #my-button green with yellow text */ + #my-button { + background: green; + color: yellow; + } + +By default, the ripple is the same color as the foreground at 25% opacity. You may +customize the color using this selector: + + /* make #my-button use a blue ripple instead of foreground color */ + #my-button::shadow #ripple { + color: blue; + } + +The opacity of the ripple is not customizable via CSS. + +@element paper-button +@extends paper-button-base +@status unstable +--> + + + + + + + + +<polymer-element name="paper-button" extends="paper-button-base" attributes="raised recenteringTouch fill" role="button" assetpath="polymer/bower_components/paper-button/"> + + <template> + + <style> + + :host { + display: inline-block; + position: relative; + box-sizing: border-box; + min-width: 5.14em; + padding: 0.7em 0.57em; + margin: 0 0.29em; + background: transparent; + text-align: center; + font: inherit; + text-transform: uppercase; + outline: none; + border-radius: 3px; + -webkit-user-select: none; + user-select: none; + cursor: pointer; + z-index: 0; + } + + :host([disabled]) { + background: #eaeaea !important; + color: #a8a8a8 !important; + cursor: auto; + pointer-events: none; + } + + ::content * { + text-transform: inherit; + } + + #ripple { + pointer-events: none; + z-index: -1; + } + + </style> + + <template if="{{raisedButton || raised}}"> + <paper-shadow id="shadow" z="{{z}}" animated=""></paper-shadow> + </template> + + <!-- this div is needed to position the ripple behind text content --> + <div relative=""> + <content></content> + {{label}} + </div> + + </template> + + <script> + Polymer('paper-button',{ + + publish: { + + label: '', + + /** + * If true, the button will be styled with a shadow. + * + * @attribute raised + * @type boolean + * @default false + */ + raised: false, + raisedButton: 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 + + }, + + labelChanged: function() { + if (this.label) { + console.warn('The "label" property is deprecated.'); + } + }, + + raisedButtonChanged: function() { + if (this.raisedButton) { + console.warn('The "raisedButton" property is deprecated.'); + } + } + + }); + </script> +</polymer-element> + +<!-- +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 +The complete set of authors may be found at http://polymer.github.io/AUTHORS +The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS +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 +--> + +<!-- +`paper-input` is a single- or multi-line text field where user can enter input. +It can optionally have a label. + +Example: + + <paper-input label="Your Name"></paper-input> + <paper-input multiline label="Enter multiple lines here"></paper-input> + +Theming +-------- + +Set `CoreStyle.g.paperInput.focusedColor` and `CoreStyle.g.paperInput.invalidColor` to theme +the focused and invalid states. + +To add custom styling to only some elements, use these selectors: + + html /deep/ paper-input[focused] .floated-label { + /* floating label color when the input has focus */ + color: green; + } + + html /deep/ paper-input .focused-underline, + html /deep/ paper-input .cursor { + /* line and cursor color when the input has focus */ + background-color: green; + } + + html /deep/ paper-input.invalid[focused] .floated-label, + html /deep/ paper-input[focused] .error-text, + html /deep/ paper-input[focused] .error-icon { + /* error text, icon, and floating label color when input is invalid */ + color: salmon; + } + + html /deep/ paper-input.invalid .focused-underline, + html /deep/ paper-input.invalid .cursor { + /* line and cursor color when the input is invalid */ + background-color: salmon; + } + +@group Paper Elements +@element paper-input +@extends core-input +@homepage github.io +--> + + + +<!-- +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 +The complete set of authors may be found at http://polymer.github.io/AUTHORS +The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS +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 +--> + +<!-- +/** + * core-input is an unstyled single- or multi-line text field where user can + * enter input. + * + * Example: + * + * <core-input placeholder="Placeholder text here"></core-input> + * + * <core-input multiline placeholder="Enter multiple lines here"></core-input> + * + * The text input's value is considered "committed" if the user hits the "enter" + * key or blurs the input after changing the value. The `change` event is fired + * when the value becomes committed, and the committed value is stored in the + * `value` property. The current value of the input is stored in the `inputValue` + * property. + * + * Validation + * ---------- + * + * core-input can optionally validate the value using the HTML5 constraints API, + * similar to native inputs. There are two methods to configure input validation: + * + * 1. By setting the `type` attribute. For example, setting it to `email` will + * check the value is a valid email, and setting it to `number` will check + * the input is a number. + * + * 2. By setting attributes related to validation. The attributes are `pattern`, + * `min`, `max`, `step` and `required`. + * + * Only `required` is supported for multiline inputs currently. + * + * Example: + * + * <core-input type="email" placeholder="enter your email"></core-input> + * + * <core-input type="number" min="5" placeholder="enter a number greater than or equal to 5"></core-input> + * + * <core-input pattern=".*abc.*" placeholder="enter something containing 'abc'"></core-input> + * + * See https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation + * for more info on validation. + * + * @group Polymer Core Elements + * @element core-input + * @homepage github.io + */ +--> + +<!-- +Fired when the inputValue of is changed. This is the same event as the DOM +"input" event. + +@event input +--> + +<!-- +Fired when the user commits the value of the input, either by the hitting the +`enter` key or blurring the input after the changing the inputValue. Also see the +DOM "change" event. + +@event change +--> + +<!-- +Fired when the inputValue of this text input changes and fails validation. + +@event input-invalid +@param {Object} detail +@param {string} value The text input's inputValue. +--> + +<!-- +Fired when the inputValue of this text input changes and passes validation. + +@event input-valid +@param {Object} detail +@param {string} value The text input's inputValue. +--> + + +<polymer-element name="core-input" on-focus="{{focusAction}}" assetpath="polymer/bower_components/core-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; + text-align: inherit; + position: relative; + width: 20em; +} + +:host:hover { + cursor: text; +} + +input, +textarea { + font: inherit; + color: inherit; + width: 100%; + margin: 0; + padding: 0; + background-color: transparent; + border: none; + outline: none; + width: 100%; +} + +textarea { + resize: none; +} + +textarea[rows=fit] { + height: 100%; +}</style> + + <template if="{{multiline}}"> + <textarea id="input" value="{{inputValue}}" rows="{{rows}}" disabled?="{{disabled}}" placeholder="{{placeholder}}" autofocus?="{{autofocus}}" required?="{{required}}" readonly?="{{readonly}}" maxlength="{{maxlength}}" aria-label="{{label || placeholder}}" aria-invalid="{{invalid}}" on-change="{{inputChangeAction}}" on-focus="{{inputFocusAction}}" on-blur="{{inputBlurAction}}"></textarea> + </template> + + <template if="{{!multiline}}"> + <input id="input" value="{{inputValue}}" disabled?="{{disabled}}" type="{{type}}" placeholder="{{placeholder}}" autofocus?="{{autofocus}}" required?="{{required}}" readonly?="{{readonly}}" pattern="{{pattern}}" min="{{min}}" max="{{max}}" step="{{step}}" maxlength="{{maxlength}}" aria-label="{{label || placeholder}}" aria-invalid="{{invalid}}" on-keypress="{{keypressAction}}" on-change="{{inputChangeAction}}" on-focus="{{inputFocusAction}}" on-blur="{{inputBlurAction}}"> + </template> + + </template> + + <script> + + Polymer('core-input', { + publish: { + /** + * Placeholder text that hints to the user what can be entered in + * the input. + * + * @attribute placeholder + * @type string + * @default '' + */ + placeholder: '', + + /** + * If true, this input cannot be focused and the user cannot change + * its value. + * + * @attribute disabled + * @type boolean + * @default false + */ + disabled: false, + + /** + * If true, the user cannot modify the value of the input. + * + * @attribute readonly + * @type boolean + * @default false + */ + readonly: false, + + /** + * If true, this input will automatically gain focus on page load. + * + * @attribute autofocus + * @type boolean + * @default false + */ + autofocus: false, + + /** + * If true, this input accepts multi-line input like a `<textarea>` + * + * @attribute multiline + * @type boolean + * @default false + */ + multiline: false, + + /** + * (multiline only) The height of this text input in rows. The input + * will scroll internally if more input is entered beyond the size + * of the component. This property is meaningless if multiline is + * false. You can also set this property to "fit" and size the + * component with CSS to make the input fit the CSS size. + * + * @attribute rows + * @type number|'fit' + * @default 'fit' + */ + rows: 'fit', + + /** + * The current value of this input. Changing inputValue programmatically + * will cause value to be out of sync. Instead, change value directly + * or call commit() after changing inputValue. + * + * @attribute inputValue + * @type string + * @default '' + */ + inputValue: '', + + /** + * The value of the input committed by the user, either by changing the + * inputValue and blurring the input, or by hitting the `enter` key. + * + * @attribute value + * @type string + * @default '' + */ + value: '', + + /** + * Set the input type. Not supported for `multiline`. + * + * @attribute type + * @type string + * @default text + */ + type: 'text', + + /** + * If true, the input is invalid if its value is null. + * + * @attribute required + * @type boolean + * @default false + */ + required: false, + + /** + * A regular expression to validate the input value against. See + * https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation#Validation-related_attributes + * for more info. Not supported if `multiline` is true. + * + * @attribute pattern + * @type string + * @default '.*' + */ + // FIXME(yvonne): The default is set to .* because we can't bind to pattern such + // that the attribute is unset if pattern is null. + pattern: '.*', + + /** + * If set, the input is invalid if the value is less than this property. See + * https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation#Validation-related_attributes + * for more info. Not supported if `multiline` is true. + * + * @attribute min + */ + min: null, + + /** + * If set, the input is invalid if the value is greater than this property. See + * https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation#Validation-related_attributes + * for more info. Not supported if `multiline` is true. + * + * @attribute max + */ + max: null, + + /** + * If set, the input is invalid if the value is not `min` plus an integral multiple + * of this property. See + * https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation#Validation-related_attributes + * for more info. Not supported if `multiline` is true. + * + * @attribute step + */ + step: null, + + /** + * The maximum length of the input value. + * + * @attribute maxlength + * @type number + */ + maxlength: null, + + /** + * If this property is true, the text input's inputValue failed validation. + * + * @attribute invalid + * @type boolean + * @default false + */ + invalid: false, + + /** + * If this property is true, validate the input as they are entered. + * + * @attribute validateImmediately + * @type boolean + * @default true + */ + validateImmediately: true + }, + + ready: function() { + this.handleTabindex(this.getAttribute('tabindex')); + }, + + disabledChanged: function() { + if (this.disabled) { + this.setAttribute('aria-disabled', true); + } else { + this.removeAttribute('aria-disabled'); + } + }, + + invalidChanged: function() { + this.classList.toggle('invalid', this.invalid); + this.fire('input-'+ (this.invalid ? 'invalid' : 'valid'), {value: this.inputValue}); + }, + + inputValueChanged: function() { + if (this.validateImmediately) { + this.updateValidity_(); + } + }, + + valueChanged: function() { + this.inputValue = this.value; + }, + + requiredChanged: function() { + if (this.validateImmediately) { + this.updateValidity_(); + } + }, + + attributeChanged: function(attr, oldVal, curVal) { + if (attr === 'tabindex') { + this.handleTabindex(curVal); + } + }, + + handleTabindex: function(tabindex) { + if (tabindex > 0) { + this.$.input.setAttribute('tabindex', -1); + } else { + this.$.input.removeAttribute('tabindex'); + } + }, + + /** + * Commits the inputValue to value. + * + * @method commit + */ + commit: function() { + this.value = this.inputValue; + }, + + updateValidity_: function() { + if (this.$.input.willValidate) { + this.invalid = !this.$.input.validity.valid; + } + }, + + keypressAction: function(e) { + // disallow non-numeric input if type = number + if (this.type !== 'number') { + return; + } + var c = String.fromCharCode(e.charCode); + if (e.charCode !== 0 && !c.match(/[\d-\.e]/)) { + e.preventDefault(); + } + }, + + inputChangeAction: function() { + this.commit(); + if (!window.ShadowDOMPolyfill) { + // re-fire event that does not bubble across shadow roots + this.fire('change', null, this); + } + }, + + focusAction: function(e) { + if (this.getAttribute('tabindex') > 0) { + // Forward focus to the inner input if tabindex is set on the element + // This will not cause an infinite loop because focus will not fire on the <input> + // again if it's already focused. + this.$.input.focus(); + } + }, + + inputFocusAction: function(e) { + if (window.ShadowDOMPolyfill) { + // re-fire non-bubbling event if polyfill + this.fire('focus', null, this, false); + } + }, + + inputBlurAction: function() { + if (window.ShadowDOMPolyfill) { + // re-fire non-bubbling event + this.fire('blur', null, this, false); + } + }, + + /** + * Forwards to the internal input / textarea element. + * + * @method blur + */ + blur: function() { + this.$.input.blur(); + }, + + /** + * Forwards to the internal input / textarea element. + * + * @method click + */ + click: function() { + this.$.input.click(); + }, + + /** + * Forwards to the internal input / textarea element. + * + * @method focus + */ + focus: function() { + this.$.input.focus(); + }, + + /** + * Forwards to the internal input / textarea element. + * + * @method select + */ + select: function() { + this.$.input.select(); + }, + + /** + * Forwards to the internal input / textarea element. + * + * @method setSelectionRange + * @param {number} selectionStart + * @param {number} selectionEnd + * @param {String} selectionDirection (optional) + */ + setSelectionRange: function(selectionStart, selectionEnd, selectionDirection) { + this.$.input.setSelectionRange(selectionStart, selectionEnd, selectionDirection); + }, + + /** + * Forwards to the internal input element, not implemented for multiline. + * + * @method setRangeText + * @param {String} replacement + * @param {number} start (optional) + * @param {number} end (optional) + * @param {String} selectMode (optional) + */ + setRangeText: function(replacement, start, end, selectMode) { + if (!this.multiline) { + this.$.input.setRangeText(replacement, start, end, selectMode); + } + }, + + /** + * Forwards to the internal input, not implemented for multiline. + * + * @method stepDown + * @param {number} n (optional) + */ + stepDown: function(n) { + if (!this.multiline) { + this.$.input.stepDown(n); + } + }, + + /** + * Forwards to the internal input, not implemented for multiline. + * + * @method stepUp + * @param {number} n (optional) + */ + stepUp: function(n) { + if (!this.multiline) { + this.$.input.stepUp(n); + } + }, + + get willValidate() { + return this.$.input.willValidate; + }, + + get validity() { + return this.$.input.validity; + }, + + get validationMessage() { + return this.$.input.validationMessage; + }, + + /** + * Forwards to the internal input / textarea element and updates state. + * + * @method checkValidity + * @return {boolean} + */ + checkValidity: function() { + var r = this.$.input.checkValidity(); + this.updateValidity_(); + return r; + }, + + /** + * Forwards to the internal input / textarea element and updates state. + * + * @method setCustomValidity + * @param {String} message + */ + setCustomValidity: function(message) { + this.$.input.setCustomValidity(message); + this.updateValidity_(); + } + + }); + </script> + +</polymer-element> + +<!-- +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 +--> +<!-- + +The `core-style` element helps manage styling inside other elements and can +be used to make themes. The `core-style` element can be either a producer +or consumer of styling. If it has its `id` property set, it's a producer. +Elements that are producers should include css styling as their text content. +If a `core-style` has its `ref` property set, it's a consumer. A `core-style` +typically sets its `ref` property to the value of the `id` property of the +`core-style` it wants to use. This allows a single producer to be used in +multiple places, for example, in many different elements. + +It's common to place `core-style` producer elements inside HTMLImports. +Remote stylesheets should be included this way, the @import css mechanism is +not currently supported. + +Here's a basic example: + + <polymer-element name="x-test" noscript> + <template> + <core-style ref="x-test"></core-style> + <content></content> + </template> + </polymer-element> + +The `x-test` element above will be styled by any `core-style` elements that have +`id` set to `x-test`. These `core-style` producers are separate from the element +definition, allowing a user of the element to style it independent of the author's +styling. For example: + + <core-style id="x-test"> + :host { + backgound-color: steelblue; + } + </core-style> + +The content of the `x-test` `core-style` producer gets included inside the +shadowRoot of the `x-test` element. If the content of the `x-test` producer +`core-style` changes, all consumers of it are automatically kept in sync. This +allows updating styling on the fly. + +The `core-style` element also supports bindings and it is the producer +`core-style` element is the model for its content. Here's an example: + + <core-style id="x-test"> + :host { + background-color: {{myColor}}; + } + </core-style> + <script> + document._currentScript.ownerDocument.getElementById('x-test').myColor = 'orange'; + </script> + +Finally, to facilitate sharing data between `core-style` elements, all +`core-style` elements have a `g` property which is set to the global +`CoreStyle.g`. Here's an example: + + <core-style id="x-test"> + :host { + background-color: {{g.myColor}}; + } + </core-style> + <script> + CoreStyle.g.myColor = 'tomato'; + </script> + +Finally, one `core-style` can be nested inside another. The `core-style` +element has a `list` property which is a map of all the `core-style` producers. +A `core-style` producer's content is available via its `cssText` property. +Putting this together: + + <core-style id="common"> + :host { + font-family: sans-serif; + } + </core-style> + + <core-style id="x-test"> + {{list.common.cssText}} + + :host { + background-color: {{g.myColor}}; + } + </core-style> + + +@group Polymer Core Elements +@element core-style +@homepage github.io +--> + + + +<polymer-element name="core-style" hidden assetpath="polymer/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 (readonly) + * @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 = Platform.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 = Platform.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"> + +:host([focused]) .floated-label { + color: {{g.paperInput.focusedColor}}; +} + +.focused-underline, +.cursor { + background-color: {{g.paperInput.focusedColor}}; +} + + +:host(.invalid[focused]) .floated-label, +:host([focused]) .error-text, +:host([focused]) .error-icon { + color: {{g.paperInput.invalidColor}}; +} + +:host(.invalid) .focused-underline, +:host(.invalid) .cursor { + background-color: {{g.paperInput.invalidColor}}; +} + +</core-style> + +<polymer-element name="paper-input" extends="core-input" layout="" vertical="" attributes="label floatingLabel maxRows error" on-transitionend="{{transitionEndAction}}" on-webkittransitionend="{{transitionEndAction}}" assetpath="polymer/bower_components/paper-input/"> + + <template> + + <!-- + Input tests: + + - set value to integer 0 + - various html5 input types + - sizing: + - single-line: size with CSS + - single-line: can fit to container + - multi-line: size with CSS + - multi-line: size with rows + - multi-line: can fit to container + - multi-line: grows with typing + - multi-line: max rows + - multi-line: max rows, scrolls after + --> + + <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; + color: #757575; + padding: 0.75em 0; +} + +:host /deep/ input, +:host /deep/ textarea { + font: inherit; + color: #000; + padding: 0; + margin: 0; + background-color: transparent; + border: none; + outline: none; + /* see comments in template */ + width: 100%; + height: 100%; +} + +input:invalid, +textarea:invalid { + box-shadow: none; +} + +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%; +} + +.cursor { + position: absolute; + top: 0.4em; + left: 0; + width: 1px; + height: 1.4em; + opacity: 0.4; + -moz-transform-origin: 0%; + -webkit-transform-origin: 0%; + transform-origin: 0%; + -webkit-transform: none; + transform: none; +} + +.cursor[invisible] { + opacity: 0.75; + -webkit-transform: translate3d(3em,0,0) scale3d(50,1,1); + transform: translate3d(3em,0,0) scale3d(50,1,1); +} + +.input-container { + position: absolute; + /* simulate padding so the input/textarea can use 100% width/height */ + top: 0.5em; + right: 0; + bottom: 0.25em; + left: 0; +} + +.underline { + height: 0px; + overflow: visible; +} + +:host([disabled]) .underline { + border-bottom: 1px dashed; +} + +.unfocused-underline { + height: 1px; + background: #757575; + border-bottom-color: #757575; +} + +.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"></core-style> + + <div class="floated-label" aria-hidden="true" hidden?="{{!floatingLabel}}" invisible?="{{!inputValue && !(type === 'number' && !validity.valid) || labelAnimated}}"> + <!-- needed for floating label animation measurement --> + <span id="floatedLabelText" class="label-text">{{label}}</span> + </div> + + <!-- <div class="input-body" flex auto relative on-down="{{downAction}}" on-up="{{upAction}}"> --> + <div class="input-body" flex="" auto="" relative=""> + + <!-- the mirror sizes the input/textarea so it grows with typing --> + <div id="mirror" class="mirror-text" invisible="" aria-hidden="true"></div> + + <div class="label" fit="" aria-hidden="true"> + <!-- needed for floating label animation measurement --> + <span id="labelText" class="label-text" invisible?="{{inputValue || !inputValue && type === 'number' && !validity.valid}}" animated?="{{labelAnimated}}">{{label}}</span> + </div> + + <div class="cursor" invisible?="{{!cursorAnimated}}" animated?="{{cursorAnimated}}"></div> + + <!-- size the input/textarea with a div, because the textarea has intrinsic size in ff --> + <div class="input-container" on-down="{{downAction}}" on-up="{{upAction}}"> + <shadow></shadow> + </div> + + </div> + + <div id="underline" class="underline" relative=""> + <div class="unfocused-underline" fit="" invisible?="{{disabled}}"></div> + <div id="focusedUnderline" class="focused-underline" fit="" invisible?="{{!focused}}" animated?="{{underlineAnimated}}"></div> + </div> + + <div layout="" horizontal="" center="" hidden?="{{!invalid}}"> + <div class="error-text" flex="" auto="" role="alert" aria-hidden="{{!invalid}}">{{error || validationMessage}}</div> + <core-icon class="error-icon" icon="warning"></core-icon> + </div> + + </template> + + <script> + + (function() { + + var paperInput = CoreStyle.g.paperInput = CoreStyle.g.paperInput || {}; + paperInput.focusedColor = '#4059a9'; + paperInput.invalidColor = '#d34336'; + + 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, + + /** + * (multiline only) If set to a non-zero value, the height of this + * text input will grow with the value changes until it is maxRows + * rows tall. If the maximum size does not fit the value, the text + * input will scroll internally. + * + * @attribute maxRows + * @type number + * @default 0 + */ + maxRows: 0, + + /** + * 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} + + }, + + get inputValueForMirror() { + var tokens = this.inputValue ? String(this.inputValue).replace(/&/gm, '&').replace(/"/gm, '"').replace(/'/gm, ''').replace(/</gm, '<').replace(/>/gm, '>').split('\n') : ['']; + + // Enforce the min and max heights for a multiline input here to + // avoid measurement + if (this.multiline) { + if (this.maxRows && tokens.length > this.maxRows) { + tokens = tokens.slice(0, this.maxRows); + } + while (this.rows && tokens.length < this.rows) { + tokens.push(''); + } + } + + return tokens.join('<br>') + ' '; + }, + + get inputHasValue() { + // if type = number, the input value is the empty string until a valid number + // is entered so we must do some hacks here + return this.inputValue || (this.type === 'number' && !this.validity.valid); + }, + + syncInputValueToMirror: function() { + this.$.mirror.innerHTML = this.inputValueForMirror; + }, + + ready: function() { + this.syncInputValueToMirror(); + }, + + 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; + } + + 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; + } + + this.labelAnimated = true; + // Handle interrupted animation + this.async(function() { + this.transitionEndAction(); + }, null, 250); + + if (this.inputHasValue) { + this.$.labelText.style.webkitTransform = this.$.labelText.cachedTransform; + this.$.labelText.style.transform = this.$.labelText.cachedTransform; + } else { + // 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 = ''; + } + }, + + inputValueChanged: function(old) { + this.super(); + + this.syncInputValueToMirror(); + if (old && !this.inputValue || !old && this.inputValue) { + this.animateFloatingLabel(); + } + }, + + placeholderChanged: function() { + this.label = this.placeholder; + }, + + inputFocusAction: function() { + this.super(arguments); + this.focused = true; + }, + + inputBlurAction: function(e) { + this.super(arguments); + this.focused = false; + }, + + downAction: function(e) { + if (this.disabled) { + return; + } + + if (this.focused) { + return; + } + + // The underline spills from the tap location + 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; + + // Cursor animation only runs if the input is empty + if (!this.inputHasValue) { + this.cursorAnimated = true; + } + // Handle interrupted animation + this.async(function() { + this.transitionEndAction(); + }, null, 250); + }, + + keydownAction: function() { + this.super(); + + // more type = number hacks. see core-input for more info + if (this.type === 'number') { + var valid = !this.inputValue && this.validity.valid; + this.async(function() { + if (valid !== (!this.inputValue && this.validity.valid)) { + this.animateFloatingLabel(); + } + }); + } + }, + + transitionEndAction: function() { + this.underlineAnimated = false; + this.cursorAnimated = false; + this.labelAnimated = false; + } + + }); + + }()); + + </script> + +</polymer-element> + + + + +<polymer-element name="events-list" attributes="api cbEventClicked" assetpath="polymer/"> + <template> + <style> + :host { + display: block; + } + + .eventContainer { + font-size: 1rem; + } + + </style> + + <template if="{{cbEventClicked}}"> + <style> + a { + text-decoration: underline; + cursor: pointer; + } + </style> + </template> + + <div> + <template repeat="{{event in events}}"> + <div class="eventContainer"> + <a on-click="{{handleClick}}">{{event.event}}</a> + ({{event.listener_count}} listeners) + </div> + </template> + + </div> + </template> + <script> + Polymer('events-list',{ + cbEventClicked: null, + events: [], + + domReady: function() { + this.events = this.api.events + + this.api.addEventListener('events-updated', this.eventsUpdated.bind(this)) + }, + + eventsUpdated: function() { + this.events = this.api.events; + }, + + handleClick: function(ev) { + if(this.cbEventClicked) { + this.cbEventClicked(ev.path[0].innerHTML); + } + }, + + }); + </script> +</polymer-element> + + +<polymer-element name="event-fire-dialog" attributes="api" assetpath="polymer/"> + <template> + <style> + paper-input:first-child { + padding-top: 0; + } + + .eventContainer { + margin-left: 30px; + } + + @media all and (max-width: 620px) { + .eventContainer { + display: none; + } + } + + </style> + + <paper-dialog id="dialog" heading="Fire Event" transition="paper-dialog-transition-bottom" backdrop="true"> + <div layout="" horizontal=""> + <div> + <paper-input id="inputType" label="Event Type" floatinglabel="true" autofocus required></paper-input> + <paper-input id="inputData" label="Event Data (JSON, optional)" floatinglabel="true" multiline=""></paper-input> + </div> + <div class="eventContainer"> + <b>Available events:</b> + <events-list api="{{api}}" cbeventclicked="{{eventSelected}}"> + </events-list></div> + </div> + <paper-button dismissive="">Cancel</paper-button> + <paper-button affirmative="" on-click="{{clickFireEvent}}">Fire Event</paper-button> + </paper-dialog> + + </template> + <script> + Polymer('event-fire-dialog',{ + ready: function() { + // to ensure callback methods work.. + this.eventSelected = this.eventSelected.bind(this) + }, + + show: function(eventType, eventData) { + this.setEventType(eventType); + this.setEventData(eventData); + + this.$.dialog.toggle(); + }, + + setEventType: function(eventType) { + this.$.inputType.value = eventType; + }, + + setEventData: function(eventData) { + this.$.inputData.value = eventData; + }, + + eventSelected: function(eventType) { + this.setEventType(eventType); + }, + + clickFireEvent: function() { + this.api.fire_event( + this.$.inputType.value, + this.$.inputData.value + ) + } + }); + </script> +</polymer-element> + + + + + + + + +<!-- +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 +--> + +<!-- +`core-menu` is a selector which styles to looks like a menu. + + <core-menu selected="0"> + <core-item icon="settings" label="Settings"></core-item> + <core-item icon="dialog" label="Dialog"></core-item> + <core-item icon="search" label="Search"></core-item> + </core-menu> + +When an item is selected the `core-selected` class is added to it. The user can +use the class to add more stylings to the selected item. + + core-item.core-selected { + color: red; + } + +The `selectedItem` property references the selected item. + + <core-menu selected="0" selectedItem="{{item}}"> + <core-item icon="settings" label="Settings"></core-item> + <core-item icon="dialog" label="Dialog"></core-item> + <core-item icon="search" label="Search"></core-item> + </core-menu> + + <div>selected label: {{item.label}}</div> + +The `core-select` event signals selection change. + + <core-menu selected="0" on-core-select="{{selectAction}}"> + <core-item icon="settings" label="Settings"></core-item> + <core-item icon="dialog" label="Dialog"></core-item> + <core-item icon="search" label="Search"></core-item> + </core-menu> + + ... + + selectAction: function(e, detail) { + if (detail.isSelected) { + var selectedItem = detail.item; + ... + } + } + +@group Polymer Core Elements +@element core-menu +@extends core-selector +--> + +<!-- +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 +--> + +<!-- +@group Polymer Core Elements + +`<core-selector>` is used to manage a list of elements that can be selected. + +The attribute `selected` indicates which item element is being selected. +The attribute `multi` indicates if multiple items can be selected at once. +Tapping on the item element would fire `core-activate` event. Use +`core-select` event to listen for selection changes. + +Example: + + <core-selector selected="0"> + <div>Item 1</div> + <div>Item 2</div> + <div>Item 3</div> + </core-selector> + +`<core-selector>` is not styled. Use the `core-selected` CSS class to style the selected element. + + <style> + .item.core-selected { + background: #eee; + } + </style> + ... + <core-selector> + <div class="item">Item 1</div> + <div class="item">Item 2</div> + <div class="item">Item 3</div> + </core-selector> + +@element core-selector +@status stable +@homepage github.io +--> + +<!-- +Fired when an item's selection state is changed. This event is fired both +when an item is selected or 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 item element +--> +<!-- +Fired when an item element is tapped. + +@event core-activate +@param {Object} detail + @param {Object} detail.item the item element +--> + + +<!-- +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 +--> +<!-- +@group Polymer Core Elements + +The `<core-selection>` element is used to manage selection state. It has no +visual appearance and is typically used in conjunction with another element. +For example, [core-selector](#core-selector) +use a `<core-selection>` to manage selection. + +To mark an item as selected, call the `select(item)` method on +`<core-selection>`. The item itself is an argument to this method. + +The `<core-selection>`element manages selection state for any given set of +items. When an item is selected, the `core-select` event is fired. + +The attribute `multi` indicates if multiple items can be selected at once. + +Example: + + <polymer-element name="selection-example"> + <template> + <style> + polyfill-next-selector { content: ':host > .selected'; } + ::content > .selected { + font-weight: bold; + font-style: italic; + } + </style> + <ul on-tap="{{itemTapAction}}"> + <content></content> + </ul> + <core-selection id="selection" multi + on-core-select="{{selectAction}}"></core-selection> + </template> + <script> + Polymer('selection-example', { + itemTapAction: function(e, detail, sender) { + this.$.selection.select(e.target); + }, + selectAction: function(e, detail, sender) { + detail.item.classList.toggle('selected', detail.isSelected); + } + }); + </script> + </polymer-element> + + <selection-example> + <li>Red</li> + <li>Green</li> + <li>Blue</li> + </selection-example> + +@element core-selection +--> + +<!-- +Fired when an item's selection state is changed. This event is fired both +when an item is selected or 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 de-selection + @param {Object} detail.item the item element +--> + + +<polymer-element name="core-selection" attributes="multi" hidden assetpath="polymer/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="polymer/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', + + 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 + */ + 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() { + this.updateSelected(); + }, + + updateSelected: function() { + this.validateSelected(); + if (this.multi) { + this.clearSelection(); + this.selected && this.selected.forEach(function(s) { + this.valueToSelection(s); + }, 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 !== undefined) { + this.selected = [this.selected]; + } + }, + + clearSelection: function() { + if (this.multi) { + this.selection.slice().forEach(function(s) { + this.$.selection.setItemSelected(s, false); + }, this); + } else { + this.$.selection.setItemSelected(this.selection, false); + } + this.selectedItem = null; + this.$.selection.clear(); + }, + + valueToSelection: function(value) { + var item = (value === null || value === undefined) ? + null : this.items[this.valueToIndex(value)]; + this.$.selection.select(item); + }, + + 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; + }, + + 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); + } + this.valueToSelection(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} wrap 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(wrap) { + var i = wrap && !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} wrap 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(wrap) { + var i = wrap && this.selectedIndex >= this.items.length - 1 ? 0 : this.selectedIndex + 1; + return this.selectIndex(i); + } + + }); + </script> +</polymer-element> + + +<polymer-element name="core-menu" extends="core-selector" assetpath="polymer/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> + + <shadow></shadow> + +</template> +<script>Polymer('core-menu');</script></polymer-element> + +<!-- +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 +The complete set of authors may be found at http://polymer.github.io/AUTHORS +The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS +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 +--> + +<!-- +Use to create nested menus inside of `core-menu` elements. + + <core-menu selected="0"> + + <core-submenu icon="settings" label="Topics"> + <core-item label="Topic 1"></core-item> + <core-item label="Topic 2"></core-item> + </core-submenu> + + <core-submenu icon="settings" label="Favorites"> + <core-item label="Favorite 1"></core-item> + <core-item label="Favorite 2"></core-item> + <core-item label="Favorite 3"></core-item> + </core-submenu> + + </core-menu> + +There is a margin set on the submenu to indent the items. +You can override the margin by doing: + + core-submenu::shadow #submenu { + margin-left: 20px; + } + +To style the item for the submenu, do something like this: + + core-submenu::shadow > #submenuItem { + color: blue; + } + +To style all the `core-item`s in the light DOM: + + polyfill-next-selector { content: 'core-submenu > #submenu > core-item'; } + core-submenu > core-item { + color: red; + } + +The above will style `Topic1` and `Topic2` to have font color red. + + <core-submenu icon="settings" label="Topics"> + <core-item label="Topic1"></core-item> + <core-item label="Topic2"></core-item> + </core-submenu> + +@group Polymer Core Elements +@element core-submenu +@extends core-item +--> + +<!-- +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 +--> + +<!-- +`core-item` is a simple line-item object: a label and/or an icon that can also +act as a link. + +Example: + + <core-item icon="settings" label="Settings"></core-item> + +To use as a link, put <a> element in the item. + +Example: + + <core-item icon="settings" label="Settings"> + <a href="#settings" target="_self"></a> + </core-item> + +@group Polymer Core Elements +@element core-item +@homepage github.io +--> + + + +<polymer-element name="core-item" attributes="label icon src" horizontal="" center="" layout="" assetpath="polymer/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> + + +<!-- +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 +--> + +<!-- +`core-collapse` creates a collapsible block of content. By default, the content +will be collapsed. Use `opened` to show/hide the content. + + <button on-click="{{toggle}}">toggle collapse</button> + + <core-collapse id="collapse"> + ... + </core-collapse> + + ... + + toggle: function() { + this.$.collapse.toggle(); + } + +@group Polymer Core Elements +@element core-collapse +--> + + + +<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-collapse { + display: block; +} + +html /deep/ .core-collapse-closed { + display: none; +} +</style> + +<polymer-element name="core-collapse" attributes="target horizontal opened duration fixedSize" assetpath="polymer/bower_components/core-collapse/"> +<template> + + <content></content> + +</template> +<script> + + Polymer('core-collapse', { + + /** + * Fired when the `core-collapse`'s `opened` property changes. + * + * @event core-collapse-open + */ + + /** + * Fired when the target element has been resized as a result of the opened + * state changing. + * + * @event core-resize + */ + + /** + * The target element. + * + * @attribute target + * @type object + * @default null + */ + target: null, + + /** + * If true, the orientation is horizontal; otherwise is vertical. + * + * @attribute horizontal + * @type boolean + * @default false + */ + horizontal: false, + + /** + * Set opened to true to show the collapse element and to false to hide it. + * + * @attribute opened + * @type boolean + * @default false + */ + opened: false, + + /** + * Collapsing/expanding animation duration in second. + * + * @attribute duration + * @type number + * @default 0.33 + */ + duration: 0.33, + + /** + * If true, the size of the target element is fixed and is set + * on the element. Otherwise it will try to + * use auto to determine the natural size to use + * for collapsing/expanding. + * + * @attribute fixedSize + * @type boolean + * @default false + */ + fixedSize: false, + + created: function() { + this.transitionEndListener = this.transitionEnd.bind(this); + }, + + ready: function() { + this.target = this.target || this; + }, + + domReady: function() { + this.async(function() { + this.afterInitialUpdate = true; + }); + }, + + detached: function() { + if (this.target) { + this.removeListeners(this.target); + } + }, + + targetChanged: function(old) { + if (old) { + this.removeListeners(old); + } + if (!this.target) { + return; + } + this.isTargetReady = !!this.target; + this.classList.toggle('core-collapse-closed', this.target !== this); + this.target.style.overflow = 'hidden'; + this.horizontalChanged(); + this.addListeners(this.target); + // set core-collapse-closed class initially to hide the target + this.toggleClosedClass(true); + this.update(); + }, + + addListeners: function(node) { + node.addEventListener('transitionend', this.transitionEndListener); + }, + + removeListeners: function(node) { + node.removeEventListener('transitionend', this.transitionEndListener); + }, + + horizontalChanged: function() { + this.dimension = this.horizontal ? 'width' : 'height'; + }, + + openedChanged: function() { + this.update(); + this.fire('core-collapse-open', this.opened); + }, + + /** + * Toggle the opened state. + * + * @method toggle + */ + toggle: function() { + this.opened = !this.opened; + }, + + setTransitionDuration: function(duration) { + var s = this.target.style; + s.transition = duration ? (this.dimension + ' ' + duration + 's') : null; + if (duration === 0) { + this.async('transitionEnd'); + } + }, + + transitionEnd: function() { + if (this.opened && !this.fixedSize) { + this.updateSize('auto', null); + } + this.setTransitionDuration(null); + this.toggleClosedClass(!this.opened); + this.asyncFire('core-resize', null, this.target); + }, + + toggleClosedClass: function(closed) { + this.hasClosedClass = closed; + this.target.classList.toggle('core-collapse-closed', closed); + }, + + updateSize: function(size, duration, forceEnd) { + this.setTransitionDuration(duration); + this.calcSize(); + var s = this.target.style; + var nochange = s[this.dimension] === size; + s[this.dimension] = size; + // transitonEnd will not be called if the size has not changed + if (forceEnd && nochange) { + this.transitionEnd(); + } + }, + + update: function() { + if (!this.target) { + return; + } + if (!this.isTargetReady) { + this.targetChanged(); + } + this.horizontalChanged(); + this[this.opened ? 'show' : 'hide'](); + }, + + calcSize: function() { + return this.target.getBoundingClientRect()[this.dimension] + 'px'; + }, + + getComputedSize: function() { + return getComputedStyle(this.target)[this.dimension]; + }, + + show: function() { + this.toggleClosedClass(false); + // for initial update, skip the expanding animation to optimize + // performance e.g. skip calcSize + if (!this.afterInitialUpdate) { + this.transitionEnd(); + return; + } + if (!this.fixedSize) { + this.updateSize('auto', null); + var s = this.calcSize(); + if (s == '0px') { + this.transitionEnd(); + return; + } + this.updateSize(0, null); + } + this.async(function() { + this.updateSize(this.size || s, this.duration, true); + }); + }, + + hide: function() { + // don't need to do anything if it's already hidden + if (this.hasClosedClass && !this.fixedSize) { + return; + } + if (this.fixedSize) { + // save the size before hiding it + this.size = this.getComputedSize(); + } else { + this.updateSize(this.calcSize(), null); + } + this.async(function() { + this.updateSize(0, this.duration); + }); + } + + }); + +</script> +</polymer-element> + + +<polymer-element name="core-submenu" attributes="selected selectedItem label icon src valueattr" assetpath="polymer/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 +The complete set of authors may be found at http://polymer.github.io/AUTHORS +The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS +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 +*/ + +:host { + display: block; + height: auto; +} + +:host(.core-selected, [active]) { + font-weight: initial; +} + +core-item { + cursor: default; +} + +::content > core-item { + cursor: default; +} + +:host(.font-scalable) > core-item { + min-height: 2.5em; +} + +:host(.font-scalable) > core-item::shadow core-icon { + margin: 0 1em 0 0.25em; + height: 1.5em; + width: 1.5em; +} + +#submenu { + margin: 0 0 0 44px; +} + +:host(.font-scalable) > #submenu { + margin: 0 0 0 2.75em; +} +</style> + + <core-item id="submenuItem" src="{{src}}" label="{{label}}" icon="{{icon}}" class="{{ {'core-selected' : active} | tokenList}}" on-tap="{{activate}}"> + <content select=".item-content"></content> + </core-item> + + <core-menu id="submenu" selected="{{selected}}" selecteditem="{{selectedItem}}" valueattr="{{valueattr}}"> + <content></content> + </core-menu> + + <core-collapse target="{{$.submenu}}" opened="{{opened}}"></core-collapse> + +</template> +<script> + + Polymer('core-submenu', { + + publish: { + active: {value: false, reflect: true} + }, + + opened: false, + + get items() { + return this.$.submenu.items; + }, + + hasItems: function() { + return !!this.items.length; + }, + + unselectAllItems: function() { + this.$.submenu.selected = null; + this.$.submenu.clearSelection(); + }, + + activeChanged: function() { + if (this.hasItems()) { + this.opened = this.active; + } + if (!this.active) { + this.unselectAllItems(); + } + }, + + toggle: function() { + this.opened = !this.opened; + }, + + activate: function() { + if (this.hasItems() && this.active) { + this.toggle(); + this.unselectAllItems(); + } + } + + }); + +</script> +</polymer-element> + + + +<polymer-element name="services-list" attributes="api cbServiceClicked" assetpath="polymer/"> + <template> + <style> + :host { + display: block; + } + + core-menu { + margin-top: 0; + font-size: 1rem; + } + + a { + display: block; + } + </style> + + <template if="{{cbServiceClicked}}"> + <style> + a { + text-decoration: underline; + cursor: pointer; + } + </style> + </template> + + <div> + <core-menu selected="0"> + + <template repeat="{{serv in services}}"> + <core-submenu icon="settings" label="{{serv.domain}}"> + <template repeat="{{service in serv.services}}"> + <a on-click="{{serviceClicked}}" data-domain="{{serv.domain}}">{{service}}</a> + </template> + </core-submenu> + </template> + + </core-menu> + + </div> + </template> + <script> + Polymer('services-list',{ + services: [], + cbServiceClicked: null, + + domReady: function() { + this.services = this.api.services + + this.api.addEventListener('services-updated', this.servicesUpdated.bind(this)) + }, + + servicesUpdated: function() { + this.services = this.api.services; + }, + + serviceClicked: function(ev) { + if(this.cbServiceClicked) { + var target = ev.path[0]; + var domain = target.getAttributeNode("data-domain").value; + var service = target.innerHTML; + + this.cbServiceClicked(domain, service); + } + } + + }); + </script> +</polymer-element> + + +<polymer-element name="service-call-dialog" attributes="api" assetpath="polymer/"> + <template> + <style> + paper-input:first-child { + padding-top: 0; + } + + .serviceContainer { + margin-left: 30px; + } + + @media all and (max-width: 620px) { + .serviceContainer { + display: none; + } + } + + </style> + + <paper-dialog id="dialog" heading="Call Service" transition="paper-dialog-transition-bottom" backdrop="true"> + <div layout="" horizontal=""> + <div> + <paper-input id="inputDomain" label="Domain" floatinglabel="true" autofocus required></paper-input> + <paper-input id="inputService" label="Service" floatinglabel="true" required></paper-input> + <paper-input id="inputData" label="Service Data (JSON, optional)" floatinglabel="true" multiline=""></paper-input> + </div> + <div class="serviceContainer"> + <b>Available services:</b> + <services-list api="{{api}}" cbserviceclicked="{{serviceSelected}}"> + </services-list></div> + </div> + <paper-button dismissive="">Cancel</paper-button> + <paper-button affirmative="" on-click="{{clickCallService}}">Call Service</paper-button> + </paper-dialog> + + </template> + <script> + Polymer('service-call-dialog',{ + ready: function() { + // to ensure callback methods work.. + this.serviceSelected = this.serviceSelected.bind(this) + }, + + show: function(domain, service, serviceData) { + this.setService(domain, service); + this.$.inputData.value = serviceData; + this.$.dialog.toggle(); + }, + + setService: function(domain, service) { + this.$.inputDomain.value = domain; + this.$.inputService.value = service; + }, + + serviceSelected: function(domain, service) { + this.setService(domain, service); + }, + + clickCallService: function() { + this.api.call_service( + this.$.inputDomain.value, + this.$.inputService.value, + this.$.inputData.value + ) + } + }); + </script> +</polymer-element> + + + + + + + + + +<polymer-element name="entity-list" attributes="api cbEntityClicked" assetpath="polymer/"> + <template> + <style> + :host { + display: block; + } + + .entityContainer { + font-size: 1rem; + } + </style> + + <template if="{{cbEntityClicked}}"> + <style> + a { + text-decoration: underline; + cursor: pointer; + } + </style> + </template> + + <div> + <template repeat="{{state in states}}"> + <div class="eventContainer"> + <a on-click="{{handleClick}}">{{state.entity_id}}</a> + </div> + </template> + + </div> + </template> + <script> + Polymer('entity-list',{ + cbEventClicked: null, + states: [], + + domReady: function() { + this.api.addEventListener('states-updated', this.statesUpdated.bind(this)) + this.statesUpdated() + }, + + statesUpdated: function() { + this.states = this.api.states; + }, + + handleClick: function(ev) { + if(this.cbEntityClicked) { + this.cbEntityClicked(ev.path[0].innerHTML); + } + }, + + }); + </script> +</polymer-element> + + +<polymer-element name="state-set-dialog" attributes="api" assetpath="polymer/"> + <template> + <style> + paper-input:first-child { + padding-top: 0; + } + + .stateContainer { + margin-left: 30px; + } + + @media all and (max-width: 620px) { + .stateContainer { + display: none; + } + } + + </style> + + <paper-dialog id="dialog" heading="Set State" transition="paper-dialog-transition-center" backdrop="true"> + <div layout="" horizontal=""> + <div> + <paper-input id="inputEntityID" label="Entity ID" floatinglabel="true" autofocus required></paper-input> + <paper-input id="inputState" label="State" floatinglabel="true" required></paper-input> + <paper-input id="inputData" label="State attributes (JSON, optional)" floatinglabel="true" multiline=""></paper-input> + </div> + <div class="stateContainer"> + <b>Current entities:</b> + <entity-list api="{{api}}" cbentityclicked="{{entitySelected}}"></entity-list> + </div> + </div> + <paper-button dismissive="">Cancel</paper-button> + <paper-button affirmative="" on-click="{{clickSetState}}">Set State</paper-button> + </paper-dialog> + + </template> + <script> + Polymer('state-set-dialog',{ + ready: function() { + // to ensure callback methods work.. + this.entitySelected = this.entitySelected.bind(this) + }, + + show: function(entityId, state, stateData) { + this.setEntityId(entityId); + this.setState(state); + this.setStateData(stateData); + + this.$.dialog.toggle(); + }, + + setEntityId: function(entityId) { + this.$.inputEntityID.value = entityId; + }, + + setState: function(state) { + this.$.inputState.value = state; + }, + + setStateData: function(stateData) { + var value = stateData ? JSON.stringify(stateData, null, ' ') : ""; + + this.$.inputData.value = value; + }, + + entitySelected: function(entityId) { + this.setEntityId(entityId); + + var state = this.api.getState(entityId); + this.setState(state.state); + this.setStateData(state.attributes); + }, + + clickSetState: function() { + this.api.set_state( + this.$.inputEntityID.value, + this.$.inputState.value, + JSON.parse(this.$.inputData.value) + ); + } + }); + </script> +</polymer-element> + + +<polymer-element name="home-assistant-api" attributes="auth" assetpath="polymer/"> + <template> + <style> + core-ajax { + display: none; + } + </style> + + <paper-toast id="toast" role="alert" text=""></paper-toast> + <event-fire-dialog id="eventDialog" api="{{api}}"></event-fire-dialog> + <service-call-dialog id="serviceDialog" api="{{api}}"></service-call-dialog> + <state-set-dialog id="stateDialog" api="{{api}}"></state-set-dialog> + + <core-ajax id="statesAjax" auto="" method="GET" url="/static/states_mock.json" url2="/api/states" headers="{{ha_headers}}" on-core-response="{{statesLoaded}}" handleas="json"> + </core-ajax> + + <core-ajax id="eventsAjax" auto="" method="GET" url="/api/events" headers="{{ha_headers}}" on-core-response="{{eventsLoaded}}" handleas="json"> + </core-ajax> + + <core-ajax id="servicesAjax" auto="" method="GET" url="/api/services" headers="{{ha_headers}}" on-core-response="{{servicesLoaded}}" handleas="json"> + </core-ajax> + + </template> + <script> + Polymer('home-assistant-api',{ + auth: "not-set", + states: [], + services: {}, + events: {}, + stateUpdateTimeout: null, + + computed: { + ha_headers: '{"HA-access": auth}' + }, + + created: function() { + this.api = this; + + // so we can pass these methods safely as callbacks + this.turn_on = this.turn_on.bind(this); + this.turn_off = this.turn_off.bind(this); + }, + + _laterFetchStates: function() { + if(this.stateUpdateTimeout) { + clearTimeout(this.stateUpdateTimeout); + } + + // update states in 60 seconds + this.stateUpdateTimeout = setTimeout(this.fetchStates.bind(this), 60000); + }, + + _sortStates: function(states) { + return states.sort(function(one, two) { + if (one.entity_id > two.entity_id) { + return 1; + } else if (one.entity_id < two.entity_id) { + return -1; + } else { + return 0; + } + }) + }, + + statesLoaded: function() { + // Make a copy of the loaded data + this.states = this._sortStates(this.$.statesAjax.response.slice(0)); + + this.fire('states-updated') + + this._laterFetchStates(); + }, + + eventsLoaded: function() { + // Make a copy of the loaded data + this.events = this.$.eventsAjax.response; + + this.fire('events-updated') + }, + + servicesLoaded: function() { + // Make a copy of the loaded data + this.services = this.$.servicesAjax.response; + + this.fire('services-updated') + }, + + _pushNewState: function(new_state) { + var state; + var stateFound = false; + + for(var i = 0; i < this.states.length; i++) { + if(this.states[i].entity_id == new_state.entity_id) { + state = this.states[i]; + state.attributes = new_state.attributes; + state.last_changed = new_state.last_changed; + state.state = new_state.state; + + stateFound = true; + break; + } + } + + if(!stateFound) { + this.states.push(new_state); + this._sortStates(this.states); + } + }, + + fetchState: function(entity_id) { + var successStateUpdate = function(new_state) { + this._pushNewState(new_state); + } + + this.call_api("GET", "states/" + entity_id, null, successStateUpdate.bind(this)); + }, + + fetchStates: function() { + this.$.statesAjax.go(); + }, + + getState: function(entityId) { + for(var i = 0; i < this.states.length; i++) { + if(this.states[i].entity_id == entityId) { + return this.states[i]; + } + } + }, + + turn_on: function(entity_id) { + this.call_service("homeassistant", "turn_on", {entity_id: entity_id}); + }, + + turn_off: function(entity_id) { + this.call_service("homeassistant", "turn_off", {entity_id: entity_id}) + }, + + set_state: function(entity_id, state, attributes) { + var payload = {state: state} + + if(attributes) { + payload.attributes = attributes; + } + + var successToast = function(new_state) { + this.showToast("State of "+entity_id+" successful set to "+state+"."); + this._pushNewState(new_state); + } + + this.call_api("POST", "states/" + entity_id, + payload, successToast.bind(this)); + }, + + call_service: function(domain, service, parameters) { + var successToast = function() { + this.showToast("Service "+domain+"/"+service+" successful called."); + } + + this.call_api("POST", "services/" + domain + "/" + service, + parameters, successToast.bind(this)); + }, + + fire_event: function(eventType, eventData) { + eventData = eventData ? JSON.parse(eventData) : ""; + + var successToast = function() { + this.showToast("Event "+eventType+" successful fired."); + } + + this.call_api("POST", "events/" + eventType, + eventData, successToast.bind(this)); + }, + + call_api: function(method, path, parameters, callback) { + var req = new XMLHttpRequest(); + req.open(method, "/api/" + path, true) + req.setRequestHeader("HA-access", this.auth); + + req.onreadystatechange = function() { + + if(req.readyState == 4 && req.status > 199 && req.status < 300) { + + if(callback) { + callback(JSON.parse(req.responseText)) + } + // if we targetted an entity id, update state after 2 seconds + if(parameters && parameters.entity_id) { + var updateCallback; + + // if a string, update just that entity, otherwise update all + if(typeof(parameters.entity_id) == "string") { + updateCallback = function() { + this.fetchState(parameters.entity_id); + } + + } else { + updateCallback = this.fetchStates(); + } + + setTimeout(updateCallback.bind(this), 2000); + } + } + }.bind(this) + + if(parameters) { + req.send(JSON.stringify(parameters)); + } else { + req.send(); + } + }, + + showEditStateDialog: function(entityId) { + var state = this.getState(entityId); + + this.showSetStateDialog(entityId, state.state, state.attributes) + }, + + showSetStateDialog: function(entityId, state, stateAttributes) { + entityId = entityId || ""; + state = state || ""; + stateAttributes = stateAttributes || null; + + this.$.stateDialog.show(entityId, state, stateAttributes); + }, + + showFireEventDialog: function(eventType, eventData) { + eventType = eventType || ""; + eventData = eventData || ""; + + this.$.eventDialog.show(eventType, eventData); + }, + + showCallServiceDialog: function(domain, service, serviceData) { + domain = domain || ""; + service = service || ""; + serviceData = serviceData || ""; + + this.$.serviceDialog.show(domain, service, serviceData); + }, + + showToast: function(message) { + this.$.toast.text = message; + this.$.toast.show(); + } + + }); + </script> +</polymer-element> +</div> +<div hidden> +<script src="polymer/bower_components/moment/moment.js"></script> + +<!-- +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 +--> + +<!-- +The `core-tooltip` element creates a hover tooltip centered for the content +it contains. It can be positioned on the top|bottom|left|right of content using +the `position` attribute. + +To include HTML in the tooltip, include the `tip` attribute on the relevant +content. + +<b>Example</b>: + + <core-tooltip label="I'm a tooltip"> + <span>Hover over me.</span> + </core-tooltip> + +<b>Example</b> - positioning the tooltip to the right: + + <core-tooltip label="I'm a tooltip to the right" position="right"> + <core-icon-button icon="drawer"></core-icon-button> + </core-tooltip> + +<b>Example</b> - no arrow and showing by default: + + <core-tooltip label="Tooltip with no arrow and always on" noarrow show> + <img src="image.jpg"> + </core-tooltip> + +<b>Example</b> - disable the tooltip. + + <core-tooltip label="Disabled label never shows" disabled> + ... + </core-tooltip> + +<b>Example</b> - rich tooltip using the `tip` attribute: + + <core-tooltip> + <div>Example of a rich information tooltip</div> + <div tip> + <img src="profile.jpg">Foo <b>Bar</b> - <a href="#">@baz</a> + </div> + </core-tooltip> + +By default, the `tip` attribute specifies the HTML content for a rich tooltip. +You can customize this attribute with the `tipAttribute` attribute: + + <core-tooltip tipAttribute="htmltooltip"> + <div>Example of a rich information tooltip</div> + <div htmltooltip> + ... + </div> + </core-tooltip> + +@group Polymer Core Elements +@element core-tooltip +@extends paper-focusable +@homepage http://www.polymer-project.org/components/core-tooltip/index.html +--> + + + + +<!-- 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. --> +<polymer-element name="core-tooltip" extends="paper-focusable" attributes="noarrow position label show tipAttribute" role="tooltip" assetpath="polymer/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-family: sans-serif; + padding: 8px; + color: white; + background-color: rgba(0,0,0,0.8); + 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> + + Polymer('core-tooltip',{ + + /** + * 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, + + 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(); + }, + + updatedChildren: function () { + this.tipElement = null; + + for (var i = 0, el; el = this.$.c.getDistributedNodes()[i]; ++i) { + if (el.hasAttribute && el.hasAttribute('tip')) { + 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; + } + } + }); + +</script> +</polymer-element> + + + +<polymer-element name="state-card" attributes="entity state last_changed state_attr cb_turn_on, cb_turn_off cb_edit" assetpath="polymer/"> + <template> + <style> + :host { + position: relative; + background-color: white; + padding: 20px 20px 55px 20px; + width: 100%; + border-radius: 2px; + } + + .header { + text-transform: capitalize; + + font-weight: 300; + + font-size: 1.5rem; + } + + .header .state { + text-align: right; + } + + .subheader { + margin-top: -5px; + color: darkgrey; + } + + .state-attributes { + margin-top: 10px; + font-size: 1rem; + } + + .state-attributes .key { + white-space: nowrap; + width: 85px; + float: left; + clear: left; + overflow: hidden; + text-overflow: ellipsis; + } + + .state-attributes .value { + margin-left: 95px; + } + + .actions { + position: absolute; + bottom: 10px; + left: 20px; + right: 20px; + + text-align: right; + } + + paper-button.toggle { + color: #03a9f4; + } + + </style> + + <div class="header" horizontal="" justified="" layout=""> + <span class="entity_id"> + <template if="{{state_attr['friendly_name']}}">{{state_attr['friendly_name']}}</template> + <template if="{{!state_attr['friendly_name']}}">{{entity_id | makeReadable}}</template> + </span> + <span class="state">{{state | makeReadable}}</span> + </div> + + <div class="subheader" horizontal="" justified="" layout=""> + <span class="domain">{{domain}}</span> + <core-tooltip label="{{last_changed}}" position="bottom"> + <span class="last_changed_from_now">{{last_changed_from_now}}</span> + </core-tooltip> + </div> + + + <div class="state-attributes"> + <template repeat="{{key in objectKeys(state_attr)}}"> + <template if="{{key != 'friendly_name'}}"> + <div class="key">{{key | makeReadable}}</div> + <div class="value">{{state_attr[key] | makeReadable}}</div> + </template> + </template> + </div> + + <div class="actions"> + <paper-button class="edit" on-click="{{editClicked}}">EDIT</paper-button> + + <template if="{{state == 'on'}}"> + <paper-button class="toggle" on-click="{{turn_off}}">TURN OFF</paper-button> + </template> + <template if="{{state == 'off'}}"> + <paper-button class="toggle" on-click="{{turn_on}}">TURN ON</paper-button> + </template> + </div> + + </template> + <script> + Polymer('state-card',{ + // attributes + entity: "", + state: "", + last_changed: "never", + state_attr: {}, + cb_turn_on: null, + cb_turn_off: null, + cb_edit: null, + + // computed + domain: "", + entity_id: "", + + entityChanged: function(oldVal, newVal) { + var parts = newVal.split(".") + + if(parts.length == 1) { + this.domain = "" + this.entity_id = parts[0] + } else { + this.domain = parts[0] + this.entity_id = parts.slice(1).join('.') + } + }, + + last_changedChanged: function(oldVal, newVal) { + this.last_changed_from_now = moment(this.last_changed, "HH:mm:ss DD-MM-YYYY").fromNow() + }, + + turn_on: function() { + if(this.cb_turn_on) { + this.cb_turn_on(this.entity); + } + }, + + turn_off: function() { + if(this.cb_turn_off) { + this.cb_turn_off(this.entity); + } + }, + + editClicked: function() { + if(this.cb_edit) { + this.cb_edit(this.entity); + } + }, + + // used as filter + makeReadable: function(value) { + if(typeof value == "string") { + return value.replace(/_/g, " "); + + } else if(Array.isArray(value)) { + return value.join(", "); + + } else { + return value; + } + }, + + objectKeys: function(obj) { + return obj ? Object.keys(obj) : []; + } + }); + </script> +</polymer-element> + + +<polymer-element name="states-cards" attributes="api" assetpath="polymer/"> + <template> + <style> + :host { + display: block; + width: 100%; + } + + state-card, state-add-card { + display: inline-block; + width: 350px; + margin: 10px 0 0 10px; + } + + state-add-card { + cursor: pointer; + } + + </style> + + <div horizontal="" layout="" wrap=""> + + <template repeat="{{state in states}}"> + <state-card entity="{{state.entity_id}}" state="{{state.state}}" last_changed="{{state.last_changed}}" state_attr="{{state.attributes}}" cb_turn_on="{{api.turn_on}}" cb_turn_off="{{api.turn_off}}" cb_edit="{{editCallback}}"> + </state-card> + </template> + + </div> + </template> + <script> + Polymer('states-cards',{ + states: [], + + ready: function() { + this.editCallback = this.editCallback.bind(this); + }, + + domReady: function() { + this.states = this.api.states + + this.api.addEventListener('states-updated', this.statesUpdated.bind(this)) + }, + + statesUpdated: function() { + this.states = this.api.states; + }, + + editCallback: function(entityId) { + this.api.showEditStateDialog(entityId); + }, + + }); + </script> +</polymer-element> +</div> + +<polymer-element name="home-assistant-main" attributes="auth" assetpath="polymer/"> + <template> + <style type="text/css"> + + :host { + font-family: 'RobotoDraft', sans-serif; + } + + core-header-panel { + height: 100%; + overflow: auto; + -webkit-overflow-scrolling: touch; + } + + core-toolbar { + background: #03a9f4; + font-size: 1.4rem; + color: white; + } + + .content { + padding-bottom: 75px; + padding-right: 10px; + } + + paper-fab { + position: fixed; + bottom: 10px; + right: 10px; + } + + </style> + + <home-assistant-api auth="{{auth}}" id="api"></home-assistant-api> + + <core-header-panel layout=""> + + <core-toolbar> + <div flex=""> + Home Assistant + </div> + <core-icon-button icon="refresh" on-click="{{handleRefreshClick}}"></core-icon-button> + <core-icon-button icon="developer-mode-tv" on-click="{{handleEventClick}}"></core-icon-button> + <core-icon-button icon="settings-remote" on-click="{{handleServiceClick}}"></core-icon-button> + </core-toolbar> + + <div class="content" flex=""> + <states-cards api="{{api}}"></states-cards> + <paper-fab icon="add" on-click="{{handleAddStateClick}}"></paper-fab> + </div> + + </core-header-panel> + + </template> + <script> + Polymer('home-assistant-main',{ + + ready: function() { + this.api = this.$.api; + }, + + handleRefreshClick: function() { + this.api.fetchStates(); + }, + + handleEventClick: function() { + this.api.showFireEventDialog(); + }, + + handleServiceClick: function() { + this.api.showCallServiceDialog(); + }, + + handleAddStateClick: function() { + this.api.showSetStateDialog(); + } + + }); + </script> +</polymer-element> diff --git a/homeassistant/components/http/www_static/polymer/home-assistant-api.html b/homeassistant/components/http/www_static/polymer/home-assistant-api.html index 8bad3463f9e8ec38c07b288cd9efa9e4d83e0ae1..6f9f51d538cb4ca5f83573329bfc8f292fbd885d 100644 --- a/homeassistant/components/http/www_static/polymer/home-assistant-api.html +++ b/homeassistant/components/http/www_static/polymer/home-assistant-api.html @@ -23,7 +23,7 @@ auto method="GET" url="/api/states" - headers='{"HA-access": "{{auth}}"}' + headers="{{ha_headers}}" on-core-response="{{statesLoaded}}" handleAs="json"> </core-ajax> @@ -32,7 +32,7 @@ auto method="GET" url="/api/events" - headers='{"HA-access": "{{auth}}"}' + headers="{{ha_headers}}" on-core-response="{{eventsLoaded}}" handleAs="json"> </core-ajax> @@ -41,7 +41,7 @@ auto method="GET" url="/api/services" - headers='{"HA-access": "{{auth}}"}' + headers="{{ha_headers}}" on-core-response="{{servicesLoaded}}" handleAs="json"> </core-ajax> @@ -49,12 +49,16 @@ </template> <script> Polymer({ - auth: "", + auth: "not-set", states: [], services: {}, events: {}, stateUpdateTimeout: null, + computed: { + ha_headers: '{"HA-access": auth}' + }, + created: function() { this.api = this; @@ -225,9 +229,9 @@ }.bind(this) if(parameters) { - req.send(JSON.stringify(parameters)) + req.send(JSON.stringify(parameters)); } else { - req.send() + req.send(); } }, @@ -249,7 +253,7 @@ eventType = eventType || ""; eventData = eventData || ""; - this.$.eventDialog.show(eventType, eventData) + this.$.eventDialog.show(eventType, eventData); }, showCallServiceDialog: function(domain, service, serviceData) {