Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4744)

Unified Diff: chrome/browser/resources/md_downloads/crisper.js

Issue 1378993003: Vulcanize new Material Design downloads UI (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@vulcanize2
Patch Set: -crisper.html Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/browser/browser_resources.grd ('k') | chrome/browser/resources/md_downloads/item.css » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: chrome/browser/resources/md_downloads/crisper.js
diff --git a/chrome/browser/resources/md_downloads/crisper.js b/chrome/browser/resources/md_downloads/crisper.js
new file mode 100644
index 0000000000000000000000000000000000000000..2f7e325881752bbf74361bc34e28c6bcccff3cde
--- /dev/null
+++ b/chrome/browser/resources/md_downloads/crisper.js
@@ -0,0 +1,15644 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+Polymer = {dom: 'shadow'};
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * The global object.
+ * @type {!Object}
+ * @const
+ */
+var global = this;
+
+/** Platform, package, object property, and Event support. **/
+var cr = function() {
+ 'use strict';
+
+ /**
+ * Builds an object structure for the provided namespace path,
+ * ensuring that names that already exist are not overwritten. For
+ * example:
+ * "a.b.c" -> a = {};a.b={};a.b.c={};
+ * @param {string} name Name of the object that this file defines.
+ * @param {*=} opt_object The object to expose at the end of the path.
+ * @param {Object=} opt_objectToExportTo The object to add the path to;
+ * default is {@code global}.
+ * @private
+ */
+ function exportPath(name, opt_object, opt_objectToExportTo) {
+ var parts = name.split('.');
+ var cur = opt_objectToExportTo || global;
+
+ for (var part; parts.length && (part = parts.shift());) {
+ if (!parts.length && opt_object !== undefined) {
+ // last part and we have an object; use it
+ cur[part] = opt_object;
+ } else if (part in cur) {
+ cur = cur[part];
+ } else {
+ cur = cur[part] = {};
+ }
+ }
+ return cur;
+ };
+
+ /**
+ * Fires a property change event on the target.
+ * @param {EventTarget} target The target to dispatch the event on.
+ * @param {string} propertyName The name of the property that changed.
+ * @param {*} newValue The new value for the property.
+ * @param {*} oldValue The old value for the property.
+ */
+ function dispatchPropertyChange(target, propertyName, newValue, oldValue) {
+ var e = new Event(propertyName + 'Change');
+ e.propertyName = propertyName;
+ e.newValue = newValue;
+ e.oldValue = oldValue;
+ target.dispatchEvent(e);
+ }
+
+ /**
+ * Converts a camelCase javascript property name to a hyphenated-lower-case
+ * attribute name.
+ * @param {string} jsName The javascript camelCase property name.
+ * @return {string} The equivalent hyphenated-lower-case attribute name.
+ */
+ function getAttributeName(jsName) {
+ return jsName.replace(/([A-Z])/g, '-$1').toLowerCase();
+ }
+
+ /**
+ * The kind of property to define in {@code defineProperty}.
+ * @enum {string}
+ * @const
+ */
+ var PropertyKind = {
+ /**
+ * Plain old JS property where the backing data is stored as a "private"
+ * field on the object.
+ * Use for properties of any type. Type will not be checked.
+ */
+ JS: 'js',
+
+ /**
+ * The property backing data is stored as an attribute on an element.
+ * Use only for properties of type {string}.
+ */
+ ATTR: 'attr',
+
+ /**
+ * The property backing data is stored as an attribute on an element. If the
+ * element has the attribute then the value is true.
+ * Use only for properties of type {boolean}.
+ */
+ BOOL_ATTR: 'boolAttr'
+ };
+
+ /**
+ * Helper function for defineProperty that returns the getter to use for the
+ * property.
+ * @param {string} name The name of the property.
+ * @param {PropertyKind} kind The kind of the property.
+ * @return {function():*} The getter for the property.
+ */
+ function getGetter(name, kind) {
+ switch (kind) {
+ case PropertyKind.JS:
+ var privateName = name + '_';
+ return function() {
+ return this[privateName];
+ };
+ case PropertyKind.ATTR:
+ var attributeName = getAttributeName(name);
+ return function() {
+ return this.getAttribute(attributeName);
+ };
+ case PropertyKind.BOOL_ATTR:
+ var attributeName = getAttributeName(name);
+ return function() {
+ return this.hasAttribute(attributeName);
+ };
+ }
+
+ // TODO(dbeam): replace with assertNotReached() in assert.js when I can coax
+ // the browser/unit tests to preprocess this file through grit.
+ throw 'not reached';
+ }
+
+ /**
+ * Helper function for defineProperty that returns the setter of the right
+ * kind.
+ * @param {string} name The name of the property we are defining the setter
+ * for.
+ * @param {PropertyKind} kind The kind of property we are getting the
+ * setter for.
+ * @param {function(*, *):void=} opt_setHook A function to run after the
+ * property is set, but before the propertyChange event is fired.
+ * @return {function(*):void} The function to use as a setter.
+ */
+ function getSetter(name, kind, opt_setHook) {
+ switch (kind) {
+ case PropertyKind.JS:
+ var privateName = name + '_';
+ return function(value) {
+ var oldValue = this[name];
+ if (value !== oldValue) {
+ this[privateName] = value;
+ if (opt_setHook)
+ opt_setHook.call(this, value, oldValue);
+ dispatchPropertyChange(this, name, value, oldValue);
+ }
+ };
+
+ case PropertyKind.ATTR:
+ var attributeName = getAttributeName(name);
+ return function(value) {
+ var oldValue = this[name];
+ if (value !== oldValue) {
+ if (value == undefined)
+ this.removeAttribute(attributeName);
+ else
+ this.setAttribute(attributeName, value);
+ if (opt_setHook)
+ opt_setHook.call(this, value, oldValue);
+ dispatchPropertyChange(this, name, value, oldValue);
+ }
+ };
+
+ case PropertyKind.BOOL_ATTR:
+ var attributeName = getAttributeName(name);
+ return function(value) {
+ var oldValue = this[name];
+ if (value !== oldValue) {
+ if (value)
+ this.setAttribute(attributeName, name);
+ else
+ this.removeAttribute(attributeName);
+ if (opt_setHook)
+ opt_setHook.call(this, value, oldValue);
+ dispatchPropertyChange(this, name, value, oldValue);
+ }
+ };
+ }
+
+ // TODO(dbeam): replace with assertNotReached() in assert.js when I can coax
+ // the browser/unit tests to preprocess this file through grit.
+ throw 'not reached';
+ }
+
+ /**
+ * Defines a property on an object. When the setter changes the value a
+ * property change event with the type {@code name + 'Change'} is fired.
+ * @param {!Object} obj The object to define the property for.
+ * @param {string} name The name of the property.
+ * @param {PropertyKind=} opt_kind What kind of underlying storage to use.
+ * @param {function(*, *):void=} opt_setHook A function to run after the
+ * property is set, but before the propertyChange event is fired.
+ */
+ function defineProperty(obj, name, opt_kind, opt_setHook) {
+ if (typeof obj == 'function')
+ obj = obj.prototype;
+
+ var kind = /** @type {PropertyKind} */ (opt_kind || PropertyKind.JS);
+
+ if (!obj.__lookupGetter__(name))
+ obj.__defineGetter__(name, getGetter(name, kind));
+
+ if (!obj.__lookupSetter__(name))
+ obj.__defineSetter__(name, getSetter(name, kind, opt_setHook));
+ }
+
+ /**
+ * Counter for use with createUid
+ */
+ var uidCounter = 1;
+
+ /**
+ * @return {number} A new unique ID.
+ */
+ function createUid() {
+ return uidCounter++;
+ }
+
+ /**
+ * Returns a unique ID for the item. This mutates the item so it needs to be
+ * an object
+ * @param {!Object} item The item to get the unique ID for.
+ * @return {number} The unique ID for the item.
+ */
+ function getUid(item) {
+ if (item.hasOwnProperty('uid'))
+ return item.uid;
+ return item.uid = createUid();
+ }
+
+ /**
+ * Dispatches a simple event on an event target.
+ * @param {!EventTarget} target The event target to dispatch the event on.
+ * @param {string} type The type of the event.
+ * @param {boolean=} opt_bubbles Whether the event bubbles or not.
+ * @param {boolean=} opt_cancelable Whether the default action of the event
+ * can be prevented. Default is true.
+ * @return {boolean} If any of the listeners called {@code preventDefault}
+ * during the dispatch this will return false.
+ */
+ function dispatchSimpleEvent(target, type, opt_bubbles, opt_cancelable) {
+ var e = new Event(type, {
+ bubbles: opt_bubbles,
+ cancelable: opt_cancelable === undefined || opt_cancelable
+ });
+ return target.dispatchEvent(e);
+ }
+
+ /**
+ * Calls |fun| and adds all the fields of the returned object to the object
+ * named by |name|. For example, cr.define('cr.ui', function() {
+ * function List() {
+ * ...
+ * }
+ * function ListItem() {
+ * ...
+ * }
+ * return {
+ * List: List,
+ * ListItem: ListItem,
+ * };
+ * });
+ * defines the functions cr.ui.List and cr.ui.ListItem.
+ * @param {string} name The name of the object that we are adding fields to.
+ * @param {!Function} fun The function that will return an object containing
+ * the names and values of the new fields.
+ */
+ function define(name, fun) {
+ var obj = exportPath(name);
+ var exports = fun();
+ for (var propertyName in exports) {
+ // Maybe we should check the prototype chain here? The current usage
+ // pattern is always using an object literal so we only care about own
+ // properties.
+ var propertyDescriptor = Object.getOwnPropertyDescriptor(exports,
+ propertyName);
+ if (propertyDescriptor)
+ Object.defineProperty(obj, propertyName, propertyDescriptor);
+ }
+ }
+
+ /**
+ * Adds a {@code getInstance} static method that always return the same
+ * instance object.
+ * @param {!Function} ctor The constructor for the class to add the static
+ * method to.
+ */
+ function addSingletonGetter(ctor) {
+ ctor.getInstance = function() {
+ return ctor.instance_ || (ctor.instance_ = new ctor());
+ };
+ }
+
+ /**
+ * Forwards public APIs to private implementations.
+ * @param {Function} ctor Constructor that have private implementations in its
+ * prototype.
+ * @param {Array<string>} methods List of public method names that have their
+ * underscored counterparts in constructor's prototype.
+ * @param {string=} opt_target Selector for target node.
+ */
+ function makePublic(ctor, methods, opt_target) {
+ methods.forEach(function(method) {
+ ctor[method] = function() {
+ var target = opt_target ? document.getElementById(opt_target) :
+ ctor.getInstance();
+ return target[method + '_'].apply(target, arguments);
+ };
+ });
+ }
+
+ /**
+ * The mapping used by the sendWithCallback mechanism to tie the callback
+ * supplied to an invocation of sendWithCallback with the WebUI response
+ * sent by the browser in response to the chrome.send call. The mapping is
+ * from ID to callback function; the ID is generated by sendWithCallback and
+ * is unique across all invocations of said method.
+ * @type {!Object<Function>}
+ */
+ var chromeSendCallbackMap = Object.create(null);
+
+ /**
+ * The named method the WebUI handler calls directly in response to a
+ * chrome.send call that expects a callback. The handler requires no knowledge
+ * of the specific name of this method, as the name is passed to the handler
+ * as the first argument in the arguments list of chrome.send. The handler
+ * must pass the ID, also sent via the chrome.send arguments list, as the
+ * first argument of the JS invocation; additionally, the handler may
+ * supply any number of other arguments that will be forwarded to the
+ * callback.
+ * @param {string} id The unique ID identifying the callback method this
+ * response is tied to.
+ */
+ function webUIResponse(id) {
+ chromeSendCallbackMap[id].apply(
+ null, Array.prototype.slice.call(arguments, 1));
+ delete chromeSendCallbackMap[id];
+ }
+
+ /**
+ * A variation of chrome.send which allows the client to receive a direct
+ * callback without requiring the handler to have specific knowledge of any
+ * JS internal method names or state. The callback will be removed from the
+ * mapping once it has fired.
+ * @param {string} methodName The name of the WebUI handler API.
+ * @param {Array|undefined} args Arguments for the method call sent to the
+ * WebUI handler. Pass undefined if no args should be sent to the handler.
+ * @param {Function} callback A callback function which is called (indirectly)
+ * by the WebUI handler.
+ */
+ function sendWithCallback(methodName, args, callback) {
+ var id = methodName + createUid();
+ chromeSendCallbackMap[id] = callback;
+ chrome.send(methodName, ['cr.webUIResponse', id].concat(args || []));
+ }
+
+ /**
+ * A registry of callbacks keyed by event name. Used by addWebUIListener to
+ * register listeners.
+ * @type {!Object<Array<Function>>}
+ */
+ var webUIListenerMap = Object.create(null);
+
+ /**
+ * The named method the WebUI handler calls directly when an event occurs.
+ * The WebUI handler must supply the name of the event as the first argument
+ * of the JS invocation; additionally, the handler may supply any number of
+ * other arguments that will be forwarded to the listener callbacks.
+ * @param {string} event The name of the event that has occurred.
+ */
+ function webUIListenerCallback(event) {
+ var listenerCallbacks = webUIListenerMap[event];
+ for (var i = 0; i < listenerCallbacks.length; i++) {
+ var callback = listenerCallbacks[i];
+ callback.apply(null, Array.prototype.slice.call(arguments, 1));
+ }
+ }
+
+ /**
+ * Registers a listener for an event fired from WebUI handlers. Any number of
+ * listeners may register for a single event.
+ * @param {string} event The event to listen to.
+ * @param {Function} callback The callback run when the event is fired.
+ */
+ function addWebUIListener(event, callback) {
+ if (event in webUIListenerMap)
+ webUIListenerMap[event].push(callback);
+ else
+ webUIListenerMap[event] = [callback];
+ }
+
+ return {
+ addSingletonGetter: addSingletonGetter,
+ createUid: createUid,
+ define: define,
+ defineProperty: defineProperty,
+ dispatchPropertyChange: dispatchPropertyChange,
+ dispatchSimpleEvent: dispatchSimpleEvent,
+ exportPath: exportPath,
+ getUid: getUid,
+ makePublic: makePublic,
+ webUIResponse: webUIResponse,
+ sendWithCallback: sendWithCallback,
+ webUIListenerCallback: webUIListenerCallback,
+ addWebUIListener: addWebUIListener,
+ PropertyKind: PropertyKind,
+
+ get doc() {
+ return document;
+ },
+
+ /** Whether we are using a Mac or not. */
+ get isMac() {
+ return /Mac/.test(navigator.platform);
+ },
+
+ /** Whether this is on the Windows platform or not. */
+ get isWindows() {
+ return /Win/.test(navigator.platform);
+ },
+
+ /** Whether this is on chromeOS or not. */
+ get isChromeOS() {
+ return /CrOS/.test(navigator.userAgent);
+ },
+
+ /** Whether this is on vanilla Linux (not chromeOS). */
+ get isLinux() {
+ return /Linux/.test(navigator.userAgent);
+ },
+ };
+}();
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('cr.ui', function() {
+
+ /**
+ * Decorates elements as an instance of a class.
+ * @param {string|!Element} source The way to find the element(s) to decorate.
+ * If this is a string then {@code querySeletorAll} is used to find the
+ * elements to decorate.
+ * @param {!Function} constr The constructor to decorate with. The constr
+ * needs to have a {@code decorate} function.
+ */
+ function decorate(source, constr) {
+ var elements;
+ if (typeof source == 'string')
+ elements = cr.doc.querySelectorAll(source);
+ else
+ elements = [source];
+
+ for (var i = 0, el; el = elements[i]; i++) {
+ if (!(el instanceof constr))
+ constr.decorate(el);
+ }
+ }
+
+ /**
+ * Helper function for creating new element for define.
+ */
+ function createElementHelper(tagName, opt_bag) {
+ // Allow passing in ownerDocument to create in a different document.
+ var doc;
+ if (opt_bag && opt_bag.ownerDocument)
+ doc = opt_bag.ownerDocument;
+ else
+ doc = cr.doc;
+ return doc.createElement(tagName);
+ }
+
+ /**
+ * Creates the constructor for a UI element class.
+ *
+ * Usage:
+ * <pre>
+ * var List = cr.ui.define('list');
+ * List.prototype = {
+ * __proto__: HTMLUListElement.prototype,
+ * decorate: function() {
+ * ...
+ * },
+ * ...
+ * };
+ * </pre>
+ *
+ * @param {string|Function} tagNameOrFunction The tagName or
+ * function to use for newly created elements. If this is a function it
+ * needs to return a new element when called.
+ * @return {function(Object=):Element} The constructor function which takes
+ * an optional property bag. The function also has a static
+ * {@code decorate} method added to it.
+ */
+ function define(tagNameOrFunction) {
+ var createFunction, tagName;
+ if (typeof tagNameOrFunction == 'function') {
+ createFunction = tagNameOrFunction;
+ tagName = '';
+ } else {
+ createFunction = createElementHelper;
+ tagName = tagNameOrFunction;
+ }
+
+ /**
+ * Creates a new UI element constructor.
+ * @param {Object=} opt_propertyBag Optional bag of properties to set on the
+ * object after created. The property {@code ownerDocument} is special
+ * cased and it allows you to create the element in a different
+ * document than the default.
+ * @constructor
+ */
+ function f(opt_propertyBag) {
+ var el = createFunction(tagName, opt_propertyBag);
+ f.decorate(el);
+ for (var propertyName in opt_propertyBag) {
+ el[propertyName] = opt_propertyBag[propertyName];
+ }
+ return el;
+ }
+
+ /**
+ * Decorates an element as a UI element class.
+ * @param {!Element} el The element to decorate.
+ */
+ f.decorate = function(el) {
+ el.__proto__ = f.prototype;
+ el.decorate();
+ };
+
+ return f;
+ }
+
+ /**
+ * Input elements do not grow and shrink with their content. This is a simple
+ * (and not very efficient) way of handling shrinking to content with support
+ * for min width and limited by the width of the parent element.
+ * @param {!HTMLElement} el The element to limit the width for.
+ * @param {!HTMLElement} parentEl The parent element that should limit the
+ * size.
+ * @param {number} min The minimum width.
+ * @param {number=} opt_scale Optional scale factor to apply to the width.
+ */
+ function limitInputWidth(el, parentEl, min, opt_scale) {
+ // Needs a size larger than borders
+ el.style.width = '10px';
+ var doc = el.ownerDocument;
+ var win = doc.defaultView;
+ var computedStyle = win.getComputedStyle(el);
+ var parentComputedStyle = win.getComputedStyle(parentEl);
+ var rtl = computedStyle.direction == 'rtl';
+
+ // To get the max width we get the width of the treeItem minus the position
+ // of the input.
+ var inputRect = el.getBoundingClientRect(); // box-sizing
+ var parentRect = parentEl.getBoundingClientRect();
+ var startPos = rtl ? parentRect.right - inputRect.right :
+ inputRect.left - parentRect.left;
+
+ // Add up border and padding of the input.
+ var inner = parseInt(computedStyle.borderLeftWidth, 10) +
+ parseInt(computedStyle.paddingLeft, 10) +
+ parseInt(computedStyle.paddingRight, 10) +
+ parseInt(computedStyle.borderRightWidth, 10);
+
+ // We also need to subtract the padding of parent to prevent it to overflow.
+ var parentPadding = rtl ? parseInt(parentComputedStyle.paddingLeft, 10) :
+ parseInt(parentComputedStyle.paddingRight, 10);
+
+ var max = parentEl.clientWidth - startPos - inner - parentPadding;
+ if (opt_scale)
+ max *= opt_scale;
+
+ function limit() {
+ if (el.scrollWidth > max) {
+ el.style.width = max + 'px';
+ } else {
+ el.style.width = 0;
+ var sw = el.scrollWidth;
+ if (sw < min) {
+ el.style.width = min + 'px';
+ } else {
+ el.style.width = sw + 'px';
+ }
+ }
+ }
+
+ el.addEventListener('input', limit);
+ limit();
+ }
+
+ /**
+ * Takes a number and spits out a value CSS will be happy with. To avoid
+ * subpixel layout issues, the value is rounded to the nearest integral value.
+ * @param {number} pixels The number of pixels.
+ * @return {string} e.g. '16px'.
+ */
+ function toCssPx(pixels) {
+ if (!window.isFinite(pixels))
+ console.error('Pixel value is not a number: ' + pixels);
+ return Math.round(pixels) + 'px';
+ }
+
+ /**
+ * Users complain they occasionaly use doubleclicks instead of clicks
+ * (http://crbug.com/140364). To fix it we freeze click handling for
+ * the doubleclick time interval.
+ * @param {MouseEvent} e Initial click event.
+ */
+ function swallowDoubleClick(e) {
+ var doc = e.target.ownerDocument;
+ var counter = Math.min(1, e.detail);
+ function swallow(e) {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ function onclick(e) {
+ if (e.detail > counter) {
+ counter = e.detail;
+ // Swallow the click since it's a click inside the doubleclick timeout.
+ swallow(e);
+ } else {
+ // Stop tracking clicks and let regular handling.
+ doc.removeEventListener('dblclick', swallow, true);
+ doc.removeEventListener('click', onclick, true);
+ }
+ }
+ // The following 'click' event (if e.type == 'mouseup') mustn't be taken
+ // into account (it mustn't stop tracking clicks). Start event listening
+ // after zero timeout.
+ setTimeout(function() {
+ doc.addEventListener('click', onclick, true);
+ doc.addEventListener('dblclick', swallow, true);
+ }, 0);
+ }
+
+ return {
+ decorate: decorate,
+ define: define,
+ limitInputWidth: limitInputWidth,
+ toCssPx: toCssPx,
+ swallowDoubleClick: swallowDoubleClick
+ };
+});
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview A command is an abstraction of an action a user can do in the
+ * UI.
+ *
+ * When the focus changes in the document for each command a canExecute event
+ * is dispatched on the active element. By listening to this event you can
+ * enable and disable the command by setting the event.canExecute property.
+ *
+ * When a command is executed a command event is dispatched on the active
+ * element. Note that you should stop the propagation after you have handled the
+ * command if there might be other command listeners higher up in the DOM tree.
+ */
+
+cr.define('cr.ui', function() {
+
+ /**
+ * This is used to identify keyboard shortcuts.
+ * @param {string} shortcut The text used to describe the keys for this
+ * keyboard shortcut.
+ * @constructor
+ */
+ function KeyboardShortcut(shortcut) {
+ var mods = {};
+ var ident = '';
+ shortcut.split('-').forEach(function(part) {
+ var partLc = part.toLowerCase();
+ switch (partLc) {
+ case 'alt':
+ case 'ctrl':
+ case 'meta':
+ case 'shift':
+ mods[partLc + 'Key'] = true;
+ break;
+ default:
+ if (ident)
+ throw Error('Invalid shortcut');
+ ident = part;
+ }
+ });
+
+ this.ident_ = ident;
+ this.mods_ = mods;
+ }
+
+ KeyboardShortcut.prototype = {
+ /**
+ * Whether the keyboard shortcut object matches a keyboard event.
+ * @param {!Event} e The keyboard event object.
+ * @return {boolean} Whether we found a match or not.
+ */
+ matchesEvent: function(e) {
+ if (e.keyIdentifier == this.ident_) {
+ // All keyboard modifiers needs to match.
+ var mods = this.mods_;
+ return ['altKey', 'ctrlKey', 'metaKey', 'shiftKey'].every(function(k) {
+ return e[k] == !!mods[k];
+ });
+ }
+ return false;
+ }
+ };
+
+ /**
+ * Creates a new command element.
+ * @constructor
+ * @extends {HTMLElement}
+ */
+ var Command = cr.ui.define('command');
+
+ Command.prototype = {
+ __proto__: HTMLElement.prototype,
+
+ /**
+ * Initializes the command.
+ */
+ decorate: function() {
+ CommandManager.init(assert(this.ownerDocument));
+
+ if (this.hasAttribute('shortcut'))
+ this.shortcut = this.getAttribute('shortcut');
+ },
+
+ /**
+ * Executes the command by dispatching a command event on the given element.
+ * If |element| isn't given, the active element is used instead.
+ * If the command is {@code disabled} this does nothing.
+ * @param {HTMLElement=} opt_element Optional element to dispatch event on.
+ */
+ execute: function(opt_element) {
+ if (this.disabled)
+ return;
+ var doc = this.ownerDocument;
+ if (doc.activeElement) {
+ var e = new Event('command', {bubbles: true});
+ e.command = this;
+
+ (opt_element || doc.activeElement).dispatchEvent(e);
+ }
+ },
+
+ /**
+ * Call this when there have been changes that might change whether the
+ * command can be executed or not.
+ * @param {Node=} opt_node Node for which to actuate command state.
+ */
+ canExecuteChange: function(opt_node) {
+ dispatchCanExecuteEvent(this,
+ opt_node || this.ownerDocument.activeElement);
+ },
+
+ /**
+ * The keyboard shortcut that triggers the command. This is a string
+ * consisting of a keyIdentifier (as reported by WebKit in keydown) as
+ * well as optional key modifiers joinded with a '-'.
+ *
+ * Multiple keyboard shortcuts can be provided by separating them by
+ * whitespace.
+ *
+ * For example:
+ * "F1"
+ * "U+0008-Meta" for Apple command backspace.
+ * "U+0041-Ctrl" for Control A
+ * "U+007F U+0008-Meta" for Delete and Command Backspace
+ *
+ * @type {string}
+ */
+ shortcut_: '',
+ get shortcut() {
+ return this.shortcut_;
+ },
+ set shortcut(shortcut) {
+ var oldShortcut = this.shortcut_;
+ if (shortcut !== oldShortcut) {
+ this.keyboardShortcuts_ = shortcut.split(/\s+/).map(function(shortcut) {
+ return new KeyboardShortcut(shortcut);
+ });
+
+ // Set this after the keyboardShortcuts_ since that might throw.
+ this.shortcut_ = shortcut;
+ cr.dispatchPropertyChange(this, 'shortcut', this.shortcut_,
+ oldShortcut);
+ }
+ },
+
+ /**
+ * Whether the event object matches the shortcut for this command.
+ * @param {!Event} e The key event object.
+ * @return {boolean} Whether it matched or not.
+ */
+ matchesEvent: function(e) {
+ if (!this.keyboardShortcuts_)
+ return false;
+
+ return this.keyboardShortcuts_.some(function(keyboardShortcut) {
+ return keyboardShortcut.matchesEvent(e);
+ });
+ },
+ };
+
+ /**
+ * The label of the command.
+ */
+ cr.defineProperty(Command, 'label', cr.PropertyKind.ATTR);
+
+ /**
+ * Whether the command is disabled or not.
+ */
+ cr.defineProperty(Command, 'disabled', cr.PropertyKind.BOOL_ATTR);
+
+ /**
+ * Whether the command is hidden or not.
+ */
+ cr.defineProperty(Command, 'hidden', cr.PropertyKind.BOOL_ATTR);
+
+ /**
+ * Whether the command is checked or not.
+ */
+ cr.defineProperty(Command, 'checked', cr.PropertyKind.BOOL_ATTR);
+
+ /**
+ * The flag that prevents the shortcut text from being displayed on menu.
+ *
+ * If false, the keyboard shortcut text (eg. "Ctrl+X" for the cut command)
+ * is displayed in menu when the command is assosiated with a menu item.
+ * Otherwise, no text is displayed.
+ */
+ cr.defineProperty(Command, 'hideShortcutText', cr.PropertyKind.BOOL_ATTR);
+
+ /**
+ * Dispatches a canExecute event on the target.
+ * @param {!cr.ui.Command} command The command that we are testing for.
+ * @param {EventTarget} target The target element to dispatch the event on.
+ */
+ function dispatchCanExecuteEvent(command, target) {
+ var e = new CanExecuteEvent(command);
+ target.dispatchEvent(e);
+ command.disabled = !e.canExecute;
+ }
+
+ /**
+ * The command managers for different documents.
+ */
+ var commandManagers = {};
+
+ /**
+ * Keeps track of the focused element and updates the commands when the focus
+ * changes.
+ * @param {!Document} doc The document that we are managing the commands for.
+ * @constructor
+ */
+ function CommandManager(doc) {
+ doc.addEventListener('focus', this.handleFocus_.bind(this), true);
+ // Make sure we add the listener to the bubbling phase so that elements can
+ // prevent the command.
+ doc.addEventListener('keydown', this.handleKeyDown_.bind(this), false);
+ }
+
+ /**
+ * Initializes a command manager for the document as needed.
+ * @param {!Document} doc The document to manage the commands for.
+ */
+ CommandManager.init = function(doc) {
+ var uid = cr.getUid(doc);
+ if (!(uid in commandManagers)) {
+ commandManagers[uid] = new CommandManager(doc);
+ }
+ };
+
+ CommandManager.prototype = {
+
+ /**
+ * Handles focus changes on the document.
+ * @param {Event} e The focus event object.
+ * @private
+ * @suppress {checkTypes}
+ * TODO(vitalyp): remove the suppression.
+ */
+ handleFocus_: function(e) {
+ var target = e.target;
+
+ // Ignore focus on a menu button or command item.
+ if (target.menu || target.command)
+ return;
+
+ var commands = Array.prototype.slice.call(
+ target.ownerDocument.querySelectorAll('command'));
+
+ commands.forEach(function(command) {
+ dispatchCanExecuteEvent(command, target);
+ });
+ },
+
+ /**
+ * Handles the keydown event and routes it to the right command.
+ * @param {!Event} e The keydown event.
+ */
+ handleKeyDown_: function(e) {
+ var target = e.target;
+ var commands = Array.prototype.slice.call(
+ target.ownerDocument.querySelectorAll('command'));
+
+ for (var i = 0, command; command = commands[i]; i++) {
+ if (command.matchesEvent(e)) {
+ // When invoking a command via a shortcut, we have to manually check
+ // if it can be executed, since focus might not have been changed
+ // what would have updated the command's state.
+ command.canExecuteChange();
+
+ if (!command.disabled) {
+ e.preventDefault();
+ // We do not want any other element to handle this.
+ e.stopPropagation();
+ command.execute();
+ return;
+ }
+ }
+ }
+ }
+ };
+
+ /**
+ * The event type used for canExecute events.
+ * @param {!cr.ui.Command} command The command that we are evaluating.
+ * @extends {Event}
+ * @constructor
+ * @class
+ */
+ function CanExecuteEvent(command) {
+ var e = new Event('canExecute', {bubbles: true, cancelable: true});
+ e.__proto__ = CanExecuteEvent.prototype;
+ e.command = command;
+ return e;
+ }
+
+ CanExecuteEvent.prototype = {
+ __proto__: Event.prototype,
+
+ /**
+ * The current command
+ * @type {cr.ui.Command}
+ */
+ command: null,
+
+ /**
+ * Whether the target can execute the command. Setting this also stops the
+ * propagation and prevents the default. Callers can tell if an event has
+ * been handled via |this.defaultPrevented|.
+ * @type {boolean}
+ */
+ canExecute_: false,
+ get canExecute() {
+ return this.canExecute_;
+ },
+ set canExecute(canExecute) {
+ this.canExecute_ = !!canExecute;
+ this.stopPropagation();
+ this.preventDefault();
+ }
+ };
+
+ // Export
+ return {
+ Command: Command,
+ CanExecuteEvent: CanExecuteEvent
+ };
+});
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview Assertion support.
+ */
+
+/**
+ * Verify |condition| is truthy and return |condition| if so.
+ * @template T
+ * @param {T} condition A condition to check for truthiness. Note that this
+ * may be used to test whether a value is defined or not, and we don't want
+ * to force a cast to Boolean.
+ * @param {string=} opt_message A message to show on failure.
+ * @return {T} A non-null |condition|.
+ */
+function assert(condition, opt_message) {
+ 'use strict';
+ if (!condition) {
+ var msg = 'Assertion failed';
+ if (opt_message)
+ msg = msg + ': ' + opt_message;
+ throw new Error(msg);
+ }
+ return condition;
+}
+
+/**
+ * Call this from places in the code that should never be reached.
+ *
+ * For example, handling all the values of enum with a switch() like this:
+ *
+ * function getValueFromEnum(enum) {
+ * switch (enum) {
+ * case ENUM_FIRST_OF_TWO:
+ * return first
+ * case ENUM_LAST_OF_TWO:
+ * return last;
+ * }
+ * assertNotReached();
+ * return document;
+ * }
+ *
+ * This code should only be hit in the case of serious programmer error or
+ * unexpected input.
+ *
+ * @param {string=} opt_message A message to show when this is hit.
+ */
+function assertNotReached(opt_message) {
+ throw new Error(opt_message || 'Unreachable code hit');
+}
+
+/**
+ * @param {*} value The value to check.
+ * @param {function(new: T, ...)} type A user-defined constructor.
+ * @param {string=} opt_message A message to show when this is hit.
+ * @return {T}
+ * @template T
+ */
+function assertInstanceof(value, type, opt_message) {
+ if (!(value instanceof type)) {
+ throw new Error(opt_message ||
+ value + ' is not a[n] ' + (type.name || typeof type));
+ }
+ return value;
+};
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview EventTracker is a simple class that manages the addition and
+ * removal of DOM event listeners. In particular, it keeps track of all
+ * listeners that have been added and makes it easy to remove some or all of
+ * them without requiring all the information again. This is particularly handy
+ * when the listener is a generated function such as a lambda or the result of
+ * calling Function.bind.
+ */
+
+/**
+ * The type of the internal tracking entry. TODO(dbeam): move this back to
+ * EventTracker.Entry when https://github.com/google/closure-compiler/issues/544
+ * is fixed.
+ * @typedef {{target: !EventTarget,
+ * eventType: string,
+ * listener: Function,
+ * capture: boolean}}
+ */
+var EventTrackerEntry;
+
+/**
+ * Create an EventTracker to track a set of events.
+ * EventTracker instances are typically tied 1:1 with other objects or
+ * DOM elements whose listeners should be removed when the object is disposed
+ * or the corresponding elements are removed from the DOM.
+ * @constructor
+ */
+function EventTracker() {
+ /**
+ * @type {Array<EventTrackerEntry>}
+ * @private
+ */
+ this.listeners_ = [];
+}
+
+EventTracker.prototype = {
+ /**
+ * Add an event listener - replacement for EventTarget.addEventListener.
+ * @param {!EventTarget} target The DOM target to add a listener to.
+ * @param {string} eventType The type of event to subscribe to.
+ * @param {EventListener|Function} listener The listener to add.
+ * @param {boolean=} opt_capture Whether to invoke during the capture phase.
+ */
+ add: function(target, eventType, listener, opt_capture) {
+ var capture = !!opt_capture;
+ var h = {
+ target: target,
+ eventType: eventType,
+ listener: listener,
+ capture: capture,
+ };
+ this.listeners_.push(h);
+ target.addEventListener(eventType, listener, capture);
+ },
+
+ /**
+ * Remove any specified event listeners added with this EventTracker.
+ * @param {!EventTarget} target The DOM target to remove a listener from.
+ * @param {?string} eventType The type of event to remove.
+ */
+ remove: function(target, eventType) {
+ this.listeners_ = this.listeners_.filter(function(h) {
+ if (h.target == target && (!eventType || (h.eventType == eventType))) {
+ EventTracker.removeEventListener_(h);
+ return false;
+ }
+ return true;
+ });
+ },
+
+ /**
+ * Remove all event listeners added with this EventTracker.
+ */
+ removeAll: function() {
+ this.listeners_.forEach(EventTracker.removeEventListener_);
+ this.listeners_ = [];
+ }
+};
+
+/**
+ * Remove a single event listener given it's tracking entry. It's up to the
+ * caller to ensure the entry is removed from listeners_.
+ * @param {EventTrackerEntry} h The entry describing the listener to remove.
+ * @private
+ */
+EventTracker.removeEventListener_ = function(h) {
+ h.target.removeEventListener(h.eventType, h.listener, h.capture);
+};
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('cr.ui', function() {
+ /**
+ * A class to manage grid of focusable elements in a 2D grid. For example,
+ * given this grid:
+ *
+ * focusable [focused] focusable (row: 0, col: 1)
+ * focusable focusable focusable
+ * focusable focusable focusable
+ *
+ * Pressing the down arrow would result in the focus moving down 1 row and
+ * keeping the same column:
+ *
+ * focusable focusable focusable
+ * focusable [focused] focusable (row: 1, col: 1)
+ * focusable focusable focusable
+ *
+ * And pressing right or tab at this point would move the focus to:
+ *
+ * focusable focusable focusable
+ * focusable focusable [focused] (row: 1, col: 2)
+ * focusable focusable focusable
+ *
+ * @constructor
+ * @implements {cr.ui.FocusRow.Delegate}
+ */
+ function FocusGrid() {
+ /** @type {!Array<!cr.ui.FocusRow>} */
+ this.rows = [];
+ }
+
+ FocusGrid.prototype = {
+ /** @private {boolean} */
+ ignoreFocusChange_: false,
+
+ /** @override */
+ onFocus: function(row, e) {
+ if (this.ignoreFocusChange_)
+ this.ignoreFocusChange_ = false;
+ else
+ this.lastFocused_ = e.currentTarget;
+
+ this.rows.forEach(function(r) { r.makeActive(r == row); });
+ },
+
+ /** @override */
+ onKeydown: function(row, e) {
+ var rowIndex = this.rows.indexOf(row);
+ assert(rowIndex >= 0);
+
+ var newRow = -1;
+
+ if (e.keyIdentifier == 'Up')
+ newRow = rowIndex - 1;
+ else if (e.keyIdentifier == 'Down')
+ newRow = rowIndex + 1;
+ else if (e.keyIdentifier == 'PageUp')
+ newRow = 0;
+ else if (e.keyIdentifier == 'PageDown')
+ newRow = this.rows.length - 1;
+
+ var rowToFocus = this.rows[newRow];
+ if (rowToFocus) {
+ this.ignoreFocusChange_ = true;
+ rowToFocus.getEquivalentElement(this.lastFocused_).focus();
+ e.preventDefault();
+ return true;
+ }
+
+ return false;
+ },
+
+ /**
+ * Unregisters event handlers and removes all |this.rows|.
+ */
+ destroy: function() {
+ this.rows.forEach(function(row) { row.destroy(); });
+ this.rows.length = 0;
+ },
+
+ /**
+ * @param {Node} target A target item to find in this grid.
+ * @return {number} The row index. -1 if not found.
+ */
+ getRowIndexForTarget: function(target) {
+ for (var i = 0; i < this.rows.length; ++i) {
+ if (this.rows[i].getElements().indexOf(target) >= 0)
+ return i;
+ }
+ return -1;
+ },
+
+ /**
+ * @param {Element} root An element to search for.
+ * @return {?cr.ui.FocusRow} The row with root of |root| or null.
+ */
+ getRowForRoot: function(root) {
+ for (var i = 0; i < this.rows.length; ++i) {
+ if (this.rows[i].root == root)
+ return this.rows[i];
+ }
+ return null;
+ },
+
+ /**
+ * Adds |row| to the end of this list.
+ * @param {!cr.ui.FocusRow} row The row that needs to be added to this grid.
+ */
+ addRow: function(row) {
+ this.addRowBefore(row, null);
+ },
+
+ /**
+ * Adds |row| before |nextRow|. If |nextRow| is not in the list or it's
+ * null, |row| is added to the end.
+ * @param {!cr.ui.FocusRow} row The row that needs to be added to this grid.
+ * @param {cr.ui.FocusRow} nextRow The row that should follow |row|.
+ */
+ addRowBefore: function(row, nextRow) {
+ row.delegate = row.delegate || this;
+
+ var nextRowIndex = this.rows.indexOf(nextRow);
+ if (nextRowIndex == -1)
+ this.rows.push(row);
+ else
+ this.rows.splice(nextRowIndex, 0, row);
+ },
+
+ /**
+ * Removes a row from the focus row. No-op if row is not in the grid.
+ * @param {cr.ui.FocusRow} row The row that needs to be removed.
+ */
+ removeRow: function(row) {
+ var nextRowIndex = this.rows.indexOf(row);
+ if (nextRowIndex > -1)
+ this.rows.splice(nextRowIndex, 1);
+ },
+
+ /**
+ * Makes sure that at least one row is active. Should be called once, after
+ * adding all rows to FocusGrid.
+ */
+ ensureRowActive: function() {
+ if (this.rows.length == 0)
+ return;
+
+ for (var i = 0; i < this.rows.length; ++i) {
+ if (this.rows[i].isActive())
+ return;
+ }
+
+ this.rows[0].makeActive(true);
+ },
+ };
+
+ return {
+ FocusGrid: FocusGrid,
+ };
+});
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// <include src="../../../../ui/webui/resources/js/assert.js">
+
+/**
+ * Alias for document.getElementById.
+ * @param {string} id The ID of the element to find.
+ * @return {HTMLElement} The found element or null if not found.
+ */
+function $(id) {
+ return document.getElementById(id);
+}
+
+/**
+ * Add an accessible message to the page that will be announced to
+ * users who have spoken feedback on, but will be invisible to all
+ * other users. It's removed right away so it doesn't clutter the DOM.
+ * @param {string} msg The text to be pronounced.
+ */
+function announceAccessibleMessage(msg) {
+ var element = document.createElement('div');
+ element.setAttribute('aria-live', 'polite');
+ element.style.position = 'relative';
+ element.style.left = '-9999px';
+ element.style.height = '0px';
+ element.innerText = msg;
+ document.body.appendChild(element);
+ window.setTimeout(function() {
+ document.body.removeChild(element);
+ }, 0);
+}
+
+/**
+ * Calls chrome.send with a callback and restores the original afterwards.
+ * @param {string} name The name of the message to send.
+ * @param {!Array} params The parameters to send.
+ * @param {string} callbackName The name of the function that the backend calls.
+ * @param {!Function} callback The function to call.
+ */
+function chromeSend(name, params, callbackName, callback) {
+ var old = global[callbackName];
+ global[callbackName] = function() {
+ // restore
+ global[callbackName] = old;
+
+ var args = Array.prototype.slice.call(arguments);
+ return callback.apply(global, args);
+ };
+ chrome.send(name, params);
+}
+
+/**
+ * Returns the scale factors supported by this platform for webui
+ * resources.
+ * @return {Array} The supported scale factors.
+ */
+function getSupportedScaleFactors() {
+ var supportedScaleFactors = [];
+ if (cr.isMac || cr.isChromeOS || cr.isWindows || cr.isLinux) {
+ // All desktop platforms support zooming which also updates the
+ // renderer's device scale factors (a.k.a devicePixelRatio), and
+ // these platforms has high DPI assets for 2.0x. Use 1x and 2x in
+ // image-set on these platforms so that the renderer can pick the
+ // closest image for the current device scale factor.
+ supportedScaleFactors.push(1);
+ supportedScaleFactors.push(2);
+ } else {
+ // For other platforms that use fixed device scale factor, use
+ // the window's device pixel ratio.
+ // TODO(oshima): Investigate if Android/iOS need to use image-set.
+ supportedScaleFactors.push(window.devicePixelRatio);
+ }
+ return supportedScaleFactors;
+}
+
+/**
+ * Generates a CSS url string.
+ * @param {string} s The URL to generate the CSS url for.
+ * @return {string} The CSS url string.
+ */
+function url(s) {
+ // http://www.w3.org/TR/css3-values/#uris
+ // Parentheses, commas, whitespace characters, single quotes (') and double
+ // quotes (") appearing in a URI must be escaped with a backslash
+ var s2 = s.replace(/(\(|\)|\,|\s|\'|\"|\\)/g, '\\$1');
+ // WebKit has a bug when it comes to URLs that end with \
+ // https://bugs.webkit.org/show_bug.cgi?id=28885
+ if (/\\\\$/.test(s2)) {
+ // Add a space to work around the WebKit bug.
+ s2 += ' ';
+ }
+ return 'url("' + s2 + '")';
+}
+
+/**
+ * Returns the URL of the image, or an image set of URLs for the profile avatar.
+ * Default avatars have resources available for multiple scalefactors, whereas
+ * the GAIA profile image only comes in one size.
+ *
+ * @param {string} path The path of the image.
+ * @return {string} The url, or an image set of URLs of the avatar image.
+ */
+function getProfileAvatarIcon(path) {
+ var chromeThemePath = 'chrome://theme';
+ var isDefaultAvatar =
+ (path.slice(0, chromeThemePath.length) == chromeThemePath);
+ return isDefaultAvatar ? imageset(path + '@scalefactorx'): url(path);
+}
+
+/**
+ * Generates a CSS -webkit-image-set for a chrome:// url.
+ * An entry in the image set is added for each of getSupportedScaleFactors().
+ * The scale-factor-specific url is generated by replacing the first instance of
+ * 'scalefactor' in |path| with the numeric scale factor.
+ * @param {string} path The URL to generate an image set for.
+ * 'scalefactor' should be a substring of |path|.
+ * @return {string} The CSS -webkit-image-set.
+ */
+function imageset(path) {
+ var supportedScaleFactors = getSupportedScaleFactors();
+
+ var replaceStartIndex = path.indexOf('scalefactor');
+ if (replaceStartIndex < 0)
+ return url(path);
+
+ var s = '';
+ for (var i = 0; i < supportedScaleFactors.length; ++i) {
+ var scaleFactor = supportedScaleFactors[i];
+ var pathWithScaleFactor = path.substr(0, replaceStartIndex) + scaleFactor +
+ path.substr(replaceStartIndex + 'scalefactor'.length);
+
+ s += url(pathWithScaleFactor) + ' ' + scaleFactor + 'x';
+
+ if (i != supportedScaleFactors.length - 1)
+ s += ', ';
+ }
+ return '-webkit-image-set(' + s + ')';
+}
+
+/**
+ * Parses query parameters from Location.
+ * @param {Location} location The URL to generate the CSS url for.
+ * @return {Object} Dictionary containing name value pairs for URL
+ */
+function parseQueryParams(location) {
+ var params = {};
+ var query = unescape(location.search.substring(1));
+ var vars = query.split('&');
+ for (var i = 0; i < vars.length; i++) {
+ var pair = vars[i].split('=');
+ params[pair[0]] = pair[1];
+ }
+ return params;
+}
+
+/**
+ * Creates a new URL by appending or replacing the given query key and value.
+ * Not supporting URL with username and password.
+ * @param {Location} location The original URL.
+ * @param {string} key The query parameter name.
+ * @param {string} value The query parameter value.
+ * @return {string} The constructed new URL.
+ */
+function setQueryParam(location, key, value) {
+ var query = parseQueryParams(location);
+ query[encodeURIComponent(key)] = encodeURIComponent(value);
+
+ var newQuery = '';
+ for (var q in query) {
+ newQuery += (newQuery ? '&' : '?') + q + '=' + query[q];
+ }
+
+ return location.origin + location.pathname + newQuery + location.hash;
+}
+
+/**
+ * @param {Node} el A node to search for ancestors with |className|.
+ * @param {string} className A class to search for.
+ * @return {Element} A node with class of |className| or null if none is found.
+ */
+function findAncestorByClass(el, className) {
+ return /** @type {Element} */(findAncestor(el, function(el) {
+ return el.classList && el.classList.contains(className);
+ }));
+}
+
+/**
+ * Return the first ancestor for which the {@code predicate} returns true.
+ * @param {Node} node The node to check.
+ * @param {function(Node):boolean} predicate The function that tests the
+ * nodes.
+ * @return {Node} The found ancestor or null if not found.
+ */
+function findAncestor(node, predicate) {
+ var last = false;
+ while (node != null && !(last = predicate(node))) {
+ node = node.parentNode;
+ }
+ return last ? node : null;
+}
+
+function swapDomNodes(a, b) {
+ var afterA = a.nextSibling;
+ if (afterA == b) {
+ swapDomNodes(b, a);
+ return;
+ }
+ var aParent = a.parentNode;
+ b.parentNode.replaceChild(a, b);
+ aParent.insertBefore(b, afterA);
+}
+
+/**
+ * Disables text selection and dragging, with optional whitelist callbacks.
+ * @param {function(Event):boolean=} opt_allowSelectStart Unless this function
+ * is defined and returns true, the onselectionstart event will be
+ * surpressed.
+ * @param {function(Event):boolean=} opt_allowDragStart Unless this function
+ * is defined and returns true, the ondragstart event will be surpressed.
+ */
+function disableTextSelectAndDrag(opt_allowSelectStart, opt_allowDragStart) {
+ // Disable text selection.
+ document.onselectstart = function(e) {
+ if (!(opt_allowSelectStart && opt_allowSelectStart.call(this, e)))
+ e.preventDefault();
+ };
+
+ // Disable dragging.
+ document.ondragstart = function(e) {
+ if (!(opt_allowDragStart && opt_allowDragStart.call(this, e)))
+ e.preventDefault();
+ };
+}
+
+/**
+ * TODO(dbeam): DO NOT USE. THIS IS DEPRECATED. Use an action-link instead.
+ * Call this to stop clicks on <a href="#"> links from scrolling to the top of
+ * the page (and possibly showing a # in the link).
+ */
+function preventDefaultOnPoundLinkClicks() {
+ document.addEventListener('click', function(e) {
+ var anchor = findAncestor(/** @type {Node} */(e.target), function(el) {
+ return el.tagName == 'A';
+ });
+ // Use getAttribute() to prevent URL normalization.
+ if (anchor && anchor.getAttribute('href') == '#')
+ e.preventDefault();
+ });
+}
+
+/**
+ * Check the directionality of the page.
+ * @return {boolean} True if Chrome is running an RTL UI.
+ */
+function isRTL() {
+ return document.documentElement.dir == 'rtl';
+}
+
+/**
+ * Get an element that's known to exist by its ID. We use this instead of just
+ * calling getElementById and not checking the result because this lets us
+ * satisfy the JSCompiler type system.
+ * @param {string} id The identifier name.
+ * @return {!HTMLElement} the Element.
+ */
+function getRequiredElement(id) {
+ return assertInstanceof($(id), HTMLElement,
+ 'Missing required element: ' + id);
+}
+
+/**
+ * Query an element that's known to exist by a selector. We use this instead of
+ * just calling querySelector and not checking the result because this lets us
+ * satisfy the JSCompiler type system.
+ * @param {string} selectors CSS selectors to query the element.
+ * @param {(!Document|!DocumentFragment|!Element)=} opt_context An optional
+ * context object for querySelector.
+ * @return {!HTMLElement} the Element.
+ */
+function queryRequiredElement(selectors, opt_context) {
+ var element = (opt_context || document).querySelector(selectors);
+ return assertInstanceof(element, HTMLElement,
+ 'Missing required element: ' + selectors);
+}
+
+// Handle click on a link. If the link points to a chrome: or file: url, then
+// call into the browser to do the navigation.
+document.addEventListener('click', function(e) {
+ if (e.defaultPrevented)
+ return;
+
+ var el = e.target;
+ if (el.nodeType == Node.ELEMENT_NODE &&
+ el.webkitMatchesSelector('A, A *')) {
+ while (el.tagName != 'A') {
+ el = el.parentElement;
+ }
+
+ if ((el.protocol == 'file:' || el.protocol == 'about:') &&
+ (e.button == 0 || e.button == 1)) {
+ chrome.send('navigateToUrl', [
+ el.href,
+ el.target,
+ e.button,
+ e.altKey,
+ e.ctrlKey,
+ e.metaKey,
+ e.shiftKey
+ ]);
+ e.preventDefault();
+ }
+ }
+});
+
+/**
+ * Creates a new URL which is the old URL with a GET param of key=value.
+ * @param {string} url The base URL. There is not sanity checking on the URL so
+ * it must be passed in a proper format.
+ * @param {string} key The key of the param.
+ * @param {string} value The value of the param.
+ * @return {string} The new URL.
+ */
+function appendParam(url, key, value) {
+ var param = encodeURIComponent(key) + '=' + encodeURIComponent(value);
+
+ if (url.indexOf('?') == -1)
+ return url + '?' + param;
+ return url + '&' + param;
+}
+
+/**
+ * Creates a CSS -webkit-image-set for a favicon request.
+ * @param {string} url The url for the favicon.
+ * @param {number=} opt_size Optional preferred size of the favicon.
+ * @param {string=} opt_type Optional type of favicon to request. Valid values
+ * are 'favicon' and 'touch-icon'. Default is 'favicon'.
+ * @return {string} -webkit-image-set for the favicon.
+ */
+function getFaviconImageSet(url, opt_size, opt_type) {
+ var size = opt_size || 16;
+ var type = opt_type || 'favicon';
+ return imageset(
+ 'chrome://' + type + '/size/' + size + '@scalefactorx/' + url);
+}
+
+/**
+ * Creates a new URL for a favicon request for the current device pixel ratio.
+ * The URL must be updated when the user moves the browser to a screen with a
+ * different device pixel ratio. Use getFaviconImageSet() for the updating to
+ * occur automatically.
+ * @param {string} url The url for the favicon.
+ * @param {number=} opt_size Optional preferred size of the favicon.
+ * @param {string=} opt_type Optional type of favicon to request. Valid values
+ * are 'favicon' and 'touch-icon'. Default is 'favicon'.
+ * @return {string} Updated URL for the favicon.
+ */
+function getFaviconUrlForCurrentDevicePixelRatio(url, opt_size, opt_type) {
+ var size = opt_size || 16;
+ var type = opt_type || 'favicon';
+ return 'chrome://' + type + '/size/' + size + '@' +
+ window.devicePixelRatio + 'x/' + url;
+}
+
+/**
+ * Creates an element of a specified type with a specified class name.
+ * @param {string} type The node type.
+ * @param {string} className The class name to use.
+ * @return {Element} The created element.
+ */
+function createElementWithClassName(type, className) {
+ var elm = document.createElement(type);
+ elm.className = className;
+ return elm;
+}
+
+/**
+ * webkitTransitionEnd does not always fire (e.g. when animation is aborted
+ * or when no paint happens during the animation). This function sets up
+ * a timer and emulate the event if it is not fired when the timer expires.
+ * @param {!HTMLElement} el The element to watch for webkitTransitionEnd.
+ * @param {number} timeOut The maximum wait time in milliseconds for the
+ * webkitTransitionEnd to happen.
+ */
+function ensureTransitionEndEvent(el, timeOut) {
+ var fired = false;
+ el.addEventListener('webkitTransitionEnd', function f(e) {
+ el.removeEventListener('webkitTransitionEnd', f);
+ fired = true;
+ });
+ window.setTimeout(function() {
+ if (!fired)
+ cr.dispatchSimpleEvent(el, 'webkitTransitionEnd', true);
+ }, timeOut);
+}
+
+/**
+ * Alias for document.scrollTop getter.
+ * @param {!HTMLDocument} doc The document node where information will be
+ * queried from.
+ * @return {number} The Y document scroll offset.
+ */
+function scrollTopForDocument(doc) {
+ return doc.documentElement.scrollTop || doc.body.scrollTop;
+}
+
+/**
+ * Alias for document.scrollTop setter.
+ * @param {!HTMLDocument} doc The document node where information will be
+ * queried from.
+ * @param {number} value The target Y scroll offset.
+ */
+function setScrollTopForDocument(doc, value) {
+ doc.documentElement.scrollTop = doc.body.scrollTop = value;
+}
+
+/**
+ * Alias for document.scrollLeft getter.
+ * @param {!HTMLDocument} doc The document node where information will be
+ * queried from.
+ * @return {number} The X document scroll offset.
+ */
+function scrollLeftForDocument(doc) {
+ return doc.documentElement.scrollLeft || doc.body.scrollLeft;
+}
+
+/**
+ * Alias for document.scrollLeft setter.
+ * @param {!HTMLDocument} doc The document node where information will be
+ * queried from.
+ * @param {number} value The target X scroll offset.
+ */
+function setScrollLeftForDocument(doc, value) {
+ doc.documentElement.scrollLeft = doc.body.scrollLeft = value;
+}
+
+/**
+ * Replaces '&', '<', '>', '"', and ''' characters with their HTML encoding.
+ * @param {string} original The original string.
+ * @return {string} The string with all the characters mentioned above replaced.
+ */
+function HTMLEscape(original) {
+ return original.replace(/&/g, '&amp;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;')
+ .replace(/"/g, '&quot;')
+ .replace(/'/g, '&#39;');
+}
+
+/**
+ * Shortens the provided string (if necessary) to a string of length at most
+ * |maxLength|.
+ * @param {string} original The original string.
+ * @param {number} maxLength The maximum length allowed for the string.
+ * @return {string} The original string if its length does not exceed
+ * |maxLength|. Otherwise the first |maxLength| - 1 characters with '...'
+ * appended.
+ */
+function elide(original, maxLength) {
+ if (original.length <= maxLength)
+ return original;
+ return original.substring(0, maxLength - 1) + '\u2026';
+};
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('downloads', function() {
+ /**
+ * @param {string} chromeSendName
+ * @return {function(string):void} A chrome.send() callback with curried name.
+ */
+ function chromeSendWithId(chromeSendName) {
+ return function(id) { chrome.send(chromeSendName, [id]); };
+ }
+
+ /** @constructor */
+ function ActionService() {}
+
+ ActionService.prototype = {
+ /** @param {string} id ID of the download to cancel. */
+ cancel: chromeSendWithId('cancel'),
+
+ /** Instructs the browser to clear all finished downloads. */
+ clearAll: function() {
+ if (loadTimeData.getBoolean('allowDeletingHistory')) {
+ chrome.send('clearAll');
+ this.search('');
+ }
+ },
+
+ /** @param {string} id ID of the dangerous download to discard. */
+ discardDangerous: chromeSendWithId('discardDangerous'),
+
+ /** @param {string} url URL of a file to download. */
+ download: function(url) {
+ var a = document.createElement('a');
+ a.href = url;
+ a.setAttribute('download', '');
+ a.click();
+ },
+
+ /** @param {string} id ID of the download that the user started dragging. */
+ drag: chromeSendWithId('drag'),
+
+ /**
+ * @return {boolean} Whether the user is currently searching for downloads
+ * (i.e. has a non-empty search term).
+ */
+ isSearching: function() {
+ return this.searchText_.length > 0;
+ },
+
+ /** Opens the current local destination for downloads. */
+ openDownloadsFolder: chrome.send.bind(chrome, 'openDownloadsFolder'),
+
+ /**
+ * @param {string} id ID of the download to run locally on the user's box.
+ */
+ openFile: chromeSendWithId('openFile'),
+
+ /** @param {string} id ID the of the progressing download to pause. */
+ pause: chromeSendWithId('pause'),
+
+ /** @param {string} id ID of the finished download to remove. */
+ remove: chromeSendWithId('remove'),
+
+ /** @param {string} id ID of the paused download to resume. */
+ resume: chromeSendWithId('resume'),
+
+ /**
+ * @param {string} id ID of the dangerous download to save despite
+ * warnings.
+ */
+ saveDangerous: chromeSendWithId('saveDangerous'),
+
+ /** @param {string} searchText What to search for. */
+ search: function(searchText) {
+ if (this.searchText_ == searchText)
+ return;
+
+ this.searchText_ = searchText;
+
+ // Split quoted terms (e.g., 'The "lazy" dog' => ['The', 'lazy', 'dog']).
+ function trim(s) { return s.trim(); }
+ chrome.send('getDownloads', searchText.split(/"([^"]*)"/).map(trim));
+ },
+
+ /**
+ * Shows the local folder a finished download resides in.
+ * @param {string} id ID of the download to show.
+ */
+ show: chromeSendWithId('show'),
+
+ /** Undo download removal. */
+ undo: chrome.send.bind(chrome, 'undo'),
+ };
+
+ cr.addSingletonGetter(ActionService);
+
+ return {ActionService: ActionService};
+});
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('downloads', function() {
+ /**
+ * Explains why a download is in DANGEROUS state.
+ * @enum {string}
+ */
+ var DangerType = {
+ NOT_DANGEROUS: 'NOT_DANGEROUS',
+ DANGEROUS_FILE: 'DANGEROUS_FILE',
+ DANGEROUS_URL: 'DANGEROUS_URL',
+ DANGEROUS_CONTENT: 'DANGEROUS_CONTENT',
+ UNCOMMON_CONTENT: 'UNCOMMON_CONTENT',
+ DANGEROUS_HOST: 'DANGEROUS_HOST',
+ POTENTIALLY_UNWANTED: 'POTENTIALLY_UNWANTED',
+ };
+
+ /**
+ * The states a download can be in. These correspond to states defined in
+ * DownloadsDOMHandler::CreateDownloadItemValue
+ * @enum {string}
+ */
+ var States = {
+ IN_PROGRESS: 'IN_PROGRESS',
+ CANCELLED: 'CANCELLED',
+ COMPLETE: 'COMPLETE',
+ PAUSED: 'PAUSED',
+ DANGEROUS: 'DANGEROUS',
+ INTERRUPTED: 'INTERRUPTED',
+ };
+
+ return {
+ DangerType: DangerType,
+ States: States,
+ };
+});
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('cr.ui', function() {
+ /**
+ * A class to manage focus between given horizontally arranged elements.
+ *
+ * Pressing left cycles backward and pressing right cycles forward in item
+ * order. Pressing Home goes to the beginning of the list and End goes to the
+ * end of the list.
+ *
+ * If an item in this row is focused, it'll stay active (accessible via tab).
+ * If no items in this row are focused, the row can stay active until focus
+ * changes to a node inside |this.boundary_|. If |boundary| isn't specified,
+ * any focus change deactivates the row.
+ *
+ * @param {!Element} root The root of this focus row. Focus classes are
+ * applied to |root| and all added elements must live within |root|.
+ * @param {?Node} boundary Focus events are ignored outside of this node.
+ * @param {cr.ui.FocusRow.Delegate=} opt_delegate An optional event delegate.
+ * @constructor
+ */
+ function FocusRow(root, boundary, opt_delegate) {
+ /** @type {!Element} */
+ this.root = root;
+
+ /** @private {!Node} */
+ this.boundary_ = boundary || document;
+
+ /** @type {cr.ui.FocusRow.Delegate|undefined} */
+ this.delegate = opt_delegate;
+
+ /** @protected {!EventTracker} */
+ this.eventTracker = new EventTracker;
+ }
+
+ /** @interface */
+ FocusRow.Delegate = function() {};
+
+ FocusRow.Delegate.prototype = {
+ /**
+ * Called when a key is pressed while on a FocusRow's item. If true is
+ * returned, further processing is skipped.
+ * @param {!cr.ui.FocusRow} row The row that detected a keydown.
+ * @param {!Event} e
+ * @return {boolean} Whether the event was handled.
+ */
+ onKeydown: assertNotReached,
+
+ /**
+ * @param {!cr.ui.FocusRow} row
+ * @param {!Event} e
+ */
+ onFocus: assertNotReached,
+ };
+
+ /** @const {string} */
+ FocusRow.ACTIVE_CLASS = 'focus-row-active';
+
+ /**
+ * Whether it's possible that |element| can be focused.
+ * @param {Element} element
+ * @return {boolean} Whether the item is focusable.
+ */
+ FocusRow.isFocusable = function(element) {
+ if (!element || element.disabled)
+ return false;
+
+ // We don't check that element.tabIndex >= 0 here because inactive rows set
+ // a tabIndex of -1.
+
+ function isVisible(element) {
+ assertInstanceof(element, Element);
+
+ var style = window.getComputedStyle(element);
+ if (style.visibility == 'hidden' || style.display == 'none')
+ return false;
+
+ var parent = element.parentNode;
+ if (!parent)
+ return false;
+
+ if (parent == element.ownerDocument || parent instanceof DocumentFragment)
+ return true;
+
+ return isVisible(parent);
+ }
+
+ return isVisible(element);
+ };
+
+ FocusRow.prototype = {
+ /**
+ * Register a new type of focusable element (or add to an existing one).
+ *
+ * Example: an (X) button might be 'delete' or 'close'.
+ *
+ * When FocusRow is used within a FocusGrid, these types are used to
+ * determine equivalent controls when Up/Down are pressed to change rows.
+ *
+ * Another example: mutually exclusive controls that hide eachother on
+ * activation (i.e. Play/Pause) could use the same type (i.e. 'play-pause')
+ * to indicate they're equivalent.
+ *
+ * @param {string} type The type of element to track focus of.
+ * @param {string} query The selector of the element from this row's root.
+ * @return {boolean} Whether a new item was added.
+ */
+ addItem: function(type, query) {
+ assert(type);
+
+ var element = this.root.querySelector(query);
+ if (!element)
+ return false;
+
+ element.setAttribute('focus-type', type);
+ element.tabIndex = this.isActive() ? 0 : -1;
+
+ this.eventTracker.add(element, 'blur', this.onBlur_.bind(this));
+ this.eventTracker.add(element, 'focus', this.onFocus_.bind(this));
+ this.eventTracker.add(element, 'keydown', this.onKeydown_.bind(this));
+ this.eventTracker.add(element, 'mousedown',
+ this.onMousedown_.bind(this));
+ return true;
+ },
+
+ /** Dereferences nodes and removes event handlers. */
+ destroy: function() {
+ this.eventTracker.removeAll();
+ },
+
+ /**
+ * @param {Element} sampleElement An element for to find an equivalent for.
+ * @return {!Element} An equivalent element to focus for |sampleElement|.
+ * @protected
+ */
+ getCustomEquivalent: function(sampleElement) {
+ return assert(this.getFirstFocusable());
+ },
+
+ /**
+ * @return {!Array<!Element>} All registered elements (regardless of
+ * focusability).
+ */
+ getElements: function() {
+ var elements = this.root.querySelectorAll('[focus-type]');
+ return Array.prototype.slice.call(elements);
+ },
+
+ /**
+ * Find the element that best matches |sampleElement|.
+ * @param {!Element} sampleElement An element from a row of the same type
+ * which previously held focus.
+ * @return {!Element} The element that best matches sampleElement.
+ */
+ getEquivalentElement: function(sampleElement) {
+ if (this.getFocusableElements().indexOf(sampleElement) >= 0)
+ return sampleElement;
+
+ var sampleFocusType = this.getTypeForElement(sampleElement);
+ if (sampleFocusType) {
+ var sameType = this.getFirstFocusable(sampleFocusType);
+ if (sameType)
+ return sameType;
+ }
+
+ return this.getCustomEquivalent(sampleElement);
+ },
+
+ /**
+ * @param {string=} opt_type An optional type to search for.
+ * @return {?Element} The first focusable element with |type|.
+ */
+ getFirstFocusable: function(opt_type) {
+ var filter = opt_type ? '="' + opt_type + '"' : '';
+ var elements = this.root.querySelectorAll('[focus-type' + filter + ']');
+ for (var i = 0; i < elements.length; ++i) {
+ if (cr.ui.FocusRow.isFocusable(elements[i]))
+ return elements[i];
+ }
+ return null;
+ },
+
+ /** @return {!Array<!Element>} Registered, focusable elements. */
+ getFocusableElements: function() {
+ return this.getElements().filter(cr.ui.FocusRow.isFocusable);
+ },
+
+ /**
+ * @param {!Element} element An element to determine a focus type for.
+ * @return {string} The focus type for |element| or '' if none.
+ */
+ getTypeForElement: function(element) {
+ return element.getAttribute('focus-type') || '';
+ },
+
+ /** @return {boolean} Whether this row is currently active. */
+ isActive: function() {
+ return this.root.classList.contains(FocusRow.ACTIVE_CLASS);
+ },
+
+ /**
+ * Enables/disables the tabIndex of the focusable elements in the FocusRow.
+ * tabIndex can be set properly.
+ * @param {boolean} active True if tab is allowed for this row.
+ */
+ makeActive: function(active) {
+ if (active == this.isActive())
+ return;
+
+ this.getElements().forEach(function(element) {
+ element.tabIndex = active ? 0 : -1;
+ });
+
+ this.root.classList.toggle(FocusRow.ACTIVE_CLASS, active);
+ },
+
+ /**
+ * @param {!Event} e
+ * @private
+ */
+ onBlur_: function(e) {
+ if (!this.boundary_.contains(/** @type {Node} */(e.relatedTarget)))
+ return;
+
+ if (this.getFocusableElements().indexOf(e.currentTarget) >= 0)
+ this.makeActive(false);
+ },
+
+ /**
+ * @param {!Event} e
+ * @private
+ */
+ onFocus_: function(e) {
+ if (this.delegate)
+ this.delegate.onFocus(this, e);
+ },
+
+ /**
+ * @param {!Event} e A mousedown event.
+ * @private
+ */
+ onMousedown_: function(e) {
+ // Only accept left mouse clicks.
+ if (e.button)
+ return;
+
+ // Allow the element under the mouse cursor to be focusable.
+ if (!e.currentTarget.disabled)
+ e.currentTarget.tabIndex = 0;
+ },
+
+ /**
+ * @param {Event} e The keydown event.
+ * @private
+ */
+ onKeydown_: function(e) {
+ var elements = this.getFocusableElements();
+ var elementIndex = elements.indexOf(e.currentTarget);
+ assert(elementIndex >= 0);
+
+ if (this.delegate && this.delegate.onKeydown(this, e))
+ return;
+
+ if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)
+ return;
+
+ var index = -1;
+
+ if (e.keyIdentifier == 'Left')
+ index = elementIndex + (isRTL() ? 1 : -1);
+ else if (e.keyIdentifier == 'Right')
+ index = elementIndex + (isRTL() ? -1 : 1);
+ else if (e.keyIdentifier == 'Home')
+ index = 0;
+ else if (e.keyIdentifier == 'End')
+ index = elements.length - 1;
+
+ var elementToFocus = elements[index];
+ if (elementToFocus) {
+ this.getEquivalentElement(elementToFocus).focus();
+ e.preventDefault();
+ }
+ },
+ };
+
+ return {
+ FocusRow: FocusRow,
+ };
+});
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('downloads', function() {
+ /**
+ * @param {!Element} root
+ * @param {?Node} boundary
+ * @constructor
+ * @extends {cr.ui.FocusRow}
+ */
+ function FocusRow(root, boundary) {
+ cr.ui.FocusRow.call(this, root, boundary);
+ this.addItems();
+ }
+
+ FocusRow.prototype = {
+ __proto__: cr.ui.FocusRow.prototype,
+
+ addItems: function() {
+ this.destroy();
+
+ this.addItem('name-file-link',
+ 'content.is-active:not(.show-progress):not(.dangerous) #name');
+ assert(this.addItem('name-file-link', '#file-link'));
+ assert(this.addItem('url', '#url'));
+ this.addItem('show-retry', '#show');
+ this.addItem('show-retry', '#retry');
+ this.addItem('pause-resume', '#pause');
+ this.addItem('pause-resume', '#resume');
+ this.addItem('cancel', '#cancel');
+ this.addItem('controlled-by', '#controlled-by a');
+ this.addItem('danger-remove-discard', '#discard');
+ this.addItem('restore-save', '#save');
+ this.addItem('danger-remove-discard', '#danger-remove');
+ this.addItem('restore-save', '#restore');
+ assert(this.addItem('remove', '#remove'));
+
+ // TODO(dbeam): it would be nice to do this asynchronously (so if multiple
+ // templates get rendered we only re-add once), but Manager#updateItem_()
+ // relies on the DOM being re-rendered synchronously.
+ this.eventTracker.add(this.root, 'dom-change', this.addItems.bind(this));
+ },
+ };
+
+ return {FocusRow: FocusRow};
+});
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Action links are elements that are used to perform an in-page navigation or
+// action (e.g. showing a dialog).
+//
+// They look like normal anchor (<a>) tags as their text color is blue. However,
+// they're subtly different as they're not initially underlined (giving users a
+// clue that underlined links navigate while action links don't).
+//
+// Action links look very similar to normal links when hovered (hand cursor,
+// underlined). This gives the user an idea that clicking this link will do
+// something similar to navigation but in the same page.
+//
+// They can be created in JavaScript like this:
+//
+// var link = document.createElement('a', 'action-link'); // Note second arg.
+//
+// or with a constructor like this:
+//
+// var link = new ActionLink();
+//
+// They can be used easily from HTML as well, like so:
+//
+// <a is="action-link">Click me!</a>
+//
+// NOTE: <action-link> and document.createElement('action-link') don't work.
+
+/**
+ * @constructor
+ * @extends {HTMLAnchorElement}
+ */
+var ActionLink = document.registerElement('action-link', {
+ prototype: {
+ __proto__: HTMLAnchorElement.prototype,
+
+ /** @this {ActionLink} */
+ createdCallback: function() {
+ // Action links can start disabled (e.g. <a is="action-link" disabled>).
+ this.tabIndex = this.disabled ? -1 : 0;
+
+ if (!this.hasAttribute('role'))
+ this.setAttribute('role', 'link');
+
+ this.addEventListener('keydown', function(e) {
+ if (!this.disabled && e.keyIdentifier == 'Enter') {
+ // Schedule a click asynchronously because other 'keydown' handlers
+ // may still run later (e.g. document.addEventListener('keydown')).
+ // Specifically options dialogs break when this timeout isn't here.
+ // NOTE: this affects the "trusted" state of the ensuing click. I
+ // haven't found anything that breaks because of this (yet).
+ window.setTimeout(this.click.bind(this), 0);
+ }
+ });
+
+ function preventDefault(e) {
+ e.preventDefault();
+ }
+
+ function removePreventDefault() {
+ document.removeEventListener('selectstart', preventDefault);
+ document.removeEventListener('mouseup', removePreventDefault);
+ }
+
+ this.addEventListener('mousedown', function() {
+ // This handlers strives to match the behavior of <a href="...">.
+
+ // While the mouse is down, prevent text selection from dragging.
+ document.addEventListener('selectstart', preventDefault);
+ document.addEventListener('mouseup', removePreventDefault);
+
+ // If focus started via mouse press, don't show an outline.
+ if (document.activeElement != this)
+ this.classList.add('no-outline');
+ });
+
+ this.addEventListener('blur', function() {
+ this.classList.remove('no-outline');
+ });
+ },
+
+ /** @type {boolean} */
+ set disabled(disabled) {
+ if (disabled)
+ HTMLAnchorElement.prototype.setAttribute.call(this, 'disabled', '');
+ else
+ HTMLAnchorElement.prototype.removeAttribute.call(this, 'disabled');
+ this.tabIndex = disabled ? -1 : 0;
+ },
+ get disabled() {
+ return this.hasAttribute('disabled');
+ },
+
+ /** @override */
+ setAttribute: function(attr, val) {
+ if (attr.toLowerCase() == 'disabled')
+ this.disabled = true;
+ else
+ HTMLAnchorElement.prototype.setAttribute.apply(this, arguments);
+ },
+
+ /** @override */
+ removeAttribute: function(attr) {
+ if (attr.toLowerCase() == 'disabled')
+ this.disabled = false;
+ else
+ HTMLAnchorElement.prototype.removeAttribute.apply(this, arguments);
+ },
+ },
+
+ extends: 'a',
+});
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/** @typedef {{img: HTMLImageElement, url: string}} */
+var LoadIconRequest;
+
+cr.define('downloads', function() {
+ /**
+ * @param {number} maxAllowed The maximum number of simultaneous downloads
+ * allowed.
+ * @constructor
+ */
+ function ThrottledIconLoader(maxAllowed) {
+ assert(maxAllowed > 0);
+
+ /** @private {number} */
+ this.maxAllowed_ = maxAllowed;
+
+ /** @private {!Array<!LoadIconRequest>} */
+ this.requests_ = [];
+ }
+
+ ThrottledIconLoader.prototype = {
+ /** @private {number} */
+ loading_: 0,
+
+ /**
+ * Load the provided |url| into |img.src| after appending ?scale=.
+ * @param {!HTMLImageElement} img An <img> to show the loaded image in.
+ * @param {string} url A remote image URL to load.
+ */
+ loadScaledIcon: function(img, url) {
+ var scaledUrl = url + '?scale=' + window.devicePixelRatio + 'x';
+ if (img.src == scaledUrl)
+ return;
+
+ this.requests_.push({img: img, url: scaledUrl});
+ this.loadNextIcon_();
+ },
+
+ /** @private */
+ loadNextIcon_: function() {
+ if (this.loading_ > this.maxAllowed_ || !this.requests_.length)
+ return;
+
+ var request = this.requests_.shift();
+ var img = request.img;
+
+ img.onabort = img.onerror = img.onload = function() {
+ this.loading_--;
+ this.loadNextIcon_();
+ }.bind(this);
+
+ this.loading_++;
+ img.src = request.url;
+ },
+ };
+
+ return {ThrottledIconLoader: ThrottledIconLoader};
+});
+// Copyright 2014 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+!function(a,b){b["true"]=a;var c={},d={},e={},f=null;!function(a){function b(a){if("number"==typeof a)return a;var b={};for(var c in a)b[c]=a[c];return b}function c(){this._delay=0,this._endDelay=0,this._fill="none",this._iterationStart=0,this._iterations=1,this._duration=0,this._playbackRate=1,this._direction="normal",this._easing="linear"}function d(b,d){var e=new c;return d&&(e.fill="both",e.duration="auto"),"number"!=typeof b||isNaN(b)?void 0!==b&&Object.getOwnPropertyNames(b).forEach(function(c){if("auto"!=b[c]){if(("number"==typeof e[c]||"duration"==c)&&("number"!=typeof b[c]||isNaN(b[c])))return;if("fill"==c&&-1==s.indexOf(b[c]))return;if("direction"==c&&-1==t.indexOf(b[c]))return;if("playbackRate"==c&&1!==b[c]&&a.isDeprecated("AnimationEffectTiming.playbackRate","2014-11-28","Use Animation.playbackRate instead."))return;e[c]=b[c]}}):e.duration=b,e}function e(a){return"number"==typeof a&&(a=isNaN(a)?{duration:0}:{duration:a}),a}function f(b,c){b=a.numericTimingToObject(b);var e=d(b,c);return e._easing=i(e.easing),e}function g(a,b,c,d){return 0>a||a>1||0>c||c>1?B:function(e){function f(a,b,c){return 3*a*(1-c)*(1-c)*c+3*b*(1-c)*c*c+c*c*c}if(0==e||1==e)return e;for(var g=0,h=1;;){var i=(g+h)/2,j=f(a,c,i);if(Math.abs(e-j)<.001)return f(b,d,i);e>j?g=i:h=i}}}function h(a,b){return function(c){if(c>=1)return 1;var d=1/a;return c+=b*d,c-c%d}}function i(a){var b=z.exec(a);if(b)return g.apply(this,b.slice(1).map(Number));var c=A.exec(a);if(c)return h(Number(c[1]),{start:u,middle:v,end:w}[c[2]]);var d=x[a];return d?d:B}function j(a){return Math.abs(k(a)/a.playbackRate)}function k(a){return a.duration*a.iterations}function l(a,b,c){return null==b?C:b<c.delay?D:b>=c.delay+a?E:F}function m(a,b,c,d,e){switch(d){case D:return"backwards"==b||"both"==b?0:null;case F:return c-e;case E:return"forwards"==b||"both"==b?a:null;case C:return null}}function n(a,b,c,d){return(d.playbackRate<0?b-a:b)*d.playbackRate+c}function o(a,b,c,d,e){return 1/0===c||c===-1/0||c-d==b&&e.iterations&&(e.iterations+e.iterationStart)%1==0?a:c%a}function p(a,b,c,d){return 0===c?0:b==a?d.iterationStart+d.iterations-1:Math.floor(c/a)}function q(a,b,c,d){var e=a%2>=1,f="normal"==d.direction||d.direction==(e?"alternate-reverse":"alternate"),g=f?c:b-c,h=g/b;return b*d.easing(h)}function r(a,b,c){var d=l(a,b,c),e=m(a,c.fill,b,d,c.delay);if(null===e)return null;if(0===a)return d===D?0:1;var f=c.iterationStart*c.duration,g=n(a,e,f,c),h=o(c.duration,k(c),g,f,c),i=p(c.duration,h,g,c);return q(i,c.duration,h,c)/c.duration}var s="backwards|forwards|both|none".split("|"),t="reverse|alternate|alternate-reverse".split("|");c.prototype={_setMember:function(b,c){this["_"+b]=c,this._effect&&(this._effect._timingInput[b]=c,this._effect._timing=a.normalizeTimingInput(a.normalizeTimingInput(this._effect._timingInput)),this._effect.activeDuration=a.calculateActiveDuration(this._effect._timing),this._effect._animation&&this._effect._animation._rebuildUnderlyingAnimation())},get playbackRate(){return this._playbackRate},set delay(a){this._setMember("delay",a)},get delay(){return this._delay},set endDelay(a){this._setMember("endDelay",a)},get endDelay(){return this._endDelay},set fill(a){this._setMember("fill",a)},get fill(){return this._fill},set iterationStart(a){this._setMember("iterationStart",a)},get iterationStart(){return this._iterationStart},set duration(a){this._setMember("duration",a)},get duration(){return this._duration},set direction(a){this._setMember("direction",a)},get direction(){return this._direction},set easing(a){this._setMember("easing",a)},get easing(){return this._easing},set iterations(a){this._setMember("iterations",a)},get iterations(){return this._iterations}};var u=1,v=.5,w=0,x={ease:g(.25,.1,.25,1),"ease-in":g(.42,0,1,1),"ease-out":g(0,0,.58,1),"ease-in-out":g(.42,0,.58,1),"step-start":h(1,u),"step-middle":h(1,v),"step-end":h(1,w)},y="\\s*(-?\\d+\\.?\\d*|-?\\.\\d+)\\s*",z=new RegExp("cubic-bezier\\("+y+","+y+","+y+","+y+"\\)"),A=/steps\(\s*(\d+)\s*,\s*(start|middle|end)\s*\)/,B=function(a){return a},C=0,D=1,E=2,F=3;a.cloneTimingInput=b,a.makeTiming=d,a.numericTimingToObject=e,a.normalizeTimingInput=f,a.calculateActiveDuration=j,a.calculateTimeFraction=r,a.calculatePhase=l,a.toTimingFunction=i}(c,f),function(a){function b(a,b){return a in h?h[a][b]||b:b}function c(a,c,d){var g=e[a];if(g){f.style[a]=c;for(var h in g){var i=g[h],j=f.style[i];d[i]=b(i,j)}}else d[a]=b(a,c)}function d(b){function d(){var a=e.length;null==e[a-1].offset&&(e[a-1].offset=1),a>1&&null==e[0].offset&&(e[0].offset=0);for(var b=0,c=e[0].offset,d=1;a>d;d++){var f=e[d].offset;if(null!=f){for(var g=1;d-b>g;g++)e[b+g].offset=c+(f-c)*g/(d-b);b=d,c=f}}}if(!Array.isArray(b)&&null!==b)throw new TypeError("Keyframes must be null or an array of keyframes");if(null==b)return[];for(var e=b.map(function(b){var d={};for(var e in b){var f=b[e];if("offset"==e){if(null!=f&&(f=Number(f),!isFinite(f)))throw new TypeError("keyframe offsets must be numbers.")}else{if("composite"==e)throw{type:DOMException.NOT_SUPPORTED_ERR,name:"NotSupportedError",message:"add compositing is not supported"};f="easing"==e?a.toTimingFunction(f):""+f}c(e,f,d)}return void 0==d.offset&&(d.offset=null),void 0==d.easing&&(d.easing=a.toTimingFunction("linear")),d}),f=!0,g=-1/0,h=0;h<e.length;h++){var i=e[h].offset;if(null!=i){if(g>i)throw{code:DOMException.INVALID_MODIFICATION_ERR,name:"InvalidModificationError",message:"Keyframes are not loosely sorted by offset. Sort or specify offsets."};g=i}else f=!1}return e=e.filter(function(a){return a.offset>=0&&a.offset<=1}),f||d(),e}var e={background:["backgroundImage","backgroundPosition","backgroundSize","backgroundRepeat","backgroundAttachment","backgroundOrigin","backgroundClip","backgroundColor"],border:["borderTopColor","borderTopStyle","borderTopWidth","borderRightColor","borderRightStyle","borderRightWidth","borderBottomColor","borderBottomStyle","borderBottomWidth","borderLeftColor","borderLeftStyle","borderLeftWidth"],borderBottom:["borderBottomWidth","borderBottomStyle","borderBottomColor"],borderColor:["borderTopColor","borderRightColor","borderBottomColor","borderLeftColor"],borderLeft:["borderLeftWidth","borderLeftStyle","borderLeftColor"],borderRadius:["borderTopLeftRadius","borderTopRightRadius","borderBottomRightRadius","borderBottomLeftRadius"],borderRight:["borderRightWidth","borderRightStyle","borderRightColor"],borderTop:["borderTopWidth","borderTopStyle","borderTopColor"],borderWidth:["borderTopWidth","borderRightWidth","borderBottomWidth","borderLeftWidth"],flex:["flexGrow","flexShrink","flexBasis"],font:["fontFamily","fontSize","fontStyle","fontVariant","fontWeight","lineHeight"],margin:["marginTop","marginRight","marginBottom","marginLeft"],outline:["outlineColor","outlineStyle","outlineWidth"],padding:["paddingTop","paddingRight","paddingBottom","paddingLeft"]},f=document.createElementNS("http://www.w3.org/1999/xhtml","div"),g={thin:"1px",medium:"3px",thick:"5px"},h={borderBottomWidth:g,borderLeftWidth:g,borderRightWidth:g,borderTopWidth:g,fontSize:{"xx-small":"60%","x-small":"75%",small:"89%",medium:"100%",large:"120%","x-large":"150%","xx-large":"200%"},fontWeight:{normal:"400",bold:"700"},outlineWidth:g,textShadow:{none:"0px 0px 0px transparent"},boxShadow:{none:"0px 0px 0px 0px transparent"}};a.normalizeKeyframes=d}(c,f),function(a){var b={};a.isDeprecated=function(a,c,d,e){var f=e?"are":"is",g=new Date,h=new Date(c);return h.setMonth(h.getMonth()+3),h>g?(a in b||console.warn("Web Animations: "+a+" "+f+" deprecated and will stop working on "+h.toDateString()+". "+d),b[a]=!0,!1):!0},a.deprecated=function(b,c,d,e){var f=e?"are":"is";if(a.isDeprecated(b,c,d,e))throw new Error(b+" "+f+" no longer supported. "+d)}}(c),function(){if(document.documentElement.animate){var a=document.documentElement.animate([],0),b=!0;if(a&&(b=!1,"play|currentTime|pause|reverse|playbackRate|cancel|finish|startTime|playState".split("|").forEach(function(c){void 0===a[c]&&(b=!0)})),!b)return}!function(a,b){function c(a){for(var b={},c=0;c<a.length;c++)for(var d in a[c])if("offset"!=d&&"easing"!=d&&"composite"!=d){var e={offset:a[c].offset,easing:a[c].easing,value:a[c][d]};b[d]=b[d]||[],b[d].push(e)}for(var f in b){var g=b[f];if(0!=g[0].offset||1!=g[g.length-1].offset)throw{type:DOMException.NOT_SUPPORTED_ERR,name:"NotSupportedError",message:"Partial keyframes are not supported"}}return b}function d(a){var c=[];for(var d in a)for(var e=a[d],f=0;f<e.length-1;f++){var g=e[f].offset,h=e[f+1].offset,i=e[f].value,j=e[f+1].value;g==h&&(1==h?i=j:j=i),c.push({startTime:g,endTime:h,easing:e[f].easing,property:d,interpolation:b.propertyInterpolation(d,i,j)})}return c.sort(function(a,b){return a.startTime-b.startTime}),c}b.convertEffectInput=function(e){var f=a.normalizeKeyframes(e),g=c(f),h=d(g);return function(a,c){if(null!=c)h.filter(function(a){return 0>=c&&0==a.startTime||c>=1&&1==a.endTime||c>=a.startTime&&c<=a.endTime}).forEach(function(d){var e=c-d.startTime,f=d.endTime-d.startTime,g=0==f?0:d.easing(e/f);b.apply(a,d.property,d.interpolation(g))});else for(var d in g)"offset"!=d&&"easing"!=d&&"composite"!=d&&b.clear(a,d)}}}(c,d,f),function(a){function b(a,b,c){e[c]=e[c]||[],e[c].push([a,b])}function c(a,c,d){for(var e=0;e<d.length;e++){var f=d[e];b(a,c,f),/-/.test(f)&&b(a,c,f.replace(/-(.)/g,function(a,b){return b.toUpperCase()}))}}function d(b,c,d){if("initial"==c||"initial"==d){var g=b.replace(/-(.)/g,function(a,b){return b.toUpperCase()});"initial"==c&&(c=f[g]),"initial"==d&&(d=f[g])}for(var h=c==d?[]:e[b],i=0;h&&i<h.length;i++){var j=h[i][0](c),k=h[i][0](d);if(void 0!==j&&void 0!==k){var l=h[i][1](j,k);if(l){var m=a.Interpolation.apply(null,l);return function(a){return 0==a?c:1==a?d:m(a)}}}}return a.Interpolation(!1,!0,function(a){return a?d:c})}var e={};a.addPropertiesHandler=c;var f={backgroundColor:"transparent",backgroundPosition:"0% 0%",borderBottomColor:"currentColor",borderBottomLeftRadius:"0px",borderBottomRightRadius:"0px",borderBottomWidth:"3px",borderLeftColor:"currentColor",borderLeftWidth:"3px",borderRightColor:"currentColor",borderRightWidth:"3px",borderSpacing:"2px",borderTopColor:"currentColor",borderTopLeftRadius:"0px",borderTopRightRadius:"0px",borderTopWidth:"3px",bottom:"auto",clip:"rect(0px, 0px, 0px, 0px)",color:"black",fontSize:"100%",fontWeight:"400",height:"auto",left:"auto",letterSpacing:"normal",lineHeight:"120%",marginBottom:"0px",marginLeft:"0px",marginRight:"0px",marginTop:"0px",maxHeight:"none",maxWidth:"none",minHeight:"0px",minWidth:"0px",opacity:"1.0",outlineColor:"invert",outlineOffset:"0px",outlineWidth:"3px",paddingBottom:"0px",paddingLeft:"0px",paddingRight:"0px",paddingTop:"0px",right:"auto",textIndent:"0px",textShadow:"0px 0px 0px transparent",top:"auto",transform:"",verticalAlign:"0px",visibility:"visible",width:"auto",wordSpacing:"normal",zIndex:"auto"};a.propertyInterpolation=d}(d,f),function(a,b){function c(b){var c=a.calculateActiveDuration(b),d=function(d){return a.calculateTimeFraction(c,d,b)};return d._totalDuration=b.delay+c+b.endDelay,d._isCurrent=function(d){var e=a.calculatePhase(c,d,b);return e===PhaseActive||e===PhaseBefore},d}b.KeyframeEffect=function(d,e,f){var g,h=c(a.normalizeTimingInput(f)),i=b.convertEffectInput(e),j=function(){i(d,g)};return j._update=function(a){return g=h(a),null!==g},j._clear=function(){i(d,null)},j._hasSameTarget=function(a){return d===a},j._isCurrent=h._isCurrent,j._totalDuration=h._totalDuration,j},b.NullEffect=function(a){var b=function(){a&&(a(),a=null)};return b._update=function(){return null},b._totalDuration=0,b._isCurrent=function(){return!1},b._hasSameTarget=function(){return!1},b}}(c,d,f),function(a){a.apply=function(b,c,d){b.style[a.propertyName(c)]=d},a.clear=function(b,c){b.style[a.propertyName(c)]=""}}(d,f),function(a){window.Element.prototype.animate=function(b,c){return a.timeline._play(a.KeyframeEffect(this,b,c))}}(d),function(a){function b(a,c,d){if("number"==typeof a&&"number"==typeof c)return a*(1-d)+c*d;if("boolean"==typeof a&&"boolean"==typeof c)return.5>d?a:c;if(a.length==c.length){for(var e=[],f=0;f<a.length;f++)e.push(b(a[f],c[f],d));return e}throw"Mismatched interpolation arguments "+a+":"+c}a.Interpolation=function(a,c,d){return function(e){return d(b(a,c,e))}}}(d,f),function(a,b){a.sequenceNumber=0;var c=function(a,b,c){this.target=a,this.currentTime=b,this.timelineTime=c,this.type="finish",this.bubbles=!1,this.cancelable=!1,this.currentTarget=a,this.defaultPrevented=!1,this.eventPhase=Event.AT_TARGET,this.timeStamp=Date.now()};b.Animation=function(b){this._sequenceNumber=a.sequenceNumber++,this._currentTime=0,this._startTime=null,this._paused=!1,this._playbackRate=1,this._inTimeline=!0,this._finishedFlag=!1,this.onfinish=null,this._finishHandlers=[],this._effect=b,this._inEffect=this._effect._update(0),this._idle=!0,this._currentTimePending=!1},b.Animation.prototype={_ensureAlive:function(){this._inEffect=this._effect._update(this.playbackRate<0&&0===this.currentTime?-1:this.currentTime),this._inTimeline||!this._inEffect&&this._finishedFlag||(this._inTimeline=!0,b.timeline._animations.push(this))},_tickCurrentTime:function(a,b){a!=this._currentTime&&(this._currentTime=a,this._isFinished&&!b&&(this._currentTime=this._playbackRate>0?this._totalDuration:0),this._ensureAlive())},get currentTime(){return this._idle||this._currentTimePending?null:this._currentTime},set currentTime(a){a=+a,isNaN(a)||(b.restart(),this._paused||null==this._startTime||(this._startTime=this._timeline.currentTime-a/this._playbackRate),this._currentTimePending=!1,this._currentTime!=a&&(this._tickCurrentTime(a,!0),b.invalidateEffects()))},get startTime(){return this._startTime},set startTime(a){a=+a,isNaN(a)||this._paused||this._idle||(this._startTime=a,this._tickCurrentTime((this._timeline.currentTime-this._startTime)*this.playbackRate),b.invalidateEffects())},get playbackRate(){return this._playbackRate},set playbackRate(a){if(a!=this._playbackRate){var b=this.currentTime;this._playbackRate=a,this._startTime=null,"paused"!=this.playState&&"idle"!=this.playState&&this.play(),null!=b&&(this.currentTime=b)}},get _isFinished(){return!this._idle&&(this._playbackRate>0&&this._currentTime>=this._totalDuration||this._playbackRate<0&&this._currentTime<=0)},get _totalDuration(){return this._effect._totalDuration},get playState(){return this._idle?"idle":null==this._startTime&&!this._paused&&0!=this.playbackRate||this._currentTimePending?"pending":this._paused?"paused":this._isFinished?"finished":"running"},play:function(){this._paused=!1,(this._isFinished||this._idle)&&(this._currentTime=this._playbackRate>0?0:this._totalDuration,this._startTime=null,b.invalidateEffects()),this._finishedFlag=!1,b.restart(),this._idle=!1,this._ensureAlive()},pause:function(){this._isFinished||this._paused||this._idle||(this._currentTimePending=!0),this._startTime=null,this._paused=!0},finish:function(){this._idle||(this.currentTime=this._playbackRate>0?this._totalDuration:0,this._startTime=this._totalDuration-this.currentTime,this._currentTimePending=!1)},cancel:function(){this._inEffect&&(this._inEffect=!1,this._idle=!0,this.currentTime=0,this._startTime=null,this._effect._update(null),b.invalidateEffects(),b.restart())},reverse:function(){this.playbackRate*=-1,this.play()},addEventListener:function(a,b){"function"==typeof b&&"finish"==a&&this._finishHandlers.push(b)},removeEventListener:function(a,b){if("finish"==a){var c=this._finishHandlers.indexOf(b);c>=0&&this._finishHandlers.splice(c,1)}},_fireEvents:function(a){var b=this._isFinished;if((b||this._idle)&&!this._finishedFlag){var d=new c(this,this._currentTime,a),e=this._finishHandlers.concat(this.onfinish?[this.onfinish]:[]);setTimeout(function(){e.forEach(function(a){a.call(d.target,d)})},0)}this._finishedFlag=b},_tick:function(a){return this._idle||this._paused||(null==this._startTime?this.startTime=a-this._currentTime/this.playbackRate:this._isFinished||this._tickCurrentTime((a-this._startTime)*this.playbackRate)),this._currentTimePending=!1,this._fireEvents(a),!this._idle&&(this._inEffect||!this._finishedFlag)}}}(c,d,f),function(a,b){function c(a){var b=i;i=[],a<s.currentTime&&(a=s.currentTime),g(a),b.forEach(function(b){b[1](a)}),o&&g(a),f(),l=void 0}function d(a,b){return a._sequenceNumber-b._sequenceNumber}function e(){this._animations=[],this.currentTime=window.performance&&performance.now?performance.now():0}function f(){p.forEach(function(a){a()}),p.length=0}function g(a){n=!1;var c=b.timeline;c.currentTime=a,c._animations.sort(d),m=!1;var e=c._animations;c._animations=[];var f=[],g=[];e=e.filter(function(b){return b._inTimeline=b._tick(a),b._inEffect?g.push(b._effect):f.push(b._effect),b._isFinished||b._paused||b._idle||(m=!0),b._inTimeline}),p.push.apply(p,f),p.push.apply(p,g),c._animations.push.apply(c._animations,e),o=!1,m&&requestAnimationFrame(function(){})}var h=window.requestAnimationFrame,i=[],j=0;window.requestAnimationFrame=function(a){var b=j++;return 0==i.length&&h(c),i.push([b,a]),b},window.cancelAnimationFrame=function(a){i.forEach(function(b){b[0]==a&&(b[1]=function(){})})},e.prototype={_play:function(c){c._timing=a.normalizeTimingInput(c.timing);var d=new b.Animation(c);return d._idle=!1,d._timeline=this,this._animations.push(d),b.restart(),b.invalidateEffects(),d}};var k,l=void 0,k=function(){return void 0==l&&(l=performance.now()),l},m=!1,n=!1;b.restart=function(){return m||(m=!0,requestAnimationFrame(function(){}),n=!0),n};var o=!1;b.invalidateEffects=function(){o=!0};var p=[],q=1e3/60,r=window.getComputedStyle;Object.defineProperty(window,"getComputedStyle",{configurable:!0,enumerable:!0,value:function(){if(o){var a=k();a-s.currentTime>0&&(s.currentTime+=q*(Math.floor((a-s.currentTime)/q)+1)),g(s.currentTime)}return f(),r.apply(this,arguments)}});var s=new e;b.timeline=s}(c,d,f),function(a){function b(a,b){var c=a.exec(b);return c?(c=a.ignoreCase?c[0].toLowerCase():c[0],[c,b.substr(c.length)]):void 0}function c(a,b){b=b.replace(/^\s*/,"");var c=a(b);return c?[c[0],c[1].replace(/^\s*/,"")]:void 0}function d(a,d,e){a=c.bind(null,a);for(var f=[];;){var g=a(e);if(!g)return[f,e];if(f.push(g[0]),e=g[1],g=b(d,e),!g||""==g[1])return[f,e];e=g[1]}}function e(a,b){for(var c=0,d=0;d<b.length&&(!/\s|,/.test(b[d])||0!=c);d++)if("("==b[d])c++;else if(")"==b[d]&&(c--,0==c&&d++,0>=c))break;var e=a(b.substr(0,d));return void 0==e?void 0:[e,b.substr(d)]}function f(a,b){for(var c=a,d=b;c&&d;)c>d?c%=d:d%=c;return c=a*b/(c+d)}function g(a){return function(b){var c=a(b);return c&&(c[0]=void 0),c}}function h(a,b){return function(c){var d=a(c);return d?d:[b,c]}}function i(b,c){for(var d=[],e=0;e<b.length;e++){var f=a.consumeTrimmed(b[e],c);if(!f||""==f[0])return;void 0!==f[0]&&d.push(f[0]),c=f[1]}return""==c?d:void 0}function j(a,b,c,d,e){for(var g=[],h=[],i=[],j=f(d.length,e.length),k=0;j>k;k++){var l=b(d[k%d.length],e[k%e.length]);if(!l)return;g.push(l[0]),h.push(l[1]),i.push(l[2])}return[g,h,function(b){var d=b.map(function(a,b){return i[b](a)}).join(c);return a?a(d):d}]}function k(a,b,c){for(var d=[],e=[],f=[],g=0,h=0;h<c.length;h++)if("function"==typeof c[h]){var i=c[h](a[g],b[g++]);d.push(i[0]),e.push(i[1]),f.push(i[2])}else!function(a){d.push(!1),e.push(!1),f.push(function(){return c[a]})}(h);return[d,e,function(a){for(var b="",c=0;c<a.length;c++)b+=f[c](a[c]);return b}]}a.consumeToken=b,a.consumeTrimmed=c,a.consumeRepeated=d,a.consumeParenthesised=e,a.ignore=g,a.optional=h,a.consumeList=i,a.mergeNestedRepeated=j.bind(null,null),a.mergeWrappedNestedRepeated=j,a.mergeList=k}(d),function(a){function b(b){function c(b){var c=a.consumeToken(/^inset/i,b);if(c)return d.inset=!0,c;var c=a.consumeLengthOrPercent(b);if(c)return d.lengths.push(c[0]),c;var c=a.consumeColor(b);return c?(d.color=c[0],c):void 0}var d={inset:!1,lengths:[],color:null},e=a.consumeRepeated(c,/^/,b);return e&&e[0].length?[d,e[1]]:void 0}function c(c){var d=a.consumeRepeated(b,/^,/,c);return d&&""==d[1]?d[0]:void 0}function d(b,c){for(;b.lengths.length<Math.max(b.lengths.length,c.lengths.length);)b.lengths.push({px:0});for(;c.lengths.length<Math.max(b.lengths.length,c.lengths.length);)c.lengths.push({px:0});if(b.inset==c.inset&&!!b.color==!!c.color){for(var d,e=[],f=[[],0],g=[[],0],h=0;h<b.lengths.length;h++){var i=a.mergeDimensions(b.lengths[h],c.lengths[h],2==h);f[0].push(i[0]),g[0].push(i[1]),e.push(i[2])}if(b.color&&c.color){var j=a.mergeColors(b.color,c.color);f[1]=j[0],g[1]=j[1],d=j[2]}return[f,g,function(a){for(var c=b.inset?"inset ":" ",f=0;f<e.length;f++)c+=e[f](a[0][f])+" ";return d&&(c+=d(a[1])),c}]}}function e(b,c,d,e){function f(a){return{inset:a,color:[0,0,0,0],lengths:[{px:0},{px:0},{px:0},{px:0}]}}for(var g=[],h=[],i=0;i<d.length||i<e.length;i++){var j=d[i]||f(e[i].inset),k=e[i]||f(d[i].inset);g.push(j),h.push(k)}return a.mergeNestedRepeated(b,c,g,h)}var f=e.bind(null,d,", ");a.addPropertiesHandler(c,f,["box-shadow","text-shadow"])}(d),function(a){function b(a){return a.toFixed(3).replace(".000","")}function c(a,b,c){return Math.min(b,Math.max(a,c))}function d(a){return/^\s*[-+]?(\d*\.)?\d+\s*$/.test(a)?Number(a):void 0}function e(a,c){return[a,c,b]}function f(a,b){return 0!=a?h(0,1/0)(a,b):void 0}function g(a,b){return[a,b,function(a){return Math.round(c(1,1/0,a))}]}function h(a,d){return function(e,f){return[e,f,function(e){return b(c(a,d,e))}]}}function i(a,b){return[a,b,Math.round]}a.clamp=c,a.addPropertiesHandler(d,h(0,1/0),["border-image-width","line-height"]),a.addPropertiesHandler(d,h(0,1),["opacity","shape-image-threshold"]),a.addPropertiesHandler(d,f,["flex-grow","flex-shrink"]),a.addPropertiesHandler(d,g,["orphans","widows"]),a.addPropertiesHandler(d,i,["z-index"]),a.parseNumber=d,a.mergeNumbers=e,a.numberToString=b}(d,f),function(a){function b(a,b){return"visible"==a||"visible"==b?[0,1,function(c){return 0>=c?a:c>=1?b:"visible"}]:void 0}a.addPropertiesHandler(String,b,["visibility"])}(d),function(a){function b(a){a=a.trim(),e.fillStyle="#000",e.fillStyle=a;var b=e.fillStyle;if(e.fillStyle="#fff",e.fillStyle=a,b==e.fillStyle){e.fillRect(0,0,1,1);var c=e.getImageData(0,0,1,1).data;e.clearRect(0,0,1,1);var d=c[3]/255;return[c[0]*d,c[1]*d,c[2]*d,d]}}function c(b,c){return[b,c,function(b){function c(a){return Math.max(0,Math.min(255,a))}if(b[3])for(var d=0;3>d;d++)b[d]=Math.round(c(b[d]/b[3]));return b[3]=a.numberToString(a.clamp(0,1,b[3])),"rgba("+b.join(",")+")"}]}var d=document.createElementNS("http://www.w3.org/1999/xhtml","canvas");d.width=d.height=1;var e=d.getContext("2d");a.addPropertiesHandler(b,c,["background-color","border-bottom-color","border-left-color","border-right-color","border-top-color","color","outline-color","text-decoration-color"]),a.consumeColor=a.consumeParenthesised.bind(null,b),a.mergeColors=c}(d,f),function(a,b){function c(a,b){if(b=b.trim().toLowerCase(),"0"==b&&"px".search(a)>=0)return{px:0};if(/^[^(]*$|^calc/.test(b)){b=b.replace(/calc\(/g,"(");var c={};b=b.replace(a,function(a){return c[a]=null,"U"+a});for(var d="U("+a.source+")",e=b.replace(/[-+]?(\d*\.)?\d+/g,"N").replace(new RegExp("N"+d,"g"),"D").replace(/\s[+-]\s/g,"O").replace(/\s/g,""),f=[/N\*(D)/g,/(N|D)[*/]N/g,/(N|D)O\1/g,/\((N|D)\)/g],g=0;g<f.length;)f[g].test(e)?(e=e.replace(f[g],"$1"),g=0):g++;if("D"==e){for(var h in c){var i=eval(b.replace(new RegExp("U"+h,"g"),"").replace(new RegExp(d,"g"),"*0"));if(!isFinite(i))return;c[h]=i}return c}}}function d(a,b){return e(a,b,!0)}function e(b,c,d){var e,f=[];for(e in b)f.push(e);for(e in c)f.indexOf(e)<0&&f.push(e);return b=f.map(function(a){return b[a]||0}),c=f.map(function(a){return c[a]||0}),[b,c,function(b){var c=b.map(function(c,e){return 1==b.length&&d&&(c=Math.max(c,0)),a.numberToString(c)+f[e]}).join(" + ");return b.length>1?"calc("+c+")":c}]}var f="px|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc",g=c.bind(null,new RegExp(f,"g")),h=c.bind(null,new RegExp(f+"|%","g")),i=c.bind(null,/deg|rad|grad|turn/g);a.parseLength=g,a.parseLengthOrPercent=h,a.consumeLengthOrPercent=a.consumeParenthesised.bind(null,h),a.parseAngle=i,a.mergeDimensions=e;var j=a.consumeParenthesised.bind(null,g),k=a.consumeRepeated.bind(void 0,j,/^/),l=a.consumeRepeated.bind(void 0,k,/^,/);a.consumeSizePairList=l;var m=function(a){var b=l(a);return b&&""==b[1]?b[0]:void 0},n=a.mergeNestedRepeated.bind(void 0,d," "),o=a.mergeNestedRepeated.bind(void 0,n,",");a.mergeNonNegativeSizePair=n,a.addPropertiesHandler(m,o,["background-size"]),a.addPropertiesHandler(h,d,["border-bottom-width","border-image-width","border-left-width","border-right-width","border-top-width","flex-basis","font-size","height","line-height","max-height","max-width","outline-width","width"]),a.addPropertiesHandler(h,e,["border-bottom-left-radius","border-bottom-right-radius","border-top-left-radius","border-top-right-radius","bottom","left","letter-spacing","margin-bottom","margin-left","margin-right","margin-top","min-height","min-width","outline-offset","padding-bottom","padding-left","padding-right","padding-top","perspective","right","shape-margin","text-indent","top","vertical-align","word-spacing"])}(d,f),function(a){function b(b){return a.consumeLengthOrPercent(b)||a.consumeToken(/^auto/,b)}function c(c){var d=a.consumeList([a.ignore(a.consumeToken.bind(null,/^rect/)),a.ignore(a.consumeToken.bind(null,/^\(/)),a.consumeRepeated.bind(null,b,/^,/),a.ignore(a.consumeToken.bind(null,/^\)/))],c);return d&&4==d[0].length?d[0]:void 0}function d(b,c){return"auto"==b||"auto"==c?[!0,!1,function(d){var e=d?b:c;if("auto"==e)return"auto";var f=a.mergeDimensions(e,e);return f[2](f[0])}]:a.mergeDimensions(b,c)}function e(a){return"rect("+a+")"}var f=a.mergeWrappedNestedRepeated.bind(null,e,d,", ");a.parseBox=c,a.mergeBoxes=f,a.addPropertiesHandler(c,f,["clip"])}(d,f),function(a){function b(a){return function(b){var c=0;return a.map(function(a){return a===j?b[c++]:a})}}function c(a){return a}function d(b){if(b=b.toLowerCase().trim(),"none"==b)return[];for(var c,d=/\s*(\w+)\(([^)]*)\)/g,e=[],f=0;c=d.exec(b);){if(c.index!=f)return;f=c.index+c[0].length;var g=c[1],h=m[g];if(!h)return;var i=c[2].split(","),j=h[0];if(j.length<i.length)return;for(var n=[],o=0;o<j.length;o++){var p,q=i[o],r=j[o];if(p=q?{A:function(b){return"0"==b.trim()?l:a.parseAngle(b)},N:a.parseNumber,T:a.parseLengthOrPercent,L:a.parseLength}[r.toUpperCase()](q):{a:l,n:n[0],t:k}[r],void 0===p)return;n.push(p)}if(e.push({t:g,d:n}),d.lastIndex==b.length)return e}}function e(a){return a.toFixed(6).replace(".000000","")}function f(b,c){if(b.decompositionPair!==c){b.decompositionPair=c;var d=a.makeMatrixDecomposition(b)}if(c.decompositionPair!==b){c.decompositionPair=b;var f=a.makeMatrixDecomposition(c)}return null==d[0]||null==f[0]?[[!1],[!0],function(a){return a?c[0].d:b[0].d}]:(d[0].push(0),f[0].push(1),[d,f,function(b){var c=a.quat(d[0][3],f[0][3],b[5]),g=a.composeMatrix(b[0],b[1],b[2],c,b[4]),h=g.map(e).join(",");return h}])}function g(a){return a.replace(/[xy]/,"")}function h(a){return a.replace(/(x|y|z|3d)?$/,"3d")}function i(b,c){var d=a.makeMatrixDecomposition&&!0,e=!1;if(!b.length||!c.length){b.length||(e=!0,b=c,c=[]);for(var i=0;i<b.length;i++){var j=b[i].t,k=b[i].d,l="scale"==j.substr(0,5)?1:0;c.push({t:j,d:k.map(function(a){if("number"==typeof a)return l;var b={};for(var c in a)b[c]=l;return b})})}}var n=function(a,b){return"perspective"==a&&"perspective"==b||("matrix"==a||"matrix3d"==a)&&("matrix"==b||"matrix3d"==b)},o=[],p=[],q=[];if(b.length!=c.length){if(!d)return;var r=f(b,c);o=[r[0]],p=[r[1]],q=[["matrix",[r[2]]]]}else for(var i=0;i<b.length;i++){var j,s=b[i].t,t=c[i].t,u=b[i].d,v=c[i].d,w=m[s],x=m[t];if(n(s,t)){if(!d)return;var r=f([b[i]],[c[i]]);o.push(r[0]),p.push(r[1]),q.push(["matrix",[r[2]]])}else{if(s==t)j=s;else if(w[2]&&x[2]&&g(s)==g(t))j=g(s),u=w[2](u),v=x[2](v);else{if(!w[1]||!x[1]||h(s)!=h(t)){if(!d)return;var r=f(b,c);o=[r[0]],p=[r[1]],q=[["matrix",[r[2]]]];break}j=h(s),u=w[1](u),v=x[1](v)}for(var y=[],z=[],A=[],B=0;B<u.length;B++){var C="number"==typeof u[B]?a.mergeNumbers:a.mergeDimensions,r=C(u[B],v[B]);y[B]=r[0],z[B]=r[1],A.push(r[2])}o.push(y),p.push(z),q.push([j,A])}}if(e){var D=o;o=p,p=D}return[o,p,function(a){return a.map(function(a,b){var c=a.map(function(a,c){return q[b][1][c](a)}).join(",");return"matrix"==q[b][0]&&16==c.split(",").length&&(q[b][0]="matrix3d"),q[b][0]+"("+c+")"}).join(" ")}]}var j=null,k={px:0},l={deg:0},m={matrix:["NNNNNN",[j,j,0,0,j,j,0,0,0,0,1,0,j,j,0,1],c],matrix3d:["NNNNNNNNNNNNNNNN",c],rotate:["A"],rotatex:["A"],rotatey:["A"],rotatez:["A"],rotate3d:["NNNA"],perspective:["L"],scale:["Nn",b([j,j,1]),c],scalex:["N",b([j,1,1]),b([j,1])],scaley:["N",b([1,j,1]),b([1,j])],scalez:["N",b([1,1,j])],scale3d:["NNN",c],skew:["Aa",null,c],skewx:["A",null,b([j,l])],skewy:["A",null,b([l,j])],translate:["Tt",b([j,j,k]),c],translatex:["T",b([j,k,k]),b([j,k])],translatey:["T",b([k,j,k]),b([k,j])],translatez:["L",b([k,k,j])],translate3d:["TTL",c]};a.addPropertiesHandler(d,i,["transform"])}(d,f),function(a){function b(a,b){b.concat([a]).forEach(function(b){b in document.documentElement.style&&(c[a]=b)})}var c={};b("transform",["webkitTransform","msTransform"]),b("transformOrigin",["webkitTransformOrigin"]),b("perspective",["webkitPerspective"]),b("perspectiveOrigin",["webkitPerspectiveOrigin"]),a.propertyName=function(a){return c[a]||a}}(d,f)}(),!function(a,b){function c(a){var b=window.document.timeline;b.currentTime=a,b._discardAnimations(),0==b._animations.length?e=!1:requestAnimationFrame(c)}var d=window.requestAnimationFrame;window.requestAnimationFrame=function(a){return d(function(b){window.document.timeline._updateAnimationsPromises(),a(b),window.document.timeline._updateAnimationsPromises()})},b.AnimationTimeline=function(){this._animations=[],this.currentTime=void 0},b.AnimationTimeline.prototype={getAnimations:function(){return this._discardAnimations(),this._animations.slice()},_updateAnimationsPromises:function(){b.animationsWithPromises=b.animationsWithPromises.filter(function(a){return a._updatePromises()})},_discardAnimations:function(){this._updateAnimationsPromises(),this._animations=this._animations.filter(function(a){return"finished"!=a.playState&&"idle"!=a.playState})},_play:function(a){var c=new b.Animation(a,this);return this._animations.push(c),b.restartWebAnimationsNextTick(),c._updatePromises(),c._animation.play(),c._updatePromises(),c},play:function(a){return a&&a.remove(),this._play(a)}};var e=!1;b.restartWebAnimationsNextTick=function(){e||(e=!0,requestAnimationFrame(c))};var f=new b.AnimationTimeline;b.timeline=f;try{Object.defineProperty(window.document,"timeline",{configurable:!0,get:function(){return f}})}catch(g){}try{window.document.timeline=f}catch(g){}}(c,e,f),function(a,b){b.animationsWithPromises=[],b.Animation=function(b,c){if(this.effect=b,b&&(b._animation=this),!c)throw new Error("Animation with null timeline is not supported");this._timeline=c,this._sequenceNumber=a.sequenceNumber++,this._holdTime=0,this._paused=!1,this._isGroup=!1,this._animation=null,this._childAnimations=[],this._callback=null,this._oldPlayState="idle",this._rebuildUnderlyingAnimation(),this._animation.cancel(),this._updatePromises()},b.Animation.prototype={_updatePromises:function(){var a=this._oldPlayState,b=this.playState;return this._readyPromise&&b!==a&&("idle"==b?(this._rejectReadyPromise(),this._readyPromise=void 0):"pending"==a?this._resolveReadyPromise():"pending"==b&&(this._readyPromise=void 0)),this._finishedPromise&&b!==a&&("idle"==b?(this._rejectFinishedPromise(),this._finishedPromise=void 0):"finished"==b?this._resolveFinishedPromise():"finished"==a&&(this._finishedPromise=void 0)),this._oldPlayState=this.playState,this._readyPromise||this._finishedPromise},_rebuildUnderlyingAnimation:function(){this._updatePromises();var a,c,d,e,f=this._animation?!0:!1;f&&(a=this.playbackRate,c=this._paused,d=this.startTime,e=this.currentTime,this._animation.cancel(),this._animation._wrapper=null,this._animation=null),(!this.effect||this.effect instanceof window.KeyframeEffect)&&(this._animation=b.newUnderlyingAnimationForKeyframeEffect(this.effect),b.bindAnimationForKeyframeEffect(this)),(this.effect instanceof window.SequenceEffect||this.effect instanceof window.GroupEffect)&&(this._animation=b.newUnderlyingAnimationForGroup(this.effect),b.bindAnimationForGroup(this)),this.effect&&this.effect._onsample&&b.bindAnimationForCustomEffect(this),f&&(1!=a&&(this.playbackRate=a),null!==d?this.startTime=d:null!==e?this.currentTime=e:null!==this._holdTime&&(this.currentTime=this._holdTime),c&&this.pause()),this._updatePromises()
+},_updateChildren:function(){if(this.effect&&"idle"!=this.playState){var a=this.effect._timing.delay;this._childAnimations.forEach(function(c){this._arrangeChildren(c,a),this.effect instanceof window.SequenceEffect&&(a+=b.groupChildDuration(c.effect))}.bind(this))}},_setExternalAnimation:function(a){if(this.effect&&this._isGroup)for(var b=0;b<this.effect.children.length;b++)this.effect.children[b]._animation=a,this._childAnimations[b]._setExternalAnimation(a)},_constructChildAnimations:function(){if(this.effect&&this._isGroup){var a=this.effect._timing.delay;this._removeChildAnimations(),this.effect.children.forEach(function(c){var d=window.document.timeline._play(c);this._childAnimations.push(d),d.playbackRate=this.playbackRate,this._paused&&d.pause(),c._animation=this.effect._animation,this._arrangeChildren(d,a),this.effect instanceof window.SequenceEffect&&(a+=b.groupChildDuration(c))}.bind(this))}},_arrangeChildren:function(a,b){null===this.startTime?a.currentTime=this.currentTime-b/this.playbackRate:a.startTime!==this.startTime+b/this.playbackRate&&(a.startTime=this.startTime+b/this.playbackRate)},get timeline(){return this._timeline},get playState(){return this._animation?this._animation.playState:"idle"},get finished(){return window.Promise?(this._finishedPromise||(-1==b.animationsWithPromises.indexOf(this)&&b.animationsWithPromises.push(this),this._finishedPromise=new Promise(function(a,b){this._resolveFinishedPromise=function(){a(this)},this._rejectFinishedPromise=function(){b({type:DOMException.ABORT_ERR,name:"AbortError"})}}.bind(this)),"finished"==this.playState&&this._resolveFinishedPromise()),this._finishedPromise):(console.warn("Animation Promises require JavaScript Promise constructor"),null)},get ready(){return window.Promise?(this._readyPromise||(-1==b.animationsWithPromises.indexOf(this)&&b.animationsWithPromises.push(this),this._readyPromise=new Promise(function(a,b){this._resolveReadyPromise=function(){a(this)},this._rejectReadyPromise=function(){b({type:DOMException.ABORT_ERR,name:"AbortError"})}}.bind(this)),"pending"!==this.playState&&this._resolveReadyPromise()),this._readyPromise):(console.warn("Animation Promises require JavaScript Promise constructor"),null)},get onfinish(){return this._onfinish},set onfinish(a){"function"==typeof a?(this._onfinish=a,this._animation.onfinish=function(b){b.target=this,a.call(this,b)}.bind(this)):(this._animation.onfinish=a,this.onfinish=this._animation.onfinish)},get currentTime(){this._updatePromises();var a=this._animation.currentTime;return this._updatePromises(),a},set currentTime(a){this._updatePromises(),this._animation.currentTime=isFinite(a)?a:Math.sign(a)*Number.MAX_VALUE,this._register(),this._forEachChild(function(b,c){b.currentTime=a-c}),this._updatePromises()},get startTime(){return this._animation.startTime},set startTime(a){this._updatePromises(),this._animation.startTime=isFinite(a)?a:Math.sign(a)*Number.MAX_VALUE,this._register(),this._forEachChild(function(b,c){b.startTime=a+c}),this._updatePromises()},get playbackRate(){return this._animation.playbackRate},set playbackRate(a){this._updatePromises();var b=this.currentTime;this._animation.playbackRate=a,this._forEachChild(function(b){b.playbackRate=a}),"paused"!=this.playState&&"idle"!=this.playState&&this.play(),null!==b&&(this.currentTime=b),this._updatePromises()},play:function(){this._updatePromises(),this._paused=!1,this._animation.play(),-1==this._timeline._animations.indexOf(this)&&this._timeline._animations.push(this),this._register(),b.awaitStartTime(this),this._forEachChild(function(a){var b=a.currentTime;a.play(),a.currentTime=b}),this._updatePromises()},pause:function(){this._updatePromises(),this.currentTime&&(this._holdTime=this.currentTime),this._animation.pause(),this._register(),this._forEachChild(function(a){a.pause()}),this._paused=!0,this._updatePromises()},finish:function(){this._updatePromises(),this._animation.finish(),this._register(),this._updatePromises()},cancel:function(){this._updatePromises(),this._animation.cancel(),this._register(),this._removeChildAnimations(),this._updatePromises()},reverse:function(){this._updatePromises();var a=this.currentTime;this._animation.reverse(),this._forEachChild(function(a){a.reverse()}),null!==a&&(this.currentTime=a),this._updatePromises()},addEventListener:function(a,b){var c=b;"function"==typeof b&&(c=function(a){a.target=this,b.call(this,a)}.bind(this),b._wrapper=c),this._animation.addEventListener(a,c)},removeEventListener:function(a,b){this._animation.removeEventListener(a,b&&b._wrapper||b)},_removeChildAnimations:function(){for(;this._childAnimations.length;)this._childAnimations.pop().cancel()},_forEachChild:function(b){var c=0;if(this.effect.children&&this._childAnimations.length<this.effect.children.length&&this._constructChildAnimations(),this._childAnimations.forEach(function(a){b.call(this,a,c),this.effect instanceof window.SequenceEffect&&(c+=a.effect.activeDuration)}.bind(this)),"pending"!=this.playState){var d=this.effect._timing,e=this.currentTime;null!==e&&(e=a.calculateTimeFraction(a.calculateActiveDuration(d),e,d)),(null==e||isNaN(e))&&this._removeChildAnimations()}}},window.Animation=b.Animation}(c,e,f),function(a,b){function c(b){this._frames=a.normalizeKeyframes(b)}function d(){for(var a=!1;h.length;){var b=h.shift();b._updateChildren(),a=!0}return a}var e=function(a){if(a._animation=void 0,a instanceof window.SequenceEffect||a instanceof window.GroupEffect)for(var b=0;b<a.children.length;b++)e(a.children[b])};b.removeMulti=function(a){for(var b=[],c=0;c<a.length;c++){var d=a[c];d._parent?(-1==b.indexOf(d._parent)&&b.push(d._parent),d._parent.children.splice(d._parent.children.indexOf(d),1),d._parent=null,e(d)):d._animation&&d._animation.effect==d&&(d._animation.cancel(),d._animation.effect=new KeyframeEffect(null,[]),d._animation._callback&&(d._animation._callback._animation=null),d._animation._rebuildUnderlyingAnimation(),e(d))}for(c=0;c<b.length;c++)b[c]._rebuild()},b.KeyframeEffect=function(b,d,e){return this.target=b,this._parent=null,e=a.numericTimingToObject(e),this._timingInput=a.cloneTimingInput(e),this._timing=a.normalizeTimingInput(e),this.timing=a.makeTiming(e,!1,this),this.timing._effect=this,"function"==typeof d?(a.deprecated("Custom KeyframeEffect","2015-06-22","Use KeyframeEffect.onsample instead."),this._normalizedKeyframes=d):this._normalizedKeyframes=new c(d),this._keyframes=d,this.activeDuration=a.calculateActiveDuration(this._timing),this},b.KeyframeEffect.prototype={getFrames:function(){return"function"==typeof this._normalizedKeyframes?this._normalizedKeyframes:this._normalizedKeyframes._frames},set onsample(a){if("function"==typeof this.getFrames())throw new Error("Setting onsample on custom effect KeyframeEffect is not supported.");this._onsample=a,this._animation&&this._animation._rebuildUnderlyingAnimation()},get parent(){return this._parent},clone:function(){if("function"==typeof this.getFrames())throw new Error("Cloning custom effects is not supported.");var b=new KeyframeEffect(this.target,[],a.cloneTimingInput(this._timingInput));return b._normalizedKeyframes=this._normalizedKeyframes,b._keyframes=this._keyframes,b},remove:function(){b.removeMulti([this])}};var f=Element.prototype.animate;Element.prototype.animate=function(a,c){return b.timeline._play(new b.KeyframeEffect(this,a,c))};var g=document.createElementNS("http://www.w3.org/1999/xhtml","div");b.newUnderlyingAnimationForKeyframeEffect=function(a){if(a){var b=a.target||g,c=a._keyframes;"function"==typeof c&&(c=[]);var d=a._timingInput}else var b=g,c=[],d=0;return f.apply(b,[c,d])},b.bindAnimationForKeyframeEffect=function(a){a.effect&&"function"==typeof a.effect._normalizedKeyframes&&b.bindAnimationForCustomEffect(a)};var h=[];b.awaitStartTime=function(a){null===a.startTime&&a._isGroup&&(0==h.length&&requestAnimationFrame(d),h.push(a))};var i=window.getComputedStyle;Object.defineProperty(window,"getComputedStyle",{configurable:!0,enumerable:!0,value:function(){window.document.timeline._updateAnimationsPromises();var a=i.apply(this,arguments);return d()&&(a=i.apply(this,arguments)),window.document.timeline._updateAnimationsPromises(),a}}),window.KeyframeEffect=b.KeyframeEffect,window.Element.prototype.getAnimations=function(){return document.timeline.getAnimations().filter(function(a){return null!==a.effect&&a.effect.target==this}.bind(this))}}(c,e,f),function(a,b){function c(a){a._registered||(a._registered=!0,f.push(a),g||(g=!0,requestAnimationFrame(d)))}function d(){var a=f;f=[],a.sort(function(a,b){return a._sequenceNumber-b._sequenceNumber}),a=a.filter(function(a){a();var b=a._animation?a._animation.playState:"idle";return"running"!=b&&"pending"!=b&&(a._registered=!1),a._registered}),f.push.apply(f,a),f.length?(g=!0,requestAnimationFrame(d)):g=!1}var e=(document.createElementNS("http://www.w3.org/1999/xhtml","div"),0);b.bindAnimationForCustomEffect=function(b){var d,f=b.effect.target,g="function"==typeof b.effect.getFrames();d=g?b.effect.getFrames():b.effect._onsample;var h=b.effect.timing,i=null;h=a.normalizeTimingInput(h);var j=function(){var c=j._animation?j._animation.currentTime:null;null!==c&&(c=a.calculateTimeFraction(a.calculateActiveDuration(h),c,h),isNaN(c)&&(c=null)),c!==i&&(g?d(c,f,b.effect):d(c,b.effect,b.effect._animation)),i=c};j._animation=b,j._registered=!1,j._sequenceNumber=e++,b._callback=j,c(j)};var f=[],g=!1;b.Animation.prototype._register=function(){this._callback&&c(this._callback)}}(c,e,f),function(a,b){function c(a){return a._timing.delay+a.activeDuration+a._timing.endDelay}function d(b,c){this._parent=null,this.children=b||[],this._reparent(this.children),c=a.numericTimingToObject(c),this._timingInput=a.cloneTimingInput(c),this._timing=a.normalizeTimingInput(c,!0),this.timing=a.makeTiming(c,!0,this),this.timing._effect=this,"auto"===this._timing.duration&&(this._timing.duration=this.activeDuration)}window.SequenceEffect=function(){d.apply(this,arguments)},window.GroupEffect=function(){d.apply(this,arguments)},d.prototype={_isAncestor:function(a){for(var b=this;null!==b;){if(b==a)return!0;b=b._parent}return!1},_rebuild:function(){for(var a=this;a;)"auto"===a.timing.duration&&(a._timing.duration=a.activeDuration),a=a._parent;this._animation&&this._animation._rebuildUnderlyingAnimation()},_reparent:function(a){b.removeMulti(a);for(var c=0;c<a.length;c++)a[c]._parent=this},_putChild:function(a,b){for(var c=b?"Cannot append an ancestor or self":"Cannot prepend an ancestor or self",d=0;d<a.length;d++)if(this._isAncestor(a[d]))throw{type:DOMException.HIERARCHY_REQUEST_ERR,name:"HierarchyRequestError",message:c};for(var d=0;d<a.length;d++)b?this.children.push(a[d]):this.children.unshift(a[d]);this._reparent(a),this._rebuild()},append:function(){this._putChild(arguments,!0)},prepend:function(){this._putChild(arguments,!1)},get parent(){return this._parent},get firstChild(){return this.children.length?this.children[0]:null},get lastChild(){return this.children.length?this.children[this.children.length-1]:null},clone:function(){for(var b=a.cloneTimingInput(this._timingInput),c=[],d=0;d<this.children.length;d++)c.push(this.children[d].clone());return this instanceof GroupEffect?new GroupEffect(c,b):new SequenceEffect(c,b)},remove:function(){b.removeMulti([this])}},window.SequenceEffect.prototype=Object.create(d.prototype),Object.defineProperty(window.SequenceEffect.prototype,"activeDuration",{get:function(){var a=0;return this.children.forEach(function(b){a+=c(b)}),Math.max(a,0)}}),window.GroupEffect.prototype=Object.create(d.prototype),Object.defineProperty(window.GroupEffect.prototype,"activeDuration",{get:function(){var a=0;return this.children.forEach(function(b){a=Math.max(a,c(b))}),a}}),b.newUnderlyingAnimationForGroup=function(c){var d,e=null,f=function(b){var c=d._wrapper;return c&&"pending"!=c.playState&&c.effect?null==b?void c._removeChildAnimations():0==b&&c.playbackRate<0&&(e||(e=a.normalizeTimingInput(c.effect.timing)),b=a.calculateTimeFraction(a.calculateActiveDuration(e),-1,e),isNaN(b)||null==b)?(c._forEachChild(function(a){a.currentTime=-1}),void c._removeChildAnimations()):void 0:void 0},g=new KeyframeEffect(null,[],c._timing);return g.onsample=f,d=b.timeline._play(g)},b.bindAnimationForGroup=function(a){a._animation._wrapper=a,a._isGroup=!0,b.awaitStartTime(a),a._constructChildAnimations(),a._setExternalAnimation(a)},b.groupChildDuration=c}(c,e,f)}({},function(){return this}());
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// <include src="../../../../ui/webui/resources/js/i18n_template_no_process.js">
+
+i18nTemplate.process(document, loadTimeData);
+(function () {
+function resolve() {
+document.body.removeAttribute('unresolved');
+}
+if (window.WebComponents) {
+addEventListener('WebComponentsReady', resolve);
+} else {
+if (document.readyState === 'interactive' || document.readyState === 'complete') {
+resolve();
+} else {
+addEventListener('DOMContentLoaded', resolve);
+}
+}
+}());
+window.Polymer = {
+Settings: function () {
+var user = window.Polymer || {};
+location.search.slice(1).split('&').forEach(function (o) {
+o = o.split('=');
+o[0] && (user[o[0]] = o[1] || true);
+});
+var wantShadow = user.dom === 'shadow';
+var hasShadow = Boolean(Element.prototype.createShadowRoot);
+var nativeShadow = hasShadow && !window.ShadowDOMPolyfill;
+var useShadow = wantShadow && hasShadow;
+var hasNativeImports = Boolean('import' in document.createElement('link'));
+var useNativeImports = hasNativeImports;
+var useNativeCustomElements = !window.CustomElements || window.CustomElements.useNative;
+return {
+wantShadow: wantShadow,
+hasShadow: hasShadow,
+nativeShadow: nativeShadow,
+useShadow: useShadow,
+useNativeShadow: useShadow && nativeShadow,
+useNativeImports: useNativeImports,
+useNativeCustomElements: useNativeCustomElements
+};
+}()
+};
+(function () {
+var userPolymer = window.Polymer;
+window.Polymer = function (prototype) {
+if (typeof prototype === 'function') {
+prototype = prototype.prototype;
+}
+if (!prototype) {
+prototype = {};
+}
+var factory = desugar(prototype);
+prototype = factory.prototype;
+var options = { prototype: prototype };
+if (prototype.extends) {
+options.extends = prototype.extends;
+}
+Polymer.telemetry._registrate(prototype);
+document.registerElement(prototype.is, options);
+return factory;
+};
+var desugar = function (prototype) {
+var base = Polymer.Base;
+if (prototype.extends) {
+base = Polymer.Base._getExtendedPrototype(prototype.extends);
+}
+prototype = Polymer.Base.chainObject(prototype, base);
+prototype.registerCallback();
+return prototype.constructor;
+};
+window.Polymer = Polymer;
+if (userPolymer) {
+for (var i in userPolymer) {
+Polymer[i] = userPolymer[i];
+}
+}
+Polymer.Class = desugar;
+}());
+Polymer.telemetry = {
+registrations: [],
+_regLog: function (prototype) {
+console.log('[' + prototype.is + ']: registered');
+},
+_registrate: function (prototype) {
+this.registrations.push(prototype);
+Polymer.log && this._regLog(prototype);
+},
+dumpRegistrations: function () {
+this.registrations.forEach(this._regLog);
+}
+};
+Object.defineProperty(window, 'currentImport', {
+enumerable: true,
+configurable: true,
+get: function () {
+return (document._currentScript || document.currentScript).ownerDocument;
+}
+});
+Polymer.RenderStatus = {
+_ready: false,
+_callbacks: [],
+whenReady: function (cb) {
+if (this._ready) {
+cb();
+} else {
+this._callbacks.push(cb);
+}
+},
+_makeReady: function () {
+this._ready = true;
+this._callbacks.forEach(function (cb) {
+cb();
+});
+this._callbacks = [];
+},
+_catchFirstRender: function () {
+requestAnimationFrame(function () {
+Polymer.RenderStatus._makeReady();
+});
+}
+};
+if (window.HTMLImports) {
+HTMLImports.whenReady(function () {
+Polymer.RenderStatus._catchFirstRender();
+});
+} else {
+Polymer.RenderStatus._catchFirstRender();
+}
+Polymer.ImportStatus = Polymer.RenderStatus;
+Polymer.ImportStatus.whenLoaded = Polymer.ImportStatus.whenReady;
+Polymer.Base = {
+__isPolymerInstance__: true,
+_addFeature: function (feature) {
+this.extend(this, feature);
+},
+registerCallback: function () {
+this._desugarBehaviors();
+this._doBehavior('beforeRegister');
+this._registerFeatures();
+this._doBehavior('registered');
+},
+createdCallback: function () {
+Polymer.telemetry.instanceCount++;
+this.root = this;
+this._doBehavior('created');
+this._initFeatures();
+},
+attachedCallback: function () {
+Polymer.RenderStatus.whenReady(function () {
+this.isAttached = true;
+this._doBehavior('attached');
+}.bind(this));
+},
+detachedCallback: function () {
+this.isAttached = false;
+this._doBehavior('detached');
+},
+attributeChangedCallback: function (name) {
+this._attributeChangedImpl(name);
+this._doBehavior('attributeChanged', arguments);
+},
+_attributeChangedImpl: function (name) {
+this._setAttributeToProperty(this, name);
+},
+extend: function (prototype, api) {
+if (prototype && api) {
+Object.getOwnPropertyNames(api).forEach(function (n) {
+this.copyOwnProperty(n, api, prototype);
+}, this);
+}
+return prototype || api;
+},
+mixin: function (target, source) {
+for (var i in source) {
+target[i] = source[i];
+}
+return target;
+},
+copyOwnProperty: function (name, source, target) {
+var pd = Object.getOwnPropertyDescriptor(source, name);
+if (pd) {
+Object.defineProperty(target, name, pd);
+}
+},
+_log: console.log.apply.bind(console.log, console),
+_warn: console.warn.apply.bind(console.warn, console),
+_error: console.error.apply.bind(console.error, console),
+_logf: function () {
+return this._logPrefix.concat([this.is]).concat(Array.prototype.slice.call(arguments, 0));
+}
+};
+Polymer.Base._logPrefix = function () {
+var color = window.chrome || /firefox/i.test(navigator.userAgent);
+return color ? [
+'%c[%s::%s]:',
+'font-weight: bold; background-color:#EEEE00;'
+] : ['[%s::%s]:'];
+}();
+Polymer.Base.chainObject = function (object, inherited) {
+if (object && inherited && object !== inherited) {
+if (!Object.__proto__) {
+object = Polymer.Base.extend(Object.create(inherited), object);
+}
+object.__proto__ = inherited;
+}
+return object;
+};
+Polymer.Base = Polymer.Base.chainObject(Polymer.Base, HTMLElement.prototype);
+if (window.CustomElements) {
+Polymer.instanceof = CustomElements.instanceof;
+} else {
+Polymer.instanceof = function (obj, ctor) {
+return obj instanceof ctor;
+};
+}
+Polymer.isInstance = function (obj) {
+return Boolean(obj && obj.__isPolymerInstance__);
+};
+Polymer.telemetry.instanceCount = 0;
+(function () {
+var modules = {};
+var lcModules = {};
+var findModule = function (id) {
+return modules[id] || lcModules[id.toLowerCase()];
+};
+var DomModule = function () {
+return document.createElement('dom-module');
+};
+DomModule.prototype = Object.create(HTMLElement.prototype);
+Polymer.Base.extend(DomModule.prototype, {
+constructor: DomModule,
+createdCallback: function () {
+this.register();
+},
+register: function (id) {
+var id = id || this.id || this.getAttribute('name') || this.getAttribute('is');
+if (id) {
+this.id = id;
+modules[id] = this;
+lcModules[id.toLowerCase()] = this;
+}
+},
+import: function (id, selector) {
+if (id) {
+var m = findModule(id);
+if (!m) {
+forceDocumentUpgrade();
+m = findModule(id);
+}
+if (m && selector) {
+m = m.querySelector(selector);
+}
+return m;
+}
+}
+});
+var cePolyfill = window.CustomElements && !CustomElements.useNative;
+document.registerElement('dom-module', DomModule);
+function forceDocumentUpgrade() {
+if (cePolyfill) {
+var script = document._currentScript || document.currentScript;
+var doc = script && script.ownerDocument;
+if (doc) {
+CustomElements.upgradeAll(doc);
+}
+}
+}
+}());
+Polymer.Base._addFeature({
+_prepIs: function () {
+if (!this.is) {
+var module = (document._currentScript || document.currentScript).parentNode;
+if (module.localName === 'dom-module') {
+var id = module.id || module.getAttribute('name') || module.getAttribute('is');
+this.is = id;
+}
+}
+if (this.is) {
+this.is = this.is.toLowerCase();
+}
+}
+});
+Polymer.Base._addFeature({
+behaviors: [],
+_desugarBehaviors: function () {
+if (this.behaviors.length) {
+this.behaviors = this._desugarSomeBehaviors(this.behaviors);
+}
+},
+_desugarSomeBehaviors: function (behaviors) {
+behaviors = this._flattenBehaviorsList(behaviors);
+for (var i = behaviors.length - 1; i >= 0; i--) {
+this._mixinBehavior(behaviors[i]);
+}
+return behaviors;
+},
+_flattenBehaviorsList: function (behaviors) {
+var flat = [];
+behaviors.forEach(function (b) {
+if (b instanceof Array) {
+flat = flat.concat(this._flattenBehaviorsList(b));
+} else if (b) {
+flat.push(b);
+} else {
+this._warn(this._logf('_flattenBehaviorsList', 'behavior is null, check for missing or 404 import'));
+}
+}, this);
+return flat;
+},
+_mixinBehavior: function (b) {
+Object.getOwnPropertyNames(b).forEach(function (n) {
+switch (n) {
+case 'hostAttributes':
+case 'registered':
+case 'properties':
+case 'observers':
+case 'listeners':
+case 'created':
+case 'attached':
+case 'detached':
+case 'attributeChanged':
+case 'configure':
+case 'ready':
+break;
+default:
+if (!this.hasOwnProperty(n)) {
+this.copyOwnProperty(n, b, this);
+}
+break;
+}
+}, this);
+},
+_prepBehaviors: function () {
+this._prepFlattenedBehaviors(this.behaviors);
+},
+_prepFlattenedBehaviors: function (behaviors) {
+for (var i = 0, l = behaviors.length; i < l; i++) {
+this._prepBehavior(behaviors[i]);
+}
+this._prepBehavior(this);
+},
+_doBehavior: function (name, args) {
+this.behaviors.forEach(function (b) {
+this._invokeBehavior(b, name, args);
+}, this);
+this._invokeBehavior(this, name, args);
+},
+_invokeBehavior: function (b, name, args) {
+var fn = b[name];
+if (fn) {
+fn.apply(this, args || Polymer.nar);
+}
+},
+_marshalBehaviors: function () {
+this.behaviors.forEach(function (b) {
+this._marshalBehavior(b);
+}, this);
+this._marshalBehavior(this);
+}
+});
+Polymer.Base._addFeature({
+_getExtendedPrototype: function (tag) {
+return this._getExtendedNativePrototype(tag);
+},
+_nativePrototypes: {},
+_getExtendedNativePrototype: function (tag) {
+var p = this._nativePrototypes[tag];
+if (!p) {
+var np = this.getNativePrototype(tag);
+p = this.extend(Object.create(np), Polymer.Base);
+this._nativePrototypes[tag] = p;
+}
+return p;
+},
+getNativePrototype: function (tag) {
+return Object.getPrototypeOf(document.createElement(tag));
+}
+});
+Polymer.Base._addFeature({
+_prepConstructor: function () {
+this._factoryArgs = this.extends ? [
+this.extends,
+this.is
+] : [this.is];
+var ctor = function () {
+return this._factory(arguments);
+};
+if (this.hasOwnProperty('extends')) {
+ctor.extends = this.extends;
+}
+Object.defineProperty(this, 'constructor', {
+value: ctor,
+writable: true,
+configurable: true
+});
+ctor.prototype = this;
+},
+_factory: function (args) {
+var elt = document.createElement.apply(document, this._factoryArgs);
+if (this.factoryImpl) {
+this.factoryImpl.apply(elt, args);
+}
+return elt;
+}
+});
+Polymer.nob = Object.create(null);
+Polymer.Base._addFeature({
+properties: {},
+getPropertyInfo: function (property) {
+var info = this._getPropertyInfo(property, this.properties);
+if (!info) {
+this.behaviors.some(function (b) {
+return info = this._getPropertyInfo(property, b.properties);
+}, this);
+}
+return info || Polymer.nob;
+},
+_getPropertyInfo: function (property, properties) {
+var p = properties && properties[property];
+if (typeof p === 'function') {
+p = properties[property] = { type: p };
+}
+if (p) {
+p.defined = true;
+}
+return p;
+}
+});
+Polymer.CaseMap = {
+_caseMap: {},
+dashToCamelCase: function (dash) {
+var mapped = Polymer.CaseMap._caseMap[dash];
+if (mapped) {
+return mapped;
+}
+if (dash.indexOf('-') < 0) {
+return Polymer.CaseMap._caseMap[dash] = dash;
+}
+return Polymer.CaseMap._caseMap[dash] = dash.replace(/-([a-z])/g, function (m) {
+return m[1].toUpperCase();
+});
+},
+camelToDashCase: function (camel) {
+var mapped = Polymer.CaseMap._caseMap[camel];
+if (mapped) {
+return mapped;
+}
+return Polymer.CaseMap._caseMap[camel] = camel.replace(/([a-z][A-Z])/g, function (g) {
+return g[0] + '-' + g[1].toLowerCase();
+});
+}
+};
+Polymer.Base._addFeature({
+_prepAttributes: function () {
+this._aggregatedAttributes = {};
+},
+_addHostAttributes: function (attributes) {
+if (attributes) {
+this.mixin(this._aggregatedAttributes, attributes);
+}
+},
+_marshalHostAttributes: function () {
+this._applyAttributes(this, this._aggregatedAttributes);
+},
+_applyAttributes: function (node, attr$) {
+for (var n in attr$) {
+if (!this.hasAttribute(n) && n !== 'class') {
+this.serializeValueToAttribute(attr$[n], n, this);
+}
+}
+},
+_marshalAttributes: function () {
+this._takeAttributesToModel(this);
+},
+_takeAttributesToModel: function (model) {
+for (var i = 0, l = this.attributes.length; i < l; i++) {
+this._setAttributeToProperty(model, this.attributes[i].name);
+}
+},
+_setAttributeToProperty: function (model, attrName) {
+if (!this._serializing) {
+var propName = Polymer.CaseMap.dashToCamelCase(attrName);
+var info = this.getPropertyInfo(propName);
+if (info.defined || this._propertyEffects && this._propertyEffects[propName]) {
+var val = this.getAttribute(attrName);
+model[propName] = this.deserialize(val, info.type);
+}
+}
+},
+_serializing: false,
+reflectPropertyToAttribute: function (name) {
+this._serializing = true;
+this.serializeValueToAttribute(this[name], Polymer.CaseMap.camelToDashCase(name));
+this._serializing = false;
+},
+serializeValueToAttribute: function (value, attribute, node) {
+var str = this.serialize(value);
+(node || this)[str === undefined ? 'removeAttribute' : 'setAttribute'](attribute, str);
+},
+deserialize: function (value, type) {
+switch (type) {
+case Number:
+value = Number(value);
+break;
+case Boolean:
+value = value !== null;
+break;
+case Object:
+try {
+value = JSON.parse(value);
+} catch (x) {
+}
+break;
+case Array:
+try {
+value = JSON.parse(value);
+} catch (x) {
+value = null;
+console.warn('Polymer::Attributes: couldn`t decode Array as JSON');
+}
+break;
+case Date:
+value = new Date(value);
+break;
+case String:
+default:
+break;
+}
+return value;
+},
+serialize: function (value) {
+switch (typeof value) {
+case 'boolean':
+return value ? '' : undefined;
+case 'object':
+if (value instanceof Date) {
+return value;
+} else if (value) {
+try {
+return JSON.stringify(value);
+} catch (x) {
+return '';
+}
+}
+default:
+return value != null ? value : undefined;
+}
+}
+});
+Polymer.Base._addFeature({
+_setupDebouncers: function () {
+this._debouncers = {};
+},
+debounce: function (jobName, callback, wait) {
+return this._debouncers[jobName] = Polymer.Debounce.call(this, this._debouncers[jobName], callback, wait);
+},
+isDebouncerActive: function (jobName) {
+var debouncer = this._debouncers[jobName];
+return debouncer && debouncer.finish;
+},
+flushDebouncer: function (jobName) {
+var debouncer = this._debouncers[jobName];
+if (debouncer) {
+debouncer.complete();
+}
+},
+cancelDebouncer: function (jobName) {
+var debouncer = this._debouncers[jobName];
+if (debouncer) {
+debouncer.stop();
+}
+}
+});
+Polymer.version = '1.1.4';
+Polymer.Base._addFeature({
+_registerFeatures: function () {
+this._prepIs();
+this._prepAttributes();
+this._prepBehaviors();
+this._prepConstructor();
+},
+_prepBehavior: function (b) {
+this._addHostAttributes(b.hostAttributes);
+},
+_marshalBehavior: function (b) {
+},
+_initFeatures: function () {
+this._marshalHostAttributes();
+this._setupDebouncers();
+this._marshalBehaviors();
+}
+});
+Polymer.Base._addFeature({
+_prepTemplate: function () {
+this._template = this._template || Polymer.DomModule.import(this.is, 'template');
+if (this._template && this._template.hasAttribute('is')) {
+this._warn(this._logf('_prepTemplate', 'top-level Polymer template ' + 'must not be a type-extension, found', this._template, 'Move inside simple <template>.'));
+}
+if (this._template && !this._template.content && HTMLTemplateElement.bootstrap) {
+HTMLTemplateElement.decorate(this._template);
+HTMLTemplateElement.bootstrap(this._template.content);
+}
+},
+_stampTemplate: function () {
+if (this._template) {
+this.root = this.instanceTemplate(this._template);
+}
+},
+instanceTemplate: function (template) {
+var dom = document.importNode(template._content || template.content, true);
+return dom;
+}
+});
+(function () {
+var baseAttachedCallback = Polymer.Base.attachedCallback;
+Polymer.Base._addFeature({
+_hostStack: [],
+ready: function () {
+},
+_pushHost: function (host) {
+this.dataHost = host = host || Polymer.Base._hostStack[Polymer.Base._hostStack.length - 1];
+if (host && host._clients) {
+host._clients.push(this);
+}
+this._beginHost();
+},
+_beginHost: function () {
+Polymer.Base._hostStack.push(this);
+if (!this._clients) {
+this._clients = [];
+}
+},
+_popHost: function () {
+Polymer.Base._hostStack.pop();
+},
+_tryReady: function () {
+if (this._canReady()) {
+this._ready();
+}
+},
+_canReady: function () {
+return !this.dataHost || this.dataHost._clientsReadied;
+},
+_ready: function () {
+this._beforeClientsReady();
+this._setupRoot();
+this._readyClients();
+this._afterClientsReady();
+this._readySelf();
+},
+_readyClients: function () {
+this._beginDistribute();
+var c$ = this._clients;
+for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) {
+c._ready();
+}
+this._finishDistribute();
+this._clientsReadied = true;
+this._clients = null;
+},
+_readySelf: function () {
+this._doBehavior('ready');
+this._readied = true;
+if (this._attachedPending) {
+this._attachedPending = false;
+this.attachedCallback();
+}
+},
+_beforeClientsReady: function () {
+},
+_afterClientsReady: function () {
+},
+_beforeAttached: function () {
+},
+attachedCallback: function () {
+if (this._readied) {
+this._beforeAttached();
+baseAttachedCallback.call(this);
+} else {
+this._attachedPending = true;
+}
+}
+});
+}());
+Polymer.ArraySplice = function () {
+function newSplice(index, removed, addedCount) {
+return {
+index: index,
+removed: removed,
+addedCount: addedCount
+};
+}
+var EDIT_LEAVE = 0;
+var EDIT_UPDATE = 1;
+var EDIT_ADD = 2;
+var EDIT_DELETE = 3;
+function ArraySplice() {
+}
+ArraySplice.prototype = {
+calcEditDistances: function (current, currentStart, currentEnd, old, oldStart, oldEnd) {
+var rowCount = oldEnd - oldStart + 1;
+var columnCount = currentEnd - currentStart + 1;
+var distances = new Array(rowCount);
+for (var i = 0; i < rowCount; i++) {
+distances[i] = new Array(columnCount);
+distances[i][0] = i;
+}
+for (var j = 0; j < columnCount; j++)
+distances[0][j] = j;
+for (var i = 1; i < rowCount; i++) {
+for (var j = 1; j < columnCount; j++) {
+if (this.equals(current[currentStart + j - 1], old[oldStart + i - 1]))
+distances[i][j] = distances[i - 1][j - 1];
+else {
+var north = distances[i - 1][j] + 1;
+var west = distances[i][j - 1] + 1;
+distances[i][j] = north < west ? north : west;
+}
+}
+}
+return distances;
+},
+spliceOperationsFromEditDistances: function (distances) {
+var i = distances.length - 1;
+var j = distances[0].length - 1;
+var current = distances[i][j];
+var edits = [];
+while (i > 0 || j > 0) {
+if (i == 0) {
+edits.push(EDIT_ADD);
+j--;
+continue;
+}
+if (j == 0) {
+edits.push(EDIT_DELETE);
+i--;
+continue;
+}
+var northWest = distances[i - 1][j - 1];
+var west = distances[i - 1][j];
+var north = distances[i][j - 1];
+var min;
+if (west < north)
+min = west < northWest ? west : northWest;
+else
+min = north < northWest ? north : northWest;
+if (min == northWest) {
+if (northWest == current) {
+edits.push(EDIT_LEAVE);
+} else {
+edits.push(EDIT_UPDATE);
+current = northWest;
+}
+i--;
+j--;
+} else if (min == west) {
+edits.push(EDIT_DELETE);
+i--;
+current = west;
+} else {
+edits.push(EDIT_ADD);
+j--;
+current = north;
+}
+}
+edits.reverse();
+return edits;
+},
+calcSplices: function (current, currentStart, currentEnd, old, oldStart, oldEnd) {
+var prefixCount = 0;
+var suffixCount = 0;
+var minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart);
+if (currentStart == 0 && oldStart == 0)
+prefixCount = this.sharedPrefix(current, old, minLength);
+if (currentEnd == current.length && oldEnd == old.length)
+suffixCount = this.sharedSuffix(current, old, minLength - prefixCount);
+currentStart += prefixCount;
+oldStart += prefixCount;
+currentEnd -= suffixCount;
+oldEnd -= suffixCount;
+if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0)
+return [];
+if (currentStart == currentEnd) {
+var splice = newSplice(currentStart, [], 0);
+while (oldStart < oldEnd)
+splice.removed.push(old[oldStart++]);
+return [splice];
+} else if (oldStart == oldEnd)
+return [newSplice(currentStart, [], currentEnd - currentStart)];
+var ops = this.spliceOperationsFromEditDistances(this.calcEditDistances(current, currentStart, currentEnd, old, oldStart, oldEnd));
+var splice = undefined;
+var splices = [];
+var index = currentStart;
+var oldIndex = oldStart;
+for (var i = 0; i < ops.length; i++) {
+switch (ops[i]) {
+case EDIT_LEAVE:
+if (splice) {
+splices.push(splice);
+splice = undefined;
+}
+index++;
+oldIndex++;
+break;
+case EDIT_UPDATE:
+if (!splice)
+splice = newSplice(index, [], 0);
+splice.addedCount++;
+index++;
+splice.removed.push(old[oldIndex]);
+oldIndex++;
+break;
+case EDIT_ADD:
+if (!splice)
+splice = newSplice(index, [], 0);
+splice.addedCount++;
+index++;
+break;
+case EDIT_DELETE:
+if (!splice)
+splice = newSplice(index, [], 0);
+splice.removed.push(old[oldIndex]);
+oldIndex++;
+break;
+}
+}
+if (splice) {
+splices.push(splice);
+}
+return splices;
+},
+sharedPrefix: function (current, old, searchLength) {
+for (var i = 0; i < searchLength; i++)
+if (!this.equals(current[i], old[i]))
+return i;
+return searchLength;
+},
+sharedSuffix: function (current, old, searchLength) {
+var index1 = current.length;
+var index2 = old.length;
+var count = 0;
+while (count < searchLength && this.equals(current[--index1], old[--index2]))
+count++;
+return count;
+},
+calculateSplices: function (current, previous) {
+return this.calcSplices(current, 0, current.length, previous, 0, previous.length);
+},
+equals: function (currentValue, previousValue) {
+return currentValue === previousValue;
+}
+};
+return new ArraySplice();
+}();
+Polymer.EventApi = function () {
+var Settings = Polymer.Settings;
+var EventApi = function (event) {
+this.event = event;
+};
+if (Settings.useShadow) {
+EventApi.prototype = {
+get rootTarget() {
+return this.event.path[0];
+},
+get localTarget() {
+return this.event.target;
+},
+get path() {
+return this.event.path;
+}
+};
+} else {
+EventApi.prototype = {
+get rootTarget() {
+return this.event.target;
+},
+get localTarget() {
+var current = this.event.currentTarget;
+var currentRoot = current && Polymer.dom(current).getOwnerRoot();
+var p$ = this.path;
+for (var i = 0; i < p$.length; i++) {
+if (Polymer.dom(p$[i]).getOwnerRoot() === currentRoot) {
+return p$[i];
+}
+}
+},
+get path() {
+if (!this.event._path) {
+var path = [];
+var o = this.rootTarget;
+while (o) {
+path.push(o);
+o = Polymer.dom(o).parentNode || o.host;
+}
+path.push(window);
+this.event._path = path;
+}
+return this.event._path;
+}
+};
+}
+var factory = function (event) {
+if (!event.__eventApi) {
+event.__eventApi = new EventApi(event);
+}
+return event.__eventApi;
+};
+return { factory: factory };
+}();
+Polymer.domInnerHTML = function () {
+var escapeAttrRegExp = /[&\u00A0"]/g;
+var escapeDataRegExp = /[&\u00A0<>]/g;
+function escapeReplace(c) {
+switch (c) {
+case '&':
+return '&amp;';
+case '<':
+return '&lt;';
+case '>':
+return '&gt;';
+case '"':
+return '&quot;';
+case '\xA0':
+return '&nbsp;';
+}
+}
+function escapeAttr(s) {
+return s.replace(escapeAttrRegExp, escapeReplace);
+}
+function escapeData(s) {
+return s.replace(escapeDataRegExp, escapeReplace);
+}
+function makeSet(arr) {
+var set = {};
+for (var i = 0; i < arr.length; i++) {
+set[arr[i]] = true;
+}
+return set;
+}
+var voidElements = makeSet([
+'area',
+'base',
+'br',
+'col',
+'command',
+'embed',
+'hr',
+'img',
+'input',
+'keygen',
+'link',
+'meta',
+'param',
+'source',
+'track',
+'wbr'
+]);
+var plaintextParents = makeSet([
+'style',
+'script',
+'xmp',
+'iframe',
+'noembed',
+'noframes',
+'plaintext',
+'noscript'
+]);
+function getOuterHTML(node, parentNode, composed) {
+switch (node.nodeType) {
+case Node.ELEMENT_NODE:
+var tagName = node.localName;
+var s = '<' + tagName;
+var attrs = node.attributes;
+for (var i = 0, attr; attr = attrs[i]; i++) {
+s += ' ' + attr.name + '="' + escapeAttr(attr.value) + '"';
+}
+s += '>';
+if (voidElements[tagName]) {
+return s;
+}
+return s + getInnerHTML(node, composed) + '</' + tagName + '>';
+case Node.TEXT_NODE:
+var data = node.data;
+if (parentNode && plaintextParents[parentNode.localName]) {
+return data;
+}
+return escapeData(data);
+case Node.COMMENT_NODE:
+return '<!--' + node.data + '-->';
+default:
+console.error(node);
+throw new Error('not implemented');
+}
+}
+function getInnerHTML(node, composed) {
+if (node instanceof HTMLTemplateElement)
+node = node.content;
+var s = '';
+var c$ = Polymer.dom(node).childNodes;
+c$ = composed ? node._composedChildren : c$;
+for (var i = 0, l = c$.length, child; i < l && (child = c$[i]); i++) {
+s += getOuterHTML(child, node, composed);
+}
+return s;
+}
+return { getInnerHTML: getInnerHTML };
+}();
+Polymer.DomApi = function () {
+'use strict';
+var Settings = Polymer.Settings;
+var getInnerHTML = Polymer.domInnerHTML.getInnerHTML;
+var nativeInsertBefore = Element.prototype.insertBefore;
+var nativeRemoveChild = Element.prototype.removeChild;
+var nativeAppendChild = Element.prototype.appendChild;
+var nativeCloneNode = Element.prototype.cloneNode;
+var nativeImportNode = Document.prototype.importNode;
+var DomApi = function (node) {
+this.node = node;
+if (this.patch) {
+this.patch();
+}
+};
+if (window.wrap && Settings.useShadow && !Settings.useNativeShadow) {
+DomApi = function (node) {
+this.node = wrap(node);
+if (this.patch) {
+this.patch();
+}
+};
+}
+DomApi.prototype = {
+flush: function () {
+Polymer.dom.flush();
+},
+_lazyDistribute: function (host) {
+if (host.shadyRoot && host.shadyRoot._distributionClean) {
+host.shadyRoot._distributionClean = false;
+Polymer.dom.addDebouncer(host.debounce('_distribute', host._distributeContent));
+}
+},
+appendChild: function (node) {
+return this._addNode(node);
+},
+insertBefore: function (node, ref_node) {
+return this._addNode(node, ref_node);
+},
+_addNode: function (node, ref_node) {
+this._removeNodeFromHost(node, true);
+var addedInsertionPoint;
+var root = this.getOwnerRoot();
+if (root) {
+addedInsertionPoint = this._maybeAddInsertionPoint(node, this.node);
+}
+if (this._nodeHasLogicalChildren(this.node)) {
+if (ref_node) {
+var children = this.childNodes;
+var index = children.indexOf(ref_node);
+if (index < 0) {
+throw Error('The ref_node to be inserted before is not a child ' + 'of this node');
+}
+}
+this._addLogicalInfo(node, this.node, index);
+}
+this._addNodeToHost(node);
+if (!this._maybeDistribute(node, this.node) && !this._tryRemoveUndistributedNode(node)) {
+if (ref_node) {
+ref_node = ref_node.localName === CONTENT ? this._firstComposedNode(ref_node) : ref_node;
+}
+var container = this.node._isShadyRoot ? this.node.host : this.node;
+addToComposedParent(container, node, ref_node);
+if (ref_node) {
+nativeInsertBefore.call(container, node, ref_node);
+} else {
+nativeAppendChild.call(container, node);
+}
+}
+if (addedInsertionPoint) {
+this._updateInsertionPoints(root.host);
+}
+return node;
+},
+removeChild: function (node) {
+if (factory(node).parentNode !== this.node) {
+console.warn('The node to be removed is not a child of this node', node);
+}
+this._removeNodeFromHost(node);
+if (!this._maybeDistribute(node, this.node)) {
+var container = this.node._isShadyRoot ? this.node.host : this.node;
+if (container === node.parentNode) {
+removeFromComposedParent(container, node);
+nativeRemoveChild.call(container, node);
+}
+}
+return node;
+},
+replaceChild: function (node, ref_node) {
+this.insertBefore(node, ref_node);
+this.removeChild(ref_node);
+return node;
+},
+_hasCachedOwnerRoot: function (node) {
+return Boolean(node._ownerShadyRoot !== undefined);
+},
+getOwnerRoot: function () {
+return this._ownerShadyRootForNode(this.node);
+},
+_ownerShadyRootForNode: function (node) {
+if (!node) {
+return;
+}
+if (node._ownerShadyRoot === undefined) {
+var root;
+if (node._isShadyRoot) {
+root = node;
+} else {
+var parent = Polymer.dom(node).parentNode;
+if (parent) {
+root = parent._isShadyRoot ? parent : this._ownerShadyRootForNode(parent);
+} else {
+root = null;
+}
+}
+node._ownerShadyRoot = root;
+}
+return node._ownerShadyRoot;
+},
+_maybeDistribute: function (node, parent) {
+var fragContent = node.nodeType === Node.DOCUMENT_FRAGMENT_NODE && !node.__noContent && Polymer.dom(node).querySelector(CONTENT);
+var wrappedContent = fragContent && Polymer.dom(fragContent).parentNode.nodeType !== Node.DOCUMENT_FRAGMENT_NODE;
+var hasContent = fragContent || node.localName === CONTENT;
+if (hasContent) {
+var root = this._ownerShadyRootForNode(parent);
+if (root) {
+var host = root.host;
+this._lazyDistribute(host);
+}
+}
+var parentNeedsDist = this._parentNeedsDistribution(parent);
+if (parentNeedsDist) {
+this._lazyDistribute(parent);
+}
+return parentNeedsDist || hasContent && !wrappedContent;
+},
+_maybeAddInsertionPoint: function (node, parent) {
+var added;
+if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE && !node.__noContent) {
+var c$ = factory(node).querySelectorAll(CONTENT);
+for (var i = 0, n, np, na; i < c$.length && (n = c$[i]); i++) {
+np = factory(n).parentNode;
+if (np === node) {
+np = parent;
+}
+na = this._maybeAddInsertionPoint(n, np);
+added = added || na;
+}
+} else if (node.localName === CONTENT) {
+saveLightChildrenIfNeeded(parent);
+saveLightChildrenIfNeeded(node);
+added = true;
+}
+return added;
+},
+_tryRemoveUndistributedNode: function (node) {
+if (this.node.shadyRoot) {
+var parent = getComposedParent(node);
+if (parent) {
+nativeRemoveChild.call(parent, node);
+}
+return true;
+}
+},
+_updateInsertionPoints: function (host) {
+var i$ = host.shadyRoot._insertionPoints = factory(host.shadyRoot).querySelectorAll(CONTENT);
+for (var i = 0, c; i < i$.length; i++) {
+c = i$[i];
+saveLightChildrenIfNeeded(c);
+saveLightChildrenIfNeeded(factory(c).parentNode);
+}
+},
+_nodeHasLogicalChildren: function (node) {
+return Boolean(node._lightChildren !== undefined);
+},
+_parentNeedsDistribution: function (parent) {
+return parent && parent.shadyRoot && hasInsertionPoint(parent.shadyRoot);
+},
+_removeNodeFromHost: function (node, ensureComposedRemoval) {
+var hostNeedsDist;
+var root;
+var parent = node._lightParent;
+if (parent) {
+factory(node)._distributeParent();
+root = this._ownerShadyRootForNode(node);
+if (root) {
+root.host._elementRemove(node);
+hostNeedsDist = this._removeDistributedChildren(root, node);
+}
+this._removeLogicalInfo(node, node._lightParent);
+}
+this._removeOwnerShadyRoot(node);
+if (root && hostNeedsDist) {
+this._updateInsertionPoints(root.host);
+this._lazyDistribute(root.host);
+} else if (ensureComposedRemoval) {
+removeFromComposedParent(getComposedParent(node), node);
+}
+},
+_removeDistributedChildren: function (root, container) {
+var hostNeedsDist;
+var ip$ = root._insertionPoints;
+for (var i = 0; i < ip$.length; i++) {
+var content = ip$[i];
+if (this._contains(container, content)) {
+var dc$ = factory(content).getDistributedNodes();
+for (var j = 0; j < dc$.length; j++) {
+hostNeedsDist = true;
+var node = dc$[j];
+var parent = node.parentNode;
+if (parent) {
+removeFromComposedParent(parent, node);
+nativeRemoveChild.call(parent, node);
+}
+}
+}
+}
+return hostNeedsDist;
+},
+_contains: function (container, node) {
+while (node) {
+if (node == container) {
+return true;
+}
+node = factory(node).parentNode;
+}
+},
+_addNodeToHost: function (node) {
+var root = this.getOwnerRoot();
+if (root) {
+root.host._elementAdd(node);
+}
+},
+_addLogicalInfo: function (node, container, index) {
+var children = factory(container).childNodes;
+index = index === undefined ? children.length : index;
+if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
+var c$ = Array.prototype.slice.call(node.childNodes);
+for (var i = 0, n; i < c$.length && (n = c$[i]); i++) {
+children.splice(index++, 0, n);
+n._lightParent = container;
+}
+} else {
+children.splice(index, 0, node);
+node._lightParent = container;
+}
+},
+_removeLogicalInfo: function (node, container) {
+var children = factory(container).childNodes;
+var index = children.indexOf(node);
+if (index < 0 || container !== node._lightParent) {
+throw Error('The node to be removed is not a child of this node');
+}
+children.splice(index, 1);
+node._lightParent = null;
+},
+_removeOwnerShadyRoot: function (node) {
+if (this._hasCachedOwnerRoot(node)) {
+var c$ = factory(node).childNodes;
+for (var i = 0, l = c$.length, n; i < l && (n = c$[i]); i++) {
+this._removeOwnerShadyRoot(n);
+}
+}
+node._ownerShadyRoot = undefined;
+},
+_firstComposedNode: function (content) {
+var n$ = factory(content).getDistributedNodes();
+for (var i = 0, l = n$.length, n, p$; i < l && (n = n$[i]); i++) {
+p$ = factory(n).getDestinationInsertionPoints();
+if (p$[p$.length - 1] === content) {
+return n;
+}
+}
+},
+querySelector: function (selector) {
+return this.querySelectorAll(selector)[0];
+},
+querySelectorAll: function (selector) {
+return this._query(function (n) {
+return matchesSelector.call(n, selector);
+}, this.node);
+},
+_query: function (matcher, node) {
+node = node || this.node;
+var list = [];
+this._queryElements(factory(node).childNodes, matcher, list);
+return list;
+},
+_queryElements: function (elements, matcher, list) {
+for (var i = 0, l = elements.length, c; i < l && (c = elements[i]); i++) {
+if (c.nodeType === Node.ELEMENT_NODE) {
+this._queryElement(c, matcher, list);
+}
+}
+},
+_queryElement: function (node, matcher, list) {
+if (matcher(node)) {
+list.push(node);
+}
+this._queryElements(factory(node).childNodes, matcher, list);
+},
+getDestinationInsertionPoints: function () {
+return this.node._destinationInsertionPoints || [];
+},
+getDistributedNodes: function () {
+return this.node._distributedNodes || [];
+},
+queryDistributedElements: function (selector) {
+var c$ = this.childNodes;
+var list = [];
+this._distributedFilter(selector, c$, list);
+for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) {
+if (c.localName === CONTENT) {
+this._distributedFilter(selector, factory(c).getDistributedNodes(), list);
+}
+}
+return list;
+},
+_distributedFilter: function (selector, list, results) {
+results = results || [];
+for (var i = 0, l = list.length, d; i < l && (d = list[i]); i++) {
+if (d.nodeType === Node.ELEMENT_NODE && d.localName !== CONTENT && matchesSelector.call(d, selector)) {
+results.push(d);
+}
+}
+return results;
+},
+_clear: function () {
+while (this.childNodes.length) {
+this.removeChild(this.childNodes[0]);
+}
+},
+setAttribute: function (name, value) {
+this.node.setAttribute(name, value);
+this._distributeParent();
+},
+removeAttribute: function (name) {
+this.node.removeAttribute(name);
+this._distributeParent();
+},
+_distributeParent: function () {
+if (this._parentNeedsDistribution(this.parentNode)) {
+this._lazyDistribute(this.parentNode);
+}
+},
+cloneNode: function (deep) {
+var n = nativeCloneNode.call(this.node, false);
+if (deep) {
+var c$ = this.childNodes;
+var d = factory(n);
+for (var i = 0, nc; i < c$.length; i++) {
+nc = factory(c$[i]).cloneNode(true);
+d.appendChild(nc);
+}
+}
+return n;
+},
+importNode: function (externalNode, deep) {
+var doc = this.node instanceof Document ? this.node : this.node.ownerDocument;
+var n = nativeImportNode.call(doc, externalNode, false);
+if (deep) {
+var c$ = factory(externalNode).childNodes;
+var d = factory(n);
+for (var i = 0, nc; i < c$.length; i++) {
+nc = factory(doc).importNode(c$[i], true);
+d.appendChild(nc);
+}
+}
+return n;
+}
+};
+Object.defineProperty(DomApi.prototype, 'classList', {
+get: function () {
+if (!this._classList) {
+this._classList = new DomApi.ClassList(this);
+}
+return this._classList;
+},
+configurable: true
+});
+DomApi.ClassList = function (host) {
+this.domApi = host;
+this.node = host.node;
+};
+DomApi.ClassList.prototype = {
+add: function () {
+this.node.classList.add.apply(this.node.classList, arguments);
+this.domApi._distributeParent();
+},
+remove: function () {
+this.node.classList.remove.apply(this.node.classList, arguments);
+this.domApi._distributeParent();
+},
+toggle: function () {
+this.node.classList.toggle.apply(this.node.classList, arguments);
+this.domApi._distributeParent();
+},
+contains: function () {
+return this.node.classList.contains.apply(this.node.classList, arguments);
+}
+};
+if (!Settings.useShadow) {
+Object.defineProperties(DomApi.prototype, {
+childNodes: {
+get: function () {
+var c$ = getLightChildren(this.node);
+return Array.isArray(c$) ? c$ : Array.prototype.slice.call(c$);
+},
+configurable: true
+},
+children: {
+get: function () {
+return Array.prototype.filter.call(this.childNodes, function (n) {
+return n.nodeType === Node.ELEMENT_NODE;
+});
+},
+configurable: true
+},
+parentNode: {
+get: function () {
+return this.node._lightParent || getComposedParent(this.node);
+},
+configurable: true
+},
+firstChild: {
+get: function () {
+return this.childNodes[0];
+},
+configurable: true
+},
+lastChild: {
+get: function () {
+var c$ = this.childNodes;
+return c$[c$.length - 1];
+},
+configurable: true
+},
+nextSibling: {
+get: function () {
+var c$ = this.parentNode && factory(this.parentNode).childNodes;
+if (c$) {
+return c$[Array.prototype.indexOf.call(c$, this.node) + 1];
+}
+},
+configurable: true
+},
+previousSibling: {
+get: function () {
+var c$ = this.parentNode && factory(this.parentNode).childNodes;
+if (c$) {
+return c$[Array.prototype.indexOf.call(c$, this.node) - 1];
+}
+},
+configurable: true
+},
+firstElementChild: {
+get: function () {
+return this.children[0];
+},
+configurable: true
+},
+lastElementChild: {
+get: function () {
+var c$ = this.children;
+return c$[c$.length - 1];
+},
+configurable: true
+},
+nextElementSibling: {
+get: function () {
+var c$ = this.parentNode && factory(this.parentNode).children;
+if (c$) {
+return c$[Array.prototype.indexOf.call(c$, this.node) + 1];
+}
+},
+configurable: true
+},
+previousElementSibling: {
+get: function () {
+var c$ = this.parentNode && factory(this.parentNode).children;
+if (c$) {
+return c$[Array.prototype.indexOf.call(c$, this.node) - 1];
+}
+},
+configurable: true
+},
+textContent: {
+get: function () {
+var nt = this.node.nodeType;
+if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) {
+return this.node.textContent;
+} else {
+var tc = [];
+for (var i = 0, cn = this.childNodes, c; c = cn[i]; i++) {
+if (c.nodeType !== Node.COMMENT_NODE) {
+tc.push(c.textContent);
+}
+}
+return tc.join('');
+}
+},
+set: function (text) {
+var nt = this.node.nodeType;
+if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) {
+this.node.textContent = text;
+} else {
+this._clear();
+if (text) {
+this.appendChild(document.createTextNode(text));
+}
+}
+},
+configurable: true
+},
+innerHTML: {
+get: function () {
+var nt = this.node.nodeType;
+if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) {
+return null;
+} else {
+return getInnerHTML(this.node);
+}
+},
+set: function (text) {
+var nt = this.node.nodeType;
+if (nt !== Node.TEXT_NODE || nt !== Node.COMMENT_NODE) {
+this._clear();
+var d = document.createElement('div');
+d.innerHTML = text;
+var c$ = Array.prototype.slice.call(d.childNodes);
+for (var i = 0; i < c$.length; i++) {
+this.appendChild(c$[i]);
+}
+}
+},
+configurable: true
+}
+});
+DomApi.prototype._getComposedInnerHTML = function () {
+return getInnerHTML(this.node, true);
+};
+} else {
+var forwardMethods = [
+'cloneNode',
+'appendChild',
+'insertBefore',
+'removeChild',
+'replaceChild'
+];
+forwardMethods.forEach(function (name) {
+DomApi.prototype[name] = function () {
+return this.node[name].apply(this.node, arguments);
+};
+});
+DomApi.prototype.querySelectorAll = function (selector) {
+return Array.prototype.slice.call(this.node.querySelectorAll(selector));
+};
+DomApi.prototype.getOwnerRoot = function () {
+var n = this.node;
+while (n) {
+if (n.nodeType === Node.DOCUMENT_FRAGMENT_NODE && n.host) {
+return n;
+}
+n = n.parentNode;
+}
+};
+DomApi.prototype.importNode = function (externalNode, deep) {
+var doc = this.node instanceof Document ? this.node : this.node.ownerDocument;
+return doc.importNode(externalNode, deep);
+};
+DomApi.prototype.getDestinationInsertionPoints = function () {
+var n$ = this.node.getDestinationInsertionPoints && this.node.getDestinationInsertionPoints();
+return n$ ? Array.prototype.slice.call(n$) : [];
+};
+DomApi.prototype.getDistributedNodes = function () {
+var n$ = this.node.getDistributedNodes && this.node.getDistributedNodes();
+return n$ ? Array.prototype.slice.call(n$) : [];
+};
+DomApi.prototype._distributeParent = function () {
+};
+Object.defineProperties(DomApi.prototype, {
+childNodes: {
+get: function () {
+return Array.prototype.slice.call(this.node.childNodes);
+},
+configurable: true
+},
+children: {
+get: function () {
+return Array.prototype.slice.call(this.node.children);
+},
+configurable: true
+},
+textContent: {
+get: function () {
+return this.node.textContent;
+},
+set: function (value) {
+return this.node.textContent = value;
+},
+configurable: true
+},
+innerHTML: {
+get: function () {
+return this.node.innerHTML;
+},
+set: function (value) {
+return this.node.innerHTML = value;
+},
+configurable: true
+}
+});
+var forwardProperties = [
+'parentNode',
+'firstChild',
+'lastChild',
+'nextSibling',
+'previousSibling',
+'firstElementChild',
+'lastElementChild',
+'nextElementSibling',
+'previousElementSibling'
+];
+forwardProperties.forEach(function (name) {
+Object.defineProperty(DomApi.prototype, name, {
+get: function () {
+return this.node[name];
+},
+configurable: true
+});
+});
+}
+var CONTENT = 'content';
+var factory = function (node, patch) {
+node = node || document;
+if (!node.__domApi) {
+node.__domApi = new DomApi(node, patch);
+}
+return node.__domApi;
+};
+Polymer.dom = function (obj, patch) {
+if (obj instanceof Event) {
+return Polymer.EventApi.factory(obj);
+} else {
+return factory(obj, patch);
+}
+};
+Polymer.Base.extend(Polymer.dom, {
+_flushGuard: 0,
+_FLUSH_MAX: 100,
+_needsTakeRecords: !Polymer.Settings.useNativeCustomElements,
+_debouncers: [],
+_finishDebouncer: null,
+flush: function () {
+for (var i = 0; i < this._debouncers.length; i++) {
+this._debouncers[i].complete();
+}
+if (this._finishDebouncer) {
+this._finishDebouncer.complete();
+}
+this._flushPolyfills();
+if (this._debouncers.length && this._flushGuard < this._FLUSH_MAX) {
+this._flushGuard++;
+this.flush();
+} else {
+if (this._flushGuard >= this._FLUSH_MAX) {
+console.warn('Polymer.dom.flush aborted. Flush may not be complete.');
+}
+this._flushGuard = 0;
+}
+},
+_flushPolyfills: function () {
+if (this._needsTakeRecords) {
+CustomElements.takeRecords();
+}
+},
+addDebouncer: function (debouncer) {
+this._debouncers.push(debouncer);
+this._finishDebouncer = Polymer.Debounce(this._finishDebouncer, this._finishFlush);
+},
+_finishFlush: function () {
+Polymer.dom._debouncers = [];
+}
+});
+function getLightChildren(node) {
+var children = node._lightChildren;
+return children ? children : node.childNodes;
+}
+function getComposedChildren(node) {
+if (!node._composedChildren) {
+node._composedChildren = Array.prototype.slice.call(node.childNodes);
+}
+return node._composedChildren;
+}
+function addToComposedParent(parent, node, ref_node) {
+var children = getComposedChildren(parent);
+var i = ref_node ? children.indexOf(ref_node) : -1;
+if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
+var fragChildren = getComposedChildren(node);
+for (var j = 0; j < fragChildren.length; j++) {
+addNodeToComposedChildren(fragChildren[j], parent, children, i + j);
+}
+node._composedChildren = null;
+} else {
+addNodeToComposedChildren(node, parent, children, i);
+}
+}
+function getComposedParent(node) {
+return node.__patched ? node._composedParent : node.parentNode;
+}
+function addNodeToComposedChildren(node, parent, children, i) {
+node._composedParent = parent;
+children.splice(i >= 0 ? i : children.length, 0, node);
+}
+function removeFromComposedParent(parent, node) {
+node._composedParent = null;
+if (parent) {
+var children = getComposedChildren(parent);
+var i = children.indexOf(node);
+if (i >= 0) {
+children.splice(i, 1);
+}
+}
+}
+function saveLightChildrenIfNeeded(node) {
+if (!node._lightChildren) {
+var c$ = Array.prototype.slice.call(node.childNodes);
+for (var i = 0, l = c$.length, child; i < l && (child = c$[i]); i++) {
+child._lightParent = child._lightParent || node;
+}
+node._lightChildren = c$;
+}
+}
+function hasInsertionPoint(root) {
+return Boolean(root && root._insertionPoints.length);
+}
+var p = Element.prototype;
+var matchesSelector = p.matches || p.matchesSelector || p.mozMatchesSelector || p.msMatchesSelector || p.oMatchesSelector || p.webkitMatchesSelector;
+return {
+getLightChildren: getLightChildren,
+getComposedParent: getComposedParent,
+getComposedChildren: getComposedChildren,
+removeFromComposedParent: removeFromComposedParent,
+saveLightChildrenIfNeeded: saveLightChildrenIfNeeded,
+matchesSelector: matchesSelector,
+hasInsertionPoint: hasInsertionPoint,
+ctor: DomApi,
+factory: factory
+};
+}();
+(function () {
+Polymer.Base._addFeature({
+_prepShady: function () {
+this._useContent = this._useContent || Boolean(this._template);
+},
+_poolContent: function () {
+if (this._useContent) {
+saveLightChildrenIfNeeded(this);
+}
+},
+_setupRoot: function () {
+if (this._useContent) {
+this._createLocalRoot();
+if (!this.dataHost) {
+upgradeLightChildren(this._lightChildren);
+}
+}
+},
+_createLocalRoot: function () {
+this.shadyRoot = this.root;
+this.shadyRoot._distributionClean = false;
+this.shadyRoot._isShadyRoot = true;
+this.shadyRoot._dirtyRoots = [];
+var i$ = this.shadyRoot._insertionPoints = !this._notes || this._notes._hasContent ? this.shadyRoot.querySelectorAll('content') : [];
+saveLightChildrenIfNeeded(this.shadyRoot);
+for (var i = 0, c; i < i$.length; i++) {
+c = i$[i];
+saveLightChildrenIfNeeded(c);
+saveLightChildrenIfNeeded(c.parentNode);
+}
+this.shadyRoot.host = this;
+},
+get domHost() {
+var root = Polymer.dom(this).getOwnerRoot();
+return root && root.host;
+},
+distributeContent: function (updateInsertionPoints) {
+if (this.shadyRoot) {
+var dom = Polymer.dom(this);
+if (updateInsertionPoints) {
+dom._updateInsertionPoints(this);
+}
+var host = getTopDistributingHost(this);
+dom._lazyDistribute(host);
+}
+},
+_distributeContent: function () {
+if (this._useContent && !this.shadyRoot._distributionClean) {
+this._beginDistribute();
+this._distributeDirtyRoots();
+this._finishDistribute();
+}
+},
+_beginDistribute: function () {
+if (this._useContent && hasInsertionPoint(this.shadyRoot)) {
+this._resetDistribution();
+this._distributePool(this.shadyRoot, this._collectPool());
+}
+},
+_distributeDirtyRoots: function () {
+var c$ = this.shadyRoot._dirtyRoots;
+for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) {
+c._distributeContent();
+}
+this.shadyRoot._dirtyRoots = [];
+},
+_finishDistribute: function () {
+if (this._useContent) {
+this.shadyRoot._distributionClean = true;
+if (hasInsertionPoint(this.shadyRoot)) {
+this._composeTree();
+} else {
+if (!this.shadyRoot._hasDistributed) {
+this.textContent = '';
+this._composedChildren = null;
+this.appendChild(this.shadyRoot);
+} else {
+var children = this._composeNode(this);
+this._updateChildNodes(this, children);
+}
+}
+this.shadyRoot._hasDistributed = true;
+}
+},
+elementMatches: function (selector, node) {
+node = node || this;
+return matchesSelector.call(node, selector);
+},
+_resetDistribution: function () {
+var children = getLightChildren(this);
+for (var i = 0; i < children.length; i++) {
+var child = children[i];
+if (child._destinationInsertionPoints) {
+child._destinationInsertionPoints = undefined;
+}
+if (isInsertionPoint(child)) {
+clearDistributedDestinationInsertionPoints(child);
+}
+}
+var root = this.shadyRoot;
+var p$ = root._insertionPoints;
+for (var j = 0; j < p$.length; j++) {
+p$[j]._distributedNodes = [];
+}
+},
+_collectPool: function () {
+var pool = [];
+var children = getLightChildren(this);
+for (var i = 0; i < children.length; i++) {
+var child = children[i];
+if (isInsertionPoint(child)) {
+pool.push.apply(pool, child._distributedNodes);
+} else {
+pool.push(child);
+}
+}
+return pool;
+},
+_distributePool: function (node, pool) {
+var p$ = node._insertionPoints;
+for (var i = 0, l = p$.length, p; i < l && (p = p$[i]); i++) {
+this._distributeInsertionPoint(p, pool);
+maybeRedistributeParent(p, this);
+}
+},
+_distributeInsertionPoint: function (content, pool) {
+var anyDistributed = false;
+for (var i = 0, l = pool.length, node; i < l; i++) {
+node = pool[i];
+if (!node) {
+continue;
+}
+if (this._matchesContentSelect(node, content)) {
+distributeNodeInto(node, content);
+pool[i] = undefined;
+anyDistributed = true;
+}
+}
+if (!anyDistributed) {
+var children = getLightChildren(content);
+for (var j = 0; j < children.length; j++) {
+distributeNodeInto(children[j], content);
+}
+}
+},
+_composeTree: function () {
+this._updateChildNodes(this, this._composeNode(this));
+var p$ = this.shadyRoot._insertionPoints;
+for (var i = 0, l = p$.length, p, parent; i < l && (p = p$[i]); i++) {
+parent = p._lightParent || p.parentNode;
+if (!parent._useContent && parent !== this && parent !== this.shadyRoot) {
+this._updateChildNodes(parent, this._composeNode(parent));
+}
+}
+},
+_composeNode: function (node) {
+var children = [];
+var c$ = getLightChildren(node.shadyRoot || node);
+for (var i = 0; i < c$.length; i++) {
+var child = c$[i];
+if (isInsertionPoint(child)) {
+var distributedNodes = child._distributedNodes;
+for (var j = 0; j < distributedNodes.length; j++) {
+var distributedNode = distributedNodes[j];
+if (isFinalDestination(child, distributedNode)) {
+children.push(distributedNode);
+}
+}
+} else {
+children.push(child);
+}
+}
+return children;
+},
+_updateChildNodes: function (container, children) {
+var composed = getComposedChildren(container);
+var splices = Polymer.ArraySplice.calculateSplices(children, composed);
+for (var i = 0, d = 0, s; i < splices.length && (s = splices[i]); i++) {
+for (var j = 0, n; j < s.removed.length && (n = s.removed[j]); j++) {
+if (getComposedParent(n) === container) {
+remove(n);
+}
+composed.splice(s.index + d, 1);
+}
+d -= s.addedCount;
+}
+for (var i = 0, s, next; i < splices.length && (s = splices[i]); i++) {
+next = composed[s.index];
+for (var j = s.index, n; j < s.index + s.addedCount; j++) {
+n = children[j];
+insertBefore(container, n, next);
+composed.splice(j, 0, n);
+}
+}
+ensureComposedParent(container, children);
+},
+_matchesContentSelect: function (node, contentElement) {
+var select = contentElement.getAttribute('select');
+if (!select) {
+return true;
+}
+select = select.trim();
+if (!select) {
+return true;
+}
+if (!(node instanceof Element)) {
+return false;
+}
+var validSelectors = /^(:not\()?[*.#[a-zA-Z_|]/;
+if (!validSelectors.test(select)) {
+return false;
+}
+return this.elementMatches(select, node);
+},
+_elementAdd: function () {
+},
+_elementRemove: function () {
+}
+});
+var saveLightChildrenIfNeeded = Polymer.DomApi.saveLightChildrenIfNeeded;
+var getLightChildren = Polymer.DomApi.getLightChildren;
+var matchesSelector = Polymer.DomApi.matchesSelector;
+var hasInsertionPoint = Polymer.DomApi.hasInsertionPoint;
+var getComposedChildren = Polymer.DomApi.getComposedChildren;
+var getComposedParent = Polymer.DomApi.getComposedParent;
+var removeFromComposedParent = Polymer.DomApi.removeFromComposedParent;
+function distributeNodeInto(child, insertionPoint) {
+insertionPoint._distributedNodes.push(child);
+var points = child._destinationInsertionPoints;
+if (!points) {
+child._destinationInsertionPoints = [insertionPoint];
+} else {
+points.push(insertionPoint);
+}
+}
+function clearDistributedDestinationInsertionPoints(content) {
+var e$ = content._distributedNodes;
+if (e$) {
+for (var i = 0; i < e$.length; i++) {
+var d = e$[i]._destinationInsertionPoints;
+if (d) {
+d.splice(d.indexOf(content) + 1, d.length);
+}
+}
+}
+}
+function maybeRedistributeParent(content, host) {
+var parent = content._lightParent;
+if (parent && parent.shadyRoot && hasInsertionPoint(parent.shadyRoot) && parent.shadyRoot._distributionClean) {
+parent.shadyRoot._distributionClean = false;
+host.shadyRoot._dirtyRoots.push(parent);
+}
+}
+function isFinalDestination(insertionPoint, node) {
+var points = node._destinationInsertionPoints;
+return points && points[points.length - 1] === insertionPoint;
+}
+function isInsertionPoint(node) {
+return node.localName == 'content';
+}
+var nativeInsertBefore = Element.prototype.insertBefore;
+var nativeRemoveChild = Element.prototype.removeChild;
+function insertBefore(parentNode, newChild, refChild) {
+var newChildParent = getComposedParent(newChild);
+if (newChildParent !== parentNode) {
+removeFromComposedParent(newChildParent, newChild);
+}
+remove(newChild);
+nativeInsertBefore.call(parentNode, newChild, refChild || null);
+newChild._composedParent = parentNode;
+}
+function remove(node) {
+var parentNode = getComposedParent(node);
+if (parentNode) {
+node._composedParent = null;
+nativeRemoveChild.call(parentNode, node);
+}
+}
+function ensureComposedParent(parent, children) {
+for (var i = 0, n; i < children.length; i++) {
+children[i]._composedParent = parent;
+}
+}
+function getTopDistributingHost(host) {
+while (host && hostNeedsRedistribution(host)) {
+host = host.domHost;
+}
+return host;
+}
+function hostNeedsRedistribution(host) {
+var c$ = Polymer.dom(host).children;
+for (var i = 0, c; i < c$.length; i++) {
+c = c$[i];
+if (c.localName === 'content') {
+return host.domHost;
+}
+}
+}
+var needsUpgrade = window.CustomElements && !CustomElements.useNative;
+function upgradeLightChildren(children) {
+if (needsUpgrade && children) {
+for (var i = 0; i < children.length; i++) {
+CustomElements.upgrade(children[i]);
+}
+}
+}
+}());
+if (Polymer.Settings.useShadow) {
+Polymer.Base._addFeature({
+_poolContent: function () {
+},
+_beginDistribute: function () {
+},
+distributeContent: function () {
+},
+_distributeContent: function () {
+},
+_finishDistribute: function () {
+},
+_createLocalRoot: function () {
+this.createShadowRoot();
+this.shadowRoot.appendChild(this.root);
+this.root = this.shadowRoot;
+}
+});
+}
+Polymer.DomModule = document.createElement('dom-module');
+Polymer.Base._addFeature({
+_registerFeatures: function () {
+this._prepIs();
+this._prepAttributes();
+this._prepBehaviors();
+this._prepConstructor();
+this._prepTemplate();
+this._prepShady();
+},
+_prepBehavior: function (b) {
+this._addHostAttributes(b.hostAttributes);
+},
+_initFeatures: function () {
+this._poolContent();
+this._pushHost();
+this._stampTemplate();
+this._popHost();
+this._marshalHostAttributes();
+this._setupDebouncers();
+this._marshalBehaviors();
+this._tryReady();
+},
+_marshalBehavior: function (b) {
+}
+});
+Polymer.nar = [];
+Polymer.Annotations = {
+parseAnnotations: function (template) {
+var list = [];
+var content = template._content || template.content;
+this._parseNodeAnnotations(content, list);
+return list;
+},
+_parseNodeAnnotations: function (node, list) {
+return node.nodeType === Node.TEXT_NODE ? this._parseTextNodeAnnotation(node, list) : this._parseElementAnnotations(node, list);
+},
+_testEscape: function (value) {
+var escape = value.slice(0, 2);
+if (escape === '{{' || escape === '[[') {
+return escape;
+}
+},
+_parseTextNodeAnnotation: function (node, list) {
+var v = node.textContent;
+var escape = this._testEscape(v);
+if (escape) {
+node.textContent = ' ';
+var annote = {
+bindings: [{
+kind: 'text',
+mode: escape[0],
+value: v.slice(2, -2).trim()
+}]
+};
+list.push(annote);
+return annote;
+}
+},
+_parseElementAnnotations: function (element, list) {
+var annote = {
+bindings: [],
+events: []
+};
+if (element.localName === 'content') {
+list._hasContent = true;
+}
+this._parseChildNodesAnnotations(element, annote, list);
+if (element.attributes) {
+this._parseNodeAttributeAnnotations(element, annote, list);
+if (this.prepElement) {
+this.prepElement(element);
+}
+}
+if (annote.bindings.length || annote.events.length || annote.id) {
+list.push(annote);
+}
+return annote;
+},
+_parseChildNodesAnnotations: function (root, annote, list, callback) {
+if (root.firstChild) {
+for (var i = 0, node = root.firstChild; node; node = node.nextSibling, i++) {
+if (node.localName === 'template' && !node.hasAttribute('preserve-content')) {
+this._parseTemplate(node, i, list, annote);
+}
+if (node.nodeType === Node.TEXT_NODE) {
+var n = node.nextSibling;
+while (n && n.nodeType === Node.TEXT_NODE) {
+node.textContent += n.textContent;
+root.removeChild(n);
+n = n.nextSibling;
+}
+}
+var childAnnotation = this._parseNodeAnnotations(node, list, callback);
+if (childAnnotation) {
+childAnnotation.parent = annote;
+childAnnotation.index = i;
+}
+}
+}
+},
+_parseTemplate: function (node, index, list, parent) {
+var content = document.createDocumentFragment();
+content._notes = this.parseAnnotations(node);
+content.appendChild(node.content);
+list.push({
+bindings: Polymer.nar,
+events: Polymer.nar,
+templateContent: content,
+parent: parent,
+index: index
+});
+},
+_parseNodeAttributeAnnotations: function (node, annotation) {
+for (var i = node.attributes.length - 1, a; a = node.attributes[i]; i--) {
+var n = a.name, v = a.value;
+if (n === 'id' && !this._testEscape(v)) {
+annotation.id = v;
+} else if (n.slice(0, 3) === 'on-') {
+node.removeAttribute(n);
+annotation.events.push({
+name: n.slice(3),
+value: v
+});
+} else {
+var b = this._parseNodeAttributeAnnotation(node, n, v);
+if (b) {
+annotation.bindings.push(b);
+}
+}
+}
+},
+_parseNodeAttributeAnnotation: function (node, n, v) {
+var escape = this._testEscape(v);
+if (escape) {
+var customEvent;
+var name = n;
+var mode = escape[0];
+v = v.slice(2, -2).trim();
+var not = false;
+if (v[0] == '!') {
+v = v.substring(1);
+not = true;
+}
+var kind = 'property';
+if (n[n.length - 1] == '$') {
+name = n.slice(0, -1);
+kind = 'attribute';
+}
+var notifyEvent, colon;
+if (mode == '{' && (colon = v.indexOf('::')) > 0) {
+notifyEvent = v.substring(colon + 2);
+v = v.substring(0, colon);
+customEvent = true;
+}
+if (node.localName == 'input' && n == 'value') {
+node.setAttribute(n, '');
+}
+node.removeAttribute(n);
+if (kind === 'property') {
+name = Polymer.CaseMap.dashToCamelCase(name);
+}
+return {
+kind: kind,
+mode: mode,
+name: name,
+value: v,
+negate: not,
+event: notifyEvent,
+customEvent: customEvent
+};
+}
+},
+_localSubTree: function (node, host) {
+return node === host ? node.childNodes : node._lightChildren || node.childNodes;
+},
+findAnnotatedNode: function (root, annote) {
+var parent = annote.parent && Polymer.Annotations.findAnnotatedNode(root, annote.parent);
+return !parent ? root : Polymer.Annotations._localSubTree(parent, root)[annote.index];
+}
+};
+(function () {
+function resolveCss(cssText, ownerDocument) {
+return cssText.replace(CSS_URL_RX, function (m, pre, url, post) {
+return pre + '\'' + resolve(url.replace(/["']/g, ''), ownerDocument) + '\'' + post;
+});
+}
+function resolveAttrs(element, ownerDocument) {
+for (var name in URL_ATTRS) {
+var a$ = URL_ATTRS[name];
+for (var i = 0, l = a$.length, a, at, v; i < l && (a = a$[i]); i++) {
+if (name === '*' || element.localName === name) {
+at = element.attributes[a];
+v = at && at.value;
+if (v && v.search(BINDING_RX) < 0) {
+at.value = a === 'style' ? resolveCss(v, ownerDocument) : resolve(v, ownerDocument);
+}
+}
+}
+}
+}
+function resolve(url, ownerDocument) {
+if (url && url[0] === '#') {
+return url;
+}
+var resolver = getUrlResolver(ownerDocument);
+resolver.href = url;
+return resolver.href || url;
+}
+var tempDoc;
+var tempDocBase;
+function resolveUrl(url, baseUri) {
+if (!tempDoc) {
+tempDoc = document.implementation.createHTMLDocument('temp');
+tempDocBase = tempDoc.createElement('base');
+tempDoc.head.appendChild(tempDocBase);
+}
+tempDocBase.href = baseUri;
+return resolve(url, tempDoc);
+}
+function getUrlResolver(ownerDocument) {
+return ownerDocument.__urlResolver || (ownerDocument.__urlResolver = ownerDocument.createElement('a'));
+}
+var CSS_URL_RX = /(url\()([^)]*)(\))/g;
+var URL_ATTRS = {
+'*': [
+'href',
+'src',
+'style',
+'url'
+],
+form: ['action']
+};
+var BINDING_RX = /\{\{|\[\[/;
+Polymer.ResolveUrl = {
+resolveCss: resolveCss,
+resolveAttrs: resolveAttrs,
+resolveUrl: resolveUrl
+};
+}());
+Polymer.Base._addFeature({
+_prepAnnotations: function () {
+if (!this._template) {
+this._notes = [];
+} else {
+Polymer.Annotations.prepElement = this._prepElement.bind(this);
+if (this._template._content && this._template._content._notes) {
+this._notes = this._template._content._notes;
+} else {
+this._notes = Polymer.Annotations.parseAnnotations(this._template);
+}
+this._processAnnotations(this._notes);
+Polymer.Annotations.prepElement = null;
+}
+},
+_processAnnotations: function (notes) {
+for (var i = 0; i < notes.length; i++) {
+var note = notes[i];
+for (var j = 0; j < note.bindings.length; j++) {
+var b = note.bindings[j];
+b.signature = this._parseMethod(b.value);
+if (!b.signature) {
+b.model = this._modelForPath(b.value);
+}
+}
+if (note.templateContent) {
+this._processAnnotations(note.templateContent._notes);
+var pp = note.templateContent._parentProps = this._discoverTemplateParentProps(note.templateContent._notes);
+var bindings = [];
+for (var prop in pp) {
+bindings.push({
+index: note.index,
+kind: 'property',
+mode: '{',
+name: '_parent_' + prop,
+model: prop,
+value: prop
+});
+}
+note.bindings = note.bindings.concat(bindings);
+}
+}
+},
+_discoverTemplateParentProps: function (notes) {
+var pp = {};
+notes.forEach(function (n) {
+n.bindings.forEach(function (b) {
+if (b.signature) {
+var args = b.signature.args;
+for (var k = 0; k < args.length; k++) {
+pp[args[k].model] = true;
+}
+} else {
+pp[b.model] = true;
+}
+});
+if (n.templateContent) {
+var tpp = n.templateContent._parentProps;
+Polymer.Base.mixin(pp, tpp);
+}
+});
+return pp;
+},
+_prepElement: function (element) {
+Polymer.ResolveUrl.resolveAttrs(element, this._template.ownerDocument);
+},
+_findAnnotatedNode: Polymer.Annotations.findAnnotatedNode,
+_marshalAnnotationReferences: function () {
+if (this._template) {
+this._marshalIdNodes();
+this._marshalAnnotatedNodes();
+this._marshalAnnotatedListeners();
+}
+},
+_configureAnnotationReferences: function () {
+this._configureTemplateContent();
+},
+_configureTemplateContent: function () {
+this._notes.forEach(function (note, i) {
+if (note.templateContent) {
+this._nodes[i]._content = note.templateContent;
+}
+}, this);
+},
+_marshalIdNodes: function () {
+this.$ = {};
+this._notes.forEach(function (a) {
+if (a.id) {
+this.$[a.id] = this._findAnnotatedNode(this.root, a);
+}
+}, this);
+},
+_marshalAnnotatedNodes: function () {
+if (this._nodes) {
+this._nodes = this._nodes.map(function (a) {
+return this._findAnnotatedNode(this.root, a);
+}, this);
+}
+},
+_marshalAnnotatedListeners: function () {
+this._notes.forEach(function (a) {
+if (a.events && a.events.length) {
+var node = this._findAnnotatedNode(this.root, a);
+a.events.forEach(function (e) {
+this.listen(node, e.name, e.value);
+}, this);
+}
+}, this);
+}
+});
+Polymer.Base._addFeature({
+listeners: {},
+_listenListeners: function (listeners) {
+var node, name, key;
+for (key in listeners) {
+if (key.indexOf('.') < 0) {
+node = this;
+name = key;
+} else {
+name = key.split('.');
+node = this.$[name[0]];
+name = name[1];
+}
+this.listen(node, name, listeners[key]);
+}
+},
+listen: function (node, eventName, methodName) {
+this._listen(node, eventName, this._createEventHandler(node, eventName, methodName));
+},
+_boundListenerKey: function (eventName, methodName) {
+return eventName + ':' + methodName;
+},
+_recordEventHandler: function (host, eventName, target, methodName, handler) {
+var hbl = host.__boundListeners;
+if (!hbl) {
+hbl = host.__boundListeners = new WeakMap();
+}
+var bl = hbl.get(target);
+if (!bl) {
+bl = {};
+hbl.set(target, bl);
+}
+var key = this._boundListenerKey(eventName, methodName);
+bl[key] = handler;
+},
+_recallEventHandler: function (host, eventName, target, methodName) {
+var hbl = host.__boundListeners;
+if (!hbl) {
+return;
+}
+var bl = hbl.get(target);
+if (!bl) {
+return;
+}
+var key = this._boundListenerKey(eventName, methodName);
+return bl[key];
+},
+_createEventHandler: function (node, eventName, methodName) {
+var host = this;
+var handler = function (e) {
+if (host[methodName]) {
+host[methodName](e, e.detail);
+} else {
+host._warn(host._logf('_createEventHandler', 'listener method `' + methodName + '` not defined'));
+}
+};
+this._recordEventHandler(host, eventName, node, methodName, handler);
+return handler;
+},
+unlisten: function (node, eventName, methodName) {
+var handler = this._recallEventHandler(this, eventName, node, methodName);
+if (handler) {
+this._unlisten(node, eventName, handler);
+}
+},
+_listen: function (node, eventName, handler) {
+node.addEventListener(eventName, handler);
+},
+_unlisten: function (node, eventName, handler) {
+node.removeEventListener(eventName, handler);
+}
+});
+(function () {
+'use strict';
+var HAS_NATIVE_TA = typeof document.head.style.touchAction === 'string';
+var GESTURE_KEY = '__polymerGestures';
+var HANDLED_OBJ = '__polymerGesturesHandled';
+var TOUCH_ACTION = '__polymerGesturesTouchAction';
+var TAP_DISTANCE = 25;
+var TRACK_DISTANCE = 5;
+var TRACK_LENGTH = 2;
+var MOUSE_TIMEOUT = 2500;
+var MOUSE_EVENTS = [
+'mousedown',
+'mousemove',
+'mouseup',
+'click'
+];
+var MOUSE_WHICH_TO_BUTTONS = [
+0,
+1,
+4,
+2
+];
+var MOUSE_HAS_BUTTONS = function () {
+try {
+return new MouseEvent('test', { buttons: 1 }).buttons === 1;
+} catch (e) {
+return false;
+}
+}();
+var IS_TOUCH_ONLY = navigator.userAgent.match(/iP(?:[oa]d|hone)|Android/);
+var mouseCanceller = function (mouseEvent) {
+mouseEvent[HANDLED_OBJ] = { skip: true };
+if (mouseEvent.type === 'click') {
+var path = Polymer.dom(mouseEvent).path;
+for (var i = 0; i < path.length; i++) {
+if (path[i] === POINTERSTATE.mouse.target) {
+return;
+}
+}
+mouseEvent.preventDefault();
+mouseEvent.stopPropagation();
+}
+};
+function setupTeardownMouseCanceller(setup) {
+for (var i = 0, en; i < MOUSE_EVENTS.length; i++) {
+en = MOUSE_EVENTS[i];
+if (setup) {
+document.addEventListener(en, mouseCanceller, true);
+} else {
+document.removeEventListener(en, mouseCanceller, true);
+}
+}
+}
+function ignoreMouse() {
+if (IS_TOUCH_ONLY) {
+return;
+}
+if (!POINTERSTATE.mouse.mouseIgnoreJob) {
+setupTeardownMouseCanceller(true);
+}
+var unset = function () {
+setupTeardownMouseCanceller();
+POINTERSTATE.mouse.target = null;
+POINTERSTATE.mouse.mouseIgnoreJob = null;
+};
+POINTERSTATE.mouse.mouseIgnoreJob = Polymer.Debounce(POINTERSTATE.mouse.mouseIgnoreJob, unset, MOUSE_TIMEOUT);
+}
+function hasLeftMouseButton(ev) {
+var type = ev.type;
+if (MOUSE_EVENTS.indexOf(type) === -1) {
+return false;
+}
+if (type === 'mousemove') {
+var buttons = ev.buttons === undefined ? 1 : ev.buttons;
+if (ev instanceof window.MouseEvent && !MOUSE_HAS_BUTTONS) {
+buttons = MOUSE_WHICH_TO_BUTTONS[ev.which] || 0;
+}
+return Boolean(buttons & 1);
+} else {
+var button = ev.button === undefined ? 0 : ev.button;
+return button === 0;
+}
+}
+function isSyntheticClick(ev) {
+if (ev.type === 'click') {
+if (ev.detail === 0) {
+return true;
+}
+var t = Gestures.findOriginalTarget(ev);
+var bcr = t.getBoundingClientRect();
+var x = ev.pageX, y = ev.pageY;
+return !(x >= bcr.left && x <= bcr.right && (y >= bcr.top && y <= bcr.bottom));
+}
+return false;
+}
+var POINTERSTATE = {
+mouse: {
+target: null,
+mouseIgnoreJob: null
+},
+touch: {
+x: 0,
+y: 0,
+id: -1,
+scrollDecided: false
+}
+};
+function firstTouchAction(ev) {
+var path = Polymer.dom(ev).path;
+var ta = 'auto';
+for (var i = 0, n; i < path.length; i++) {
+n = path[i];
+if (n[TOUCH_ACTION]) {
+ta = n[TOUCH_ACTION];
+break;
+}
+}
+return ta;
+}
+function trackDocument(stateObj, movefn, upfn) {
+stateObj.movefn = movefn;
+stateObj.upfn = upfn;
+document.addEventListener('mousemove', movefn);
+document.addEventListener('mouseup', upfn);
+}
+function untrackDocument(stateObj) {
+document.removeEventListener('mousemove', stateObj.movefn);
+document.removeEventListener('mouseup', stateObj.upfn);
+}
+var Gestures = {
+gestures: {},
+recognizers: [],
+deepTargetFind: function (x, y) {
+var node = document.elementFromPoint(x, y);
+var next = node;
+while (next && next.shadowRoot) {
+next = next.shadowRoot.elementFromPoint(x, y);
+if (next) {
+node = next;
+}
+}
+return node;
+},
+findOriginalTarget: function (ev) {
+if (ev.path) {
+return ev.path[0];
+}
+return ev.target;
+},
+handleNative: function (ev) {
+var handled;
+var type = ev.type;
+var node = ev.currentTarget;
+var gobj = node[GESTURE_KEY];
+var gs = gobj[type];
+if (!gs) {
+return;
+}
+if (!ev[HANDLED_OBJ]) {
+ev[HANDLED_OBJ] = {};
+if (type.slice(0, 5) === 'touch') {
+var t = ev.changedTouches[0];
+if (type === 'touchstart') {
+if (ev.touches.length === 1) {
+POINTERSTATE.touch.id = t.identifier;
+}
+}
+if (POINTERSTATE.touch.id !== t.identifier) {
+return;
+}
+if (!HAS_NATIVE_TA) {
+if (type === 'touchstart' || type === 'touchmove') {
+Gestures.handleTouchAction(ev);
+}
+}
+if (type === 'touchend') {
+POINTERSTATE.mouse.target = Polymer.dom(ev).rootTarget;
+ignoreMouse(true);
+}
+}
+}
+handled = ev[HANDLED_OBJ];
+if (handled.skip) {
+return;
+}
+var recognizers = Gestures.recognizers;
+for (var i = 0, r; i < recognizers.length; i++) {
+r = recognizers[i];
+if (gs[r.name] && !handled[r.name]) {
+if (r.flow && r.flow.start.indexOf(ev.type) > -1) {
+if (r.reset) {
+r.reset();
+}
+}
+}
+}
+for (var i = 0, r; i < recognizers.length; i++) {
+r = recognizers[i];
+if (gs[r.name] && !handled[r.name]) {
+handled[r.name] = true;
+r[type](ev);
+}
+}
+},
+handleTouchAction: function (ev) {
+var t = ev.changedTouches[0];
+var type = ev.type;
+if (type === 'touchstart') {
+POINTERSTATE.touch.x = t.clientX;
+POINTERSTATE.touch.y = t.clientY;
+POINTERSTATE.touch.scrollDecided = false;
+} else if (type === 'touchmove') {
+if (POINTERSTATE.touch.scrollDecided) {
+return;
+}
+POINTERSTATE.touch.scrollDecided = true;
+var ta = firstTouchAction(ev);
+var prevent = false;
+var dx = Math.abs(POINTERSTATE.touch.x - t.clientX);
+var dy = Math.abs(POINTERSTATE.touch.y - t.clientY);
+if (!ev.cancelable) {
+} else if (ta === 'none') {
+prevent = true;
+} else if (ta === 'pan-x') {
+prevent = dy > dx;
+} else if (ta === 'pan-y') {
+prevent = dx > dy;
+}
+if (prevent) {
+ev.preventDefault();
+} else {
+Gestures.prevent('track');
+}
+}
+},
+add: function (node, evType, handler) {
+var recognizer = this.gestures[evType];
+var deps = recognizer.deps;
+var name = recognizer.name;
+var gobj = node[GESTURE_KEY];
+if (!gobj) {
+node[GESTURE_KEY] = gobj = {};
+}
+for (var i = 0, dep, gd; i < deps.length; i++) {
+dep = deps[i];
+if (IS_TOUCH_ONLY && MOUSE_EVENTS.indexOf(dep) > -1) {
+continue;
+}
+gd = gobj[dep];
+if (!gd) {
+gobj[dep] = gd = { _count: 0 };
+}
+if (gd._count === 0) {
+node.addEventListener(dep, this.handleNative);
+}
+gd[name] = (gd[name] || 0) + 1;
+gd._count = (gd._count || 0) + 1;
+}
+node.addEventListener(evType, handler);
+if (recognizer.touchAction) {
+this.setTouchAction(node, recognizer.touchAction);
+}
+},
+remove: function (node, evType, handler) {
+var recognizer = this.gestures[evType];
+var deps = recognizer.deps;
+var name = recognizer.name;
+var gobj = node[GESTURE_KEY];
+if (gobj) {
+for (var i = 0, dep, gd; i < deps.length; i++) {
+dep = deps[i];
+gd = gobj[dep];
+if (gd && gd[name]) {
+gd[name] = (gd[name] || 1) - 1;
+gd._count = (gd._count || 1) - 1;
+if (gd._count === 0) {
+node.removeEventListener(dep, this.handleNative);
+}
+}
+}
+}
+node.removeEventListener(evType, handler);
+},
+register: function (recog) {
+this.recognizers.push(recog);
+for (var i = 0; i < recog.emits.length; i++) {
+this.gestures[recog.emits[i]] = recog;
+}
+},
+findRecognizerByEvent: function (evName) {
+for (var i = 0, r; i < this.recognizers.length; i++) {
+r = this.recognizers[i];
+for (var j = 0, n; j < r.emits.length; j++) {
+n = r.emits[j];
+if (n === evName) {
+return r;
+}
+}
+}
+return null;
+},
+setTouchAction: function (node, value) {
+if (HAS_NATIVE_TA) {
+node.style.touchAction = value;
+}
+node[TOUCH_ACTION] = value;
+},
+fire: function (target, type, detail) {
+var ev = Polymer.Base.fire(type, detail, {
+node: target,
+bubbles: true,
+cancelable: true
+});
+if (ev.defaultPrevented) {
+var se = detail.sourceEvent;
+if (se && se.preventDefault) {
+se.preventDefault();
+}
+}
+},
+prevent: function (evName) {
+var recognizer = this.findRecognizerByEvent(evName);
+if (recognizer.info) {
+recognizer.info.prevent = true;
+}
+}
+};
+Gestures.register({
+name: 'downup',
+deps: [
+'mousedown',
+'touchstart',
+'touchend'
+],
+flow: {
+start: [
+'mousedown',
+'touchstart'
+],
+end: [
+'mouseup',
+'touchend'
+]
+},
+emits: [
+'down',
+'up'
+],
+info: {
+movefn: function () {
+},
+upfn: function () {
+}
+},
+reset: function () {
+untrackDocument(this.info);
+},
+mousedown: function (e) {
+if (!hasLeftMouseButton(e)) {
+return;
+}
+var t = Gestures.findOriginalTarget(e);
+var self = this;
+var movefn = function movefn(e) {
+if (!hasLeftMouseButton(e)) {
+self.fire('up', t, e);
+untrackDocument(self.info);
+}
+};
+var upfn = function upfn(e) {
+if (hasLeftMouseButton(e)) {
+self.fire('up', t, e);
+}
+untrackDocument(self.info);
+};
+trackDocument(this.info, movefn, upfn);
+this.fire('down', t, e);
+},
+touchstart: function (e) {
+this.fire('down', Gestures.findOriginalTarget(e), e.changedTouches[0]);
+},
+touchend: function (e) {
+this.fire('up', Gestures.findOriginalTarget(e), e.changedTouches[0]);
+},
+fire: function (type, target, event) {
+var self = this;
+Gestures.fire(target, type, {
+x: event.clientX,
+y: event.clientY,
+sourceEvent: event,
+prevent: Gestures.prevent.bind(Gestures)
+});
+}
+});
+Gestures.register({
+name: 'track',
+touchAction: 'none',
+deps: [
+'mousedown',
+'touchstart',
+'touchmove',
+'touchend'
+],
+flow: {
+start: [
+'mousedown',
+'touchstart'
+],
+end: [
+'mouseup',
+'touchend'
+]
+},
+emits: ['track'],
+info: {
+x: 0,
+y: 0,
+state: 'start',
+started: false,
+moves: [],
+addMove: function (move) {
+if (this.moves.length > TRACK_LENGTH) {
+this.moves.shift();
+}
+this.moves.push(move);
+},
+movefn: function () {
+},
+upfn: function () {
+},
+prevent: false
+},
+reset: function () {
+this.info.state = 'start';
+this.info.started = false;
+this.info.moves = [];
+this.info.x = 0;
+this.info.y = 0;
+this.info.prevent = false;
+untrackDocument(this.info);
+},
+hasMovedEnough: function (x, y) {
+if (this.info.prevent) {
+return false;
+}
+if (this.info.started) {
+return true;
+}
+var dx = Math.abs(this.info.x - x);
+var dy = Math.abs(this.info.y - y);
+return dx >= TRACK_DISTANCE || dy >= TRACK_DISTANCE;
+},
+mousedown: function (e) {
+if (!hasLeftMouseButton(e)) {
+return;
+}
+var t = Gestures.findOriginalTarget(e);
+var self = this;
+var movefn = function movefn(e) {
+var x = e.clientX, y = e.clientY;
+if (self.hasMovedEnough(x, y)) {
+self.info.state = self.info.started ? e.type === 'mouseup' ? 'end' : 'track' : 'start';
+self.info.addMove({
+x: x,
+y: y
+});
+if (!hasLeftMouseButton(e)) {
+self.info.state = 'end';
+untrackDocument(self.info);
+}
+self.fire(t, e);
+self.info.started = true;
+}
+};
+var upfn = function upfn(e) {
+if (self.info.started) {
+Gestures.prevent('tap');
+movefn(e);
+}
+untrackDocument(self.info);
+};
+trackDocument(this.info, movefn, upfn);
+this.info.x = e.clientX;
+this.info.y = e.clientY;
+},
+touchstart: function (e) {
+var ct = e.changedTouches[0];
+this.info.x = ct.clientX;
+this.info.y = ct.clientY;
+},
+touchmove: function (e) {
+var t = Gestures.findOriginalTarget(e);
+var ct = e.changedTouches[0];
+var x = ct.clientX, y = ct.clientY;
+if (this.hasMovedEnough(x, y)) {
+this.info.addMove({
+x: x,
+y: y
+});
+this.fire(t, ct);
+this.info.state = 'track';
+this.info.started = true;
+}
+},
+touchend: function (e) {
+var t = Gestures.findOriginalTarget(e);
+var ct = e.changedTouches[0];
+if (this.info.started) {
+Gestures.prevent('tap');
+this.info.state = 'end';
+this.info.addMove({
+x: ct.clientX,
+y: ct.clientY
+});
+this.fire(t, ct);
+}
+},
+fire: function (target, touch) {
+var secondlast = this.info.moves[this.info.moves.length - 2];
+var lastmove = this.info.moves[this.info.moves.length - 1];
+var dx = lastmove.x - this.info.x;
+var dy = lastmove.y - this.info.y;
+var ddx, ddy = 0;
+if (secondlast) {
+ddx = lastmove.x - secondlast.x;
+ddy = lastmove.y - secondlast.y;
+}
+return Gestures.fire(target, 'track', {
+state: this.info.state,
+x: touch.clientX,
+y: touch.clientY,
+dx: dx,
+dy: dy,
+ddx: ddx,
+ddy: ddy,
+sourceEvent: touch,
+hover: function () {
+return Gestures.deepTargetFind(touch.clientX, touch.clientY);
+}
+});
+}
+});
+Gestures.register({
+name: 'tap',
+deps: [
+'mousedown',
+'click',
+'touchstart',
+'touchend'
+],
+flow: {
+start: [
+'mousedown',
+'touchstart'
+],
+end: [
+'click',
+'touchend'
+]
+},
+emits: ['tap'],
+info: {
+x: NaN,
+y: NaN,
+prevent: false
+},
+reset: function () {
+this.info.x = NaN;
+this.info.y = NaN;
+this.info.prevent = false;
+},
+save: function (e) {
+this.info.x = e.clientX;
+this.info.y = e.clientY;
+},
+mousedown: function (e) {
+if (hasLeftMouseButton(e)) {
+this.save(e);
+}
+},
+click: function (e) {
+if (hasLeftMouseButton(e)) {
+this.forward(e);
+}
+},
+touchstart: function (e) {
+this.save(e.changedTouches[0]);
+},
+touchend: function (e) {
+this.forward(e.changedTouches[0]);
+},
+forward: function (e) {
+var dx = Math.abs(e.clientX - this.info.x);
+var dy = Math.abs(e.clientY - this.info.y);
+var t = Gestures.findOriginalTarget(e);
+if (isNaN(dx) || isNaN(dy) || dx <= TAP_DISTANCE && dy <= TAP_DISTANCE || isSyntheticClick(e)) {
+if (!this.info.prevent) {
+Gestures.fire(t, 'tap', {
+x: e.clientX,
+y: e.clientY,
+sourceEvent: e
+});
+}
+}
+}
+});
+var DIRECTION_MAP = {
+x: 'pan-x',
+y: 'pan-y',
+none: 'none',
+all: 'auto'
+};
+Polymer.Base._addFeature({
+_listen: function (node, eventName, handler) {
+if (Gestures.gestures[eventName]) {
+Gestures.add(node, eventName, handler);
+} else {
+node.addEventListener(eventName, handler);
+}
+},
+_unlisten: function (node, eventName, handler) {
+if (Gestures.gestures[eventName]) {
+Gestures.remove(node, eventName, handler);
+} else {
+node.removeEventListener(eventName, handler);
+}
+},
+setScrollDirection: function (direction, node) {
+node = node || this;
+Gestures.setTouchAction(node, DIRECTION_MAP[direction] || 'auto');
+}
+});
+Polymer.Gestures = Gestures;
+}());
+Polymer.Async = {
+_currVal: 0,
+_lastVal: 0,
+_callbacks: [],
+_twiddleContent: 0,
+_twiddle: document.createTextNode(''),
+run: function (callback, waitTime) {
+if (waitTime > 0) {
+return ~setTimeout(callback, waitTime);
+} else {
+this._twiddle.textContent = this._twiddleContent++;
+this._callbacks.push(callback);
+return this._currVal++;
+}
+},
+cancel: function (handle) {
+if (handle < 0) {
+clearTimeout(~handle);
+} else {
+var idx = handle - this._lastVal;
+if (idx >= 0) {
+if (!this._callbacks[idx]) {
+throw 'invalid async handle: ' + handle;
+}
+this._callbacks[idx] = null;
+}
+}
+},
+_atEndOfMicrotask: function () {
+var len = this._callbacks.length;
+for (var i = 0; i < len; i++) {
+var cb = this._callbacks[i];
+if (cb) {
+try {
+cb();
+} catch (e) {
+i++;
+this._callbacks.splice(0, i);
+this._lastVal += i;
+this._twiddle.textContent = this._twiddleContent++;
+throw e;
+}
+}
+}
+this._callbacks.splice(0, len);
+this._lastVal += len;
+}
+};
+new (window.MutationObserver || JsMutationObserver)(Polymer.Async._atEndOfMicrotask.bind(Polymer.Async)).observe(Polymer.Async._twiddle, { characterData: true });
+Polymer.Debounce = function () {
+var Async = Polymer.Async;
+var Debouncer = function (context) {
+this.context = context;
+this.boundComplete = this.complete.bind(this);
+};
+Debouncer.prototype = {
+go: function (callback, wait) {
+var h;
+this.finish = function () {
+Async.cancel(h);
+};
+h = Async.run(this.boundComplete, wait);
+this.callback = callback;
+},
+stop: function () {
+if (this.finish) {
+this.finish();
+this.finish = null;
+}
+},
+complete: function () {
+if (this.finish) {
+this.stop();
+this.callback.call(this.context);
+}
+}
+};
+function debounce(debouncer, callback, wait) {
+if (debouncer) {
+debouncer.stop();
+} else {
+debouncer = new Debouncer(this);
+}
+debouncer.go(callback, wait);
+return debouncer;
+}
+return debounce;
+}();
+Polymer.Base._addFeature({
+$$: function (slctr) {
+return Polymer.dom(this.root).querySelector(slctr);
+},
+toggleClass: function (name, bool, node) {
+node = node || this;
+if (arguments.length == 1) {
+bool = !node.classList.contains(name);
+}
+if (bool) {
+Polymer.dom(node).classList.add(name);
+} else {
+Polymer.dom(node).classList.remove(name);
+}
+},
+toggleAttribute: function (name, bool, node) {
+node = node || this;
+if (arguments.length == 1) {
+bool = !node.hasAttribute(name);
+}
+if (bool) {
+Polymer.dom(node).setAttribute(name, '');
+} else {
+Polymer.dom(node).removeAttribute(name);
+}
+},
+classFollows: function (name, toElement, fromElement) {
+if (fromElement) {
+Polymer.dom(fromElement).classList.remove(name);
+}
+if (toElement) {
+Polymer.dom(toElement).classList.add(name);
+}
+},
+attributeFollows: function (name, toElement, fromElement) {
+if (fromElement) {
+Polymer.dom(fromElement).removeAttribute(name);
+}
+if (toElement) {
+Polymer.dom(toElement).setAttribute(name, '');
+}
+},
+getContentChildNodes: function (slctr) {
+var content = Polymer.dom(this.root).querySelector(slctr || 'content');
+return content ? Polymer.dom(content).getDistributedNodes() : [];
+},
+getContentChildren: function (slctr) {
+return this.getContentChildNodes(slctr).filter(function (n) {
+return n.nodeType === Node.ELEMENT_NODE;
+});
+},
+fire: function (type, detail, options) {
+options = options || Polymer.nob;
+var node = options.node || this;
+var detail = detail === null || detail === undefined ? Polymer.nob : detail;
+var bubbles = options.bubbles === undefined ? true : options.bubbles;
+var cancelable = Boolean(options.cancelable);
+var event = new CustomEvent(type, {
+bubbles: Boolean(bubbles),
+cancelable: cancelable,
+detail: detail
+});
+node.dispatchEvent(event);
+return event;
+},
+async: function (callback, waitTime) {
+return Polymer.Async.run(callback.bind(this), waitTime);
+},
+cancelAsync: function (handle) {
+Polymer.Async.cancel(handle);
+},
+arrayDelete: function (path, item) {
+var index;
+if (Array.isArray(path)) {
+index = path.indexOf(item);
+if (index >= 0) {
+return path.splice(index, 1);
+}
+} else {
+var arr = this.get(path);
+index = arr.indexOf(item);
+if (index >= 0) {
+return this.splice(path, index, 1);
+}
+}
+},
+transform: function (transform, node) {
+node = node || this;
+node.style.webkitTransform = transform;
+node.style.transform = transform;
+},
+translate3d: function (x, y, z, node) {
+node = node || this;
+this.transform('translate3d(' + x + ',' + y + ',' + z + ')', node);
+},
+importHref: function (href, onload, onerror) {
+var l = document.createElement('link');
+l.rel = 'import';
+l.href = href;
+if (onload) {
+l.onload = onload.bind(this);
+}
+if (onerror) {
+l.onerror = onerror.bind(this);
+}
+document.head.appendChild(l);
+return l;
+},
+create: function (tag, props) {
+var elt = document.createElement(tag);
+if (props) {
+for (var n in props) {
+elt[n] = props[n];
+}
+}
+return elt;
+}
+});
+Polymer.Bind = {
+prepareModel: function (model) {
+model._propertyEffects = {};
+model._bindListeners = [];
+Polymer.Base.mixin(model, this._modelApi);
+},
+_modelApi: {
+_notifyChange: function (property) {
+var eventName = Polymer.CaseMap.camelToDashCase(property) + '-changed';
+Polymer.Base.fire(eventName, { value: this[property] }, {
+bubbles: false,
+node: this
+});
+},
+_propertySetter: function (property, value, effects, fromAbove) {
+var old = this.__data__[property];
+if (old !== value && (old === old || value === value)) {
+this.__data__[property] = value;
+if (typeof value == 'object') {
+this._clearPath(property);
+}
+if (this._propertyChanged) {
+this._propertyChanged(property, value, old);
+}
+if (effects) {
+this._effectEffects(property, value, effects, old, fromAbove);
+}
+}
+return old;
+},
+__setProperty: function (property, value, quiet, node) {
+node = node || this;
+var effects = node._propertyEffects && node._propertyEffects[property];
+if (effects) {
+node._propertySetter(property, value, effects, quiet);
+} else {
+node[property] = value;
+}
+},
+_effectEffects: function (property, value, effects, old, fromAbove) {
+effects.forEach(function (fx) {
+var fn = Polymer.Bind['_' + fx.kind + 'Effect'];
+if (fn) {
+fn.call(this, property, value, fx.effect, old, fromAbove);
+}
+}, this);
+},
+_clearPath: function (path) {
+for (var prop in this.__data__) {
+if (prop.indexOf(path + '.') === 0) {
+this.__data__[prop] = undefined;
+}
+}
+}
+},
+ensurePropertyEffects: function (model, property) {
+var fx = model._propertyEffects[property];
+if (!fx) {
+fx = model._propertyEffects[property] = [];
+}
+return fx;
+},
+addPropertyEffect: function (model, property, kind, effect) {
+var fx = this.ensurePropertyEffects(model, property);
+fx.push({
+kind: kind,
+effect: effect
+});
+},
+createBindings: function (model) {
+var fx$ = model._propertyEffects;
+if (fx$) {
+for (var n in fx$) {
+var fx = fx$[n];
+fx.sort(this._sortPropertyEffects);
+this._createAccessors(model, n, fx);
+}
+}
+},
+_sortPropertyEffects: function () {
+var EFFECT_ORDER = {
+'compute': 0,
+'annotation': 1,
+'computedAnnotation': 2,
+'reflect': 3,
+'notify': 4,
+'observer': 5,
+'complexObserver': 6,
+'function': 7
+};
+return function (a, b) {
+return EFFECT_ORDER[a.kind] - EFFECT_ORDER[b.kind];
+};
+}(),
+_createAccessors: function (model, property, effects) {
+var defun = {
+get: function () {
+return this.__data__[property];
+}
+};
+var setter = function (value) {
+this._propertySetter(property, value, effects);
+};
+var info = model.getPropertyInfo && model.getPropertyInfo(property);
+if (info && info.readOnly) {
+if (!info.computed) {
+model['_set' + this.upper(property)] = setter;
+}
+} else {
+defun.set = setter;
+}
+Object.defineProperty(model, property, defun);
+},
+upper: function (name) {
+return name[0].toUpperCase() + name.substring(1);
+},
+_addAnnotatedListener: function (model, index, property, path, event) {
+var fn = this._notedListenerFactory(property, path, this._isStructured(path), this._isEventBogus);
+var eventName = event || Polymer.CaseMap.camelToDashCase(property) + '-changed';
+model._bindListeners.push({
+index: index,
+property: property,
+path: path,
+changedFn: fn,
+event: eventName
+});
+},
+_isStructured: function (path) {
+return path.indexOf('.') > 0;
+},
+_isEventBogus: function (e, target) {
+return e.path && e.path[0] !== target;
+},
+_notedListenerFactory: function (property, path, isStructured, bogusTest) {
+return function (e, target) {
+if (!bogusTest(e, target)) {
+if (e.detail && e.detail.path) {
+this.notifyPath(this._fixPath(path, property, e.detail.path), e.detail.value);
+} else {
+var value = target[property];
+if (!isStructured) {
+this[path] = target[property];
+} else {
+if (this.__data__[path] != value) {
+this.set(path, value);
+}
+}
+}
+}
+};
+},
+prepareInstance: function (inst) {
+inst.__data__ = Object.create(null);
+},
+setupBindListeners: function (inst) {
+inst._bindListeners.forEach(function (info) {
+var node = inst._nodes[info.index];
+node.addEventListener(info.event, inst._notifyListener.bind(inst, info.changedFn));
+});
+}
+};
+Polymer.Base.extend(Polymer.Bind, {
+_shouldAddListener: function (effect) {
+return effect.name && effect.mode === '{' && !effect.negate && effect.kind != 'attribute';
+},
+_annotationEffect: function (source, value, effect) {
+if (source != effect.value) {
+value = this.get(effect.value);
+this.__data__[effect.value] = value;
+}
+var calc = effect.negate ? !value : value;
+if (!effect.customEvent || this._nodes[effect.index][effect.name] !== calc) {
+return this._applyEffectValue(calc, effect);
+}
+},
+_reflectEffect: function (source) {
+this.reflectPropertyToAttribute(source);
+},
+_notifyEffect: function (source, value, effect, old, fromAbove) {
+if (!fromAbove) {
+this._notifyChange(source);
+}
+},
+_functionEffect: function (source, value, fn, old, fromAbove) {
+fn.call(this, source, value, old, fromAbove);
+},
+_observerEffect: function (source, value, effect, old) {
+var fn = this[effect.method];
+if (fn) {
+fn.call(this, value, old);
+} else {
+this._warn(this._logf('_observerEffect', 'observer method `' + effect.method + '` not defined'));
+}
+},
+_complexObserverEffect: function (source, value, effect) {
+var fn = this[effect.method];
+if (fn) {
+var args = Polymer.Bind._marshalArgs(this.__data__, effect, source, value);
+if (args) {
+fn.apply(this, args);
+}
+} else {
+this._warn(this._logf('_complexObserverEffect', 'observer method `' + effect.method + '` not defined'));
+}
+},
+_computeEffect: function (source, value, effect) {
+var args = Polymer.Bind._marshalArgs(this.__data__, effect, source, value);
+if (args) {
+var fn = this[effect.method];
+if (fn) {
+this.__setProperty(effect.property, fn.apply(this, args));
+} else {
+this._warn(this._logf('_computeEffect', 'compute method `' + effect.method + '` not defined'));
+}
+}
+},
+_annotatedComputationEffect: function (source, value, effect) {
+var computedHost = this._rootDataHost || this;
+var fn = computedHost[effect.method];
+if (fn) {
+var args = Polymer.Bind._marshalArgs(this.__data__, effect, source, value);
+if (args) {
+var computedvalue = fn.apply(computedHost, args);
+if (effect.negate) {
+computedvalue = !computedvalue;
+}
+this._applyEffectValue(computedvalue, effect);
+}
+} else {
+computedHost._warn(computedHost._logf('_annotatedComputationEffect', 'compute method `' + effect.method + '` not defined'));
+}
+},
+_marshalArgs: function (model, effect, path, value) {
+var values = [];
+var args = effect.args;
+for (var i = 0, l = args.length; i < l; i++) {
+var arg = args[i];
+var name = arg.name;
+var v;
+if (arg.literal) {
+v = arg.value;
+} else if (arg.structured) {
+v = Polymer.Base.get(name, model);
+} else {
+v = model[name];
+}
+if (args.length > 1 && v === undefined) {
+return;
+}
+if (arg.wildcard) {
+var baseChanged = name.indexOf(path + '.') === 0;
+var matches = effect.trigger.name.indexOf(name) === 0 && !baseChanged;
+values[i] = {
+path: matches ? path : name,
+value: matches ? value : v,
+base: v
+};
+} else {
+values[i] = v;
+}
+}
+return values;
+}
+});
+Polymer.Base._addFeature({
+_addPropertyEffect: function (property, kind, effect) {
+Polymer.Bind.addPropertyEffect(this, property, kind, effect);
+},
+_prepEffects: function () {
+Polymer.Bind.prepareModel(this);
+this._addAnnotationEffects(this._notes);
+},
+_prepBindings: function () {
+Polymer.Bind.createBindings(this);
+},
+_addPropertyEffects: function (properties) {
+if (properties) {
+for (var p in properties) {
+var prop = properties[p];
+if (prop.observer) {
+this._addObserverEffect(p, prop.observer);
+}
+if (prop.computed) {
+prop.readOnly = true;
+this._addComputedEffect(p, prop.computed);
+}
+if (prop.notify) {
+this._addPropertyEffect(p, 'notify');
+}
+if (prop.reflectToAttribute) {
+this._addPropertyEffect(p, 'reflect');
+}
+if (prop.readOnly) {
+Polymer.Bind.ensurePropertyEffects(this, p);
+}
+}
+}
+},
+_addComputedEffect: function (name, expression) {
+var sig = this._parseMethod(expression);
+sig.args.forEach(function (arg) {
+this._addPropertyEffect(arg.model, 'compute', {
+method: sig.method,
+args: sig.args,
+trigger: arg,
+property: name
+});
+}, this);
+},
+_addObserverEffect: function (property, observer) {
+this._addPropertyEffect(property, 'observer', {
+method: observer,
+property: property
+});
+},
+_addComplexObserverEffects: function (observers) {
+if (observers) {
+observers.forEach(function (observer) {
+this._addComplexObserverEffect(observer);
+}, this);
+}
+},
+_addComplexObserverEffect: function (observer) {
+var sig = this._parseMethod(observer);
+sig.args.forEach(function (arg) {
+this._addPropertyEffect(arg.model, 'complexObserver', {
+method: sig.method,
+args: sig.args,
+trigger: arg
+});
+}, this);
+},
+_addAnnotationEffects: function (notes) {
+this._nodes = [];
+notes.forEach(function (note) {
+var index = this._nodes.push(note) - 1;
+note.bindings.forEach(function (binding) {
+this._addAnnotationEffect(binding, index);
+}, this);
+}, this);
+},
+_addAnnotationEffect: function (note, index) {
+if (Polymer.Bind._shouldAddListener(note)) {
+Polymer.Bind._addAnnotatedListener(this, index, note.name, note.value, note.event);
+}
+if (note.signature) {
+this._addAnnotatedComputationEffect(note, index);
+} else {
+note.index = index;
+this._addPropertyEffect(note.model, 'annotation', note);
+}
+},
+_addAnnotatedComputationEffect: function (note, index) {
+var sig = note.signature;
+if (sig.static) {
+this.__addAnnotatedComputationEffect('__static__', index, note, sig, null);
+} else {
+sig.args.forEach(function (arg) {
+if (!arg.literal) {
+this.__addAnnotatedComputationEffect(arg.model, index, note, sig, arg);
+}
+}, this);
+}
+},
+__addAnnotatedComputationEffect: function (property, index, note, sig, trigger) {
+this._addPropertyEffect(property, 'annotatedComputation', {
+index: index,
+kind: note.kind,
+property: note.name,
+negate: note.negate,
+method: sig.method,
+args: sig.args,
+trigger: trigger
+});
+},
+_parseMethod: function (expression) {
+var m = expression.match(/([^\s]+)\((.*)\)/);
+if (m) {
+var sig = {
+method: m[1],
+static: true
+};
+if (m[2].trim()) {
+var args = m[2].replace(/\\,/g, '&comma;').split(',');
+return this._parseArgs(args, sig);
+} else {
+sig.args = Polymer.nar;
+return sig;
+}
+}
+},
+_parseArgs: function (argList, sig) {
+sig.args = argList.map(function (rawArg) {
+var arg = this._parseArg(rawArg);
+if (!arg.literal) {
+sig.static = false;
+}
+return arg;
+}, this);
+return sig;
+},
+_parseArg: function (rawArg) {
+var arg = rawArg.trim().replace(/&comma;/g, ',').replace(/\\(.)/g, '$1');
+var a = {
+name: arg,
+model: this._modelForPath(arg)
+};
+var fc = arg[0];
+if (fc === '-') {
+fc = arg[1];
+}
+if (fc >= '0' && fc <= '9') {
+fc = '#';
+}
+switch (fc) {
+case '\'':
+case '"':
+a.value = arg.slice(1, -1);
+a.literal = true;
+break;
+case '#':
+a.value = Number(arg);
+a.literal = true;
+break;
+}
+if (!a.literal) {
+a.structured = arg.indexOf('.') > 0;
+if (a.structured) {
+a.wildcard = arg.slice(-2) == '.*';
+if (a.wildcard) {
+a.name = arg.slice(0, -2);
+}
+}
+}
+return a;
+},
+_marshalInstanceEffects: function () {
+Polymer.Bind.prepareInstance(this);
+Polymer.Bind.setupBindListeners(this);
+},
+_applyEffectValue: function (value, info) {
+var node = this._nodes[info.index];
+var property = info.property || info.name || 'textContent';
+if (info.kind == 'attribute') {
+this.serializeValueToAttribute(value, property, node);
+} else {
+if (property === 'className') {
+value = this._scopeElementClass(node, value);
+}
+if (property === 'textContent' || node.localName == 'input' && property == 'value') {
+value = value == undefined ? '' : value;
+}
+return node[property] = value;
+}
+},
+_executeStaticEffects: function () {
+if (this._propertyEffects.__static__) {
+this._effectEffects('__static__', null, this._propertyEffects.__static__);
+}
+}
+});
+Polymer.Base._addFeature({
+_setupConfigure: function (initialConfig) {
+this._config = {};
+for (var i in initialConfig) {
+if (initialConfig[i] !== undefined) {
+this._config[i] = initialConfig[i];
+}
+}
+this._handlers = [];
+},
+_marshalAttributes: function () {
+this._takeAttributesToModel(this._config);
+},
+_attributeChangedImpl: function (name) {
+var model = this._clientsReadied ? this : this._config;
+this._setAttributeToProperty(model, name);
+},
+_configValue: function (name, value) {
+this._config[name] = value;
+},
+_beforeClientsReady: function () {
+this._configure();
+},
+_configure: function () {
+this._configureAnnotationReferences();
+this._aboveConfig = this.mixin({}, this._config);
+var config = {};
+this.behaviors.forEach(function (b) {
+this._configureProperties(b.properties, config);
+}, this);
+this._configureProperties(this.properties, config);
+this._mixinConfigure(config, this._aboveConfig);
+this._config = config;
+this._distributeConfig(this._config);
+},
+_configureProperties: function (properties, config) {
+for (var i in properties) {
+var c = properties[i];
+if (c.value !== undefined) {
+var value = c.value;
+if (typeof value == 'function') {
+value = value.call(this, this._config);
+}
+config[i] = value;
+}
+}
+},
+_mixinConfigure: function (a, b) {
+for (var prop in b) {
+if (!this.getPropertyInfo(prop).readOnly) {
+a[prop] = b[prop];
+}
+}
+},
+_distributeConfig: function (config) {
+var fx$ = this._propertyEffects;
+if (fx$) {
+for (var p in config) {
+var fx = fx$[p];
+if (fx) {
+for (var i = 0, l = fx.length, x; i < l && (x = fx[i]); i++) {
+if (x.kind === 'annotation') {
+var node = this._nodes[x.effect.index];
+if (node._configValue) {
+var value = p === x.effect.value ? config[p] : this.get(x.effect.value, config);
+node._configValue(x.effect.name, value);
+}
+}
+}
+}
+}
+}
+},
+_afterClientsReady: function () {
+this._executeStaticEffects();
+this._applyConfig(this._config, this._aboveConfig);
+this._flushHandlers();
+},
+_applyConfig: function (config, aboveConfig) {
+for (var n in config) {
+if (this[n] === undefined) {
+this.__setProperty(n, config[n], n in aboveConfig);
+}
+}
+},
+_notifyListener: function (fn, e) {
+if (!this._clientsReadied) {
+this._queueHandler([
+fn,
+e,
+e.target
+]);
+} else {
+return fn.call(this, e, e.target);
+}
+},
+_queueHandler: function (args) {
+this._handlers.push(args);
+},
+_flushHandlers: function () {
+var h$ = this._handlers;
+for (var i = 0, l = h$.length, h; i < l && (h = h$[i]); i++) {
+h[0].call(this, h[1], h[2]);
+}
+this._handlers = [];
+}
+});
+(function () {
+'use strict';
+Polymer.Base._addFeature({
+notifyPath: function (path, value, fromAbove) {
+var old = this._propertySetter(path, value);
+if (old !== value && (old === old || value === value)) {
+this._pathEffector(path, value);
+if (!fromAbove) {
+this._notifyPath(path, value);
+}
+return true;
+}
+},
+_getPathParts: function (path) {
+if (Array.isArray(path)) {
+var parts = [];
+for (var i = 0; i < path.length; i++) {
+var args = path[i].toString().split('.');
+for (var j = 0; j < args.length; j++) {
+parts.push(args[j]);
+}
+}
+return parts;
+} else {
+return path.toString().split('.');
+}
+},
+set: function (path, value, root) {
+var prop = root || this;
+var parts = this._getPathParts(path);
+var array;
+var last = parts[parts.length - 1];
+if (parts.length > 1) {
+for (var i = 0; i < parts.length - 1; i++) {
+var part = parts[i];
+prop = prop[part];
+if (array && parseInt(part) == part) {
+parts[i] = Polymer.Collection.get(array).getKey(prop);
+}
+if (!prop) {
+return;
+}
+array = Array.isArray(prop) ? prop : null;
+}
+if (array && parseInt(last) == last) {
+var coll = Polymer.Collection.get(array);
+var old = prop[last];
+var key = coll.getKey(old);
+parts[i] = key;
+coll.setItem(key, value);
+}
+prop[last] = value;
+if (!root) {
+this.notifyPath(parts.join('.'), value);
+}
+} else {
+prop[path] = value;
+}
+},
+get: function (path, root) {
+var prop = root || this;
+var parts = this._getPathParts(path);
+var last = parts.pop();
+while (parts.length) {
+prop = prop[parts.shift()];
+if (!prop) {
+return;
+}
+}
+return prop[last];
+},
+_pathEffector: function (path, value) {
+var model = this._modelForPath(path);
+var fx$ = this._propertyEffects[model];
+if (fx$) {
+fx$.forEach(function (fx) {
+var fxFn = this['_' + fx.kind + 'PathEffect'];
+if (fxFn) {
+fxFn.call(this, path, value, fx.effect);
+}
+}, this);
+}
+if (this._boundPaths) {
+this._notifyBoundPaths(path, value);
+}
+},
+_annotationPathEffect: function (path, value, effect) {
+if (effect.value === path || effect.value.indexOf(path + '.') === 0) {
+Polymer.Bind._annotationEffect.call(this, path, value, effect);
+} else if (path.indexOf(effect.value + '.') === 0 && !effect.negate) {
+var node = this._nodes[effect.index];
+if (node && node.notifyPath) {
+var p = this._fixPath(effect.name, effect.value, path);
+node.notifyPath(p, value, true);
+}
+}
+},
+_complexObserverPathEffect: function (path, value, effect) {
+if (this._pathMatchesEffect(path, effect)) {
+Polymer.Bind._complexObserverEffect.call(this, path, value, effect);
+}
+},
+_computePathEffect: function (path, value, effect) {
+if (this._pathMatchesEffect(path, effect)) {
+Polymer.Bind._computeEffect.call(this, path, value, effect);
+}
+},
+_annotatedComputationPathEffect: function (path, value, effect) {
+if (this._pathMatchesEffect(path, effect)) {
+Polymer.Bind._annotatedComputationEffect.call(this, path, value, effect);
+}
+},
+_pathMatchesEffect: function (path, effect) {
+var effectArg = effect.trigger.name;
+return effectArg == path || effectArg.indexOf(path + '.') === 0 || effect.trigger.wildcard && path.indexOf(effectArg) === 0;
+},
+linkPaths: function (to, from) {
+this._boundPaths = this._boundPaths || {};
+if (from) {
+this._boundPaths[to] = from;
+} else {
+this.unlinkPaths(to);
+}
+},
+unlinkPaths: function (path) {
+if (this._boundPaths) {
+delete this._boundPaths[path];
+}
+},
+_notifyBoundPaths: function (path, value) {
+for (var a in this._boundPaths) {
+var b = this._boundPaths[a];
+if (path.indexOf(a + '.') == 0) {
+this.notifyPath(this._fixPath(b, a, path), value);
+} else if (path.indexOf(b + '.') == 0) {
+this.notifyPath(this._fixPath(a, b, path), value);
+}
+}
+},
+_fixPath: function (property, root, path) {
+return property + path.slice(root.length);
+},
+_notifyPath: function (path, value) {
+var rootName = this._modelForPath(path);
+var dashCaseName = Polymer.CaseMap.camelToDashCase(rootName);
+var eventName = dashCaseName + this._EVENT_CHANGED;
+this.fire(eventName, {
+path: path,
+value: value
+}, { bubbles: false });
+},
+_modelForPath: function (path) {
+var dot = path.indexOf('.');
+return dot < 0 ? path : path.slice(0, dot);
+},
+_EVENT_CHANGED: '-changed',
+_notifySplice: function (array, path, index, added, removed) {
+var splices = [{
+index: index,
+addedCount: added,
+removed: removed,
+object: array,
+type: 'splice'
+}];
+var change = {
+keySplices: Polymer.Collection.applySplices(array, splices),
+indexSplices: splices
+};
+this.set(path + '.splices', change);
+if (added != removed.length) {
+this.notifyPath(path + '.length', array.length);
+}
+change.keySplices = null;
+change.indexSplices = null;
+},
+push: function (path) {
+var array = this.get(path);
+var args = Array.prototype.slice.call(arguments, 1);
+var len = array.length;
+var ret = array.push.apply(array, args);
+if (args.length) {
+this._notifySplice(array, path, len, args.length, []);
+}
+return ret;
+},
+pop: function (path) {
+var array = this.get(path);
+var hadLength = Boolean(array.length);
+var args = Array.prototype.slice.call(arguments, 1);
+var ret = array.pop.apply(array, args);
+if (hadLength) {
+this._notifySplice(array, path, array.length, 0, [ret]);
+}
+return ret;
+},
+splice: function (path, start, deleteCount) {
+var array = this.get(path);
+if (start < 0) {
+start = array.length - Math.floor(-start);
+} else {
+start = Math.floor(start);
+}
+if (!start) {
+start = 0;
+}
+var args = Array.prototype.slice.call(arguments, 1);
+var ret = array.splice.apply(array, args);
+var addedCount = Math.max(args.length - 2, 0);
+if (addedCount || ret.length) {
+this._notifySplice(array, path, start, addedCount, ret);
+}
+return ret;
+},
+shift: function (path) {
+var array = this.get(path);
+var hadLength = Boolean(array.length);
+var args = Array.prototype.slice.call(arguments, 1);
+var ret = array.shift.apply(array, args);
+if (hadLength) {
+this._notifySplice(array, path, 0, 0, [ret]);
+}
+return ret;
+},
+unshift: function (path) {
+var array = this.get(path);
+var args = Array.prototype.slice.call(arguments, 1);
+var ret = array.unshift.apply(array, args);
+if (args.length) {
+this._notifySplice(array, path, 0, args.length, []);
+}
+return ret;
+}
+});
+}());
+Polymer.Base._addFeature({
+resolveUrl: function (url) {
+var module = Polymer.DomModule.import(this.is);
+var root = '';
+if (module) {
+var assetPath = module.getAttribute('assetpath') || '';
+root = Polymer.ResolveUrl.resolveUrl(assetPath, module.ownerDocument.baseURI);
+}
+return Polymer.ResolveUrl.resolveUrl(url, root);
+}
+});
+Polymer.CssParse = function () {
+var api = {
+parse: function (text) {
+text = this._clean(text);
+return this._parseCss(this._lex(text), text);
+},
+_clean: function (cssText) {
+return cssText.replace(this._rx.comments, '').replace(this._rx.port, '');
+},
+_lex: function (text) {
+var root = {
+start: 0,
+end: text.length
+};
+var n = root;
+for (var i = 0, s = 0, l = text.length; i < l; i++) {
+switch (text[i]) {
+case this.OPEN_BRACE:
+if (!n.rules) {
+n.rules = [];
+}
+var p = n;
+var previous = p.rules[p.rules.length - 1];
+n = {
+start: i + 1,
+parent: p,
+previous: previous
+};
+p.rules.push(n);
+break;
+case this.CLOSE_BRACE:
+n.end = i + 1;
+n = n.parent || root;
+break;
+}
+}
+return root;
+},
+_parseCss: function (node, text) {
+var t = text.substring(node.start, node.end - 1);
+node.parsedCssText = node.cssText = t.trim();
+if (node.parent) {
+var ss = node.previous ? node.previous.end : node.parent.start;
+t = text.substring(ss, node.start - 1);
+t = t.substring(t.lastIndexOf(';') + 1);
+var s = node.parsedSelector = node.selector = t.trim();
+node.atRule = s.indexOf(this.AT_START) === 0;
+if (node.atRule) {
+if (s.indexOf(this.MEDIA_START) === 0) {
+node.type = this.types.MEDIA_RULE;
+} else if (s.match(this._rx.keyframesRule)) {
+node.type = this.types.KEYFRAMES_RULE;
+}
+} else {
+if (s.indexOf(this.VAR_START) === 0) {
+node.type = this.types.MIXIN_RULE;
+} else {
+node.type = this.types.STYLE_RULE;
+}
+}
+}
+var r$ = node.rules;
+if (r$) {
+for (var i = 0, l = r$.length, r; i < l && (r = r$[i]); i++) {
+this._parseCss(r, text);
+}
+}
+return node;
+},
+stringify: function (node, preserveProperties, text) {
+text = text || '';
+var cssText = '';
+if (node.cssText || node.rules) {
+var r$ = node.rules;
+if (r$ && (preserveProperties || !this._hasMixinRules(r$))) {
+for (var i = 0, l = r$.length, r; i < l && (r = r$[i]); i++) {
+cssText = this.stringify(r, preserveProperties, cssText);
+}
+} else {
+cssText = preserveProperties ? node.cssText : this.removeCustomProps(node.cssText);
+cssText = cssText.trim();
+if (cssText) {
+cssText = ' ' + cssText + '\n';
+}
+}
+}
+if (cssText) {
+if (node.selector) {
+text += node.selector + ' ' + this.OPEN_BRACE + '\n';
+}
+text += cssText;
+if (node.selector) {
+text += this.CLOSE_BRACE + '\n\n';
+}
+}
+return text;
+},
+_hasMixinRules: function (rules) {
+return rules[0].selector.indexOf(this.VAR_START) >= 0;
+},
+removeCustomProps: function (cssText) {
+return cssText;
+},
+removeCustomPropAssignment: function (cssText) {
+return cssText.replace(this._rx.customProp, '').replace(this._rx.mixinProp, '');
+},
+removeCustomPropApply: function (cssText) {
+return cssText.replace(this._rx.mixinApply, '').replace(this._rx.varApply, '');
+},
+types: {
+STYLE_RULE: 1,
+KEYFRAMES_RULE: 7,
+MEDIA_RULE: 4,
+MIXIN_RULE: 1000
+},
+OPEN_BRACE: '{',
+CLOSE_BRACE: '}',
+_rx: {
+comments: /\/\*[^*]*\*+([^\/*][^*]*\*+)*\//gim,
+port: /@import[^;]*;/gim,
+customProp: /(?:^|[\s;])--[^;{]*?:[^{};]*?(?:[;\n]|$)/gim,
+mixinProp: /(?:^|[\s;])--[^;{]*?:[^{;]*?{[^}]*?}(?:[;\n]|$)?/gim,
+mixinApply: /@apply[\s]*\([^)]*?\)[\s]*(?:[;\n]|$)?/gim,
+varApply: /[^;:]*?:[^;]*var[^;]*(?:[;\n]|$)?/gim,
+keyframesRule: /^@[^\s]*keyframes/
+},
+VAR_START: '--',
+MEDIA_START: '@media',
+AT_START: '@'
+};
+return api;
+}();
+Polymer.StyleUtil = function () {
+return {
+MODULE_STYLES_SELECTOR: 'style, link[rel=import][type~=css], template',
+INCLUDE_ATTR: 'include',
+toCssText: function (rules, callback, preserveProperties) {
+if (typeof rules === 'string') {
+rules = this.parser.parse(rules);
+}
+if (callback) {
+this.forEachStyleRule(rules, callback);
+}
+return this.parser.stringify(rules, preserveProperties);
+},
+forRulesInStyles: function (styles, callback) {
+if (styles) {
+for (var i = 0, l = styles.length, s; i < l && (s = styles[i]); i++) {
+this.forEachStyleRule(this.rulesForStyle(s), callback);
+}
+}
+},
+rulesForStyle: function (style) {
+if (!style.__cssRules && style.textContent) {
+style.__cssRules = this.parser.parse(style.textContent);
+}
+return style.__cssRules;
+},
+clearStyleRules: function (style) {
+style.__cssRules = null;
+},
+forEachStyleRule: function (node, callback) {
+if (!node) {
+return;
+}
+var s = node.parsedSelector;
+var skipRules = false;
+if (node.type === this.ruleTypes.STYLE_RULE) {
+callback(node);
+} else if (node.type === this.ruleTypes.KEYFRAMES_RULE || node.type === this.ruleTypes.MIXIN_RULE) {
+skipRules = true;
+}
+var r$ = node.rules;
+if (r$ && !skipRules) {
+for (var i = 0, l = r$.length, r; i < l && (r = r$[i]); i++) {
+this.forEachStyleRule(r, callback);
+}
+}
+},
+applyCss: function (cssText, moniker, target, afterNode) {
+var style = document.createElement('style');
+if (moniker) {
+style.setAttribute('scope', moniker);
+}
+style.textContent = cssText;
+target = target || document.head;
+if (!afterNode) {
+var n$ = target.querySelectorAll('style[scope]');
+afterNode = n$[n$.length - 1];
+}
+target.insertBefore(style, afterNode && afterNode.nextSibling || target.firstChild);
+return style;
+},
+cssFromModules: function (moduleIds, warnIfNotFound) {
+var modules = moduleIds.trim().split(' ');
+var cssText = '';
+for (var i = 0; i < modules.length; i++) {
+cssText += this.cssFromModule(modules[i], warnIfNotFound);
+}
+return cssText;
+},
+cssFromModule: function (moduleId, warnIfNotFound) {
+var m = Polymer.DomModule.import(moduleId);
+if (m && !m._cssText) {
+m._cssText = this._cssFromElement(m);
+}
+if (!m && warnIfNotFound) {
+console.warn('Could not find style data in module named', moduleId);
+}
+return m && m._cssText || '';
+},
+_cssFromElement: function (element) {
+var cssText = '';
+var content = element.content || element;
+var e$ = Array.prototype.slice.call(content.querySelectorAll(this.MODULE_STYLES_SELECTOR));
+for (var i = 0, e; i < e$.length; i++) {
+e = e$[i];
+if (e.localName === 'template') {
+cssText += this._cssFromElement(e);
+} else {
+if (e.localName === 'style') {
+var include = e.getAttribute(this.INCLUDE_ATTR);
+if (include) {
+cssText += this.cssFromModules(include, true);
+}
+e = e.__appliedElement || e;
+e.parentNode.removeChild(e);
+cssText += this.resolveCss(e.textContent, element.ownerDocument);
+} else if (e.import && e.import.body) {
+cssText += this.resolveCss(e.import.body.textContent, e.import);
+}
+}
+}
+return cssText;
+},
+resolveCss: Polymer.ResolveUrl.resolveCss,
+parser: Polymer.CssParse,
+ruleTypes: Polymer.CssParse.types
+};
+}();
+Polymer.StyleTransformer = function () {
+var nativeShadow = Polymer.Settings.useNativeShadow;
+var styleUtil = Polymer.StyleUtil;
+var api = {
+dom: function (node, scope, useAttr, shouldRemoveScope) {
+this._transformDom(node, scope || '', useAttr, shouldRemoveScope);
+},
+_transformDom: function (node, selector, useAttr, shouldRemoveScope) {
+if (node.setAttribute) {
+this.element(node, selector, useAttr, shouldRemoveScope);
+}
+var c$ = Polymer.dom(node).childNodes;
+for (var i = 0; i < c$.length; i++) {
+this._transformDom(c$[i], selector, useAttr, shouldRemoveScope);
+}
+},
+element: function (element, scope, useAttr, shouldRemoveScope) {
+if (useAttr) {
+if (shouldRemoveScope) {
+element.removeAttribute(SCOPE_NAME);
+} else {
+element.setAttribute(SCOPE_NAME, scope);
+}
+} else {
+if (scope) {
+if (element.classList) {
+if (shouldRemoveScope) {
+element.classList.remove(SCOPE_NAME);
+element.classList.remove(scope);
+} else {
+element.classList.add(SCOPE_NAME);
+element.classList.add(scope);
+}
+} else if (element.getAttribute) {
+var c = element.getAttribute(CLASS);
+if (shouldRemoveScope) {
+if (c) {
+element.setAttribute(CLASS, c.replace(SCOPE_NAME, '').replace(scope, ''));
+}
+} else {
+element.setAttribute(CLASS, c + (c ? ' ' : '') + SCOPE_NAME + ' ' + scope);
+}
+}
+}
+}
+},
+elementStyles: function (element, callback) {
+var styles = element._styles;
+var cssText = '';
+for (var i = 0, l = styles.length, s, text; i < l && (s = styles[i]); i++) {
+var rules = styleUtil.rulesForStyle(s);
+cssText += nativeShadow ? styleUtil.toCssText(rules, callback) : this.css(rules, element.is, element.extends, callback, element._scopeCssViaAttr) + '\n\n';
+}
+return cssText.trim();
+},
+css: function (rules, scope, ext, callback, useAttr) {
+var hostScope = this._calcHostScope(scope, ext);
+scope = this._calcElementScope(scope, useAttr);
+var self = this;
+return styleUtil.toCssText(rules, function (rule) {
+if (!rule.isScoped) {
+self.rule(rule, scope, hostScope);
+rule.isScoped = true;
+}
+if (callback) {
+callback(rule, scope, hostScope);
+}
+});
+},
+_calcElementScope: function (scope, useAttr) {
+if (scope) {
+return useAttr ? CSS_ATTR_PREFIX + scope + CSS_ATTR_SUFFIX : CSS_CLASS_PREFIX + scope;
+} else {
+return '';
+}
+},
+_calcHostScope: function (scope, ext) {
+return ext ? '[is=' + scope + ']' : scope;
+},
+rule: function (rule, scope, hostScope) {
+this._transformRule(rule, this._transformComplexSelector, scope, hostScope);
+},
+_transformRule: function (rule, transformer, scope, hostScope) {
+var p$ = rule.selector.split(COMPLEX_SELECTOR_SEP);
+for (var i = 0, l = p$.length, p; i < l && (p = p$[i]); i++) {
+p$[i] = transformer.call(this, p, scope, hostScope);
+}
+rule.selector = rule.transformedSelector = p$.join(COMPLEX_SELECTOR_SEP);
+},
+_transformComplexSelector: function (selector, scope, hostScope) {
+var stop = false;
+var hostContext = false;
+var self = this;
+selector = selector.replace(SIMPLE_SELECTOR_SEP, function (m, c, s) {
+if (!stop) {
+var info = self._transformCompoundSelector(s, c, scope, hostScope);
+stop = stop || info.stop;
+hostContext = hostContext || info.hostContext;
+c = info.combinator;
+s = info.value;
+} else {
+s = s.replace(SCOPE_JUMP, ' ');
+}
+return c + s;
+});
+if (hostContext) {
+selector = selector.replace(HOST_CONTEXT_PAREN, function (m, pre, paren, post) {
+return pre + paren + ' ' + hostScope + post + COMPLEX_SELECTOR_SEP + ' ' + pre + hostScope + paren + post;
+});
+}
+return selector;
+},
+_transformCompoundSelector: function (selector, combinator, scope, hostScope) {
+var jumpIndex = selector.search(SCOPE_JUMP);
+var hostContext = false;
+if (selector.indexOf(HOST_CONTEXT) >= 0) {
+hostContext = true;
+} else if (selector.indexOf(HOST) >= 0) {
+selector = selector.replace(HOST_PAREN, function (m, host, paren) {
+return hostScope + paren;
+});
+selector = selector.replace(HOST, hostScope);
+} else if (jumpIndex !== 0) {
+selector = scope ? this._transformSimpleSelector(selector, scope) : selector;
+}
+if (selector.indexOf(CONTENT) >= 0) {
+combinator = '';
+}
+var stop;
+if (jumpIndex >= 0) {
+selector = selector.replace(SCOPE_JUMP, ' ');
+stop = true;
+}
+return {
+value: selector,
+combinator: combinator,
+stop: stop,
+hostContext: hostContext
+};
+},
+_transformSimpleSelector: function (selector, scope) {
+var p$ = selector.split(PSEUDO_PREFIX);
+p$[0] += scope;
+return p$.join(PSEUDO_PREFIX);
+},
+documentRule: function (rule) {
+rule.selector = rule.parsedSelector;
+this.normalizeRootSelector(rule);
+if (!nativeShadow) {
+this._transformRule(rule, this._transformDocumentSelector);
+}
+},
+normalizeRootSelector: function (rule) {
+if (rule.selector === ROOT) {
+rule.selector = 'body';
+}
+},
+_transformDocumentSelector: function (selector) {
+return selector.match(SCOPE_JUMP) ? this._transformComplexSelector(selector, SCOPE_DOC_SELECTOR) : this._transformSimpleSelector(selector.trim(), SCOPE_DOC_SELECTOR);
+},
+SCOPE_NAME: 'style-scope'
+};
+var SCOPE_NAME = api.SCOPE_NAME;
+var SCOPE_DOC_SELECTOR = ':not([' + SCOPE_NAME + '])' + ':not(.' + SCOPE_NAME + ')';
+var COMPLEX_SELECTOR_SEP = ',';
+var SIMPLE_SELECTOR_SEP = /(^|[\s>+~]+)([^\s>+~]+)/g;
+var HOST = ':host';
+var ROOT = ':root';
+var HOST_PAREN = /(\:host)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/g;
+var HOST_CONTEXT = ':host-context';
+var HOST_CONTEXT_PAREN = /(.*)(?:\:host-context)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))(.*)/;
+var CONTENT = '::content';
+var SCOPE_JUMP = /\:\:content|\:\:shadow|\/deep\//;
+var CSS_CLASS_PREFIX = '.';
+var CSS_ATTR_PREFIX = '[' + SCOPE_NAME + '~=';
+var CSS_ATTR_SUFFIX = ']';
+var PSEUDO_PREFIX = ':';
+var CLASS = 'class';
+return api;
+}();
+Polymer.StyleExtends = function () {
+var styleUtil = Polymer.StyleUtil;
+return {
+hasExtends: function (cssText) {
+return Boolean(cssText.match(this.rx.EXTEND));
+},
+transform: function (style) {
+var rules = styleUtil.rulesForStyle(style);
+var self = this;
+styleUtil.forEachStyleRule(rules, function (rule) {
+var map = self._mapRule(rule);
+if (rule.parent) {
+var m;
+while (m = self.rx.EXTEND.exec(rule.cssText)) {
+var extend = m[1];
+var extendor = self._findExtendor(extend, rule);
+if (extendor) {
+self._extendRule(rule, extendor);
+}
+}
+}
+rule.cssText = rule.cssText.replace(self.rx.EXTEND, '');
+});
+return styleUtil.toCssText(rules, function (rule) {
+if (rule.selector.match(self.rx.STRIP)) {
+rule.cssText = '';
+}
+}, true);
+},
+_mapRule: function (rule) {
+if (rule.parent) {
+var map = rule.parent.map || (rule.parent.map = {});
+var parts = rule.selector.split(',');
+for (var i = 0, p; i < parts.length; i++) {
+p = parts[i];
+map[p.trim()] = rule;
+}
+return map;
+}
+},
+_findExtendor: function (extend, rule) {
+return rule.parent && rule.parent.map && rule.parent.map[extend] || this._findExtendor(extend, rule.parent);
+},
+_extendRule: function (target, source) {
+if (target.parent !== source.parent) {
+this._cloneAndAddRuleToParent(source, target.parent);
+}
+target.extends = target.extends || (target.extends = []);
+target.extends.push(source);
+source.selector = source.selector.replace(this.rx.STRIP, '');
+source.selector = (source.selector && source.selector + ',\n') + target.selector;
+if (source.extends) {
+source.extends.forEach(function (e) {
+this._extendRule(target, e);
+}, this);
+}
+},
+_cloneAndAddRuleToParent: function (rule, parent) {
+rule = Object.create(rule);
+rule.parent = parent;
+if (rule.extends) {
+rule.extends = rule.extends.slice();
+}
+parent.rules.push(rule);
+},
+rx: {
+EXTEND: /@extends\(([^)]*)\)\s*?;/gim,
+STRIP: /%[^,]*$/
+}
+};
+}();
+(function () {
+var prepElement = Polymer.Base._prepElement;
+var nativeShadow = Polymer.Settings.useNativeShadow;
+var styleUtil = Polymer.StyleUtil;
+var styleTransformer = Polymer.StyleTransformer;
+var styleExtends = Polymer.StyleExtends;
+Polymer.Base._addFeature({
+_prepElement: function (element) {
+if (this._encapsulateStyle) {
+styleTransformer.element(element, this.is, this._scopeCssViaAttr);
+}
+prepElement.call(this, element);
+},
+_prepStyles: function () {
+if (this._encapsulateStyle === undefined) {
+this._encapsulateStyle = !nativeShadow && Boolean(this._template);
+}
+this._styles = this._collectStyles();
+var cssText = styleTransformer.elementStyles(this);
+if (cssText && this._template) {
+var style = styleUtil.applyCss(cssText, this.is, nativeShadow ? this._template.content : null);
+if (!nativeShadow) {
+this._scopeStyle = style;
+}
+}
+},
+_collectStyles: function () {
+var styles = [];
+var cssText = '', m$ = this.styleModules;
+if (m$) {
+for (var i = 0, l = m$.length, m; i < l && (m = m$[i]); i++) {
+cssText += styleUtil.cssFromModule(m);
+}
+}
+cssText += styleUtil.cssFromModule(this.is);
+if (cssText) {
+var style = document.createElement('style');
+style.textContent = cssText;
+if (styleExtends.hasExtends(style.textContent)) {
+cssText = styleExtends.transform(style);
+}
+styles.push(style);
+}
+return styles;
+},
+_elementAdd: function (node) {
+if (this._encapsulateStyle) {
+if (node.__styleScoped) {
+node.__styleScoped = false;
+} else {
+styleTransformer.dom(node, this.is, this._scopeCssViaAttr);
+}
+}
+},
+_elementRemove: function (node) {
+if (this._encapsulateStyle) {
+styleTransformer.dom(node, this.is, this._scopeCssViaAttr, true);
+}
+},
+scopeSubtree: function (container, shouldObserve) {
+if (nativeShadow) {
+return;
+}
+var self = this;
+var scopify = function (node) {
+if (node.nodeType === Node.ELEMENT_NODE) {
+node.className = self._scopeElementClass(node, node.className);
+var n$ = node.querySelectorAll('*');
+Array.prototype.forEach.call(n$, function (n) {
+n.className = self._scopeElementClass(n, n.className);
+});
+}
+};
+scopify(container);
+if (shouldObserve) {
+var mo = new MutationObserver(function (mxns) {
+mxns.forEach(function (m) {
+if (m.addedNodes) {
+for (var i = 0; i < m.addedNodes.length; i++) {
+scopify(m.addedNodes[i]);
+}
+}
+});
+});
+mo.observe(container, {
+childList: true,
+subtree: true
+});
+return mo;
+}
+}
+});
+}());
+Polymer.StyleProperties = function () {
+'use strict';
+var nativeShadow = Polymer.Settings.useNativeShadow;
+var matchesSelector = Polymer.DomApi.matchesSelector;
+var styleUtil = Polymer.StyleUtil;
+var styleTransformer = Polymer.StyleTransformer;
+return {
+decorateStyles: function (styles) {
+var self = this, props = {};
+styleUtil.forRulesInStyles(styles, function (rule) {
+self.decorateRule(rule);
+self.collectPropertiesInCssText(rule.propertyInfo.cssText, props);
+});
+var names = [];
+for (var i in props) {
+names.push(i);
+}
+return names;
+},
+decorateRule: function (rule) {
+if (rule.propertyInfo) {
+return rule.propertyInfo;
+}
+var info = {}, properties = {};
+var hasProperties = this.collectProperties(rule, properties);
+if (hasProperties) {
+info.properties = properties;
+rule.rules = null;
+}
+info.cssText = this.collectCssText(rule);
+rule.propertyInfo = info;
+return info;
+},
+collectProperties: function (rule, properties) {
+var info = rule.propertyInfo;
+if (info) {
+if (info.properties) {
+Polymer.Base.mixin(properties, info.properties);
+return true;
+}
+} else {
+var m, rx = this.rx.VAR_ASSIGN;
+var cssText = rule.parsedCssText;
+var any;
+while (m = rx.exec(cssText)) {
+properties[m[1]] = (m[2] || m[3]).trim();
+any = true;
+}
+return any;
+}
+},
+collectCssText: function (rule) {
+var customCssText = '';
+var cssText = rule.parsedCssText;
+cssText = cssText.replace(this.rx.BRACKETED, '').replace(this.rx.VAR_ASSIGN, '');
+var parts = cssText.split(';');
+for (var i = 0, p; i < parts.length; i++) {
+p = parts[i];
+if (p.match(this.rx.MIXIN_MATCH) || p.match(this.rx.VAR_MATCH)) {
+customCssText += p + ';\n';
+}
+}
+return customCssText;
+},
+collectPropertiesInCssText: function (cssText, props) {
+var m;
+while (m = this.rx.VAR_CAPTURE.exec(cssText)) {
+props[m[1]] = true;
+var def = m[2];
+if (def && def.match(this.rx.IS_VAR)) {
+props[def] = true;
+}
+}
+},
+reify: function (props) {
+var names = Object.getOwnPropertyNames(props);
+for (var i = 0, n; i < names.length; i++) {
+n = names[i];
+props[n] = this.valueForProperty(props[n], props);
+}
+},
+valueForProperty: function (property, props) {
+if (property) {
+if (property.indexOf(';') >= 0) {
+property = this.valueForProperties(property, props);
+} else {
+var self = this;
+var fn = function (all, prefix, value, fallback) {
+var propertyValue = self.valueForProperty(props[value], props) || (props[fallback] ? self.valueForProperty(props[fallback], props) : fallback);
+return prefix + (propertyValue || '');
+};
+property = property.replace(this.rx.VAR_MATCH, fn);
+}
+}
+return property && property.trim() || '';
+},
+valueForProperties: function (property, props) {
+var parts = property.split(';');
+for (var i = 0, p, m; i < parts.length; i++) {
+if (p = parts[i]) {
+m = p.match(this.rx.MIXIN_MATCH);
+if (m) {
+p = this.valueForProperty(props[m[1]], props);
+} else {
+var pp = p.split(':');
+if (pp[1]) {
+pp[1] = pp[1].trim();
+pp[1] = this.valueForProperty(pp[1], props) || pp[1];
+}
+p = pp.join(':');
+}
+parts[i] = p && p.lastIndexOf(';') === p.length - 1 ? p.slice(0, -1) : p || '';
+}
+}
+return parts.join(';');
+},
+applyProperties: function (rule, props) {
+var output = '';
+if (!rule.propertyInfo) {
+this.decorateRule(rule);
+}
+if (rule.propertyInfo.cssText) {
+output = this.valueForProperties(rule.propertyInfo.cssText, props);
+}
+rule.cssText = output;
+},
+propertyDataFromStyles: function (styles, element) {
+var props = {}, self = this;
+var o = [], i = 0;
+styleUtil.forRulesInStyles(styles, function (rule) {
+if (!rule.propertyInfo) {
+self.decorateRule(rule);
+}
+if (element && rule.propertyInfo.properties && matchesSelector.call(element, rule.transformedSelector || rule.parsedSelector)) {
+self.collectProperties(rule, props);
+addToBitMask(i, o);
+}
+i++;
+});
+return {
+properties: props,
+key: o
+};
+},
+scopePropertiesFromStyles: function (styles) {
+if (!styles._scopeStyleProperties) {
+styles._scopeStyleProperties = this.selectedPropertiesFromStyles(styles, this.SCOPE_SELECTORS);
+}
+return styles._scopeStyleProperties;
+},
+hostPropertiesFromStyles: function (styles) {
+if (!styles._hostStyleProperties) {
+styles._hostStyleProperties = this.selectedPropertiesFromStyles(styles, this.HOST_SELECTORS);
+}
+return styles._hostStyleProperties;
+},
+selectedPropertiesFromStyles: function (styles, selectors) {
+var props = {}, self = this;
+styleUtil.forRulesInStyles(styles, function (rule) {
+if (!rule.propertyInfo) {
+self.decorateRule(rule);
+}
+for (var i = 0; i < selectors.length; i++) {
+if (rule.parsedSelector === selectors[i]) {
+self.collectProperties(rule, props);
+return;
+}
+}
+});
+return props;
+},
+transformStyles: function (element, properties, scopeSelector) {
+var self = this;
+var hostSelector = styleTransformer._calcHostScope(element.is, element.extends);
+var rxHostSelector = element.extends ? '\\' + hostSelector.slice(0, -1) + '\\]' : hostSelector;
+var hostRx = new RegExp(this.rx.HOST_PREFIX + rxHostSelector + this.rx.HOST_SUFFIX);
+return styleTransformer.elementStyles(element, function (rule) {
+self.applyProperties(rule, properties);
+if (rule.cssText && !nativeShadow) {
+self._scopeSelector(rule, hostRx, hostSelector, element._scopeCssViaAttr, scopeSelector);
+}
+});
+},
+_scopeSelector: function (rule, hostRx, hostSelector, viaAttr, scopeId) {
+rule.transformedSelector = rule.transformedSelector || rule.selector;
+var selector = rule.transformedSelector;
+var scope = viaAttr ? '[' + styleTransformer.SCOPE_NAME + '~=' + scopeId + ']' : '.' + scopeId;
+var parts = selector.split(',');
+for (var i = 0, l = parts.length, p; i < l && (p = parts[i]); i++) {
+parts[i] = p.match(hostRx) ? p.replace(hostSelector, hostSelector + scope) : scope + ' ' + p;
+}
+rule.selector = parts.join(',');
+},
+applyElementScopeSelector: function (element, selector, old, viaAttr) {
+var c = viaAttr ? element.getAttribute(styleTransformer.SCOPE_NAME) : element.className;
+var v = old ? c.replace(old, selector) : (c ? c + ' ' : '') + this.XSCOPE_NAME + ' ' + selector;
+if (c !== v) {
+if (viaAttr) {
+element.setAttribute(styleTransformer.SCOPE_NAME, v);
+} else {
+element.className = v;
+}
+}
+},
+applyElementStyle: function (element, properties, selector, style) {
+var cssText = style ? style.textContent || '' : this.transformStyles(element, properties, selector);
+var s = element._customStyle;
+if (s && !nativeShadow && s !== style) {
+s._useCount--;
+if (s._useCount <= 0 && s.parentNode) {
+s.parentNode.removeChild(s);
+}
+}
+if (nativeShadow || (!style || !style.parentNode)) {
+if (nativeShadow && element._customStyle) {
+element._customStyle.textContent = cssText;
+style = element._customStyle;
+} else if (cssText) {
+style = styleUtil.applyCss(cssText, selector, nativeShadow ? element.root : null, element._scopeStyle);
+}
+}
+if (style) {
+style._useCount = style._useCount || 0;
+if (element._customStyle != style) {
+style._useCount++;
+}
+element._customStyle = style;
+}
+return style;
+},
+mixinCustomStyle: function (props, customStyle) {
+var v;
+for (var i in customStyle) {
+v = customStyle[i];
+if (v || v === 0) {
+props[i] = v;
+}
+}
+},
+rx: {
+VAR_ASSIGN: /(?:^|[;\n]\s*)(--[\w-]*?):\s*(?:([^;{]*)|{([^}]*)})(?:(?=[;\n])|$)/gi,
+MIXIN_MATCH: /(?:^|\W+)@apply[\s]*\(([^)]*)\)/i,
+VAR_MATCH: /(^|\W+)var\([\s]*([^,)]*)[\s]*,?[\s]*((?:[^,)]*)|(?:[^;]*\([^;)]*\)))[\s]*?\)/gi,
+VAR_CAPTURE: /\([\s]*(--[^,\s)]*)(?:,[\s]*(--[^,\s)]*))?(?:\)|,)/gi,
+IS_VAR: /^--/,
+BRACKETED: /\{[^}]*\}/g,
+HOST_PREFIX: '(?:^|[^.#[:])',
+HOST_SUFFIX: '($|[.:[\\s>+~])'
+},
+HOST_SELECTORS: [':host'],
+SCOPE_SELECTORS: [':root'],
+XSCOPE_NAME: 'x-scope'
+};
+function addToBitMask(n, bits) {
+var o = parseInt(n / 32);
+var v = 1 << n % 32;
+bits[o] = (bits[o] || 0) | v;
+}
+}();
+(function () {
+Polymer.StyleCache = function () {
+this.cache = {};
+};
+Polymer.StyleCache.prototype = {
+MAX: 100,
+store: function (is, data, keyValues, keyStyles) {
+data.keyValues = keyValues;
+data.styles = keyStyles;
+var s$ = this.cache[is] = this.cache[is] || [];
+s$.push(data);
+if (s$.length > this.MAX) {
+s$.shift();
+}
+},
+retrieve: function (is, keyValues, keyStyles) {
+var cache = this.cache[is];
+if (cache) {
+for (var i = cache.length - 1, data; i >= 0; i--) {
+data = cache[i];
+if (keyStyles === data.styles && this._objectsEqual(keyValues, data.keyValues)) {
+return data;
+}
+}
+}
+},
+clear: function () {
+this.cache = {};
+},
+_objectsEqual: function (target, source) {
+var t, s;
+for (var i in target) {
+t = target[i], s = source[i];
+if (!(typeof t === 'object' && t ? this._objectsStrictlyEqual(t, s) : t === s)) {
+return false;
+}
+}
+if (Array.isArray(target)) {
+return target.length === source.length;
+}
+return true;
+},
+_objectsStrictlyEqual: function (target, source) {
+return this._objectsEqual(target, source) && this._objectsEqual(source, target);
+}
+};
+}());
+Polymer.StyleDefaults = function () {
+var styleProperties = Polymer.StyleProperties;
+var styleUtil = Polymer.StyleUtil;
+var StyleCache = Polymer.StyleCache;
+var api = {
+_styles: [],
+_properties: null,
+customStyle: {},
+_styleCache: new StyleCache(),
+addStyle: function (style) {
+this._styles.push(style);
+this._properties = null;
+},
+get _styleProperties() {
+if (!this._properties) {
+styleProperties.decorateStyles(this._styles);
+this._styles._scopeStyleProperties = null;
+this._properties = styleProperties.scopePropertiesFromStyles(this._styles);
+styleProperties.mixinCustomStyle(this._properties, this.customStyle);
+styleProperties.reify(this._properties);
+}
+return this._properties;
+},
+_needsStyleProperties: function () {
+},
+_computeStyleProperties: function () {
+return this._styleProperties;
+},
+updateStyles: function (properties) {
+this._properties = null;
+if (properties) {
+Polymer.Base.mixin(this.customStyle, properties);
+}
+this._styleCache.clear();
+for (var i = 0, s; i < this._styles.length; i++) {
+s = this._styles[i];
+s = s.__importElement || s;
+s._apply();
+}
+}
+};
+return api;
+}();
+(function () {
+'use strict';
+var serializeValueToAttribute = Polymer.Base.serializeValueToAttribute;
+var propertyUtils = Polymer.StyleProperties;
+var styleTransformer = Polymer.StyleTransformer;
+var styleUtil = Polymer.StyleUtil;
+var styleDefaults = Polymer.StyleDefaults;
+var nativeShadow = Polymer.Settings.useNativeShadow;
+Polymer.Base._addFeature({
+_prepStyleProperties: function () {
+this._ownStylePropertyNames = this._styles ? propertyUtils.decorateStyles(this._styles) : [];
+},
+customStyle: {},
+_setupStyleProperties: function () {
+this.customStyle = {};
+},
+_needsStyleProperties: function () {
+return Boolean(this._ownStylePropertyNames && this._ownStylePropertyNames.length);
+},
+_beforeAttached: function () {
+if (!this._scopeSelector && this._needsStyleProperties()) {
+this._updateStyleProperties();
+}
+},
+_findStyleHost: function () {
+var e = this, root;
+while (root = Polymer.dom(e).getOwnerRoot()) {
+if (Polymer.isInstance(root.host)) {
+return root.host;
+}
+e = root.host;
+}
+return styleDefaults;
+},
+_updateStyleProperties: function () {
+var info, scope = this._findStyleHost();
+if (!scope._styleCache) {
+scope._styleCache = new Polymer.StyleCache();
+}
+var scopeData = propertyUtils.propertyDataFromStyles(scope._styles, this);
+scopeData.key.customStyle = this.customStyle;
+info = scope._styleCache.retrieve(this.is, scopeData.key, this._styles);
+var scopeCached = Boolean(info);
+if (scopeCached) {
+this._styleProperties = info._styleProperties;
+} else {
+this._computeStyleProperties(scopeData.properties);
+}
+this._computeOwnStyleProperties();
+if (!scopeCached) {
+info = styleCache.retrieve(this.is, this._ownStyleProperties, this._styles);
+}
+var globalCached = Boolean(info) && !scopeCached;
+var style = this._applyStyleProperties(info);
+if (!scopeCached) {
+style = style && nativeShadow ? style.cloneNode(true) : style;
+info = {
+style: style,
+_scopeSelector: this._scopeSelector,
+_styleProperties: this._styleProperties
+};
+scopeData.key.customStyle = {};
+this.mixin(scopeData.key.customStyle, this.customStyle);
+scope._styleCache.store(this.is, info, scopeData.key, this._styles);
+if (!globalCached) {
+styleCache.store(this.is, Object.create(info), this._ownStyleProperties, this._styles);
+}
+}
+},
+_computeStyleProperties: function (scopeProps) {
+var scope = this._findStyleHost();
+if (!scope._styleProperties) {
+scope._computeStyleProperties();
+}
+var props = Object.create(scope._styleProperties);
+this.mixin(props, propertyUtils.hostPropertiesFromStyles(this._styles));
+scopeProps = scopeProps || propertyUtils.propertyDataFromStyles(scope._styles, this).properties;
+this.mixin(props, scopeProps);
+this.mixin(props, propertyUtils.scopePropertiesFromStyles(this._styles));
+propertyUtils.mixinCustomStyle(props, this.customStyle);
+propertyUtils.reify(props);
+this._styleProperties = props;
+},
+_computeOwnStyleProperties: function () {
+var props = {};
+for (var i = 0, n; i < this._ownStylePropertyNames.length; i++) {
+n = this._ownStylePropertyNames[i];
+props[n] = this._styleProperties[n];
+}
+this._ownStyleProperties = props;
+},
+_scopeCount: 0,
+_applyStyleProperties: function (info) {
+var oldScopeSelector = this._scopeSelector;
+this._scopeSelector = info ? info._scopeSelector : this.is + '-' + this.__proto__._scopeCount++;
+var style = propertyUtils.applyElementStyle(this, this._styleProperties, this._scopeSelector, info && info.style);
+if (!nativeShadow) {
+propertyUtils.applyElementScopeSelector(this, this._scopeSelector, oldScopeSelector, this._scopeCssViaAttr);
+}
+return style;
+},
+serializeValueToAttribute: function (value, attribute, node) {
+node = node || this;
+if (attribute === 'class' && !nativeShadow) {
+var host = node === this ? this.domHost || this.dataHost : this;
+if (host) {
+value = host._scopeElementClass(node, value);
+}
+}
+node = Polymer.dom(node);
+serializeValueToAttribute.call(this, value, attribute, node);
+},
+_scopeElementClass: function (element, selector) {
+if (!nativeShadow && !this._scopeCssViaAttr) {
+selector += (selector ? ' ' : '') + SCOPE_NAME + ' ' + this.is + (element._scopeSelector ? ' ' + XSCOPE_NAME + ' ' + element._scopeSelector : '');
+}
+return selector;
+},
+updateStyles: function (properties) {
+if (this.isAttached) {
+if (properties) {
+this.mixin(this.customStyle, properties);
+}
+if (this._needsStyleProperties()) {
+this._updateStyleProperties();
+} else {
+this._styleProperties = null;
+}
+if (this._styleCache) {
+this._styleCache.clear();
+}
+this._updateRootStyles();
+}
+},
+_updateRootStyles: function (root) {
+root = root || this.root;
+var c$ = Polymer.dom(root)._query(function (e) {
+return e.shadyRoot || e.shadowRoot;
+});
+for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) {
+if (c.updateStyles) {
+c.updateStyles();
+}
+}
+}
+});
+Polymer.updateStyles = function (properties) {
+styleDefaults.updateStyles(properties);
+Polymer.Base._updateRootStyles(document);
+};
+var styleCache = new Polymer.StyleCache();
+Polymer.customStyleCache = styleCache;
+var SCOPE_NAME = styleTransformer.SCOPE_NAME;
+var XSCOPE_NAME = propertyUtils.XSCOPE_NAME;
+}());
+Polymer.Base._addFeature({
+_registerFeatures: function () {
+this._prepIs();
+this._prepAttributes();
+this._prepConstructor();
+this._prepTemplate();
+this._prepStyles();
+this._prepStyleProperties();
+this._prepAnnotations();
+this._prepEffects();
+this._prepBehaviors();
+this._prepBindings();
+this._prepShady();
+},
+_prepBehavior: function (b) {
+this._addPropertyEffects(b.properties);
+this._addComplexObserverEffects(b.observers);
+this._addHostAttributes(b.hostAttributes);
+},
+_initFeatures: function () {
+this._poolContent();
+this._setupConfigure();
+this._setupStyleProperties();
+this._pushHost();
+this._stampTemplate();
+this._popHost();
+this._marshalAnnotationReferences();
+this._setupDebouncers();
+this._marshalInstanceEffects();
+this._marshalHostAttributes();
+this._marshalBehaviors();
+this._marshalAttributes();
+this._tryReady();
+},
+_marshalBehavior: function (b) {
+this._listenListeners(b.listeners);
+}
+});
+(function () {
+var nativeShadow = Polymer.Settings.useNativeShadow;
+var propertyUtils = Polymer.StyleProperties;
+var styleUtil = Polymer.StyleUtil;
+var cssParse = Polymer.CssParse;
+var styleDefaults = Polymer.StyleDefaults;
+var styleTransformer = Polymer.StyleTransformer;
+Polymer({
+is: 'custom-style',
+extends: 'style',
+properties: { include: String },
+ready: function () {
+this._tryApply();
+},
+attached: function () {
+this._tryApply();
+},
+_tryApply: function () {
+if (!this._appliesToDocument) {
+if (this.parentNode && this.parentNode.localName !== 'dom-module') {
+this._appliesToDocument = true;
+var e = this.__appliedElement || this;
+styleDefaults.addStyle(e);
+if (e.textContent || this.include) {
+this._apply();
+} else {
+var observer = new MutationObserver(function () {
+observer.disconnect();
+this._apply();
+}.bind(this));
+observer.observe(e, { childList: true });
+}
+}
+}
+},
+_apply: function () {
+var e = this.__appliedElement || this;
+if (this.include) {
+e.textContent = styleUtil.cssFromModules(this.include, true) + e.textContent;
+}
+if (e.textContent) {
+styleUtil.forEachStyleRule(styleUtil.rulesForStyle(e), function (rule) {
+styleTransformer.documentRule(rule);
+});
+this._applyCustomProperties(e);
+}
+},
+_applyCustomProperties: function (element) {
+this._computeStyleProperties();
+var props = this._styleProperties;
+var rules = styleUtil.rulesForStyle(element);
+element.textContent = styleUtil.toCssText(rules, function (rule) {
+var css = rule.cssText = rule.parsedCssText;
+if (rule.propertyInfo && rule.propertyInfo.cssText) {
+css = cssParse.removeCustomPropAssignment(css);
+rule.cssText = propertyUtils.valueForProperties(css, props);
+}
+});
+}
+});
+}());
+Polymer.Templatizer = {
+properties: { __hideTemplateChildren__: { observer: '_showHideChildren' } },
+_instanceProps: Polymer.nob,
+_parentPropPrefix: '_parent_',
+templatize: function (template) {
+if (!template._content) {
+template._content = template.content;
+}
+if (template._content._ctor) {
+this.ctor = template._content._ctor;
+this._prepParentProperties(this.ctor.prototype, template);
+return;
+}
+var archetype = Object.create(Polymer.Base);
+this._customPrepAnnotations(archetype, template);
+archetype._prepEffects();
+this._customPrepEffects(archetype);
+archetype._prepBehaviors();
+archetype._prepBindings();
+this._prepParentProperties(archetype, template);
+archetype._notifyPath = this._notifyPathImpl;
+archetype._scopeElementClass = this._scopeElementClassImpl;
+archetype.listen = this._listenImpl;
+archetype._showHideChildren = this._showHideChildrenImpl;
+var _constructor = this._constructorImpl;
+var ctor = function TemplateInstance(model, host) {
+_constructor.call(this, model, host);
+};
+ctor.prototype = archetype;
+archetype.constructor = ctor;
+template._content._ctor = ctor;
+this.ctor = ctor;
+},
+_getRootDataHost: function () {
+return this.dataHost && this.dataHost._rootDataHost || this.dataHost;
+},
+_showHideChildrenImpl: function (hide) {
+var c = this._children;
+for (var i = 0; i < c.length; i++) {
+var n = c[i];
+if (Boolean(hide) != Boolean(n.__hideTemplateChildren__)) {
+if (n.nodeType === Node.TEXT_NODE) {
+if (hide) {
+n.__polymerTextContent__ = n.textContent;
+n.textContent = '';
+} else {
+n.textContent = n.__polymerTextContent__;
+}
+} else if (n.style) {
+if (hide) {
+n.__polymerDisplay__ = n.style.display;
+n.style.display = 'none';
+} else {
+n.style.display = n.__polymerDisplay__;
+}
+}
+}
+n.__hideTemplateChildren__ = hide;
+}
+},
+_debounceTemplate: function (fn) {
+Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', fn));
+},
+_flushTemplates: function (debouncerExpired) {
+Polymer.dom.flush();
+},
+_customPrepEffects: function (archetype) {
+var parentProps = archetype._parentProps;
+for (var prop in parentProps) {
+archetype._addPropertyEffect(prop, 'function', this._createHostPropEffector(prop));
+}
+for (var prop in this._instanceProps) {
+archetype._addPropertyEffect(prop, 'function', this._createInstancePropEffector(prop));
+}
+},
+_customPrepAnnotations: function (archetype, template) {
+archetype._template = template;
+var c = template._content;
+if (!c._notes) {
+var rootDataHost = archetype._rootDataHost;
+if (rootDataHost) {
+Polymer.Annotations.prepElement = rootDataHost._prepElement.bind(rootDataHost);
+}
+c._notes = Polymer.Annotations.parseAnnotations(template);
+Polymer.Annotations.prepElement = null;
+this._processAnnotations(c._notes);
+}
+archetype._notes = c._notes;
+archetype._parentProps = c._parentProps;
+},
+_prepParentProperties: function (archetype, template) {
+var parentProps = this._parentProps = archetype._parentProps;
+if (this._forwardParentProp && parentProps) {
+var proto = archetype._parentPropProto;
+var prop;
+if (!proto) {
+for (prop in this._instanceProps) {
+delete parentProps[prop];
+}
+proto = archetype._parentPropProto = Object.create(null);
+if (template != this) {
+Polymer.Bind.prepareModel(proto);
+}
+for (prop in parentProps) {
+var parentProp = this._parentPropPrefix + prop;
+var effects = [
+{
+kind: 'function',
+effect: this._createForwardPropEffector(prop)
+},
+{ kind: 'notify' }
+];
+Polymer.Bind._createAccessors(proto, parentProp, effects);
+}
+}
+if (template != this) {
+Polymer.Bind.prepareInstance(template);
+template._forwardParentProp = this._forwardParentProp.bind(this);
+}
+this._extendTemplate(template, proto);
+}
+},
+_createForwardPropEffector: function (prop) {
+return function (source, value) {
+this._forwardParentProp(prop, value);
+};
+},
+_createHostPropEffector: function (prop) {
+var prefix = this._parentPropPrefix;
+return function (source, value) {
+this.dataHost[prefix + prop] = value;
+};
+},
+_createInstancePropEffector: function (prop) {
+return function (source, value, old, fromAbove) {
+if (!fromAbove) {
+this.dataHost._forwardInstanceProp(this, prop, value);
+}
+};
+},
+_extendTemplate: function (template, proto) {
+Object.getOwnPropertyNames(proto).forEach(function (n) {
+var val = template[n];
+var pd = Object.getOwnPropertyDescriptor(proto, n);
+Object.defineProperty(template, n, pd);
+if (val !== undefined) {
+template._propertySetter(n, val);
+}
+});
+},
+_showHideChildren: function (hidden) {
+},
+_forwardInstancePath: function (inst, path, value) {
+},
+_forwardInstanceProp: function (inst, prop, value) {
+},
+_notifyPathImpl: function (path, value) {
+var dataHost = this.dataHost;
+var dot = path.indexOf('.');
+var root = dot < 0 ? path : path.slice(0, dot);
+dataHost._forwardInstancePath.call(dataHost, this, path, value);
+if (root in dataHost._parentProps) {
+dataHost.notifyPath(dataHost._parentPropPrefix + path, value);
+}
+},
+_pathEffector: function (path, value, fromAbove) {
+if (this._forwardParentPath) {
+if (path.indexOf(this._parentPropPrefix) === 0) {
+this._forwardParentPath(path.substring(8), value);
+}
+}
+Polymer.Base._pathEffector.apply(this, arguments);
+},
+_constructorImpl: function (model, host) {
+this._rootDataHost = host._getRootDataHost();
+this._setupConfigure(model);
+this._pushHost(host);
+this.root = this.instanceTemplate(this._template);
+this.root.__noContent = !this._notes._hasContent;
+this.root.__styleScoped = true;
+this._popHost();
+this._marshalAnnotatedNodes();
+this._marshalInstanceEffects();
+this._marshalAnnotatedListeners();
+var children = [];
+for (var n = this.root.firstChild; n; n = n.nextSibling) {
+children.push(n);
+n._templateInstance = this;
+}
+this._children = children;
+if (host.__hideTemplateChildren__) {
+this._showHideChildren(true);
+}
+this._tryReady();
+},
+_listenImpl: function (node, eventName, methodName) {
+var model = this;
+var host = this._rootDataHost;
+var handler = host._createEventHandler(node, eventName, methodName);
+var decorated = function (e) {
+e.model = model;
+handler(e);
+};
+host._listen(node, eventName, decorated);
+},
+_scopeElementClassImpl: function (node, value) {
+var host = this._rootDataHost;
+if (host) {
+return host._scopeElementClass(node, value);
+}
+},
+stamp: function (model) {
+model = model || {};
+if (this._parentProps) {
+for (var prop in this._parentProps) {
+model[prop] = this[this._parentPropPrefix + prop];
+}
+}
+return new this.ctor(model, this);
+},
+modelForElement: function (el) {
+var model;
+while (el) {
+if (model = el._templateInstance) {
+if (model.dataHost != this) {
+el = model.dataHost;
+} else {
+return model;
+}
+} else {
+el = el.parentNode;
+}
+}
+}
+};
+Polymer({
+is: 'dom-template',
+extends: 'template',
+behaviors: [Polymer.Templatizer],
+ready: function () {
+this.templatize(this);
+}
+});
+Polymer._collections = new WeakMap();
+Polymer.Collection = function (userArray) {
+Polymer._collections.set(userArray, this);
+this.userArray = userArray;
+this.store = userArray.slice();
+this.initMap();
+};
+Polymer.Collection.prototype = {
+constructor: Polymer.Collection,
+initMap: function () {
+var omap = this.omap = new WeakMap();
+var pmap = this.pmap = {};
+var s = this.store;
+for (var i = 0; i < s.length; i++) {
+var item = s[i];
+if (item && typeof item == 'object') {
+omap.set(item, i);
+} else {
+pmap[item] = i;
+}
+}
+},
+add: function (item) {
+var key = this.store.push(item) - 1;
+if (item && typeof item == 'object') {
+this.omap.set(item, key);
+} else {
+this.pmap[item] = key;
+}
+return key;
+},
+removeKey: function (key) {
+this._removeFromMap(this.store[key]);
+delete this.store[key];
+},
+_removeFromMap: function (item) {
+if (item && typeof item == 'object') {
+this.omap.delete(item);
+} else {
+delete this.pmap[item];
+}
+},
+remove: function (item) {
+var key = this.getKey(item);
+this.removeKey(key);
+return key;
+},
+getKey: function (item) {
+if (item && typeof item == 'object') {
+return this.omap.get(item);
+} else {
+return this.pmap[item];
+}
+},
+getKeys: function () {
+return Object.keys(this.store);
+},
+setItem: function (key, item) {
+var old = this.store[key];
+if (old) {
+this._removeFromMap(old);
+}
+if (item && typeof item == 'object') {
+this.omap.set(item, key);
+} else {
+this.pmap[item] = key;
+}
+this.store[key] = item;
+},
+getItem: function (key) {
+return this.store[key];
+},
+getItems: function () {
+var items = [], store = this.store;
+for (var key in store) {
+items.push(store[key]);
+}
+return items;
+},
+_applySplices: function (splices) {
+var keyMap = {}, key, i;
+splices.forEach(function (s) {
+s.addedKeys = [];
+for (i = 0; i < s.removed.length; i++) {
+key = this.getKey(s.removed[i]);
+keyMap[key] = keyMap[key] ? null : -1;
+}
+for (i = 0; i < s.addedCount; i++) {
+var item = this.userArray[s.index + i];
+key = this.getKey(item);
+key = key === undefined ? this.add(item) : key;
+keyMap[key] = keyMap[key] ? null : 1;
+s.addedKeys.push(key);
+}
+}, this);
+var removed = [];
+var added = [];
+for (var key in keyMap) {
+if (keyMap[key] < 0) {
+this.removeKey(key);
+removed.push(key);
+}
+if (keyMap[key] > 0) {
+added.push(key);
+}
+}
+return [{
+removed: removed,
+added: added
+}];
+}
+};
+Polymer.Collection.get = function (userArray) {
+return Polymer._collections.get(userArray) || new Polymer.Collection(userArray);
+};
+Polymer.Collection.applySplices = function (userArray, splices) {
+var coll = Polymer._collections.get(userArray);
+return coll ? coll._applySplices(splices) : null;
+};
+Polymer({
+is: 'dom-repeat',
+extends: 'template',
+properties: {
+items: { type: Array },
+as: {
+type: String,
+value: 'item'
+},
+indexAs: {
+type: String,
+value: 'index'
+},
+sort: {
+type: Function,
+observer: '_sortChanged'
+},
+filter: {
+type: Function,
+observer: '_filterChanged'
+},
+observe: {
+type: String,
+observer: '_observeChanged'
+},
+delay: Number
+},
+behaviors: [Polymer.Templatizer],
+observers: ['_itemsChanged(items.*)'],
+created: function () {
+this._instances = [];
+},
+detached: function () {
+for (var i = 0; i < this._instances.length; i++) {
+this._detachRow(i);
+}
+},
+attached: function () {
+var parentNode = Polymer.dom(this).parentNode;
+for (var i = 0; i < this._instances.length; i++) {
+Polymer.dom(parentNode).insertBefore(this._instances[i].root, this);
+}
+},
+ready: function () {
+this._instanceProps = { __key__: true };
+this._instanceProps[this.as] = true;
+this._instanceProps[this.indexAs] = true;
+if (!this.ctor) {
+this.templatize(this);
+}
+},
+_sortChanged: function () {
+var dataHost = this._getRootDataHost();
+var sort = this.sort;
+this._sortFn = sort && (typeof sort == 'function' ? sort : function () {
+return dataHost[sort].apply(dataHost, arguments);
+});
+this._needFullRefresh = true;
+if (this.items) {
+this._debounceTemplate(this._render);
+}
+},
+_filterChanged: function () {
+var dataHost = this._getRootDataHost();
+var filter = this.filter;
+this._filterFn = filter && (typeof filter == 'function' ? filter : function () {
+return dataHost[filter].apply(dataHost, arguments);
+});
+this._needFullRefresh = true;
+if (this.items) {
+this._debounceTemplate(this._render);
+}
+},
+_observeChanged: function () {
+this._observePaths = this.observe && this.observe.replace('.*', '.').split(' ');
+},
+_itemsChanged: function (change) {
+if (change.path == 'items') {
+if (Array.isArray(this.items)) {
+this.collection = Polymer.Collection.get(this.items);
+} else if (!this.items) {
+this.collection = null;
+} else {
+this._error(this._logf('dom-repeat', 'expected array for `items`,' + ' found', this.items));
+}
+this._keySplices = [];
+this._indexSplices = [];
+this._needFullRefresh = true;
+this._debounceTemplate(this._render);
+} else if (change.path == 'items.splices') {
+this._keySplices = this._keySplices.concat(change.value.keySplices);
+this._indexSplices = this._indexSplices.concat(change.value.indexSplices);
+this._debounceTemplate(this._render);
+} else {
+var subpath = change.path.slice(6);
+this._forwardItemPath(subpath, change.value);
+this._checkObservedPaths(subpath);
+}
+},
+_checkObservedPaths: function (path) {
+if (this._observePaths) {
+path = path.substring(path.indexOf('.') + 1);
+var paths = this._observePaths;
+for (var i = 0; i < paths.length; i++) {
+if (path.indexOf(paths[i]) === 0) {
+this._needFullRefresh = true;
+if (this.delay) {
+this.debounce('render', this._render, this.delay);
+} else {
+this._debounceTemplate(this._render);
+}
+return;
+}
+}
+}
+},
+render: function () {
+this._needFullRefresh = true;
+this._debounceTemplate(this._render);
+this._flushTemplates();
+},
+_render: function () {
+var c = this.collection;
+if (this._needFullRefresh) {
+this._applyFullRefresh();
+this._needFullRefresh = false;
+} else {
+if (this._sortFn) {
+this._applySplicesUserSort(this._keySplices);
+} else {
+if (this._filterFn) {
+this._applyFullRefresh();
+} else {
+this._applySplicesArrayOrder(this._indexSplices);
+}
+}
+}
+this._keySplices = [];
+this._indexSplices = [];
+var keyToIdx = this._keyToInstIdx = {};
+for (var i = 0; i < this._instances.length; i++) {
+var inst = this._instances[i];
+keyToIdx[inst.__key__] = i;
+inst.__setProperty(this.indexAs, i, true);
+}
+this.fire('dom-change');
+},
+_applyFullRefresh: function () {
+var c = this.collection;
+var keys;
+if (this._sortFn) {
+keys = c ? c.getKeys() : [];
+} else {
+keys = [];
+var items = this.items;
+if (items) {
+for (var i = 0; i < items.length; i++) {
+keys.push(c.getKey(items[i]));
+}
+}
+}
+if (this._filterFn) {
+keys = keys.filter(function (a) {
+return this._filterFn(c.getItem(a));
+}, this);
+}
+if (this._sortFn) {
+keys.sort(function (a, b) {
+return this._sortFn(c.getItem(a), c.getItem(b));
+}.bind(this));
+}
+for (var i = 0; i < keys.length; i++) {
+var key = keys[i];
+var inst = this._instances[i];
+if (inst) {
+inst.__setProperty('__key__', key, true);
+inst.__setProperty(this.as, c.getItem(key), true);
+} else {
+this._instances.push(this._insertRow(i, key));
+}
+}
+for (; i < this._instances.length; i++) {
+this._detachRow(i);
+}
+this._instances.splice(keys.length, this._instances.length - keys.length);
+},
+_keySort: function (a, b) {
+return this.collection.getKey(a) - this.collection.getKey(b);
+},
+_numericSort: function (a, b) {
+return a - b;
+},
+_applySplicesUserSort: function (splices) {
+var c = this.collection;
+var instances = this._instances;
+var keyMap = {};
+var pool = [];
+var sortFn = this._sortFn || this._keySort.bind(this);
+splices.forEach(function (s) {
+for (var i = 0; i < s.removed.length; i++) {
+var key = s.removed[i];
+keyMap[key] = keyMap[key] ? null : -1;
+}
+for (var i = 0; i < s.added.length; i++) {
+var key = s.added[i];
+keyMap[key] = keyMap[key] ? null : 1;
+}
+}, this);
+var removedIdxs = [];
+var addedKeys = [];
+for (var key in keyMap) {
+if (keyMap[key] === -1) {
+removedIdxs.push(this._keyToInstIdx[key]);
+}
+if (keyMap[key] === 1) {
+addedKeys.push(key);
+}
+}
+if (removedIdxs.length) {
+removedIdxs.sort(this._numericSort);
+for (var i = removedIdxs.length - 1; i >= 0; i--) {
+var idx = removedIdxs[i];
+if (idx !== undefined) {
+pool.push(this._detachRow(idx));
+instances.splice(idx, 1);
+}
+}
+}
+if (addedKeys.length) {
+if (this._filterFn) {
+addedKeys = addedKeys.filter(function (a) {
+return this._filterFn(c.getItem(a));
+}, this);
+}
+addedKeys.sort(function (a, b) {
+return this._sortFn(c.getItem(a), c.getItem(b));
+}.bind(this));
+var start = 0;
+for (var i = 0; i < addedKeys.length; i++) {
+start = this._insertRowUserSort(start, addedKeys[i], pool);
+}
+}
+},
+_insertRowUserSort: function (start, key, pool) {
+var c = this.collection;
+var item = c.getItem(key);
+var end = this._instances.length - 1;
+var idx = -1;
+var sortFn = this._sortFn || this._keySort.bind(this);
+while (start <= end) {
+var mid = start + end >> 1;
+var midKey = this._instances[mid].__key__;
+var cmp = sortFn(c.getItem(midKey), item);
+if (cmp < 0) {
+start = mid + 1;
+} else if (cmp > 0) {
+end = mid - 1;
+} else {
+idx = mid;
+break;
+}
+}
+if (idx < 0) {
+idx = end + 1;
+}
+this._instances.splice(idx, 0, this._insertRow(idx, key, pool));
+return idx;
+},
+_applySplicesArrayOrder: function (splices) {
+var pool = [];
+var c = this.collection;
+splices.forEach(function (s) {
+for (var i = 0; i < s.removed.length; i++) {
+var inst = this._detachRow(s.index + i);
+if (!inst.isPlaceholder) {
+pool.push(inst);
+}
+}
+this._instances.splice(s.index, s.removed.length);
+for (var i = 0; i < s.addedKeys.length; i++) {
+var inst = {
+isPlaceholder: true,
+key: s.addedKeys[i]
+};
+this._instances.splice(s.index + i, 0, inst);
+}
+}, this);
+for (var i = this._instances.length - 1; i >= 0; i--) {
+var inst = this._instances[i];
+if (inst.isPlaceholder) {
+this._instances[i] = this._insertRow(i, inst.key, pool, true);
+}
+}
+},
+_detachRow: function (idx) {
+var inst = this._instances[idx];
+if (!inst.isPlaceholder) {
+var parentNode = Polymer.dom(this).parentNode;
+for (var i = 0; i < inst._children.length; i++) {
+var el = inst._children[i];
+Polymer.dom(inst.root).appendChild(el);
+}
+}
+return inst;
+},
+_insertRow: function (idx, key, pool, replace) {
+var inst;
+if (inst = pool && pool.pop()) {
+inst.__setProperty(this.as, this.collection.getItem(key), true);
+inst.__setProperty('__key__', key, true);
+} else {
+inst = this._generateRow(idx, key);
+}
+var beforeRow = this._instances[replace ? idx + 1 : idx];
+var beforeNode = beforeRow ? beforeRow._children[0] : this;
+var parentNode = Polymer.dom(this).parentNode;
+Polymer.dom(parentNode).insertBefore(inst.root, beforeNode);
+return inst;
+},
+_generateRow: function (idx, key) {
+var model = { __key__: key };
+model[this.as] = this.collection.getItem(key);
+model[this.indexAs] = idx;
+var inst = this.stamp(model);
+return inst;
+},
+_showHideChildren: function (hidden) {
+for (var i = 0; i < this._instances.length; i++) {
+this._instances[i]._showHideChildren(hidden);
+}
+},
+_forwardInstanceProp: function (inst, prop, value) {
+if (prop == this.as) {
+var idx;
+if (this._sortFn || this._filterFn) {
+idx = this.items.indexOf(this.collection.getItem(inst.__key__));
+} else {
+idx = inst[this.indexAs];
+}
+this.set('items.' + idx, value);
+}
+},
+_forwardInstancePath: function (inst, path, value) {
+if (path.indexOf(this.as + '.') === 0) {
+this.notifyPath('items.' + inst.__key__ + '.' + path.slice(this.as.length + 1), value);
+}
+},
+_forwardParentProp: function (prop, value) {
+this._instances.forEach(function (inst) {
+inst.__setProperty(prop, value, true);
+}, this);
+},
+_forwardParentPath: function (path, value) {
+this._instances.forEach(function (inst) {
+inst.notifyPath(path, value, true);
+}, this);
+},
+_forwardItemPath: function (path, value) {
+if (this._keyToInstIdx) {
+var dot = path.indexOf('.');
+var key = path.substring(0, dot < 0 ? path.length : dot);
+var idx = this._keyToInstIdx[key];
+var inst = this._instances[idx];
+if (inst) {
+if (dot >= 0) {
+path = this.as + '.' + path.substring(dot + 1);
+inst.notifyPath(path, value, true);
+} else {
+inst.__setProperty(this.as, value, true);
+}
+}
+}
+},
+itemForElement: function (el) {
+var instance = this.modelForElement(el);
+return instance && instance[this.as];
+},
+keyForElement: function (el) {
+var instance = this.modelForElement(el);
+return instance && instance.__key__;
+},
+indexForElement: function (el) {
+var instance = this.modelForElement(el);
+return instance && instance[this.indexAs];
+}
+});
+Polymer({
+is: 'array-selector',
+properties: {
+items: {
+type: Array,
+observer: 'clearSelection'
+},
+multi: {
+type: Boolean,
+value: false,
+observer: 'clearSelection'
+},
+selected: {
+type: Object,
+notify: true
+},
+selectedItem: {
+type: Object,
+notify: true
+},
+toggle: {
+type: Boolean,
+value: false
+}
+},
+clearSelection: function () {
+if (Array.isArray(this.selected)) {
+for (var i = 0; i < this.selected.length; i++) {
+this.unlinkPaths('selected.' + i);
+}
+} else {
+this.unlinkPaths('selected');
+}
+if (this.multi) {
+if (!this.selected || this.selected.length) {
+this.selected = [];
+this._selectedColl = Polymer.Collection.get(this.selected);
+}
+} else {
+this.selected = null;
+this._selectedColl = null;
+}
+this.selectedItem = null;
+},
+isSelected: function (item) {
+if (this.multi) {
+return this._selectedColl.getKey(item) !== undefined;
+} else {
+return this.selected == item;
+}
+},
+deselect: function (item) {
+if (this.multi) {
+if (this.isSelected(item)) {
+var skey = this._selectedColl.getKey(item);
+this.arrayDelete('selected', item);
+this.unlinkPaths('selected.' + skey);
+}
+} else {
+this.selected = null;
+this.selectedItem = null;
+this.unlinkPaths('selected');
+this.unlinkPaths('selectedItem');
+}
+},
+select: function (item) {
+var icol = Polymer.Collection.get(this.items);
+var key = icol.getKey(item);
+if (this.multi) {
+if (this.isSelected(item)) {
+if (this.toggle) {
+this.deselect(item);
+}
+} else {
+this.push('selected', item);
+skey = this._selectedColl.getKey(item);
+this.linkPaths('selected.' + skey, 'items.' + key);
+}
+} else {
+if (this.toggle && item == this.selected) {
+this.deselect();
+} else {
+this.selected = item;
+this.selectedItem = item;
+this.linkPaths('selected', 'items.' + key);
+this.linkPaths('selectedItem', 'items.' + key);
+}
+}
+}
+});
+Polymer({
+is: 'dom-if',
+extends: 'template',
+properties: {
+'if': {
+type: Boolean,
+value: false,
+observer: '_queueRender'
+},
+restamp: {
+type: Boolean,
+value: false,
+observer: '_queueRender'
+}
+},
+behaviors: [Polymer.Templatizer],
+_queueRender: function () {
+this._debounceTemplate(this._render);
+},
+detached: function () {
+this._teardownInstance();
+},
+attached: function () {
+if (this.if && this.ctor) {
+this.async(this._ensureInstance);
+}
+},
+render: function () {
+this._flushTemplates();
+},
+_render: function () {
+if (this.if) {
+if (!this.ctor) {
+this.templatize(this);
+}
+this._ensureInstance();
+this._showHideChildren();
+} else if (this.restamp) {
+this._teardownInstance();
+}
+if (!this.restamp && this._instance) {
+this._showHideChildren();
+}
+if (this.if != this._lastIf) {
+this.fire('dom-change');
+this._lastIf = this.if;
+}
+},
+_ensureInstance: function () {
+if (!this._instance) {
+this._instance = this.stamp();
+var root = this._instance.root;
+var parent = Polymer.dom(Polymer.dom(this).parentNode);
+parent.insertBefore(root, this);
+}
+},
+_teardownInstance: function () {
+if (this._instance) {
+var c = this._instance._children;
+if (c) {
+var parent = Polymer.dom(Polymer.dom(c[0]).parentNode);
+c.forEach(function (n) {
+parent.removeChild(n);
+});
+}
+this._instance = null;
+}
+},
+_showHideChildren: function () {
+var hidden = this.__hideTemplateChildren__ || !this.if;
+if (this._instance) {
+this._instance._showHideChildren(hidden);
+}
+},
+_forwardParentProp: function (prop, value) {
+if (this._instance) {
+this._instance[prop] = value;
+}
+},
+_forwardParentPath: function (path, value) {
+if (this._instance) {
+this._instance.notifyPath(path, value, true);
+}
+}
+});
+Polymer({
+is: 'dom-bind',
+extends: 'template',
+created: function () {
+Polymer.RenderStatus.whenReady(this._markImportsReady.bind(this));
+},
+_ensureReady: function () {
+if (!this._readied) {
+this._readySelf();
+}
+},
+_markImportsReady: function () {
+this._importsReady = true;
+this._ensureReady();
+},
+_registerFeatures: function () {
+this._prepConstructor();
+},
+_insertChildren: function () {
+var parentDom = Polymer.dom(Polymer.dom(this).parentNode);
+parentDom.insertBefore(this.root, this);
+},
+_removeChildren: function () {
+if (this._children) {
+for (var i = 0; i < this._children.length; i++) {
+this.root.appendChild(this._children[i]);
+}
+}
+},
+_initFeatures: function () {
+},
+_scopeElementClass: function (element, selector) {
+if (this.dataHost) {
+return this.dataHost._scopeElementClass(element, selector);
+} else {
+return selector;
+}
+},
+_prepConfigure: function () {
+var config = {};
+for (var prop in this._propertyEffects) {
+config[prop] = this[prop];
+}
+this._setupConfigure = this._setupConfigure.bind(this, config);
+},
+attached: function () {
+if (this._importsReady) {
+this.render();
+}
+},
+detached: function () {
+this._removeChildren();
+},
+render: function () {
+this._ensureReady();
+if (!this._children) {
+this._template = this;
+this._prepAnnotations();
+this._prepEffects();
+this._prepBehaviors();
+this._prepConfigure();
+this._prepBindings();
+Polymer.Base._initFeatures.call(this);
+this._children = Array.prototype.slice.call(this.root.childNodes);
+}
+this._insertChildren();
+this.fire('dom-change');
+}
+});
+(function() {
+
+ 'use strict';
+
+ var SHADOW_WHEN_SCROLLING = 1;
+ var SHADOW_ALWAYS = 2;
+
+
+ var MODE_CONFIGS = {
+
+ outerScroll: {
+ 'scroll': true
+ },
+
+ shadowMode: {
+ 'standard': SHADOW_ALWAYS,
+ 'waterfall': SHADOW_WHEN_SCROLLING,
+ 'waterfall-tall': SHADOW_WHEN_SCROLLING
+ },
+
+ tallMode: {
+ 'waterfall-tall': true
+ }
+ };
+
+ Polymer({
+
+ is: 'paper-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`.
+ *
+ * <paper-header-panel on-content-scroll="scrollHandler">
+ * ...
+ * </paper-header-panel>
+ *
+ *
+ * scrollHandler: function(event) {
+ * var scroller = event.detail.target;
+ * console.log(scroller.scrollTop);
+ * }
+ *
+ * @event content-scroll
+ */
+
+ properties: {
+
+ /**
+ * 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 `paper-header-panel` including the
+ * header. This allows user to style the panel in such a way that the panel is
+ * partially covering the header.
+ *
+ * <paper-header-panel mode="cover">
+ * <paper-toolbar class="tall">
+ * <core-icon-button icon="menu"></core-icon-button>
+ * </paper-toolbar>
+ * <div class="content"></div>
+ * </paper-header-panel>
+ */
+ mode: {
+ type: String,
+ value: 'standard',
+ observer: '_modeChanged',
+ reflectToAttribute: true
+ },
+
+ /**
+ * If true, the drop-shadow is always shown no matter what mode is set to.
+ */
+ shadow: {
+ type: Boolean,
+ value: false
+ },
+
+ /**
+ * The class used in waterfall-tall mode. Change this if the header
+ * accepts a different class for toggling height, e.g. "medium-tall"
+ */
+ tallClass: {
+ type: String,
+ value: 'tall'
+ },
+
+ /**
+ * If true, the scroller is at the top
+ */
+ atTop: {
+ type: Boolean,
+ value: true,
+ readOnly: true
+ }
+ },
+
+ observers: [
+ '_computeDropShadowHidden(atTop, mode, shadow)'
+ ],
+
+ ready: function() {
+ this.scrollHandler = this._scroll.bind(this);
+ this._addListener();
+
+ // Run `scroll` logic once to initialze class names, etc.
+ this._keepScrollingState();
+ },
+
+ detached: function() {
+ this._removeListener();
+ },
+
+ /**
+ * Returns the header element
+ *
+ * @property header
+ * @type Object
+ */
+ get header() {
+ return Polymer.dom(this.$.headerContent).getDistributedNodes()[0];
+ },
+
+ /**
+ * Returns the scrollable element.
+ *
+ * @property scroller
+ * @type Object
+ */
+ get scroller() {
+ return this._getScrollerForMode(this.mode);
+ },
+
+ /**
+ * Returns true if the scroller has a visible shadow.
+ *
+ * @property visibleShadow
+ * @type Boolean
+ */
+ get visibleShadow() {
+ return this.$.dropShadow.classList.contains('has-shadow');
+ },
+
+ _computeDropShadowHidden: function(atTop, mode, shadow) {
+
+ var shadowMode = MODE_CONFIGS.shadowMode[mode];
+
+ if (this.shadow) {
+ this.toggleClass('has-shadow', true, this.$.dropShadow);
+
+ } else if (shadowMode === SHADOW_ALWAYS) {
+ this.toggleClass('has-shadow', true, this.$.dropShadow);
+
+ } else if (shadowMode === SHADOW_WHEN_SCROLLING && !atTop) {
+ this.toggleClass('has-shadow', true, this.$.dropShadow);
+
+ } else {
+ this.toggleClass('has-shadow', false, this.$.dropShadow);
+
+ }
+ },
+
+ _computeMainContainerClass: function(mode) {
+ // TODO: It will be useful to have a utility for classes
+ // e.g. Polymer.Utils.classes({ foo: true });
+
+ var classes = {};
+
+ classes['flex'] = mode !== 'cover';
+
+ return Object.keys(classes).filter(
+ function(className) {
+ return classes[className];
+ }).join(' ');
+ },
+
+ _addListener: function() {
+ this.scroller.addEventListener('scroll', this.scrollHandler, false);
+ },
+
+ _removeListener: function() {
+ this.scroller.removeEventListener('scroll', this.scrollHandler);
+ },
+
+ _modeChanged: function(newMode, oldMode) {
+ var configs = MODE_CONFIGS;
+ var header = this.header;
+ var animateDuration = 200;
+
+ if (header) {
+ // in tallMode it may add tallClass to the header; so do the cleanup
+ // when mode is changed from tallMode to not tallMode
+ if (configs.tallMode[oldMode] && !configs.tallMode[newMode]) {
+ header.classList.remove(this.tallClass);
+ this.async(function() {
+ header.classList.remove('animate');
+ }, animateDuration);
+ } else {
+ header.classList.toggle('animate', configs.tallMode[newMode]);
+ }
+ }
+ this._keepScrollingState();
+ },
+
+ _keepScrollingState: function() {
+ var main = this.scroller;
+ var header = this.header;
+
+ this._setAtTop(main.scrollTop === 0);
+
+ if (header && this.tallClass && MODE_CONFIGS.tallMode[this.mode]) {
+ this.toggleClass(this.tallClass, this.atTop ||
+ header.classList.contains(this.tallClass) &&
+ main.scrollHeight < this.offsetHeight, header);
+ }
+ },
+
+ _scroll: function() {
+ this._keepScrollingState();
+ this.fire('content-scroll', {target: this.scroller}, {bubbles: false});
+ },
+
+ _getScrollerForMode: function(mode) {
+ return MODE_CONFIGS.outerScroll[mode] ?
+ this : this.$.mainContainer;
+ }
+
+ });
+
+ })();
+Polymer({
+ is: 'paper-material',
+
+ properties: {
+
+ /**
+ * The z-depth of this element, from 0-5. Setting to 0 will remove the
+ * shadow, and each increasing number greater than 0 will be "deeper"
+ * than the last.
+ *
+ * @attribute elevation
+ * @type number
+ * @default 1
+ */
+ elevation: {
+ type: Number,
+ reflectToAttribute: true,
+ value: 1
+ },
+
+ /**
+ * Set this to true to animate the shadow when setting a new
+ * `elevation` value.
+ *
+ * @attribute animated
+ * @type boolean
+ * @default false
+ */
+ animated: {
+ type: Boolean,
+ reflectToAttribute: true,
+ value: false
+ }
+ }
+ });
+(function() {
+ 'use strict';
+
+ /**
+ * Chrome uses an older version of DOM Level 3 Keyboard Events
+ *
+ * Most keys are labeled as text, but some are Unicode codepoints.
+ * Values taken from: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/keyset.html#KeySet-Set
+ */
+ var KEY_IDENTIFIER = {
+ 'U+0009': 'tab',
+ 'U+001B': 'esc',
+ 'U+0020': 'space',
+ 'U+002A': '*',
+ 'U+0030': '0',
+ 'U+0031': '1',
+ 'U+0032': '2',
+ 'U+0033': '3',
+ 'U+0034': '4',
+ 'U+0035': '5',
+ 'U+0036': '6',
+ 'U+0037': '7',
+ 'U+0038': '8',
+ 'U+0039': '9',
+ 'U+0041': 'a',
+ 'U+0042': 'b',
+ 'U+0043': 'c',
+ 'U+0044': 'd',
+ 'U+0045': 'e',
+ 'U+0046': 'f',
+ 'U+0047': 'g',
+ 'U+0048': 'h',
+ 'U+0049': 'i',
+ 'U+004A': 'j',
+ 'U+004B': 'k',
+ 'U+004C': 'l',
+ 'U+004D': 'm',
+ 'U+004E': 'n',
+ 'U+004F': 'o',
+ 'U+0050': 'p',
+ 'U+0051': 'q',
+ 'U+0052': 'r',
+ 'U+0053': 's',
+ 'U+0054': 't',
+ 'U+0055': 'u',
+ 'U+0056': 'v',
+ 'U+0057': 'w',
+ 'U+0058': 'x',
+ 'U+0059': 'y',
+ 'U+005A': 'z',
+ 'U+007F': 'del'
+ };
+
+ /**
+ * Special table for KeyboardEvent.keyCode.
+ * KeyboardEvent.keyIdentifier is better, and KeyBoardEvent.key is even better
+ * than that.
+ *
+ * Values from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.keyCode#Value_of_keyCode
+ */
+ var KEY_CODE = {
+ 9: 'tab',
+ 13: 'enter',
+ 27: 'esc',
+ 33: 'pageup',
+ 34: 'pagedown',
+ 35: 'end',
+ 36: 'home',
+ 32: 'space',
+ 37: 'left',
+ 38: 'up',
+ 39: 'right',
+ 40: 'down',
+ 46: 'del',
+ 106: '*'
+ };
+
+ /**
+ * MODIFIER_KEYS maps the short name for modifier keys used in a key
+ * combo string to the property name that references those same keys
+ * in a KeyboardEvent instance.
+ */
+ var MODIFIER_KEYS = {
+ 'shift': 'shiftKey',
+ 'ctrl': 'ctrlKey',
+ 'alt': 'altKey',
+ 'meta': 'metaKey'
+ };
+
+ /**
+ * KeyboardEvent.key is mostly represented by printable character made by
+ * the keyboard, with unprintable keys labeled nicely.
+ *
+ * However, on OS X, Alt+char can make a Unicode character that follows an
+ * Apple-specific mapping. In this case, we
+ * fall back to .keyCode.
+ */
+ var KEY_CHAR = /[a-z0-9*]/;
+
+ /**
+ * Matches a keyIdentifier string.
+ */
+ var IDENT_CHAR = /U\+/;
+
+ /**
+ * Matches arrow keys in Gecko 27.0+
+ */
+ var ARROW_KEY = /^arrow/;
+
+ /**
+ * Matches space keys everywhere (notably including IE10's exceptional name
+ * `spacebar`).
+ */
+ var SPACE_KEY = /^space(bar)?/;
+
+ function transformKey(key) {
+ var validKey = '';
+ if (key) {
+ var lKey = key.toLowerCase();
+ if (lKey.length == 1) {
+ if (KEY_CHAR.test(lKey)) {
+ validKey = lKey;
+ }
+ } else if (ARROW_KEY.test(lKey)) {
+ validKey = lKey.replace('arrow', '');
+ } else if (SPACE_KEY.test(lKey)) {
+ validKey = 'space';
+ } else if (lKey == 'multiply') {
+ // numpad '*' can map to Multiply on IE/Windows
+ validKey = '*';
+ } else {
+ validKey = lKey;
+ }
+ }
+ return validKey;
+ }
+
+ function transformKeyIdentifier(keyIdent) {
+ var validKey = '';
+ if (keyIdent) {
+ if (IDENT_CHAR.test(keyIdent)) {
+ validKey = KEY_IDENTIFIER[keyIdent];
+ } else {
+ validKey = keyIdent.toLowerCase();
+ }
+ }
+ return validKey;
+ }
+
+ function transformKeyCode(keyCode) {
+ var validKey = '';
+ if (Number(keyCode)) {
+ if (keyCode >= 65 && keyCode <= 90) {
+ // ascii a-z
+ // lowercase is 32 offset from uppercase
+ validKey = String.fromCharCode(32 + keyCode);
+ } else if (keyCode >= 112 && keyCode <= 123) {
+ // function keys f1-f12
+ validKey = 'f' + (keyCode - 112);
+ } else if (keyCode >= 48 && keyCode <= 57) {
+ // top 0-9 keys
+ validKey = String(48 - keyCode);
+ } else if (keyCode >= 96 && keyCode <= 105) {
+ // num pad 0-9
+ validKey = String(96 - keyCode);
+ } else {
+ validKey = KEY_CODE[keyCode];
+ }
+ }
+ return validKey;
+ }
+
+ function normalizedKeyForEvent(keyEvent) {
+ // fall back from .key, to .keyIdentifier, to .keyCode, and then to
+ // .detail.key to support artificial keyboard events
+ return transformKey(keyEvent.key) ||
+ transformKeyIdentifier(keyEvent.keyIdentifier) ||
+ transformKeyCode(keyEvent.keyCode) ||
+ transformKey(keyEvent.detail.key) || '';
+ }
+
+ function keyComboMatchesEvent(keyCombo, keyEvent) {
+ return normalizedKeyForEvent(keyEvent) === keyCombo.key &&
+ !!keyEvent.shiftKey === !!keyCombo.shiftKey &&
+ !!keyEvent.ctrlKey === !!keyCombo.ctrlKey &&
+ !!keyEvent.altKey === !!keyCombo.altKey &&
+ !!keyEvent.metaKey === !!keyCombo.metaKey;
+ }
+
+ function parseKeyComboString(keyComboString) {
+ return keyComboString.split('+').reduce(function(parsedKeyCombo, keyComboPart) {
+ var eventParts = keyComboPart.split(':');
+ var keyName = eventParts[0];
+ var event = eventParts[1];
+
+ if (keyName in MODIFIER_KEYS) {
+ parsedKeyCombo[MODIFIER_KEYS[keyName]] = true;
+ } else {
+ parsedKeyCombo.key = keyName;
+ parsedKeyCombo.event = event || 'keydown';
+ }
+
+ return parsedKeyCombo;
+ }, {
+ combo: keyComboString.split(':').shift()
+ });
+ }
+
+ function parseEventString(eventString) {
+ return eventString.split(' ').map(function(keyComboString) {
+ return parseKeyComboString(keyComboString);
+ });
+ }
+
+
+ /**
+ * `Polymer.IronA11yKeysBehavior` provides a normalized interface for processing
+ * keyboard commands that pertain to [WAI-ARIA best practices](http://www.w3.org/TR/wai-aria-practices/#kbd_general_binding).
+ * The element takes care of browser differences with respect to Keyboard events
+ * and uses an expressive syntax to filter key presses.
+ *
+ * Use the `keyBindings` prototype property to express what combination of keys
+ * will trigger the event to fire.
+ *
+ * Use the `key-event-target` attribute to set up event handlers on a specific
+ * node.
+ * The `keys-pressed` event will fire when one of the key combinations set with the
+ * `keys` property is pressed.
+ *
+ * @demo demo/index.html
+ * @polymerBehavior
+ */
+ Polymer.IronA11yKeysBehavior = {
+ properties: {
+ /**
+ * The HTMLElement that will be firing relevant KeyboardEvents.
+ */
+ keyEventTarget: {
+ type: Object,
+ value: function() {
+ return this;
+ }
+ },
+
+ _boundKeyHandlers: {
+ type: Array,
+ value: function() {
+ return [];
+ }
+ },
+
+ // We use this due to a limitation in IE10 where instances will have
+ // own properties of everything on the "prototype".
+ _imperativeKeyBindings: {
+ type: Object,
+ value: function() {
+ return {};
+ }
+ }
+ },
+
+ observers: [
+ '_resetKeyEventListeners(keyEventTarget, _boundKeyHandlers)'
+ ],
+
+ keyBindings: {},
+
+ registered: function() {
+ this._prepKeyBindings();
+ },
+
+ attached: function() {
+ this._listenKeyEventListeners();
+ },
+
+ detached: function() {
+ this._unlistenKeyEventListeners();
+ },
+
+ /**
+ * Can be used to imperatively add a key binding to the implementing
+ * element. This is the imperative equivalent of declaring a keybinding
+ * in the `keyBindings` prototype property.
+ */
+ addOwnKeyBinding: function(eventString, handlerName) {
+ this._imperativeKeyBindings[eventString] = handlerName;
+ this._prepKeyBindings();
+ this._resetKeyEventListeners();
+ },
+
+ /**
+ * When called, will remove all imperatively-added key bindings.
+ */
+ removeOwnKeyBindings: function() {
+ this._imperativeKeyBindings = {};
+ this._prepKeyBindings();
+ this._resetKeyEventListeners();
+ },
+
+ keyboardEventMatchesKeys: function(event, eventString) {
+ var keyCombos = parseEventString(eventString);
+ var index;
+
+ for (index = 0; index < keyCombos.length; ++index) {
+ if (keyComboMatchesEvent(keyCombos[index], event)) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ _collectKeyBindings: function() {
+ var keyBindings = this.behaviors.map(function(behavior) {
+ return behavior.keyBindings;
+ });
+
+ if (keyBindings.indexOf(this.keyBindings) === -1) {
+ keyBindings.push(this.keyBindings);
+ }
+
+ return keyBindings;
+ },
+
+ _prepKeyBindings: function() {
+ this._keyBindings = {};
+
+ this._collectKeyBindings().forEach(function(keyBindings) {
+ for (var eventString in keyBindings) {
+ this._addKeyBinding(eventString, keyBindings[eventString]);
+ }
+ }, this);
+
+ for (var eventString in this._imperativeKeyBindings) {
+ this._addKeyBinding(eventString, this._imperativeKeyBindings[eventString]);
+ }
+ },
+
+ _addKeyBinding: function(eventString, handlerName) {
+ parseEventString(eventString).forEach(function(keyCombo) {
+ this._keyBindings[keyCombo.event] =
+ this._keyBindings[keyCombo.event] || [];
+
+ this._keyBindings[keyCombo.event].push([
+ keyCombo,
+ handlerName
+ ]);
+ }, this);
+ },
+
+ _resetKeyEventListeners: function() {
+ this._unlistenKeyEventListeners();
+
+ if (this.isAttached) {
+ this._listenKeyEventListeners();
+ }
+ },
+
+ _listenKeyEventListeners: function() {
+ Object.keys(this._keyBindings).forEach(function(eventName) {
+ var keyBindings = this._keyBindings[eventName];
+ var boundKeyHandler = this._onKeyBindingEvent.bind(this, keyBindings);
+
+ this._boundKeyHandlers.push([this.keyEventTarget, eventName, boundKeyHandler]);
+
+ this.keyEventTarget.addEventListener(eventName, boundKeyHandler);
+ }, this);
+ },
+
+ _unlistenKeyEventListeners: function() {
+ var keyHandlerTuple;
+ var keyEventTarget;
+ var eventName;
+ var boundKeyHandler;
+
+ while (this._boundKeyHandlers.length) {
+ // My kingdom for block-scope binding and destructuring assignment..
+ keyHandlerTuple = this._boundKeyHandlers.pop();
+ keyEventTarget = keyHandlerTuple[0];
+ eventName = keyHandlerTuple[1];
+ boundKeyHandler = keyHandlerTuple[2];
+
+ keyEventTarget.removeEventListener(eventName, boundKeyHandler);
+ }
+ },
+
+ _onKeyBindingEvent: function(keyBindings, event) {
+ keyBindings.forEach(function(keyBinding) {
+ var keyCombo = keyBinding[0];
+ var handlerName = keyBinding[1];
+
+ if (!event.defaultPrevented && keyComboMatchesEvent(keyCombo, event)) {
+ this._triggerKeyHandler(keyCombo, handlerName, event);
+ }
+ }, this);
+ },
+
+ _triggerKeyHandler: function(keyCombo, handlerName, keyboardEvent) {
+ var detail = Object.create(keyCombo);
+ detail.keyboardEvent = keyboardEvent;
+
+ this[handlerName].call(this, new CustomEvent(keyCombo.event, {
+ detail: detail
+ }));
+ }
+ };
+ })();
+(function() {
+ var Utility = {
+ distance: function(x1, y1, x2, y2) {
+ var xDelta = (x1 - x2);
+ var yDelta = (y1 - y2);
+
+ return Math.sqrt(xDelta * xDelta + yDelta * yDelta);
+ },
+
+ now: window.performance && window.performance.now ?
+ window.performance.now.bind(window.performance) : Date.now
+ };
+
+ /**
+ * @param {HTMLElement} element
+ * @constructor
+ */
+ function ElementMetrics(element) {
+ this.element = element;
+ this.width = this.boundingRect.width;
+ this.height = this.boundingRect.height;
+
+ this.size = Math.max(this.width, this.height);
+ }
+
+ ElementMetrics.prototype = {
+ get boundingRect () {
+ return this.element.getBoundingClientRect();
+ },
+
+ furthestCornerDistanceFrom: function(x, y) {
+ var topLeft = Utility.distance(x, y, 0, 0);
+ var topRight = Utility.distance(x, y, this.width, 0);
+ var bottomLeft = Utility.distance(x, y, 0, this.height);
+ var bottomRight = Utility.distance(x, y, this.width, this.height);
+
+ return Math.max(topLeft, topRight, bottomLeft, bottomRight);
+ }
+ };
+
+ /**
+ * @param {HTMLElement} element
+ * @constructor
+ */
+ function Ripple(element) {
+ this.element = element;
+ this.color = window.getComputedStyle(element).color;
+
+ this.wave = document.createElement('div');
+ this.waveContainer = document.createElement('div');
+ this.wave.style.backgroundColor = this.color;
+ this.wave.classList.add('wave');
+ this.waveContainer.classList.add('wave-container');
+ Polymer.dom(this.waveContainer).appendChild(this.wave);
+
+ this.resetInteractionState();
+ }
+
+ Ripple.MAX_RADIUS = 300;
+
+ Ripple.prototype = {
+ get recenters() {
+ return this.element.recenters;
+ },
+
+ get center() {
+ return this.element.center;
+ },
+
+ get mouseDownElapsed() {
+ var elapsed;
+
+ if (!this.mouseDownStart) {
+ return 0;
+ }
+
+ elapsed = Utility.now() - this.mouseDownStart;
+
+ if (this.mouseUpStart) {
+ elapsed -= this.mouseUpElapsed;
+ }
+
+ return elapsed;
+ },
+
+ get mouseUpElapsed() {
+ return this.mouseUpStart ?
+ Utility.now () - this.mouseUpStart : 0;
+ },
+
+ get mouseDownElapsedSeconds() {
+ return this.mouseDownElapsed / 1000;
+ },
+
+ get mouseUpElapsedSeconds() {
+ return this.mouseUpElapsed / 1000;
+ },
+
+ get mouseInteractionSeconds() {
+ return this.mouseDownElapsedSeconds + this.mouseUpElapsedSeconds;
+ },
+
+ get initialOpacity() {
+ return this.element.initialOpacity;
+ },
+
+ get opacityDecayVelocity() {
+ return this.element.opacityDecayVelocity;
+ },
+
+ get radius() {
+ var width2 = this.containerMetrics.width * this.containerMetrics.width;
+ var height2 = this.containerMetrics.height * this.containerMetrics.height;
+ var waveRadius = Math.min(
+ Math.sqrt(width2 + height2),
+ Ripple.MAX_RADIUS
+ ) * 1.1 + 5;
+
+ var duration = 1.1 - 0.2 * (waveRadius / Ripple.MAX_RADIUS);
+ var timeNow = this.mouseInteractionSeconds / duration;
+ var size = waveRadius * (1 - Math.pow(80, -timeNow));
+
+ return Math.abs(size);
+ },
+
+ get opacity() {
+ if (!this.mouseUpStart) {
+ return this.initialOpacity;
+ }
+
+ return Math.max(
+ 0,
+ this.initialOpacity - this.mouseUpElapsedSeconds * this.opacityDecayVelocity
+ );
+ },
+
+ get outerOpacity() {
+ // Linear increase in background opacity, capped at the opacity
+ // of the wavefront (waveOpacity).
+ var outerOpacity = this.mouseUpElapsedSeconds * 0.3;
+ var waveOpacity = this.opacity;
+
+ return Math.max(
+ 0,
+ Math.min(outerOpacity, waveOpacity)
+ );
+ },
+
+ get isOpacityFullyDecayed() {
+ return this.opacity < 0.01 &&
+ this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS);
+ },
+
+ get isRestingAtMaxRadius() {
+ return this.opacity >= this.initialOpacity &&
+ this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS);
+ },
+
+ get isAnimationComplete() {
+ return this.mouseUpStart ?
+ this.isOpacityFullyDecayed : this.isRestingAtMaxRadius;
+ },
+
+ get translationFraction() {
+ return Math.min(
+ 1,
+ this.radius / this.containerMetrics.size * 2 / Math.sqrt(2)
+ );
+ },
+
+ get xNow() {
+ if (this.xEnd) {
+ return this.xStart + this.translationFraction * (this.xEnd - this.xStart);
+ }
+
+ return this.xStart;
+ },
+
+ get yNow() {
+ if (this.yEnd) {
+ return this.yStart + this.translationFraction * (this.yEnd - this.yStart);
+ }
+
+ return this.yStart;
+ },
+
+ get isMouseDown() {
+ return this.mouseDownStart && !this.mouseUpStart;
+ },
+
+ resetInteractionState: function() {
+ this.maxRadius = 0;
+ this.mouseDownStart = 0;
+ this.mouseUpStart = 0;
+
+ this.xStart = 0;
+ this.yStart = 0;
+ this.xEnd = 0;
+ this.yEnd = 0;
+ this.slideDistance = 0;
+
+ this.containerMetrics = new ElementMetrics(this.element);
+ },
+
+ draw: function() {
+ var scale;
+ var translateString;
+ var dx;
+ var dy;
+
+ this.wave.style.opacity = this.opacity;
+
+ scale = this.radius / (this.containerMetrics.size / 2);
+ dx = this.xNow - (this.containerMetrics.width / 2);
+ dy = this.yNow - (this.containerMetrics.height / 2);
+
+
+ // 2d transform for safari because of border-radius and overflow:hidden clipping bug.
+ // https://bugs.webkit.org/show_bug.cgi?id=98538
+ this.waveContainer.style.webkitTransform = 'translate(' + dx + 'px, ' + dy + 'px)';
+ this.waveContainer.style.transform = 'translate3d(' + dx + 'px, ' + dy + 'px, 0)';
+ this.wave.style.webkitTransform = 'scale(' + scale + ',' + scale + ')';
+ this.wave.style.transform = 'scale3d(' + scale + ',' + scale + ',1)';
+ },
+
+ /** @param {Event=} event */
+ downAction: function(event) {
+ var xCenter = this.containerMetrics.width / 2;
+ var yCenter = this.containerMetrics.height / 2;
+
+ this.resetInteractionState();
+ this.mouseDownStart = Utility.now();
+
+ if (this.center) {
+ this.xStart = xCenter;
+ this.yStart = yCenter;
+ this.slideDistance = Utility.distance(
+ this.xStart, this.yStart, this.xEnd, this.yEnd
+ );
+ } else {
+ this.xStart = event ?
+ event.detail.x - this.containerMetrics.boundingRect.left :
+ this.containerMetrics.width / 2;
+ this.yStart = event ?
+ event.detail.y - this.containerMetrics.boundingRect.top :
+ this.containerMetrics.height / 2;
+ }
+
+ if (this.recenters) {
+ this.xEnd = xCenter;
+ this.yEnd = yCenter;
+ this.slideDistance = Utility.distance(
+ this.xStart, this.yStart, this.xEnd, this.yEnd
+ );
+ }
+
+ this.maxRadius = this.containerMetrics.furthestCornerDistanceFrom(
+ this.xStart,
+ this.yStart
+ );
+
+ this.waveContainer.style.top =
+ (this.containerMetrics.height - this.containerMetrics.size) / 2 + 'px';
+ this.waveContainer.style.left =
+ (this.containerMetrics.width - this.containerMetrics.size) / 2 + 'px';
+
+ this.waveContainer.style.width = this.containerMetrics.size + 'px';
+ this.waveContainer.style.height = this.containerMetrics.size + 'px';
+ },
+
+ /** @param {Event=} event */
+ upAction: function(event) {
+ if (!this.isMouseDown) {
+ return;
+ }
+
+ this.mouseUpStart = Utility.now();
+ },
+
+ remove: function() {
+ Polymer.dom(this.waveContainer.parentNode).removeChild(
+ this.waveContainer
+ );
+ }
+ };
+
+ Polymer({
+ is: 'paper-ripple',
+
+ behaviors: [
+ Polymer.IronA11yKeysBehavior
+ ],
+
+ properties: {
+ /**
+ * The initial opacity set on the wave.
+ *
+ * @attribute initialOpacity
+ * @type number
+ * @default 0.25
+ */
+ initialOpacity: {
+ type: Number,
+ value: 0.25
+ },
+
+ /**
+ * How fast (opacity per second) the wave fades out.
+ *
+ * @attribute opacityDecayVelocity
+ * @type number
+ * @default 0.8
+ */
+ opacityDecayVelocity: {
+ type: Number,
+ value: 0.8
+ },
+
+ /**
+ * If true, ripples will exhibit a gravitational pull towards
+ * the center of their container as they fade away.
+ *
+ * @attribute recenters
+ * @type boolean
+ * @default false
+ */
+ recenters: {
+ type: Boolean,
+ value: false
+ },
+
+ /**
+ * If true, ripples will center inside its container
+ *
+ * @attribute recenters
+ * @type boolean
+ * @default false
+ */
+ center: {
+ type: Boolean,
+ value: false
+ },
+
+ /**
+ * A list of the visual ripples.
+ *
+ * @attribute ripples
+ * @type Array
+ * @default []
+ */
+ ripples: {
+ type: Array,
+ value: function() {
+ return [];
+ }
+ },
+
+ /**
+ * True when there are visible ripples animating within the
+ * element.
+ */
+ animating: {
+ type: Boolean,
+ readOnly: true,
+ reflectToAttribute: true,
+ value: false
+ },
+
+ /**
+ * If true, the ripple will remain in the "down" state until `holdDown`
+ * is set to false again.
+ */
+ holdDown: {
+ type: Boolean,
+ value: false,
+ observer: '_holdDownChanged'
+ },
+
+ _animating: {
+ type: Boolean
+ },
+
+ _boundAnimate: {
+ type: Function,
+ value: function() {
+ return this.animate.bind(this);
+ }
+ }
+ },
+
+ get target () {
+ var ownerRoot = Polymer.dom(this).getOwnerRoot();
+ var target;
+
+ if (this.parentNode.nodeType == 11) { // DOCUMENT_FRAGMENT_NODE
+ target = ownerRoot.host;
+ } else {
+ target = this.parentNode;
+ }
+
+ return target;
+ },
+
+ keyBindings: {
+ 'enter:keydown': '_onEnterKeydown',
+ 'space:keydown': '_onSpaceKeydown',
+ 'space:keyup': '_onSpaceKeyup'
+ },
+
+ attached: function() {
+ this.listen(this.target, 'up', 'upAction');
+ this.listen(this.target, 'down', 'downAction');
+
+ if (!this.target.hasAttribute('noink')) {
+ this.keyEventTarget = this.target;
+ }
+ },
+
+ get shouldKeepAnimating () {
+ for (var index = 0; index < this.ripples.length; ++index) {
+ if (!this.ripples[index].isAnimationComplete) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ simulatedRipple: function() {
+ this.downAction(null);
+
+ // Please see polymer/polymer#1305
+ this.async(function() {
+ this.upAction();
+ }, 1);
+ },
+
+ /** @param {Event=} event */
+ downAction: function(event) {
+ if (this.holdDown && this.ripples.length > 0) {
+ return;
+ }
+
+ var ripple = this.addRipple();
+
+ ripple.downAction(event);
+
+ if (!this._animating) {
+ this.animate();
+ }
+ },
+
+ /** @param {Event=} event */
+ upAction: function(event) {
+ if (this.holdDown) {
+ return;
+ }
+
+ this.ripples.forEach(function(ripple) {
+ ripple.upAction(event);
+ });
+
+ this.animate();
+ },
+
+ onAnimationComplete: function() {
+ this._animating = false;
+ this.$.background.style.backgroundColor = null;
+ this.fire('transitionend');
+ },
+
+ addRipple: function() {
+ var ripple = new Ripple(this);
+
+ Polymer.dom(this.$.waves).appendChild(ripple.waveContainer);
+ this.$.background.style.backgroundColor = ripple.color;
+ this.ripples.push(ripple);
+
+ this._setAnimating(true);
+
+ return ripple;
+ },
+
+ removeRipple: function(ripple) {
+ var rippleIndex = this.ripples.indexOf(ripple);
+
+ if (rippleIndex < 0) {
+ return;
+ }
+
+ this.ripples.splice(rippleIndex, 1);
+
+ ripple.remove();
+
+ if (!this.ripples.length) {
+ this._setAnimating(false);
+ }
+ },
+
+ animate: function() {
+ var index;
+ var ripple;
+
+ this._animating = true;
+
+ for (index = 0; index < this.ripples.length; ++index) {
+ ripple = this.ripples[index];
+
+ ripple.draw();
+
+ this.$.background.style.opacity = ripple.outerOpacity;
+
+ if (ripple.isOpacityFullyDecayed && !ripple.isRestingAtMaxRadius) {
+ this.removeRipple(ripple);
+ }
+ }
+
+ if (!this.shouldKeepAnimating && this.ripples.length === 0) {
+ this.onAnimationComplete();
+ } else {
+ window.requestAnimationFrame(this._boundAnimate);
+ }
+ },
+
+ _onEnterKeydown: function() {
+ this.downAction();
+ this.async(this.upAction, 1);
+ },
+
+ _onSpaceKeydown: function() {
+ this.downAction();
+ },
+
+ _onSpaceKeyup: function() {
+ this.upAction();
+ },
+
+ _holdDownChanged: function(holdDown) {
+ if (holdDown) {
+ this.downAction();
+ } else {
+ this.upAction();
+ }
+ }
+ });
+ })();
+/**
+ * @demo demo/index.html
+ * @polymerBehavior
+ */
+ Polymer.IronControlState = {
+
+ properties: {
+
+ /**
+ * If true, the element currently has focus.
+ */
+ focused: {
+ type: Boolean,
+ value: false,
+ notify: true,
+ readOnly: true,
+ reflectToAttribute: true
+ },
+
+ /**
+ * If true, the user cannot interact with this element.
+ */
+ disabled: {
+ type: Boolean,
+ value: false,
+ notify: true,
+ observer: '_disabledChanged',
+ reflectToAttribute: true
+ },
+
+ _oldTabIndex: {
+ type: Number
+ },
+
+ _boundFocusBlurHandler: {
+ type: Function,
+ value: function() {
+ return this._focusBlurHandler.bind(this);
+ }
+ }
+
+ },
+
+ observers: [
+ '_changedControlState(focused, disabled)'
+ ],
+
+ ready: function() {
+ this.addEventListener('focus', this._boundFocusBlurHandler, true);
+ this.addEventListener('blur', this._boundFocusBlurHandler, true);
+ },
+
+ _focusBlurHandler: function(event) {
+ // NOTE(cdata): if we are in ShadowDOM land, `event.target` will
+ // eventually become `this` due to retargeting; if we are not in
+ // ShadowDOM land, `event.target` will eventually become `this` due
+ // to the second conditional which fires a synthetic event (that is also
+ // handled). In either case, we can disregard `event.path`.
+
+ if (event.target === this) {
+ var focused = event.type === 'focus';
+ this._setFocused(focused);
+ } else if (!this.shadowRoot) {
+ this.fire(event.type, {sourceEvent: event}, {
+ node: this,
+ bubbles: event.bubbles,
+ cancelable: event.cancelable
+ });
+ }
+ },
+
+ _disabledChanged: function(disabled, old) {
+ this.setAttribute('aria-disabled', disabled ? 'true' : 'false');
+ this.style.pointerEvents = disabled ? 'none' : '';
+ if (disabled) {
+ this._oldTabIndex = this.tabIndex;
+ this.focused = false;
+ this.tabIndex = -1;
+ } else if (this._oldTabIndex !== undefined) {
+ this.tabIndex = this._oldTabIndex;
+ }
+ },
+
+ _changedControlState: function() {
+ // _controlStateChanged is abstract, follow-on behaviors may implement it
+ if (this._controlStateChanged) {
+ this._controlStateChanged();
+ }
+ }
+
+ };
+/**
+ * @demo demo/index.html
+ * @polymerBehavior Polymer.IronButtonState
+ */
+ Polymer.IronButtonStateImpl = {
+
+ properties: {
+
+ /**
+ * If true, the user is currently holding down the button.
+ */
+ pressed: {
+ type: Boolean,
+ readOnly: true,
+ value: false,
+ reflectToAttribute: true,
+ observer: '_pressedChanged'
+ },
+
+ /**
+ * If true, the button toggles the active state with each tap or press
+ * of the spacebar.
+ */
+ toggles: {
+ type: Boolean,
+ value: false,
+ reflectToAttribute: true
+ },
+
+ /**
+ * If true, the button is a toggle and is currently in the active state.
+ */
+ active: {
+ type: Boolean,
+ value: false,
+ notify: true,
+ reflectToAttribute: true
+ },
+
+ /**
+ * True if the element is currently being pressed by a "pointer," which
+ * is loosely defined as mouse or touch input (but specifically excluding
+ * keyboard input).
+ */
+ pointerDown: {
+ type: Boolean,
+ readOnly: true,
+ value: false
+ },
+
+ /**
+ * True if the input device that caused the element to receive focus
+ * was a keyboard.
+ */
+ receivedFocusFromKeyboard: {
+ type: Boolean,
+ readOnly: true
+ },
+
+ /**
+ * The aria attribute to be set if the button is a toggle and in the
+ * active state.
+ */
+ ariaActiveAttribute: {
+ type: String,
+ value: 'aria-pressed',
+ observer: '_ariaActiveAttributeChanged'
+ }
+ },
+
+ listeners: {
+ down: '_downHandler',
+ up: '_upHandler',
+ tap: '_tapHandler'
+ },
+
+ observers: [
+ '_detectKeyboardFocus(focused)',
+ '_activeChanged(active, ariaActiveAttribute)'
+ ],
+
+ keyBindings: {
+ 'enter:keydown': '_asyncClick',
+ 'space:keydown': '_spaceKeyDownHandler',
+ 'space:keyup': '_spaceKeyUpHandler',
+ },
+
+ _mouseEventRe: /^mouse/,
+
+ _tapHandler: function() {
+ if (this.toggles) {
+ // a tap is needed to toggle the active state
+ this._userActivate(!this.active);
+ } else {
+ this.active = false;
+ }
+ },
+
+ _detectKeyboardFocus: function(focused) {
+ this._setReceivedFocusFromKeyboard(!this.pointerDown && focused);
+ },
+
+ // to emulate native checkbox, (de-)activations from a user interaction fire
+ // 'change' events
+ _userActivate: function(active) {
+ if (this.active !== active) {
+ this.active = active;
+ this.fire('change');
+ }
+ },
+
+ _eventSourceIsPrimaryInput: function(event) {
+ event = event.detail.sourceEvent || event;
+
+ // Always true for non-mouse events....
+ if (!this._mouseEventRe.test(event.type)) {
+ return true;
+ }
+
+ // http://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
+ if ('buttons' in event) {
+ return event.buttons === 1;
+ }
+
+ // http://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/which
+ if (typeof event.which === 'number') {
+ return event.which < 2;
+ }
+
+ // http://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
+ return event.button < 1;
+ },
+
+ _downHandler: function(event) {
+ if (!this._eventSourceIsPrimaryInput(event)) {
+ return;
+ }
+
+ this._setPointerDown(true);
+ this._setPressed(true);
+ this._setReceivedFocusFromKeyboard(false);
+ },
+
+ _upHandler: function() {
+ this._setPointerDown(false);
+ this._setPressed(false);
+ },
+
+ _spaceKeyDownHandler: function(event) {
+ var keyboardEvent = event.detail.keyboardEvent;
+ keyboardEvent.preventDefault();
+ keyboardEvent.stopImmediatePropagation();
+ this._setPressed(true);
+ },
+
+ _spaceKeyUpHandler: function() {
+ if (this.pressed) {
+ this._asyncClick();
+ }
+ this._setPressed(false);
+ },
+
+ // trigger click asynchronously, the asynchrony is useful to allow one
+ // event handler to unwind before triggering another event
+ _asyncClick: function() {
+ this.async(function() {
+ this.click();
+ }, 1);
+ },
+
+ // any of these changes are considered a change to button state
+
+ _pressedChanged: function(pressed) {
+ this._changedButtonState();
+ },
+
+ _ariaActiveAttributeChanged: function(value, oldValue) {
+ if (oldValue && oldValue != value && this.hasAttribute(oldValue)) {
+ this.removeAttribute(oldValue);
+ }
+ },
+
+ _activeChanged: function(active, ariaActiveAttribute) {
+ if (this.toggles) {
+ this.setAttribute(this.ariaActiveAttribute,
+ active ? 'true' : 'false');
+ } else {
+ this.removeAttribute(this.ariaActiveAttribute);
+ }
+ this._changedButtonState();
+ },
+
+ _controlStateChanged: function() {
+ if (this.disabled) {
+ this._setPressed(false);
+ } else {
+ this._changedButtonState();
+ }
+ },
+
+ // provide hook for follow-on behaviors to react to button-state
+
+ _changedButtonState: function() {
+ if (this._buttonStateChanged) {
+ this._buttonStateChanged(); // abstract
+ }
+ }
+
+ };
+
+ /** @polymerBehavior */
+ Polymer.IronButtonState = [
+ Polymer.IronA11yKeysBehavior,
+ Polymer.IronButtonStateImpl
+ ];
+/** @polymerBehavior */
+ Polymer.PaperButtonBehaviorImpl = {
+
+ properties: {
+
+ _elevation: {
+ type: Number
+ }
+
+ },
+
+ observers: [
+ '_calculateElevation(focused, disabled, active, pressed, receivedFocusFromKeyboard)'
+ ],
+
+ hostAttributes: {
+ role: 'button',
+ tabindex: '0'
+ },
+
+ _calculateElevation: function() {
+ var e = 1;
+ if (this.disabled) {
+ e = 0;
+ } else if (this.active || this.pressed) {
+ e = 4;
+ } else if (this.receivedFocusFromKeyboard) {
+ e = 3;
+ }
+ this._elevation = e;
+ }
+ };
+
+ /** @polymerBehavior */
+ Polymer.PaperButtonBehavior = [
+ Polymer.IronButtonState,
+ Polymer.IronControlState,
+ Polymer.PaperButtonBehaviorImpl
+ ];
+Polymer({
+ is: 'paper-button',
+
+ behaviors: [
+ Polymer.PaperButtonBehavior
+ ],
+
+ properties: {
+ /**
+ * If true, the button should be styled with a shadow.
+ */
+ raised: {
+ type: Boolean,
+ reflectToAttribute: true,
+ value: false,
+ observer: '_calculateElevation'
+ }
+ },
+
+ _calculateElevation: function() {
+ if (!this.raised) {
+ this._elevation = 0;
+ } else {
+ Polymer.PaperButtonBehaviorImpl._calculateElevation.apply(this);
+ }
+ },
+
+ _computeContentClass: function(receivedFocusFromKeyboard) {
+ var className = 'content ';
+ if (receivedFocusFromKeyboard) {
+ className += ' keyboard-focus';
+ }
+ return className;
+ }
+ });
+/**
+ * `iron-range-behavior` provides the behavior for something with a minimum to maximum range.
+ *
+ * @demo demo/index.html
+ * @polymerBehavior
+ */
+ Polymer.IronRangeBehavior = {
+
+ properties: {
+
+ /**
+ * The number that represents the current value.
+ */
+ value: {
+ type: Number,
+ value: 0,
+ notify: true,
+ reflectToAttribute: true
+ },
+
+ /**
+ * The number that indicates the minimum value of the range.
+ */
+ min: {
+ type: Number,
+ value: 0,
+ notify: true
+ },
+
+ /**
+ * The number that indicates the maximum value of the range.
+ */
+ max: {
+ type: Number,
+ value: 100,
+ notify: true
+ },
+
+ /**
+ * Specifies the value granularity of the range's value.
+ */
+ step: {
+ type: Number,
+ value: 1,
+ notify: true
+ },
+
+ /**
+ * Returns the ratio of the value.
+ */
+ ratio: {
+ type: Number,
+ value: 0,
+ readOnly: true,
+ notify: true
+ },
+ },
+
+ observers: [
+ '_update(value, min, max, step)'
+ ],
+
+ _calcRatio: function(value) {
+ return (this._clampValue(value) - this.min) / (this.max - this.min);
+ },
+
+ _clampValue: function(value) {
+ return Math.min(this.max, Math.max(this.min, this._calcStep(value)));
+ },
+
+ _calcStep: function(value) {
+ /**
+ * if we calculate the step using
+ * `Math.round(value / step) * step` we may hit a precision point issue
+ * eg. 0.1 * 0.2 = 0.020000000000000004
+ * http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
+ *
+ * as a work around we can divide by the reciprocal of `step`
+ */
+ // polymer/issues/2493
+ value = parseFloat(value);
+ return this.step ? (Math.round((value + this.min) / this.step) / (1 / this.step)) - this.min : value;
+ },
+
+ _validateValue: function() {
+ var v = this._clampValue(this.value);
+ this.value = this.oldValue = isNaN(v) ? this.oldValue : v;
+ return this.value !== v;
+ },
+
+ _update: function() {
+ this._validateValue();
+ this._setRatio(this._calcRatio(this.value) * 100);
+ }
+
+};
+Polymer({
+
+ is: 'paper-progress',
+
+ behaviors: [
+ Polymer.IronRangeBehavior
+ ],
+
+ properties: {
+
+ /**
+ * The number that represents the current secondary progress.
+ */
+ secondaryProgress: {
+ type: Number,
+ value: 0
+ },
+
+ /**
+ * The secondary ratio
+ */
+ secondaryRatio: {
+ type: Number,
+ value: 0,
+ readOnly: true
+ },
+
+ /**
+ * Use an indeterminate progress indicator.
+ */
+ indeterminate: {
+ type: Boolean,
+ value: false,
+ observer: '_toggleIndeterminate'
+ },
+
+ /**
+ * True if the progress is disabled.
+ */
+ disabled: {
+ type: Boolean,
+ value: false,
+ reflectToAttribute: true,
+ observer: '_disabledChanged'
+ }
+ },
+
+ observers: [
+ '_progressChanged(secondaryProgress, value, min, max)'
+ ],
+
+ hostAttributes: {
+ role: 'progressbar'
+ },
+
+ _toggleIndeterminate: function(indeterminate) {
+ // If we use attribute/class binding, the animation sometimes doesn't translate properly
+ // on Safari 7.1. So instead, we toggle the class here in the update method.
+ this.toggleClass('indeterminate', indeterminate, this.$.primaryProgress);
+ },
+
+ _transformProgress: function(progress, ratio) {
+ var transform = 'scaleX(' + (ratio / 100) + ')';
+ progress.style.transform = progress.style.webkitTransform = transform;
+ },
+
+ _mainRatioChanged: function(ratio) {
+ this._transformProgress(this.$.primaryProgress, ratio);
+ },
+
+ _progressChanged: function(secondaryProgress, value, min, max) {
+ secondaryProgress = this._clampValue(secondaryProgress);
+ value = this._clampValue(value);
+
+ var secondaryRatio = this._calcRatio(secondaryProgress) * 100;
+ var mainRatio = this._calcRatio(value) * 100;
+
+ this._setSecondaryRatio(secondaryRatio);
+ this._transformProgress(this.$.secondaryProgress, secondaryRatio);
+ this._transformProgress(this.$.primaryProgress, mainRatio);
+
+ this.secondaryProgress = secondaryProgress;
+
+ this.setAttribute('aria-valuenow', value);
+ this.setAttribute('aria-valuemin', min);
+ this.setAttribute('aria-valuemax', max);
+ },
+
+ _disabledChanged: function(disabled) {
+ this.setAttribute('aria-disabled', disabled ? 'true' : 'false');
+ },
+
+ _hideSecondaryProgress: function(secondaryRatio) {
+ return secondaryRatio === 0;
+ }
+
+ });
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('downloads', function() {
+ var Item = Polymer({
+ is: 'downloads-item',
+
+ /**
+ * @param {!downloads.ThrottledIconLoader} iconLoader
+ */
+ factoryImpl: function(iconLoader) {
+ /** @private {!downloads.ThrottledIconLoader} */
+ this.iconLoader_ = iconLoader;
+ },
+
+ properties: {
+ data: {
+ type: Object,
+ },
+
+ hideDate: {
+ type: Boolean,
+ value: true,
+ },
+
+ readyPromise: {
+ type: Object,
+ value: function() {
+ return new Promise(function(resolve, reject) {
+ this.resolveReadyPromise_ = resolve;
+ }.bind(this));
+ },
+ },
+
+ completelyOnDisk_: {
+ computed: 'computeCompletelyOnDisk_(' +
+ 'data.state, data.file_externally_removed)',
+ type: Boolean,
+ value: true,
+ },
+
+ controlledBy_: {
+ computed: 'computeControlledBy_(data.by_ext_id, data.by_ext_name)',
+ type: String,
+ value: '',
+ },
+
+ i18n_: {
+ readOnly: true,
+ type: Object,
+ value: function() {
+ return {
+ cancel: loadTimeData.getString('controlCancel'),
+ discard: loadTimeData.getString('dangerDiscard'),
+ pause: loadTimeData.getString('controlPause'),
+ remove: loadTimeData.getString('controlRemoveFromList'),
+ resume: loadTimeData.getString('controlResume'),
+ restore: loadTimeData.getString('dangerRestore'),
+ retry: loadTimeData.getString('controlRetry'),
+ save: loadTimeData.getString('dangerSave'),
+ };
+ },
+ },
+
+ isActive_: {
+ computed: 'computeIsActive_(' +
+ 'data.state, data.file_externally_removed)',
+ type: Boolean,
+ value: true,
+ },
+
+ isDangerous_: {
+ computed: 'computeIsDangerous_(data.state)',
+ type: Boolean,
+ value: false,
+ },
+
+ isInProgress_: {
+ computed: 'computeIsInProgress_(data.state)',
+ type: Boolean,
+ value: false,
+ },
+
+ showCancel_: {
+ computed: 'computeShowCancel_(data.state)',
+ type: Boolean,
+ value: false,
+ },
+
+ showProgress_: {
+ computed: 'computeShowProgress_(showCancel_, data.percent)',
+ type: Boolean,
+ value: false,
+ },
+
+ isMalware_: {
+ computed: 'computeIsMalware_(isDangerous_, data.danger_type)',
+ type: Boolean,
+ value: false,
+ },
+ },
+
+ observers: [
+ // TODO(dbeam): this gets called way more when I observe data.by_ext_id
+ // and data.by_ext_name directly. Why?
+ 'observeControlledBy_(controlledBy_)',
+ ],
+
+ ready: function() {
+ this.content = this.$.content;
+ this.resolveReadyPromise_();
+ },
+
+ /** @param {!downloads.Data} data */
+ update: function(data) {
+ this.data = data;
+
+ if (!this.isDangerous_) {
+ var icon = 'chrome://fileicon/' + encodeURIComponent(data.file_path);
+ this.iconLoader_.loadScaledIcon(this.$['file-icon'], icon);
+ }
+ },
+
+ /** @private */
+ computeClass_: function() {
+ var classes = [];
+
+ if (this.isActive_)
+ classes.push('is-active');
+
+ if (this.isDangerous_)
+ classes.push('dangerous');
+
+ if (this.showProgress_)
+ classes.push('show-progress');
+
+ return classes.join(' ');
+ },
+
+ /** @private */
+ computeCompletelyOnDisk_: function() {
+ return this.data.state == downloads.States.COMPLETE &&
+ !this.data.file_externally_removed;
+ },
+
+ /** @private */
+ computeControlledBy_: function() {
+ if (!this.data.by_ext_id || !this.data.by_ext_name)
+ return '';
+
+ var url = 'chrome://extensions#' + this.data.by_ext_id;
+ var name = this.data.by_ext_name;
+ return loadTimeData.getStringF('controlledByUrl', url, name);
+ },
+
+ /** @private */
+ computeDate_: function() {
+ if (this.hideDate)
+ return '';
+ return assert(this.data.since_string || this.data.date_string);
+ },
+
+ /** @private */
+ computeDescription_: function() {
+ var data = this.data;
+
+ switch (data.state) {
+ case downloads.States.DANGEROUS:
+ var fileName = data.file_name;
+ switch (data.danger_type) {
+ case downloads.DangerType.DANGEROUS_FILE:
+ return loadTimeData.getStringF('dangerFileDesc', fileName);
+ case downloads.DangerType.DANGEROUS_URL:
+ return loadTimeData.getString('dangerUrlDesc');
+ case downloads.DangerType.DANGEROUS_CONTENT: // Fall through.
+ case downloads.DangerType.DANGEROUS_HOST:
+ return loadTimeData.getStringF('dangerContentDesc', fileName);
+ case downloads.DangerType.UNCOMMON_CONTENT:
+ return loadTimeData.getStringF('dangerUncommonDesc', fileName);
+ case downloads.DangerType.POTENTIALLY_UNWANTED:
+ return loadTimeData.getStringF('dangerSettingsDesc', fileName);
+ }
+ break;
+
+ case downloads.States.IN_PROGRESS:
+ case downloads.States.PAUSED: // Fallthrough.
+ return data.progress_status_text;
+ }
+
+ return '';
+ },
+
+ /** @private */
+ computeIsActive_: function() {
+ return this.data.state != downloads.States.CANCELLED &&
+ this.data.state != downloads.States.INTERRUPTED &&
+ !this.data.file_externally_removed;
+ },
+
+ /** @private */
+ computeIsDangerous_: function() {
+ return this.data.state == downloads.States.DANGEROUS;
+ },
+
+ /** @private */
+ computeIsInProgress_: function() {
+ return this.data.state == downloads.States.IN_PROGRESS;
+ },
+
+ /** @private */
+ computeIsMalware_: function() {
+ return this.isDangerous_ &&
+ (this.data.danger_type == downloads.DangerType.DANGEROUS_CONTENT ||
+ this.data.danger_type == downloads.DangerType.DANGEROUS_HOST ||
+ this.data.danger_type == downloads.DangerType.DANGEROUS_URL ||
+ this.data.danger_type == downloads.DangerType.POTENTIALLY_UNWANTED);
+ },
+
+ /** @private */
+ computeRemoveStyle_: function() {
+ var canDelete = loadTimeData.getBoolean('allowDeletingHistory');
+ var hideRemove = this.isDangerous_ || this.showCancel_ || !canDelete;
+ return hideRemove ? 'visibility: hidden' : '';
+ },
+
+ /** @private */
+ computeShowCancel_: function() {
+ return this.data.state == downloads.States.IN_PROGRESS ||
+ this.data.state == downloads.States.PAUSED;
+ },
+
+ /** @private */
+ computeShowProgress_: function() {
+ return this.showCancel_ && this.data.percent >= -1;
+ },
+
+ /** @private */
+ computeTag_: function() {
+ switch (this.data.state) {
+ case downloads.States.CANCELLED:
+ return loadTimeData.getString('statusCancelled');
+
+ case downloads.States.INTERRUPTED:
+ return this.data.last_reason_text;
+
+ case downloads.States.COMPLETE:
+ return this.data.file_externally_removed ?
+ loadTimeData.getString('statusRemoved') : '';
+ }
+
+ return '';
+ },
+
+ /** @private */
+ isIndeterminate_: function() {
+ return this.data.percent == -1;
+ },
+
+ /** @private */
+ observeControlledBy_: function() {
+ this.$['controlled-by'].innerHTML = this.controlledBy_;
+ },
+
+ /** @private */
+ onCancelClick_: function() {
+ downloads.ActionService.getInstance().cancel(this.data.id);
+ },
+
+ /** @private */
+ onDiscardDangerous_: function() {
+ downloads.ActionService.getInstance().discardDangerous(this.data.id);
+ },
+
+ /**
+ * @private
+ * @param {Event} e
+ */
+ onDragStart_: function(e) {
+ e.preventDefault();
+ downloads.ActionService.getInstance().drag(this.data.id);
+ },
+
+ /**
+ * @param {Event} e
+ * @private
+ */
+ onFileLinkClick_: function(e) {
+ e.preventDefault();
+ downloads.ActionService.getInstance().openFile(this.data.id);
+ },
+
+ /** @private */
+ onPauseClick_: function() {
+ downloads.ActionService.getInstance().pause(this.data.id);
+ },
+
+ /** @private */
+ onRemoveClick_: function() {
+ downloads.ActionService.getInstance().remove(this.data.id);
+ },
+
+ /** @private */
+ onResumeClick_: function() {
+ downloads.ActionService.getInstance().resume(this.data.id);
+ },
+
+ /** @private */
+ onRetryClick_: function() {
+ downloads.ActionService.getInstance().download(this.data.url);
+ },
+
+ /** @private */
+ onSaveDangerous_: function() {
+ downloads.ActionService.getInstance().saveDangerous(this.data.id);
+ },
+
+ /** @private */
+ onShowClick_: function() {
+ downloads.ActionService.getInstance().show(this.data.id);
+ },
+ });
+
+ return {Item: Item};
+});
+(function() {
+
+ // monostate data
+ var metaDatas = {};
+ var metaArrays = {};
+
+ Polymer.IronMeta = Polymer({
+
+ is: 'iron-meta',
+
+ properties: {
+
+ /**
+ * The type of meta-data. All meta-data of the same type is stored
+ * together.
+ */
+ type: {
+ type: String,
+ value: 'default',
+ observer: '_typeChanged'
+ },
+
+ /**
+ * The key used to store `value` under the `type` namespace.
+ */
+ key: {
+ type: String,
+ observer: '_keyChanged'
+ },
+
+ /**
+ * The meta-data to store or retrieve.
+ */
+ value: {
+ type: Object,
+ notify: true,
+ observer: '_valueChanged'
+ },
+
+ /**
+ * If true, `value` is set to the iron-meta instance itself.
+ */
+ self: {
+ type: Boolean,
+ observer: '_selfChanged'
+ },
+
+ /**
+ * Array of all meta-data values for the given type.
+ */
+ list: {
+ type: Array,
+ notify: true
+ }
+
+ },
+
+ /**
+ * Only runs if someone invokes the factory/constructor directly
+ * e.g. `new Polymer.IronMeta()`
+ */
+ factoryImpl: function(config) {
+ if (config) {
+ for (var n in config) {
+ switch(n) {
+ case 'type':
+ case 'key':
+ case 'value':
+ this[n] = config[n];
+ break;
+ }
+ }
+ }
+ },
+
+ created: function() {
+ // TODO(sjmiles): good for debugging?
+ this._metaDatas = metaDatas;
+ this._metaArrays = metaArrays;
+ },
+
+ _keyChanged: function(key, old) {
+ this._resetRegistration(old);
+ },
+
+ _valueChanged: function(value) {
+ this._resetRegistration(this.key);
+ },
+
+ _selfChanged: function(self) {
+ if (self) {
+ this.value = this;
+ }
+ },
+
+ _typeChanged: function(type) {
+ this._unregisterKey(this.key);
+ if (!metaDatas[type]) {
+ metaDatas[type] = {};
+ }
+ this._metaData = metaDatas[type];
+ if (!metaArrays[type]) {
+ metaArrays[type] = [];
+ }
+ this.list = metaArrays[type];
+ this._registerKeyValue(this.key, this.value);
+ },
+
+ /**
+ * Retrieves meta data value by key.
+ *
+ * @method byKey
+ * @param {string} key The key of the meta-data to be returned.
+ * @return {*}
+ */
+ byKey: function(key) {
+ return this._metaData && this._metaData[key];
+ },
+
+ _resetRegistration: function(oldKey) {
+ this._unregisterKey(oldKey);
+ this._registerKeyValue(this.key, this.value);
+ },
+
+ _unregisterKey: function(key) {
+ this._unregister(key, this._metaData, this.list);
+ },
+
+ _registerKeyValue: function(key, value) {
+ this._register(key, value, this._metaData, this.list);
+ },
+
+ _register: function(key, value, data, list) {
+ if (key && data && value !== undefined) {
+ data[key] = value;
+ list.push(value);
+ }
+ },
+
+ _unregister: function(key, data, list) {
+ if (key && data) {
+ if (key in data) {
+ var value = data[key];
+ delete data[key];
+ this.arrayDelete(list, value);
+ }
+ }
+ }
+
+ });
+
+ /**
+ `iron-meta-query` can be used to access infomation stored in `iron-meta`.
+
+ Examples:
+
+ If I create an instance like this:
+
+ <iron-meta key="info" value="foo/bar"></iron-meta>
+
+ Note that value="foo/bar" is the metadata I've defined. I could define more
+ attributes or use child nodes to define additional metadata.
+
+ Now I can access that element (and it's metadata) from any `iron-meta-query` instance:
+
+ var value = new Polymer.IronMetaQuery({key: 'info'}).value;
+
+ @group Polymer Iron Elements
+ @element iron-meta-query
+ */
+ Polymer.IronMetaQuery = Polymer({
+
+ is: 'iron-meta-query',
+
+ properties: {
+
+ /**
+ * The type of meta-data. All meta-data of the same type is stored
+ * together.
+ */
+ type: {
+ type: String,
+ value: 'default',
+ observer: '_typeChanged'
+ },
+
+ /**
+ * Specifies a key to use for retrieving `value` from the `type`
+ * namespace.
+ */
+ key: {
+ type: String,
+ observer: '_keyChanged'
+ },
+
+ /**
+ * The meta-data to store or retrieve.
+ */
+ value: {
+ type: Object,
+ notify: true,
+ readOnly: true
+ },
+
+ /**
+ * Array of all meta-data values for the given type.
+ */
+ list: {
+ type: Array,
+ notify: true
+ }
+
+ },
+
+ /**
+ * Actually a factory method, not a true constructor. Only runs if
+ * someone invokes it directly (via `new Polymer.IronMeta()`);
+ */
+ factoryImpl: function(config) {
+ if (config) {
+ for (var n in config) {
+ switch(n) {
+ case 'type':
+ case 'key':
+ this[n] = config[n];
+ break;
+ }
+ }
+ }
+ },
+
+ created: function() {
+ // TODO(sjmiles): good for debugging?
+ this._metaDatas = metaDatas;
+ this._metaArrays = metaArrays;
+ },
+
+ _keyChanged: function(key) {
+ this._setValue(this._metaData && this._metaData[key]);
+ },
+
+ _typeChanged: function(type) {
+ this._metaData = metaDatas[type];
+ this.list = metaArrays[type];
+ if (this.key) {
+ this._keyChanged(this.key);
+ }
+ },
+
+ /**
+ * Retrieves meta data value by key.
+ * @param {string} key The key of the meta-data to be returned.
+ * @return {*}
+ */
+ byKey: function(key) {
+ return this._metaData && this._metaData[key];
+ }
+
+ });
+
+ })();
+Polymer({
+
+ is: 'iron-icon',
+
+ properties: {
+
+ /**
+ * The name of the icon to use. The name should be of the form:
+ * `iconset_name:icon_name`.
+ */
+ icon: {
+ type: String,
+ observer: '_iconChanged'
+ },
+
+ /**
+ * The name of the theme to used, if one is specified by the
+ * iconset.
+ */
+ theme: {
+ type: String,
+ observer: '_updateIcon'
+ },
+
+ /**
+ * If using iron-icon without an iconset, you can set the src to be
+ * the URL of an individual icon image file. Note that this will take
+ * precedence over a given icon attribute.
+ */
+ src: {
+ type: String,
+ observer: '_srcChanged'
+ },
+
+ /**
+ * @type {!Polymer.IronMeta}
+ */
+ _meta: {
+ value: Polymer.Base.create('iron-meta', {type: 'iconset'})
+ }
+
+ },
+
+ _DEFAULT_ICONSET: 'icons',
+
+ _iconChanged: function(icon) {
+ var parts = (icon || '').split(':');
+ this._iconName = parts.pop();
+ this._iconsetName = parts.pop() || this._DEFAULT_ICONSET;
+ this._updateIcon();
+ },
+
+ _srcChanged: function(src) {
+ this._updateIcon();
+ },
+
+ _usesIconset: function() {
+ return this.icon || !this.src;
+ },
+
+ /** @suppress {visibility} */
+ _updateIcon: function() {
+ if (this._usesIconset()) {
+ if (this._iconsetName) {
+ this._iconset = /** @type {?Polymer.Iconset} */ (
+ this._meta.byKey(this._iconsetName));
+ if (this._iconset) {
+ this._iconset.applyIcon(this, this._iconName, this.theme);
+ this.unlisten(window, 'iron-iconset-added', '_updateIcon');
+ } else {
+ this.listen(window, 'iron-iconset-added', '_updateIcon');
+ }
+ }
+ } else {
+ if (!this._img) {
+ this._img = document.createElement('img');
+ this._img.style.width = '100%';
+ this._img.style.height = '100%';
+ this._img.draggable = false;
+ }
+ this._img.src = this.src;
+ Polymer.dom(this.root).appendChild(this._img);
+ }
+ }
+
+ });
+/**
+ * The `iron-iconset-svg` element allows users to define their own icon sets
+ * that contain svg icons. The svg icon elements should be children of the
+ * `iron-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:
+ *
+ * <iron-iconset-svg name="my-svg-icons" size="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>
+ * </iron-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
+ * `iron-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 iron-iconset-svg
+ * @demo demo/index.html
+ */
+ Polymer({
+
+ is: 'iron-iconset-svg',
+
+ properties: {
+
+ /**
+ * The name of the iconset.
+ *
+ * @attribute name
+ * @type string
+ */
+ name: {
+ type: String,
+ observer: '_nameChanged'
+ },
+
+ /**
+ * The size of an individual icon. Note that icons must be square.
+ *
+ * @attribute iconSize
+ * @type number
+ * @default 24
+ */
+ size: {
+ type: Number,
+ value: 24
+ }
+
+ },
+
+ /**
+ * Construct an array of all icon names in this iconset.
+ *
+ * @return {!Array} Array of icon names.
+ */
+ getIconNames: function() {
+ this._icons = this._createIconMap();
+ return Object.keys(this._icons).map(function(n) {
+ return this.name + ':' + n;
+ }, this);
+ },
+
+ /**
+ * Applies an icon to the given element.
+ *
+ * An svg icon is prepended to the element's shadowRoot if it exists,
+ * otherwise to the element itself.
+ *
+ * @method applyIcon
+ * @param {Element} element Element to which the icon is applied.
+ * @param {string} iconName Name of the icon to apply.
+ * @return {Element} The svg element which renders the icon.
+ */
+ applyIcon: function(element, iconName) {
+ // insert svg element into shadow root, if it exists
+ element = element.root || element;
+ // Remove old svg element
+ this.removeIcon(element);
+ // install new svg element
+ var svg = this._cloneIcon(iconName);
+ if (svg) {
+ var pde = Polymer.dom(element);
+ pde.insertBefore(svg, pde.childNodes[0]);
+ return element._svgIcon = svg;
+ }
+ return null;
+ },
+
+ /**
+ * Remove an icon from the given element by undoing the changes effected
+ * by `applyIcon`.
+ *
+ * @param {Element} element The element from which the icon is removed.
+ */
+ removeIcon: function(element) {
+ // Remove old svg element
+ if (element._svgIcon) {
+ Polymer.dom(element).removeChild(element._svgIcon);
+ element._svgIcon = null;
+ }
+ },
+
+ /**
+ *
+ * When name is changed, register iconset metadata
+ *
+ */
+ _nameChanged: function() {
+ new Polymer.IronMeta({type: 'iconset', key: this.name, value: this});
+ this.async(function() {
+ this.fire('iron-iconset-added', this, {node: window});
+ });
+ },
+
+ /**
+ * Create a map of child SVG elements by id.
+ *
+ * @return {!Object} Map of id's to SVG elements.
+ */
+ _createIconMap: function() {
+ // Objects chained to Object.prototype (`{}`) have members. Specifically,
+ // on FF there is a `watch` method that confuses the icon map, so we
+ // need to use a null-based object here.
+ var icons = Object.create(null);
+ Polymer.dom(this).querySelectorAll('[id]')
+ .forEach(function(icon) {
+ icons[icon.id] = icon;
+ });
+ return icons;
+ },
+
+ /**
+ * Produce installable clone of the SVG element matching `id` in this
+ * iconset, or `undefined` if there is no matching element.
+ *
+ * @return {Element} Returns an installable clone of the SVG element
+ * matching `id`.
+ */
+ _cloneIcon: function(id) {
+ // create the icon map on-demand, since the iconset itself has no discrete
+ // signal to know when it's children are fully parsed
+ this._icons = this._icons || this._createIconMap();
+ return this._prepareSvgClone(this._icons[id], this.size);
+ },
+
+ /**
+ * @param {Element} sourceSvg
+ * @param {number} size
+ * @return {Element}
+ */
+ _prepareSvgClone: function(sourceSvg, size) {
+ if (sourceSvg) {
+ var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+ svg.setAttribute('viewBox', ['0', '0', size, size].join(' '));
+ svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
+ // TODO(dfreedm): `pointer-events: none` works around https://crbug.com/370136
+ // TODO(sjmiles): inline style may not be ideal, but avoids requiring a shadow-root
+ svg.style.cssText = 'pointer-events: none; display: block; width: 100%; height: 100%;';
+ svg.appendChild(sourceSvg.cloneNode(true)).removeAttribute('id');
+ return svg;
+ }
+ return null;
+ }
+
+ });
+Polymer({
+ is: 'paper-item',
+
+ hostAttributes: {
+ role: 'listitem',
+ tabindex: '0'
+ },
+
+ behaviors: [
+ Polymer.IronControlState,
+ Polymer.IronButtonState
+ ]
+ });
+/**
+ * @param {!Function} selectCallback
+ * @constructor
+ */
+ Polymer.IronSelection = function(selectCallback) {
+ this.selection = [];
+ this.selectCallback = selectCallback;
+ };
+
+ Polymer.IronSelection.prototype = {
+
+ /**
+ * Retrieves the selected item(s).
+ *
+ * @method get
+ * @returns Returns the selected item(s). If the multi property is true,
+ * `get` will return an array, otherwise it will return
+ * the selected item or undefined if there is no selection.
+ */
+ get: function() {
+ return this.multi ? this.selection.slice() : this.selection[0];
+ },
+
+ /**
+ * Clears all the selection except the ones indicated.
+ *
+ * @method clear
+ * @param {Array} excludes items to be excluded.
+ */
+ clear: function(excludes) {
+ this.selection.slice().forEach(function(item) {
+ if (!excludes || excludes.indexOf(item) < 0) {
+ this.setItemSelected(item, false);
+ }
+ }, this);
+ },
+
+ /**
+ * Indicates if a given item is selected.
+ *
+ * @method isSelected
+ * @param {*} 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;
+ },
+
+ /**
+ * Sets the selection state for a given item to either selected or deselected.
+ *
+ * @method setItemSelected
+ * @param {*} item The item to select.
+ * @param {boolean} isSelected True for selected, false for deselected.
+ */
+ setItemSelected: function(item, isSelected) {
+ if (item != null) {
+ if (isSelected) {
+ this.selection.push(item);
+ } else {
+ var i = this.selection.indexOf(item);
+ if (i >= 0) {
+ this.selection.splice(i, 1);
+ }
+ }
+ if (this.selectCallback) {
+ this.selectCallback(item, isSelected);
+ }
+ }
+ },
+
+ /**
+ * Sets 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 {*} item The item to select.
+ */
+ select: function(item) {
+ if (this.multi) {
+ this.toggle(item);
+ } else if (this.get() !== item) {
+ this.setItemSelected(this.get(), false);
+ this.setItemSelected(item, true);
+ }
+ },
+
+ /**
+ * Toggles the selection state for `item`.
+ *
+ * @method toggle
+ * @param {*} item The item to toggle.
+ */
+ toggle: function(item) {
+ this.setItemSelected(item, !this.isSelected(item));
+ }
+
+ };
+/** @polymerBehavior */
+ Polymer.IronSelectableBehavior = {
+
+ /**
+ * Fired when iron-selector is activated (selected or deselected).
+ * It is fired before the selected items are changed.
+ * Cancel the event to abort selection.
+ *
+ * @event iron-activate
+ */
+
+ /**
+ * Fired when an item is selected
+ *
+ * @event iron-select
+ */
+
+ /**
+ * Fired when an item is deselected
+ *
+ * @event iron-deselect
+ */
+
+ /**
+ * Fired when the list of selectable items changes (e.g., items are
+ * added or removed). The detail of the event is a list of mutation
+ * records that describe what changed.
+ *
+ * @event iron-items-changed
+ */
+
+ properties: {
+
+ /**
+ * If you want to use the attribute value of an element for `selected` instead of the index,
+ * set this to the name of the attribute.
+ */
+ attrForSelected: {
+ type: String,
+ value: null
+ },
+
+ /**
+ * Gets or sets the selected element. The default is to use the index of the item.
+ */
+ selected: {
+ type: String,
+ notify: true
+ },
+
+ /**
+ * Returns the currently selected item.
+ */
+ selectedItem: {
+ type: Object,
+ readOnly: true,
+ notify: true
+ },
+
+ /**
+ * The event that fires from items when they are selected. Selectable
+ * will listen for this event from items and update the selection state.
+ * Set to empty string to listen to no events.
+ */
+ activateEvent: {
+ type: String,
+ value: 'tap',
+ observer: '_activateEventChanged'
+ },
+
+ /**
+ * This is a CSS selector string. If this is set, only items that match the CSS selector
+ * are selectable.
+ */
+ selectable: String,
+
+ /**
+ * The class to set on elements when selected.
+ */
+ selectedClass: {
+ type: String,
+ value: 'iron-selected'
+ },
+
+ /**
+ * The attribute to set on elements when selected.
+ */
+ selectedAttribute: {
+ type: String,
+ value: null
+ },
+
+ /**
+ * The set of excluded elements where the key is the `localName`
+ * of the element that will be ignored from the item list.
+ *
+ * @type {object}
+ * @default {template: 1}
+ */
+ excludedLocalNames: {
+ type: Object,
+ value: function() {
+ return {
+ 'template': 1
+ };
+ }
+ }
+ },
+
+ observers: [
+ '_updateSelected(attrForSelected, selected)'
+ ],
+
+ created: function() {
+ this._bindFilterItem = this._filterItem.bind(this);
+ this._selection = new Polymer.IronSelection(this._applySelection.bind(this));
+ },
+
+ attached: function() {
+ this._observer = this._observeItems(this);
+ this._contentObserver = this._observeContent(this);
+ if (!this.selectedItem && this.selected) {
+ this._updateSelected(this.attrForSelected,this.selected)
+ }
+ },
+
+ detached: function() {
+ if (this._observer) {
+ this._observer.disconnect();
+ }
+ if (this._contentObserver) {
+ this._contentObserver.disconnect();
+ }
+ this._removeListener(this.activateEvent);
+ },
+
+ /**
+ * Returns an array of selectable items.
+ *
+ * @property items
+ * @type Array
+ */
+ get items() {
+ var nodes = Polymer.dom(this).queryDistributedElements(this.selectable || '*');
+ return Array.prototype.filter.call(nodes, this._bindFilterItem);
+ },
+
+ /**
+ * Returns the index of the given item.
+ *
+ * @method indexOf
+ * @param {Object} item
+ * @returns Returns the index of the item
+ */
+ indexOf: function(item) {
+ return this.items.indexOf(item);
+ },
+
+ /**
+ * Selects the given value.
+ *
+ * @method select
+ * @param {string} value the value to select.
+ */
+ select: function(value) {
+ this.selected = value;
+ },
+
+ /**
+ * Selects the previous item.
+ *
+ * @method selectPrevious
+ */
+ selectPrevious: function() {
+ var length = this.items.length;
+ var index = (Number(this._valueToIndex(this.selected)) - 1 + length) % length;
+ this.selected = this._indexToValue(index);
+ },
+
+ /**
+ * Selects the next item.
+ *
+ * @method selectNext
+ */
+ selectNext: function() {
+ var index = (Number(this._valueToIndex(this.selected)) + 1) % this.items.length;
+ this.selected = this._indexToValue(index);
+ },
+
+ _addListener: function(eventName) {
+ this.listen(this, eventName, '_activateHandler');
+ },
+
+ _removeListener: function(eventName) {
+ this.unlisten(this, eventName, '_activateHandler');
+ },
+
+ _activateEventChanged: function(eventName, old) {
+ this._removeListener(old);
+ this._addListener(eventName);
+ },
+
+ _updateSelected: function() {
+ this._selectSelected(this.selected);
+ },
+
+ _selectSelected: function(selected) {
+ this._selection.select(this._valueToItem(this.selected));
+ },
+
+ _filterItem: function(node) {
+ return !this.excludedLocalNames[node.localName];
+ },
+
+ _valueToItem: function(value) {
+ return (value == null) ? null : this.items[this._valueToIndex(value)];
+ },
+
+ _valueToIndex: function(value) {
+ if (this.attrForSelected) {
+ for (var i = 0, item; item = this.items[i]; i++) {
+ if (this._valueForItem(item) == value) {
+ return i;
+ }
+ }
+ } else {
+ return Number(value);
+ }
+ },
+
+ _indexToValue: function(index) {
+ if (this.attrForSelected) {
+ var item = this.items[index];
+ if (item) {
+ return this._valueForItem(item);
+ }
+ } else {
+ return index;
+ }
+ },
+
+ _valueForItem: function(item) {
+ return item[this.attrForSelected] || item.getAttribute(this.attrForSelected);
+ },
+
+ _applySelection: function(item, isSelected) {
+ if (this.selectedClass) {
+ this.toggleClass(this.selectedClass, isSelected, item);
+ }
+ if (this.selectedAttribute) {
+ this.toggleAttribute(this.selectedAttribute, isSelected, item);
+ }
+ this._selectionChange();
+ this.fire('iron-' + (isSelected ? 'select' : 'deselect'), {item: item});
+ },
+
+ _selectionChange: function() {
+ this._setSelectedItem(this._selection.get());
+ },
+
+ // observe content changes under the given node.
+ _observeContent: function(node) {
+ var content = node.querySelector('content');
+ if (content && content.parentElement === node) {
+ return this._observeItems(node.domHost);
+ }
+ },
+
+ // observe items change under the given node.
+ _observeItems: function(node) {
+ // TODO(cdata): Update this when we get distributed children changed.
+ var observer = new MutationObserver(function(mutations) {
+ // Let other interested parties know about the change so that
+ // we don't have to recreate mutation observers everywher.
+ this.fire('iron-items-changed', mutations, {
+ bubbles: false,
+ cancelable: false
+ });
+
+ if (this.selected != null) {
+ this._updateSelected();
+ }
+ }.bind(this));
+ observer.observe(node, {
+ childList: true,
+ subtree: true
+ });
+ return observer;
+ },
+
+ _activateHandler: function(e) {
+ var t = e.target;
+ var items = this.items;
+ while (t && t != this) {
+ var i = items.indexOf(t);
+ if (i >= 0) {
+ var value = this._indexToValue(i);
+ this._itemActivate(value, t);
+ return;
+ }
+ t = t.parentNode;
+ }
+ },
+
+ _itemActivate: function(value, item) {
+ if (!this.fire('iron-activate',
+ {selected: value, item: item}, {cancelable: true}).defaultPrevented) {
+ this.select(value);
+ }
+ }
+
+ };
+/** @polymerBehavior Polymer.IronMultiSelectableBehavior */
+ Polymer.IronMultiSelectableBehaviorImpl = {
+ properties: {
+
+ /**
+ * If true, multiple selections are allowed.
+ */
+ multi: {
+ type: Boolean,
+ value: false,
+ observer: 'multiChanged'
+ },
+
+ /**
+ * Gets or sets the selected elements. This is used instead of `selected` when `multi`
+ * is true.
+ */
+ selectedValues: {
+ type: Array,
+ notify: true
+ },
+
+ /**
+ * Returns an array of currently selected items.
+ */
+ selectedItems: {
+ type: Array,
+ readOnly: true,
+ notify: true
+ },
+
+ },
+
+ observers: [
+ '_updateSelected(attrForSelected, selectedValues)'
+ ],
+
+ /**
+ * Selects the given value. If the `multi` property is true, then the selected state of the
+ * `value` will be toggled; otherwise the `value` will be selected.
+ *
+ * @method select
+ * @param {string} value the value to select.
+ */
+ select: function(value) {
+ if (this.multi) {
+ if (this.selectedValues) {
+ this._toggleSelected(value);
+ } else {
+ this.selectedValues = [value];
+ }
+ } else {
+ this.selected = value;
+ }
+ },
+
+ multiChanged: function(multi) {
+ this._selection.multi = multi;
+ },
+
+ _updateSelected: function() {
+ if (this.multi) {
+ this._selectMulti(this.selectedValues);
+ } else {
+ this._selectSelected(this.selected);
+ }
+ },
+
+ _selectMulti: function(values) {
+ this._selection.clear();
+ if (values) {
+ for (var i = 0; i < values.length; i++) {
+ this._selection.setItemSelected(this._valueToItem(values[i]), true);
+ }
+ }
+ },
+
+ _selectionChange: function() {
+ var s = this._selection.get();
+ if (this.multi) {
+ this._setSelectedItems(s);
+ } else {
+ this._setSelectedItems([s]);
+ this._setSelectedItem(s);
+ }
+ },
+
+ _toggleSelected: function(value) {
+ var i = this.selectedValues.indexOf(value);
+ var unselected = i < 0;
+ if (unselected) {
+ this.push('selectedValues',value);
+ } else {
+ this.splice('selectedValues',i,1);
+ }
+ this._selection.setItemSelected(this._valueToItem(value), unselected);
+ }
+ };
+
+ /** @polymerBehavior */
+ Polymer.IronMultiSelectableBehavior = [
+ Polymer.IronSelectableBehavior,
+ Polymer.IronMultiSelectableBehaviorImpl
+ ];
+/**
+ * `Polymer.IronMenuBehavior` implements accessible menu behavior.
+ *
+ * @demo demo/index.html
+ * @polymerBehavior Polymer.IronMenuBehavior
+ */
+ Polymer.IronMenuBehaviorImpl = {
+
+ properties: {
+
+ /**
+ * Returns the currently focused item.
+ * @type {?Object}
+ */
+ focusedItem: {
+ observer: '_focusedItemChanged',
+ readOnly: true,
+ type: Object
+ },
+
+ /**
+ * The attribute to use on menu items to look up the item title. Typing the first
+ * letter of an item when the menu is open focuses that item. If unset, `textContent`
+ * will be used.
+ */
+ attrForItemTitle: {
+ type: String
+ }
+ },
+
+ hostAttributes: {
+ 'role': 'menu',
+ 'tabindex': '0'
+ },
+
+ observers: [
+ '_updateMultiselectable(multi)'
+ ],
+
+ listeners: {
+ 'focus': '_onFocus',
+ 'keydown': '_onKeydown',
+ 'iron-items-changed': '_onIronItemsChanged'
+ },
+
+ keyBindings: {
+ 'up': '_onUpKey',
+ 'down': '_onDownKey',
+ 'esc': '_onEscKey',
+ 'shift+tab:keydown': '_onShiftTabDown'
+ },
+
+ attached: function() {
+ this._resetTabindices();
+ },
+
+ /**
+ * Selects the given value. If the `multi` property is true, then the selected state of the
+ * `value` will be toggled; otherwise the `value` will be selected.
+ *
+ * @param {string} value the value to select.
+ */
+ select: function(value) {
+ if (this._defaultFocusAsync) {
+ this.cancelAsync(this._defaultFocusAsync);
+ this._defaultFocusAsync = null;
+ }
+ var item = this._valueToItem(value);
+ if (item && item.hasAttribute('disabled')) return;
+ this._setFocusedItem(item);
+ Polymer.IronMultiSelectableBehaviorImpl.select.apply(this, arguments);
+ },
+
+ /**
+ * Resets all tabindex attributes to the appropriate value based on the
+ * current selection state. The appropriate value is `0` (focusable) for
+ * the default selected item, and `-1` (not keyboard focusable) for all
+ * other items.
+ */
+ _resetTabindices: function() {
+ var selectedItem = this.multi ? (this.selectedItems && this.selectedItems[0]) : this.selectedItem;
+
+ this.items.forEach(function(item) {
+ item.setAttribute('tabindex', item === selectedItem ? '0' : '-1');
+ }, this);
+ },
+
+ /**
+ * Sets appropriate ARIA based on whether or not the menu is meant to be
+ * multi-selectable.
+ *
+ * @param {boolean} multi True if the menu should be multi-selectable.
+ */
+ _updateMultiselectable: function(multi) {
+ if (multi) {
+ this.setAttribute('aria-multiselectable', 'true');
+ } else {
+ this.removeAttribute('aria-multiselectable');
+ }
+ },
+
+ /**
+ * Given a KeyboardEvent, this method will focus the appropriate item in the
+ * menu (if there is a relevant item, and it is possible to focus it).
+ *
+ * @param {KeyboardEvent} event A KeyboardEvent.
+ */
+ _focusWithKeyboardEvent: function(event) {
+ for (var i = 0, item; item = this.items[i]; i++) {
+ var attr = this.attrForItemTitle || 'textContent';
+ var title = item[attr] || item.getAttribute(attr);
+ if (title && title.trim().charAt(0).toLowerCase() === String.fromCharCode(event.keyCode).toLowerCase()) {
+ this._setFocusedItem(item);
+ break;
+ }
+ }
+ },
+
+ /**
+ * Focuses the previous item (relative to the currently focused item) in the
+ * menu.
+ */
+ _focusPrevious: function() {
+ var length = this.items.length;
+ var index = (Number(this.indexOf(this.focusedItem)) - 1 + length) % length;
+ this._setFocusedItem(this.items[index]);
+ },
+
+ /**
+ * Focuses the next item (relative to the currently focused item) in the
+ * menu.
+ */
+ _focusNext: function() {
+ var index = (Number(this.indexOf(this.focusedItem)) + 1) % this.items.length;
+ this._setFocusedItem(this.items[index]);
+ },
+
+ /**
+ * Mutates items in the menu based on provided selection details, so that
+ * all items correctly reflect selection state.
+ *
+ * @param {Element} item An item in the menu.
+ * @param {boolean} isSelected True if the item should be shown in a
+ * selected state, otherwise false.
+ */
+ _applySelection: function(item, isSelected) {
+ if (isSelected) {
+ item.setAttribute('aria-selected', 'true');
+ } else {
+ item.removeAttribute('aria-selected');
+ }
+
+ Polymer.IronSelectableBehavior._applySelection.apply(this, arguments);
+ },
+
+ /**
+ * Discretely updates tabindex values among menu items as the focused item
+ * changes.
+ *
+ * @param {Element} focusedItem The element that is currently focused.
+ * @param {?Element} old The last element that was considered focused, if
+ * applicable.
+ */
+ _focusedItemChanged: function(focusedItem, old) {
+ old && old.setAttribute('tabindex', '-1');
+ if (focusedItem) {
+ focusedItem.setAttribute('tabindex', '0');
+ focusedItem.focus();
+ }
+ },
+
+ /**
+ * A handler that responds to mutation changes related to the list of items
+ * in the menu.
+ *
+ * @param {CustomEvent} event An event containing mutation records as its
+ * detail.
+ */
+ _onIronItemsChanged: function(event) {
+ var mutations = event.detail;
+ var mutation;
+ var index;
+
+ for (index = 0; index < mutations.length; ++index) {
+ mutation = mutations[index];
+
+ if (mutation.addedNodes.length) {
+ this._resetTabindices();
+ break;
+ }
+ }
+ },
+
+ /**
+ * Handler that is called when a shift+tab keypress is detected by the menu.
+ *
+ * @param {CustomEvent} event A key combination event.
+ */
+ _onShiftTabDown: function(event) {
+ var oldTabIndex;
+
+ Polymer.IronMenuBehaviorImpl._shiftTabPressed = true;
+
+ oldTabIndex = this.getAttribute('tabindex');
+
+ this.setAttribute('tabindex', '-1');
+
+ this.async(function() {
+ this.setAttribute('tabindex', oldTabIndex);
+ Polymer.IronMenuBehaviorImpl._shiftTabPressed = false;
+ // NOTE(cdata): polymer/polymer#1305
+ }, 1);
+ },
+
+ /**
+ * Handler that is called when the menu receives focus.
+ *
+ * @param {FocusEvent} event A focus event.
+ */
+ _onFocus: function(event) {
+ if (Polymer.IronMenuBehaviorImpl._shiftTabPressed) {
+ return;
+ }
+ // do not focus the menu itself
+ this.blur();
+ // clear the cached focus item
+ this._setFocusedItem(null);
+ this._defaultFocusAsync = this.async(function() {
+ // focus the selected item when the menu receives focus, or the first item
+ // if no item is selected
+ var selectedItem = this.multi ? (this.selectedItems && this.selectedItems[0]) : this.selectedItem;
+ if (selectedItem) {
+ this._setFocusedItem(selectedItem);
+ } else {
+ this._setFocusedItem(this.items[0]);
+ }
+ // async 100ms to wait for `select` to get called from `_itemActivate`
+ }, 100);
+ },
+
+ /**
+ * Handler that is called when the up key is pressed.
+ *
+ * @param {CustomEvent} event A key combination event.
+ */
+ _onUpKey: function(event) {
+ // up and down arrows moves the focus
+ this._focusPrevious();
+ },
+
+ /**
+ * Handler that is called when the down key is pressed.
+ *
+ * @param {CustomEvent} event A key combination event.
+ */
+ _onDownKey: function(event) {
+ this._focusNext();
+ },
+
+ /**
+ * Handler that is called when the esc key is pressed.
+ *
+ * @param {CustomEvent} event A key combination event.
+ */
+ _onEscKey: function(event) {
+ // esc blurs the control
+ this.focusedItem.blur();
+ },
+
+ /**
+ * Handler that is called when a keydown event is detected.
+ *
+ * @param {KeyboardEvent} event A keyboard event.
+ */
+ _onKeydown: function(event) {
+ if (this.keyboardEventMatchesKeys(event, 'up down esc')) {
+ return;
+ }
+
+ // all other keys focus the menu item starting with that character
+ this._focusWithKeyboardEvent(event);
+ }
+ };
+
+ Polymer.IronMenuBehaviorImpl._shiftTabPressed = false;
+
+ /** @polymerBehavior Polymer.IronMenuBehavior */
+ Polymer.IronMenuBehavior = [
+ Polymer.IronMultiSelectableBehavior,
+ Polymer.IronA11yKeysBehavior,
+ Polymer.IronMenuBehaviorImpl
+ ];
+(function() {
+
+ Polymer({
+
+ is: 'paper-menu',
+
+ behaviors: [
+ Polymer.IronMenuBehavior
+ ]
+
+ });
+
+})();
+/**
+ * `IronResizableBehavior` is a behavior that can be used in Polymer elements to
+ * coordinate the flow of resize events between "resizers" (elements that control the
+ * size or hidden state of their children) and "resizables" (elements that need to be
+ * notified when they are resized or un-hidden by their parents in order to take
+ * action on their new measurements).
+ * Elements that perform measurement should add the `IronResizableBehavior` behavior to
+ * their element definition and listen for the `iron-resize` event on themselves.
+ * This event will be fired when they become showing after having been hidden,
+ * when they are resized explicitly by another resizable, or when the window has been
+ * resized.
+ * Note, the `iron-resize` event is non-bubbling.
+ *
+ * @polymerBehavior Polymer.IronResizableBehavior
+ * @demo demo/index.html
+ **/
+ Polymer.IronResizableBehavior = {
+ properties: {
+ /**
+ * The closest ancestor element that implements `IronResizableBehavior`.
+ */
+ _parentResizable: {
+ type: Object,
+ observer: '_parentResizableChanged'
+ },
+
+ /**
+ * True if this element is currently notifying its descedant elements of
+ * resize.
+ */
+ _notifyingDescendant: {
+ type: Boolean,
+ value: false
+ }
+ },
+
+ listeners: {
+ 'iron-request-resize-notifications': '_onIronRequestResizeNotifications'
+ },
+
+ created: function() {
+ // We don't really need property effects on these, and also we want them
+ // to be created before the `_parentResizable` observer fires:
+ this._interestedResizables = [];
+ this._boundNotifyResize = this.notifyResize.bind(this);
+ },
+
+ attached: function() {
+ this.fire('iron-request-resize-notifications', null, {
+ node: this,
+ bubbles: true,
+ cancelable: true
+ });
+
+ if (!this._parentResizable) {
+ window.addEventListener('resize', this._boundNotifyResize);
+ this.notifyResize();
+ }
+ },
+
+ detached: function() {
+ if (this._parentResizable) {
+ this._parentResizable.stopResizeNotificationsFor(this);
+ } else {
+ window.removeEventListener('resize', this._boundNotifyResize);
+ }
+
+ this._parentResizable = null;
+ },
+
+ /**
+ * Can be called to manually notify a resizable and its descendant
+ * resizables of a resize change.
+ */
+ notifyResize: function() {
+ if (!this.isAttached) {
+ return;
+ }
+
+ this._interestedResizables.forEach(function(resizable) {
+ if (this.resizerShouldNotify(resizable)) {
+ this._notifyDescendant(resizable);
+ }
+ }, this);
+
+ this._fireResize();
+ },
+
+ /**
+ * Used to assign the closest resizable ancestor to this resizable
+ * if the ancestor detects a request for notifications.
+ */
+ assignParentResizable: function(parentResizable) {
+ this._parentResizable = parentResizable;
+ },
+
+ /**
+ * Used to remove a resizable descendant from the list of descendants
+ * that should be notified of a resize change.
+ */
+ stopResizeNotificationsFor: function(target) {
+ var index = this._interestedResizables.indexOf(target);
+
+ if (index > -1) {
+ this._interestedResizables.splice(index, 1);
+ this.unlisten(target, 'iron-resize', '_onDescendantIronResize');
+ }
+ },
+
+ /**
+ * This method can be overridden to filter nested elements that should or
+ * should not be notified by the current element. Return true if an element
+ * should be notified, or false if it should not be notified.
+ *
+ * @param {HTMLElement} element A candidate descendant element that
+ * implements `IronResizableBehavior`.
+ * @return {boolean} True if the `element` should be notified of resize.
+ */
+ resizerShouldNotify: function(element) { return true; },
+
+ _onDescendantIronResize: function(event) {
+ if (this._notifyingDescendant) {
+ event.stopPropagation();
+ return;
+ }
+
+ // NOTE(cdata): In ShadowDOM, event retargetting makes echoing of the
+ // otherwise non-bubbling event "just work." We do it manually here for
+ // the case where Polymer is not using shadow roots for whatever reason:
+ if (!Polymer.Settings.useShadow) {
+ this._fireResize();
+ }
+ },
+
+ _fireResize: function() {
+ this.fire('iron-resize', null, {
+ node: this,
+ bubbles: false
+ });
+ },
+
+ _onIronRequestResizeNotifications: function(event) {
+ var target = event.path ? event.path[0] : event.target;
+
+ if (target === this) {
+ return;
+ }
+
+ if (this._interestedResizables.indexOf(target) === -1) {
+ this._interestedResizables.push(target);
+ this.listen(target, 'iron-resize', '_onDescendantIronResize');
+ }
+
+ target.assignParentResizable(this);
+ this._notifyDescendant(target);
+
+ event.stopPropagation();
+ },
+
+ _parentResizableChanged: function(parentResizable) {
+ if (parentResizable) {
+ window.removeEventListener('resize', this._boundNotifyResize);
+ }
+ },
+
+ _notifyDescendant: function(descendant) {
+ // NOTE(cdata): In IE10, attached is fired on children first, so it's
+ // important not to notify them if the parent is not attached yet (or
+ // else they will get redundantly notified when the parent attaches).
+ if (!this.isAttached) {
+ return;
+ }
+
+ this._notifyingDescendant = true;
+ descendant.notifyResize();
+ this._notifyingDescendant = false;
+ }
+ };
+/**
+Polymer.IronFitBehavior fits an element in another element using `max-height` and `max-width`, and
+optionally centers it in the window or another element.
+
+The element will only be sized and/or positioned if it has not already been sized and/or positioned
+by CSS.
+
+CSS properties | Action
+-----------------------------|-------------------------------------------
+`position` set | Element is not centered horizontally or vertically
+`top` or `bottom` set | Element is not vertically centered
+`left` or `right` set | Element is not horizontally centered
+`max-height` or `height` set | Element respects `max-height` or `height`
+`max-width` or `width` set | Element respects `max-width` or `width`
+
+@demo demo/index.html
+@polymerBehavior
+*/
+
+ Polymer.IronFitBehavior = {
+
+ properties: {
+
+ /**
+ * The element that will receive a `max-height`/`width`. By default it is the same as `this`,
+ * but it can be set to a child element. This is useful, for example, for implementing a
+ * scrolling region inside the element.
+ * @type {!Element}
+ */
+ sizingTarget: {
+ type: Object,
+ value: function() {
+ return this;
+ }
+ },
+
+ /**
+ * The element to fit `this` into.
+ */
+ fitInto: {
+ type: Object,
+ value: window
+ },
+
+ /**
+ * Set to true to auto-fit on attach.
+ */
+ autoFitOnAttach: {
+ type: Boolean,
+ value: false
+ },
+
+ /** @type {?Object} */
+ _fitInfo: {
+ type: Object
+ }
+
+ },
+
+ get _fitWidth() {
+ var fitWidth;
+ if (this.fitInto === window) {
+ fitWidth = this.fitInto.innerWidth;
+ } else {
+ fitWidth = this.fitInto.getBoundingClientRect().width;
+ }
+ return fitWidth;
+ },
+
+ get _fitHeight() {
+ var fitHeight;
+ if (this.fitInto === window) {
+ fitHeight = this.fitInto.innerHeight;
+ } else {
+ fitHeight = this.fitInto.getBoundingClientRect().height;
+ }
+ return fitHeight;
+ },
+
+ get _fitLeft() {
+ var fitLeft;
+ if (this.fitInto === window) {
+ fitLeft = 0;
+ } else {
+ fitLeft = this.fitInto.getBoundingClientRect().left;
+ }
+ return fitLeft;
+ },
+
+ get _fitTop() {
+ var fitTop;
+ if (this.fitInto === window) {
+ fitTop = 0;
+ } else {
+ fitTop = this.fitInto.getBoundingClientRect().top;
+ }
+ return fitTop;
+ },
+
+ attached: function() {
+ if (this.autoFitOnAttach) {
+ if (window.getComputedStyle(this).display === 'none') {
+ setTimeout(function() {
+ this.fit();
+ }.bind(this));
+ } else {
+ this.fit();
+ }
+ }
+ },
+
+ /**
+ * Fits and optionally centers the element into the window, or `fitInfo` if specified.
+ */
+ fit: function() {
+ this._discoverInfo();
+ this.constrain();
+ this.center();
+ },
+
+ /**
+ * Memoize information needed to position and size the target element.
+ */
+ _discoverInfo: function() {
+ if (this._fitInfo) {
+ return;
+ }
+ var target = window.getComputedStyle(this);
+ var sizer = window.getComputedStyle(this.sizingTarget);
+ this._fitInfo = {
+ inlineStyle: {
+ top: this.style.top || '',
+ left: this.style.left || ''
+ },
+ positionedBy: {
+ vertically: target.top !== 'auto' ? 'top' : (target.bottom !== 'auto' ?
+ 'bottom' : null),
+ horizontally: target.left !== 'auto' ? 'left' : (target.right !== 'auto' ?
+ 'right' : null),
+ css: target.position
+ },
+ sizedBy: {
+ height: sizer.maxHeight !== 'none',
+ width: sizer.maxWidth !== 'none'
+ },
+ margin: {
+ top: parseInt(target.marginTop, 10) || 0,
+ right: parseInt(target.marginRight, 10) || 0,
+ bottom: parseInt(target.marginBottom, 10) || 0,
+ left: parseInt(target.marginLeft, 10) || 0
+ }
+ };
+ },
+
+ /**
+ * Resets the target element's position and size constraints, and clear
+ * the memoized data.
+ */
+ resetFit: function() {
+ if (!this._fitInfo || !this._fitInfo.sizedBy.height) {
+ this.sizingTarget.style.maxHeight = '';
+ this.style.top = this._fitInfo ? this._fitInfo.inlineStyle.top : '';
+ }
+ if (!this._fitInfo || !this._fitInfo.sizedBy.width) {
+ this.sizingTarget.style.maxWidth = '';
+ this.style.left = this._fitInfo ? this._fitInfo.inlineStyle.left : '';
+ }
+ if (this._fitInfo) {
+ this.style.position = this._fitInfo.positionedBy.css;
+ }
+ this._fitInfo = null;
+ },
+
+ /**
+ * Equivalent to calling `resetFit()` and `fit()`. Useful to call this after the element,
+ * the window, or the `fitInfo` element has been resized.
+ */
+ refit: function() {
+ this.resetFit();
+ this.fit();
+ },
+
+ /**
+ * Constrains the size of the element to the window or `fitInfo` by setting `max-height`
+ * and/or `max-width`.
+ */
+ constrain: function() {
+ var info = this._fitInfo;
+ // position at (0px, 0px) if not already positioned, so we can measure the natural size.
+ if (!this._fitInfo.positionedBy.vertically) {
+ this.style.top = '0px';
+ }
+ if (!this._fitInfo.positionedBy.horizontally) {
+ this.style.left = '0px';
+ }
+ // need border-box for margin/padding
+ this.sizingTarget.style.boxSizing = 'border-box';
+ // constrain the width and height if not already set
+ var rect = this.getBoundingClientRect();
+ if (!info.sizedBy.height) {
+ this._sizeDimension(rect, info.positionedBy.vertically, 'top', 'bottom', 'Height');
+ }
+ if (!info.sizedBy.width) {
+ this._sizeDimension(rect, info.positionedBy.horizontally, 'left', 'right', 'Width');
+ }
+ },
+
+ _sizeDimension: function(rect, positionedBy, start, end, extent) {
+ var info = this._fitInfo;
+ var max = extent === 'Width' ? this._fitWidth : this._fitHeight;
+ var flip = (positionedBy === end);
+ var offset = flip ? max - rect[end] : rect[start];
+ var margin = info.margin[flip ? start : end];
+ var offsetExtent = 'offset' + extent;
+ var sizingOffset = this[offsetExtent] - this.sizingTarget[offsetExtent];
+ this.sizingTarget.style['max' + extent] = (max - margin - offset - sizingOffset) + 'px';
+ },
+
+ /**
+ * Centers horizontally and vertically if not already positioned. This also sets
+ * `position:fixed`.
+ */
+ center: function() {
+ if (!this._fitInfo.positionedBy.vertically || !this._fitInfo.positionedBy.horizontally) {
+ // need position:fixed to center
+ this.style.position = 'fixed';
+ }
+ if (!this._fitInfo.positionedBy.vertically) {
+ var top = (this._fitHeight - this.offsetHeight) / 2 + this._fitTop;
+ top -= this._fitInfo.margin.top;
+ this.style.top = top + 'px';
+ }
+ if (!this._fitInfo.positionedBy.horizontally) {
+ var left = (this._fitWidth - this.offsetWidth) / 2 + this._fitLeft;
+ left -= this._fitInfo.margin.left;
+ this.style.left = left + 'px';
+ }
+ }
+
+ };
+Polymer.IronOverlayManager = (function() {
+
+ var overlays = [];
+ var DEFAULT_Z = 10;
+ var backdrops = [];
+
+ // track overlays for z-index and focus managemant
+ 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, aboveZ + 2);
+ }
+
+ function setZ(element, z) {
+ element.style.zIndex = z;
+ }
+
+ function currentOverlay() {
+ var i = overlays.length - 1;
+ while (overlays[i] && !overlays[i].opened) {
+ --i;
+ }
+ return overlays[i];
+ }
+
+ function currentOverlayZ() {
+ var z;
+ var current = currentOverlay();
+ if (current) {
+ var z1 = window.getComputedStyle(current).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();
+ }
+ }
+
+ function trackBackdrop(element) {
+ // backdrops contains the overlays with a backdrop that are currently
+ // visible
+ if (element.opened) {
+ backdrops.push(element);
+ } else {
+ var index = backdrops.indexOf(element);
+ if (index >= 0) {
+ backdrops.splice(index, 1);
+ }
+ }
+ }
+
+ function getBackdrops() {
+ return backdrops;
+ }
+
+ return {
+ addOverlay: addOverlay,
+ removeOverlay: removeOverlay,
+ currentOverlay: currentOverlay,
+ currentOverlayZ: currentOverlayZ,
+ focusOverlay: focusOverlay,
+ trackBackdrop: trackBackdrop,
+ getBackdrops: getBackdrops
+ };
+
+ })();
+(function() {
+
+ Polymer({
+
+ is: 'iron-overlay-backdrop',
+
+ properties: {
+
+ /**
+ * Returns true if the backdrop is opened.
+ */
+ opened: {
+ readOnly: true,
+ reflectToAttribute: true,
+ type: Boolean,
+ value: false
+ },
+
+ _manager: {
+ type: Object,
+ value: Polymer.IronOverlayManager
+ }
+
+ },
+
+ /**
+ * Appends the backdrop to document body and sets its `z-index` to be below the latest overlay.
+ */
+ prepare: function() {
+ if (!this.parentNode) {
+ Polymer.dom(document.body).appendChild(this);
+ this.style.zIndex = this._manager.currentOverlayZ() - 1;
+ }
+ },
+
+ /**
+ * Shows the backdrop if needed.
+ */
+ open: function() {
+ // only need to make the backdrop visible if this is called by the first overlay with a backdrop
+ if (this._manager.getBackdrops().length < 2) {
+ this._setOpened(true);
+ }
+ },
+
+ /**
+ * Hides the backdrop if needed.
+ */
+ close: function() {
+ // only need to make the backdrop invisible if this is called by the last overlay with a backdrop
+ if (this._manager.getBackdrops().length < 2) {
+ this._setOpened(false);
+ }
+ },
+
+ /**
+ * Removes the backdrop from document body if needed.
+ */
+ complete: function() {
+ // only remove the backdrop if there are no more overlays with backdrops
+ if (this._manager.getBackdrops().length === 0 && this.parentNode) {
+ Polymer.dom(this.parentNode).removeChild(this);
+ }
+ }
+
+ });
+
+})();
+/**
+Use `Polymer.IronOverlayBehavior` to implement an element that can be hidden or shown, and displays
+on top of other content. It includes an optional backdrop, and can be used to implement a variety
+of UI controls including dialogs and drop downs. Multiple overlays may be displayed at once.
+
+### Closing and canceling
+
+A dialog may be hidden by closing or canceling. The difference between close and cancel is user
+intent. Closing generally implies that the user acknowledged the content on the overlay. By default,
+it will cancel whenever the user taps outside it or presses the escape key. This behavior is
+configurable with the `no-cancel-on-esc-key` and the `no-cancel-on-outside-click` properties.
+`close()` should be called explicitly by the implementer when the user interacts with a control
+in the overlay element.
+
+### Positioning
+
+By default the element is sized and positioned to fit and centered inside the window. You can
+position and size it manually using CSS. See `Polymer.IronFitBehavior`.
+
+### Backdrop
+
+Set the `with-backdrop` attribute to display a backdrop behind the overlay. The backdrop is
+appended to `<body>` and is of type `<iron-overlay-backdrop>`. See its doc page for styling
+options.
+
+### Limitations
+
+The element is styled to appear on top of other content by setting its `z-index` property. You
+must ensure no element has a stacking context with a higher `z-index` than its parent stacking
+context. You should place this element as a child of `<body>` whenever possible.
+
+@demo demo/index.html
+@polymerBehavior Polymer.IronOverlayBehavior
+*/
+
+ Polymer.IronOverlayBehaviorImpl = {
+
+ properties: {
+
+ /**
+ * True if the overlay is currently displayed.
+ */
+ opened: {
+ observer: '_openedChanged',
+ type: Boolean,
+ value: false,
+ notify: true
+ },
+
+ /**
+ * True if the overlay was canceled when it was last closed.
+ */
+ canceled: {
+ observer: '_canceledChanged',
+ readOnly: true,
+ type: Boolean,
+ value: false
+ },
+
+ /**
+ * Set to true to display a backdrop behind the overlay.
+ */
+ withBackdrop: {
+ type: Boolean,
+ value: false
+ },
+
+ /**
+ * Set to true to disable auto-focusing the overlay or child nodes with
+ * the `autofocus` attribute` when the overlay is opened.
+ */
+ noAutoFocus: {
+ type: Boolean,
+ value: false
+ },
+
+ /**
+ * Set to true to disable canceling the overlay with the ESC key.
+ */
+ noCancelOnEscKey: {
+ type: Boolean,
+ value: false
+ },
+
+ /**
+ * Set to true to disable canceling the overlay by clicking outside it.
+ */
+ noCancelOnOutsideClick: {
+ type: Boolean,
+ value: false
+ },
+
+ /**
+ * Returns the reason this dialog was last closed.
+ */
+ closingReason: {
+ // was a getter before, but needs to be a property so other
+ // behaviors can override this.
+ type: Object
+ },
+
+ _manager: {
+ type: Object,
+ value: Polymer.IronOverlayManager
+ },
+
+ _boundOnCaptureClick: {
+ type: Function,
+ value: function() {
+ return this._onCaptureClick.bind(this);
+ }
+ },
+
+ _boundOnCaptureKeydown: {
+ type: Function,
+ value: function() {
+ return this._onCaptureKeydown.bind(this);
+ }
+ }
+
+ },
+
+ listeners: {
+ 'tap': '_onClick',
+ 'iron-resize': '_onIronResize'
+ },
+
+ /**
+ * The backdrop element.
+ * @type Node
+ */
+ get backdropElement() {
+ return this._backdrop;
+ },
+
+ get _focusNode() {
+ return Polymer.dom(this).querySelector('[autofocus]') || this;
+ },
+
+ registered: function() {
+ this._backdrop = document.createElement('iron-overlay-backdrop');
+ },
+
+ ready: function() {
+ this._ensureSetup();
+ if (this._callOpenedWhenReady) {
+ this._openedChanged();
+ }
+ },
+
+ detached: function() {
+ this.opened = false;
+ this._completeBackdrop();
+ this._manager.removeOverlay(this);
+ },
+
+ /**
+ * Toggle the opened state of the overlay.
+ */
+ toggle: function() {
+ this.opened = !this.opened;
+ },
+
+ /**
+ * Open the overlay.
+ */
+ open: function() {
+ this.opened = true;
+ this.closingReason = {canceled: false};
+ },
+
+ /**
+ * Close the overlay.
+ */
+ close: function() {
+ this.opened = false;
+ this._setCanceled(false);
+ },
+
+ /**
+ * Cancels the overlay.
+ */
+ cancel: function() {
+ this.opened = false;
+ this._setCanceled(true);
+ },
+
+ _ensureSetup: function() {
+ if (this._overlaySetup) {
+ return;
+ }
+ this._overlaySetup = true;
+ this.style.outline = 'none';
+ this.style.display = 'none';
+ },
+
+ _openedChanged: function() {
+ if (this.opened) {
+ this.removeAttribute('aria-hidden');
+ } else {
+ this.setAttribute('aria-hidden', 'true');
+ }
+
+ // wait to call after ready only if we're initially open
+ if (!this._overlaySetup) {
+ this._callOpenedWhenReady = this.opened;
+ return;
+ }
+ if (this._openChangedAsync) {
+ this.cancelAsync(this._openChangedAsync);
+ }
+
+ this._toggleListeners();
+
+ if (this.opened) {
+ this._prepareRenderOpened();
+ }
+
+ // async here to allow overlay layer to become visible.
+ this._openChangedAsync = this.async(function() {
+ // overlay becomes visible here
+ this.style.display = '';
+ // force layout to ensure transitions will go
+ /** @suppress {suspiciousCode} */ this.offsetWidth;
+ if (this.opened) {
+ this._renderOpened();
+ } else {
+ this._renderClosed();
+ }
+ this._openChangedAsync = null;
+ });
+
+ },
+
+ _canceledChanged: function() {
+ this.closingReason = this.closingReason || {};
+ this.closingReason.canceled = this.canceled;
+ },
+
+ _toggleListener: function(enable, node, event, boundListener, capture) {
+ if (enable) {
+ // enable document-wide tap recognizer
+ if (event === 'tap') {
+ Polymer.Gestures.add(document, 'tap', null);
+ }
+ node.addEventListener(event, boundListener, capture);
+ } else {
+ // disable document-wide tap recognizer
+ if (event === 'tap') {
+ Polymer.Gestures.remove(document, 'tap', null);
+ }
+ node.removeEventListener(event, boundListener, capture);
+ }
+ },
+
+ _toggleListeners: function() {
+ if (this._toggleListenersAsync) {
+ this.cancelAsync(this._toggleListenersAsync);
+ }
+ // async so we don't auto-close immediately via a click.
+ this._toggleListenersAsync = this.async(function() {
+ this._toggleListener(this.opened, document, 'tap', this._boundOnCaptureClick, true);
+ this._toggleListener(this.opened, document, 'keydown', this._boundOnCaptureKeydown, true);
+ this._toggleListenersAsync = null;
+ }, 1);
+ },
+
+ // tasks which must occur before opening; e.g. making the element visible
+ _prepareRenderOpened: function() {
+ this._manager.addOverlay(this);
+
+ if (this.withBackdrop) {
+ this.backdropElement.prepare();
+ this._manager.trackBackdrop(this);
+ }
+
+ this._preparePositioning();
+ this.fit();
+ this._finishPositioning();
+ },
+
+ // tasks which cause the overlay to actually open; typically play an
+ // animation
+ _renderOpened: function() {
+ if (this.withBackdrop) {
+ this.backdropElement.open();
+ }
+ this._finishRenderOpened();
+ },
+
+ _renderClosed: function() {
+ if (this.withBackdrop) {
+ this.backdropElement.close();
+ }
+ this._finishRenderClosed();
+ },
+
+ _onTransitionend: function(event) {
+ // make sure this is our transition event.
+ if (event && event.target !== this) {
+ return;
+ }
+ if (this.opened) {
+ this._finishRenderOpened();
+ } else {
+ this._finishRenderClosed();
+ }
+ },
+
+ _finishRenderOpened: function() {
+ // focus the child node with [autofocus]
+ if (!this.noAutoFocus) {
+ this._focusNode.focus();
+ }
+
+ this.fire('iron-overlay-opened');
+
+ this._squelchNextResize = true;
+ this.async(this.notifyResize);
+ },
+
+ _finishRenderClosed: function() {
+ // hide the overlay and remove the backdrop
+ this.resetFit();
+ this.style.display = 'none';
+ this._completeBackdrop();
+ this._manager.removeOverlay(this);
+
+ this._focusNode.blur();
+ // focus the next overlay, if there is one
+ this._manager.focusOverlay();
+
+ this.fire('iron-overlay-closed', this.closingReason);
+
+ this._squelchNextResize = true;
+ this.async(this.notifyResize);
+ },
+
+ _completeBackdrop: function() {
+ if (this.withBackdrop) {
+ this._manager.trackBackdrop(this);
+ this.backdropElement.complete();
+ }
+ },
+
+ _preparePositioning: function() {
+ this.style.transition = this.style.webkitTransition = 'none';
+ this.style.transform = this.style.webkitTransform = 'none';
+ this.style.display = '';
+ },
+
+ _finishPositioning: function() {
+ this.style.display = 'none';
+ this.style.transform = this.style.webkitTransform = '';
+ // force layout to avoid application of transform
+ /** @suppress {suspiciousCode} */ this.offsetWidth;
+ this.style.transition = this.style.webkitTransition = '';
+ },
+
+ _applyFocus: function() {
+ if (this.opened) {
+ if (!this.noAutoFocus) {
+ this._focusNode.focus();
+ }
+ } else {
+ this._focusNode.blur();
+ this._manager.focusOverlay();
+ }
+ },
+
+ _onCaptureClick: function(event) {
+ // attempt to close asynchronously and prevent the close of a tap event is immediately heard
+ // on target. This is because in shadow dom due to event retargetting event.target is not
+ // useful.
+ if (!this.noCancelOnOutsideClick && (this._manager.currentOverlay() == this)) {
+ this._cancelJob = this.async(function() {
+ this.cancel();
+ }, 10);
+ }
+ },
+
+ _onClick: function(event) {
+ if (this._cancelJob) {
+ this.cancelAsync(this._cancelJob);
+ this._cancelJob = null;
+ }
+ },
+
+ _onCaptureKeydown: function(event) {
+ var ESC = 27;
+ if (!this.noCancelOnEscKey && (event.keyCode === ESC)) {
+ this.cancel();
+ event.stopPropagation();
+ }
+ },
+
+ _onIronResize: function() {
+ if (this._squelchNextResize) {
+ this._squelchNextResize = false;
+ return;
+ }
+ if (this.opened) {
+ this.refit();
+ }
+ }
+
+/**
+ * Fired after the `iron-overlay` opens.
+ * @event iron-overlay-opened
+ */
+
+/**
+ * Fired after the `iron-overlay` closes.
+ * @event iron-overlay-closed
+ * @param {{canceled: (boolean|undefined)}} set to the `closingReason` attribute
+ */
+ };
+
+ /** @polymerBehavior */
+ Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableBehavior, Polymer.IronOverlayBehaviorImpl];
+/**
+ * Use `Polymer.NeonAnimationBehavior` to implement an animation.
+ * @polymerBehavior
+ */
+ Polymer.NeonAnimationBehavior = {
+
+ properties: {
+
+ /**
+ * Defines the animation timing.
+ */
+ animationTiming: {
+ type: Object,
+ value: function() {
+ return {
+ duration: 500,
+ easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
+ fill: 'both'
+ }
+ }
+ }
+
+ },
+
+ registered: function() {
+ new Polymer.IronMeta({type: 'animation', key: this.is, value: this.constructor});
+ },
+
+ /**
+ * Do any animation configuration here.
+ */
+ // configure: function(config) {
+ // },
+
+ /**
+ * Returns the animation timing by mixing in properties from `config` to the defaults defined
+ * by the animation.
+ */
+ timingFromConfig: function(config) {
+ if (config.timing) {
+ for (var property in config.timing) {
+ this.animationTiming[property] = config.timing[property];
+ }
+ }
+ return this.animationTiming;
+ },
+
+ /**
+ * Sets `transform` and `transformOrigin` properties along with the prefixed versions.
+ */
+ setPrefixedProperty: function(node, property, value) {
+ var map = {
+ 'transform': ['webkitTransform'],
+ 'transformOrigin': ['mozTransformOrigin', 'webkitTransformOrigin']
+ };
+ var prefixes = map[property];
+ for (var prefix, index = 0; prefix = prefixes[index]; index++) {
+ node.style[prefix] = value;
+ }
+ node.style[property] = value;
+ },
+
+ /**
+ * Called when the animation finishes.
+ */
+ complete: function() {}
+
+ };
+Polymer({
+
+ is: 'opaque-animation',
+
+ behaviors: [
+ Polymer.NeonAnimationBehavior
+ ],
+
+ configure: function(config) {
+ var node = config.node;
+ node.style.opacity = '0';
+ this._effect = new KeyframeEffect(node, [
+ {'opacity': '1'},
+ {'opacity': '1'}
+ ], this.timingFromConfig(config));
+ return this._effect;
+ },
+
+ complete: function(config) {
+ config.node.style.opacity = '';
+ }
+
+ });
+/**
+ * `Polymer.NeonAnimatableBehavior` is implemented by elements containing animations for use with
+ * elements implementing `Polymer.NeonAnimationRunnerBehavior`.
+ * @polymerBehavior
+ */
+ Polymer.NeonAnimatableBehavior = {
+
+ properties: {
+
+ /**
+ * Animation configuration. See README for more info.
+ */
+ animationConfig: {
+ type: Object
+ },
+
+ /**
+ * Convenience property for setting an 'entry' animation. Do not set `animationConfig.entry`
+ * manually if using this. The animated node is set to `this` if using this property.
+ */
+ entryAnimation: {
+ observer: '_entryAnimationChanged',
+ type: String
+ },
+
+ /**
+ * Convenience property for setting an 'exit' animation. Do not set `animationConfig.exit`
+ * manually if using this. The animated node is set to `this` if using this property.
+ */
+ exitAnimation: {
+ observer: '_exitAnimationChanged',
+ type: String
+ }
+
+ },
+
+ _entryAnimationChanged: function() {
+ this.animationConfig = this.animationConfig || {};
+ if (this.entryAnimation !== 'fade-in-animation') {
+ // insert polyfill hack
+ this.animationConfig['entry'] = [{
+ name: 'opaque-animation',
+ node: this
+ }, {
+ name: this.entryAnimation,
+ node: this
+ }];
+ } else {
+ this.animationConfig['entry'] = [{
+ name: this.entryAnimation,
+ node: this
+ }];
+ }
+ },
+
+ _exitAnimationChanged: function() {
+ this.animationConfig = this.animationConfig || {};
+ this.animationConfig['exit'] = [{
+ name: this.exitAnimation,
+ node: this
+ }];
+ },
+
+ _copyProperties: function(config1, config2) {
+ // shallowly copy properties from config2 to config1
+ for (var property in config2) {
+ config1[property] = config2[property];
+ }
+ },
+
+ _cloneConfig: function(config) {
+ var clone = {
+ isClone: true
+ };
+ this._copyProperties(clone, config);
+ return clone;
+ },
+
+ _getAnimationConfigRecursive: function(type, map, allConfigs) {
+ if (!this.animationConfig) {
+ return;
+ }
+
+ // type is optional
+ var thisConfig;
+ if (type) {
+ thisConfig = this.animationConfig[type];
+ } else {
+ thisConfig = this.animationConfig;
+ }
+
+ if (!Array.isArray(thisConfig)) {
+ thisConfig = [thisConfig];
+ }
+
+ // iterate animations and recurse to process configurations from child nodes
+ if (thisConfig) {
+ for (var config, index = 0; config = thisConfig[index]; index++) {
+ if (config.animatable) {
+ config.animatable._getAnimationConfigRecursive(config.type || type, map, allConfigs);
+ } else {
+ if (config.id) {
+ var cachedConfig = map[config.id];
+ if (cachedConfig) {
+ // merge configurations with the same id, making a clone lazily
+ if (!cachedConfig.isClone) {
+ map[config.id] = this._cloneConfig(cachedConfig)
+ cachedConfig = map[config.id];
+ }
+ this._copyProperties(cachedConfig, config);
+ } else {
+ // put any configs with an id into a map
+ map[config.id] = config;
+ }
+ } else {
+ allConfigs.push(config);
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * An element implementing `Polymer.NeonAnimationRunnerBehavior` calls this method to configure
+ * an animation with an optional type. Elements implementing `Polymer.NeonAnimatableBehavior`
+ * should define the property `animationConfig`, which is either a configuration object
+ * or a map of animation type to array of configuration objects.
+ */
+ getAnimationConfig: function(type) {
+ var map = [];
+ var allConfigs = [];
+ this._getAnimationConfigRecursive(type, map, allConfigs);
+ // append the configurations saved in the map to the array
+ for (var key in map) {
+ allConfigs.push(map[key]);
+ }
+ return allConfigs;
+ }
+
+ };
+/**
+ * `Polymer.NeonAnimationRunnerBehavior` adds a method to run animations.
+ *
+ * @polymerBehavior Polymer.NeonAnimationRunnerBehavior
+ */
+ Polymer.NeonAnimationRunnerBehaviorImpl = {
+
+ properties: {
+
+ _animationMeta: {
+ type: Object,
+ value: function() {
+ return new Polymer.IronMeta({type: 'animation'});
+ }
+ },
+
+ /** @type {?Object} */
+ _player: {
+ type: Object
+ }
+
+ },
+
+ _configureAnimationEffects: function(allConfigs) {
+ var allAnimations = [];
+ if (allConfigs.length > 0) {
+ for (var config, index = 0; config = allConfigs[index]; index++) {
+ var animationConstructor = this._animationMeta.byKey(config.name);
+ if (animationConstructor) {
+ var animation = animationConstructor && new animationConstructor();
+ var effect = animation.configure(config);
+ if (effect) {
+ allAnimations.push({
+ animation: animation,
+ config: config,
+ effect: effect
+ });
+ }
+ } else {
+ console.warn(this.is + ':', config.name, 'not found!');
+ }
+ }
+ }
+ return allAnimations;
+ },
+
+ _runAnimationEffects: function(allEffects) {
+ return document.timeline.play(new GroupEffect(allEffects));
+ },
+
+ _completeAnimations: function(allAnimations) {
+ for (var animation, index = 0; animation = allAnimations[index]; index++) {
+ animation.animation.complete(animation.config);
+ }
+ },
+
+ /**
+ * Plays an animation with an optional `type`.
+ * @param {string=} type
+ * @param {!Object=} cookie
+ */
+ playAnimation: function(type, cookie) {
+ var allConfigs = this.getAnimationConfig(type);
+ if (!allConfigs) {
+ return;
+ }
+ var allAnimations = this._configureAnimationEffects(allConfigs);
+ var allEffects = allAnimations.map(function(animation) {
+ return animation.effect;
+ });
+
+ if (allEffects.length > 0) {
+ this._player = this._runAnimationEffects(allEffects);
+ this._player.onfinish = function() {
+ this._completeAnimations(allAnimations);
+
+ if (this._player) {
+ this._player.cancel();
+ this._player = null;
+ }
+
+ this.fire('neon-animation-finish', cookie, {bubbles: false});
+ }.bind(this);
+
+ } else {
+ this.fire('neon-animation-finish', cookie, {bubbles: false});
+ }
+ },
+
+ /**
+ * Cancels the currently running animation.
+ */
+ cancelAnimation: function() {
+ if (this._player) {
+ this._player.cancel();
+ }
+ }
+ };
+
+ /** @polymerBehavior Polymer.NeonAnimationRunnerBehavior */
+ Polymer.NeonAnimationRunnerBehavior = [
+ Polymer.NeonAnimatableBehavior,
+ Polymer.NeonAnimationRunnerBehaviorImpl
+ ];
+(function() {
+ 'use strict';
+
+ /**
+ * The IronDropdownScrollManager is intended to provide a central source
+ * of authority and control over which elements in a document are currently
+ * allowed to scroll.
+ */
+
+ Polymer.IronDropdownScrollManager = {
+
+ /**
+ * The current element that defines the DOM boundaries of the
+ * scroll lock. This is always the most recently locking element.
+ */
+ get currentLockingElement() {
+ return this._lockingElements[this._lockingElements.length - 1];
+ },
+
+
+ /**
+ * Returns true if the provided element is "scroll locked," which is to
+ * say that it cannot be scrolled via pointer or keyboard interactions.
+ *
+ * @param {HTMLElement} element An HTML element instance which may or may
+ * not be scroll locked.
+ */
+ elementIsScrollLocked: function(element) {
+ var currentLockingElement = this.currentLockingElement;
+ var scrollLocked;
+
+ if (this._hasCachedLockedElement(element)) {
+ return true;
+ }
+
+ if (this._hasCachedUnlockedElement(element)) {
+ return false;
+ }
+
+ scrollLocked = !!currentLockingElement &&
+ currentLockingElement !== element &&
+ !this._composedTreeContains(currentLockingElement, element);
+
+ if (scrollLocked) {
+ this._lockedElementCache.push(element);
+ } else {
+ this._unlockedElementCache.push(element);
+ }
+
+ return scrollLocked;
+ },
+
+ /**
+ * Push an element onto the current scroll lock stack. The most recently
+ * pushed element and its children will be considered scrollable. All
+ * other elements will not be scrollable.
+ *
+ * Scroll locking is implemented as a stack so that cases such as
+ * dropdowns within dropdowns are handled well.
+ *
+ * @param {HTMLElement} element The element that should lock scroll.
+ */
+ pushScrollLock: function(element) {
+ if (this._lockingElements.length === 0) {
+ this._lockScrollInteractions();
+ }
+
+ this._lockingElements.push(element);
+
+ this._lockedElementCache = [];
+ this._unlockedElementCache = [];
+ },
+
+ /**
+ * Remove an element from the scroll lock stack. The element being
+ * removed does not need to be the most recently pushed element. However,
+ * the scroll lock constraints only change when the most recently pushed
+ * element is removed.
+ *
+ * @param {HTMLElement} element The element to remove from the scroll
+ * lock stack.
+ */
+ removeScrollLock: function(element) {
+ var index = this._lockingElements.indexOf(element);
+
+ if (index === -1) {
+ return;
+ }
+
+ this._lockingElements.splice(index, 1);
+
+ this._lockedElementCache = [];
+ this._unlockedElementCache = [];
+
+ if (this._lockingElements.length === 0) {
+ this._unlockScrollInteractions();
+ }
+ },
+
+ _lockingElements: [],
+
+ _lockedElementCache: null,
+
+ _unlockedElementCache: null,
+
+ _originalBodyStyles: {},
+
+ _isScrollingKeypress: function(event) {
+ return Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(
+ event, 'pageup pagedown home end up left down right');
+ },
+
+ _hasCachedLockedElement: function(element) {
+ return this._lockedElementCache.indexOf(element) > -1;
+ },
+
+ _hasCachedUnlockedElement: function(element) {
+ return this._unlockedElementCache.indexOf(element) > -1;
+ },
+
+ _composedTreeContains: function(element, child) {
+ // NOTE(cdata): This method iterates over content elements and their
+ // corresponding distributed nodes to implement a contains-like method
+ // that pierces through the composed tree of the ShadowDOM. Results of
+ // this operation are cached (elsewhere) on a per-scroll-lock basis, to
+ // guard against potentially expensive lookups happening repeatedly as
+ // a user scrolls / touchmoves.
+ var contentElements;
+ var distributedNodes;
+ var contentIndex;
+ var nodeIndex;
+
+ if (element.contains(child)) {
+ return true;
+ }
+
+ contentElements = Polymer.dom(element).querySelectorAll('content');
+
+ for (contentIndex = 0; contentIndex < contentElements.length; ++contentIndex) {
+
+ distributedNodes = Polymer.dom(contentElements[contentIndex]).getDistributedNodes();
+
+ for (nodeIndex = 0; nodeIndex < distributedNodes.length; ++nodeIndex) {
+
+ if (this._composedTreeContains(distributedNodes[nodeIndex], child)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ },
+
+ _scrollInteractionHandler: function(event) {
+ if (Polymer
+ .IronDropdownScrollManager
+ .elementIsScrollLocked(event.target)) {
+ if (event.type === 'keydown' &&
+ !Polymer.IronDropdownScrollManager._isScrollingKeypress(event)) {
+ return;
+ }
+
+ event.preventDefault();
+ }
+ },
+
+ _lockScrollInteractions: function() {
+ // Memoize body inline styles:
+ this._originalBodyStyles.overflow = document.body.style.overflow;
+ this._originalBodyStyles.overflowX = document.body.style.overflowX;
+ this._originalBodyStyles.overflowY = document.body.style.overflowY;
+
+ // Disable overflow scrolling on body:
+ // TODO(cdata): It is technically not sufficient to hide overflow on
+ // body alone. A better solution might be to traverse all ancestors of
+ // the current scroll locking element and hide overflow on them. This
+ // becomes expensive, though, as it would have to be redone every time
+ // a new scroll locking element is added.
+ document.body.style.overflow = 'hidden';
+ document.body.style.overflowX = 'hidden';
+ document.body.style.overflowY = 'hidden';
+
+ // Modern `wheel` event for mouse wheel scrolling:
+ window.addEventListener('wheel', this._scrollInteractionHandler, true);
+ // Older, non-standard `mousewheel` event for some FF:
+ window.addEventListener('mousewheel', this._scrollInteractionHandler, true);
+ // IE:
+ window.addEventListener('DOMMouseScroll', this._scrollInteractionHandler, true);
+ // Mobile devices can scroll on touch move:
+ window.addEventListener('touchmove', this._scrollInteractionHandler, true);
+ // Capture keydown to prevent scrolling keys (pageup, pagedown etc.)
+ document.addEventListener('keydown', this._scrollInteractionHandler, true);
+ },
+
+ _unlockScrollInteractions: function() {
+ document.body.style.overflow = this._originalBodyStyles.overflow;
+ document.body.style.overflowX = this._originalBodyStyles.overflowX;
+ document.body.style.overflowY = this._originalBodyStyles.overflowY;
+
+ window.removeEventListener('wheel', this._scrollInteractionHandler, true);
+ window.removeEventListener('mousewheel', this._scrollInteractionHandler, true);
+ window.removeEventListener('DOMMouseScroll', this._scrollInteractionHandler, true);
+ window.removeEventListener('touchmove', this._scrollInteractionHandler, true);
+ document.removeEventListener('keydown', this._scrollInteractionHandler, true);
+ }
+ };
+ })();
+(function() {
+ 'use strict';
+
+ Polymer({
+ is: 'iron-dropdown',
+
+ behaviors: [
+ Polymer.IronControlState,
+ Polymer.IronA11yKeysBehavior,
+ Polymer.IronOverlayBehavior,
+ Polymer.NeonAnimationRunnerBehavior
+ ],
+
+ properties: {
+ /**
+ * The orientation against which to align the dropdown content
+ * horizontally relative to the dropdown trigger.
+ */
+ horizontalAlign: {
+ type: String,
+ value: 'left',
+ reflectToAttribute: true
+ },
+
+ /**
+ * The orientation against which to align the dropdown content
+ * vertically relative to the dropdown trigger.
+ */
+ verticalAlign: {
+ type: String,
+ value: 'top',
+ reflectToAttribute: true
+ },
+
+ /**
+ * A pixel value that will be added to the position calculated for the
+ * given `horizontalAlign`. Use a negative value to offset to the
+ * left, or a positive value to offset to the right.
+ */
+ horizontalOffset: {
+ type: Number,
+ value: 0,
+ notify: true
+ },
+
+ /**
+ * A pixel value that will be added to the position calculated for the
+ * given `verticalAlign`. Use a negative value to offset towards the
+ * top, or a positive value to offset towards the bottom.
+ */
+ verticalOffset: {
+ type: Number,
+ value: 0,
+ notify: true
+ },
+
+ /**
+ * The element that should be used to position the dropdown when
+ * it is opened.
+ */
+ positionTarget: {
+ type: Object,
+ observer: '_positionTargetChanged'
+ },
+
+ /**
+ * An animation config. If provided, this will be used to animate the
+ * opening of the dropdown.
+ */
+ openAnimationConfig: {
+ type: Object
+ },
+
+ /**
+ * An animation config. If provided, this will be used to animate the
+ * closing of the dropdown.
+ */
+ closeAnimationConfig: {
+ type: Object
+ },
+
+ /**
+ * If provided, this will be the element that will be focused when
+ * the dropdown opens.
+ */
+ focusTarget: {
+ type: Object
+ },
+
+ /**
+ * Set to true to disable animations when opening and closing the
+ * dropdown.
+ */
+ noAnimations: {
+ type: Boolean,
+ value: false
+ },
+
+ /**
+ * By default, the dropdown will constrain scrolling on the page
+ * to itself when opened.
+ * Set to true in order to prevent scroll from being constrained
+ * to the dropdown when it opens.
+ */
+ allowOutsideScroll: {
+ type: Boolean,
+ value: false
+ },
+
+ /**
+ * We memoize the positionTarget bounding rectangle so that we can
+ * limit the number of times it is queried per resize / relayout.
+ * @type {?Object}
+ */
+ _positionRectMemo: {
+ type: Object
+ }
+ },
+
+ listeners: {
+ 'neon-animation-finish': '_onNeonAnimationFinish'
+ },
+
+ observers: [
+ '_updateOverlayPosition(verticalAlign, horizontalAlign, verticalOffset, horizontalOffset)'
+ ],
+
+ attached: function() {
+ if (this.positionTarget === undefined) {
+ this.positionTarget = this._defaultPositionTarget;
+ }
+ },
+
+ /**
+ * The element that is contained by the dropdown, if any.
+ */
+ get containedElement() {
+ return Polymer.dom(this.$.content).getDistributedNodes()[0];
+ },
+
+ /**
+ * The element that should be focused when the dropdown opens.
+ */
+ get _focusTarget() {
+ return this.focusTarget || this.containedElement;
+ },
+
+ /**
+ * The element that should be used to position the dropdown when
+ * it opens, if no position target is configured.
+ */
+ get _defaultPositionTarget() {
+ var parent = Polymer.dom(this).parentNode;
+
+ if (parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
+ parent = parent.host;
+ }
+
+ return parent;
+ },
+
+ /**
+ * The bounding rect of the position target.
+ */
+ get _positionRect() {
+ if (!this._positionRectMemo && this.positionTarget) {
+ this._positionRectMemo = this.positionTarget.getBoundingClientRect();
+ }
+
+ return this._positionRectMemo;
+ },
+
+ /**
+ * The horizontal offset value used to position the dropdown.
+ */
+ get _horizontalAlignTargetValue() {
+ var target;
+
+ if (this.horizontalAlign === 'right') {
+ target = document.documentElement.clientWidth - this._positionRect.right;
+ } else {
+ target = this._positionRect.left;
+ }
+
+ target += this.horizontalOffset;
+
+ return Math.max(target, 0);
+ },
+
+ /**
+ * The vertical offset value used to position the dropdown.
+ */
+ get _verticalAlignTargetValue() {
+ var target;
+
+ if (this.verticalAlign === 'bottom') {
+ target = document.documentElement.clientHeight - this._positionRect.bottom;
+ } else {
+ target = this._positionRect.top;
+ }
+
+ target += this.verticalOffset;
+
+ return Math.max(target, 0);
+ },
+
+ /**
+ * Called when the value of `opened` changes.
+ *
+ * @param {boolean} opened True if the dropdown is opened.
+ */
+ _openedChanged: function(opened) {
+ if (opened && this.disabled) {
+ this.cancel();
+ } else {
+ this.cancelAnimation();
+ this._prepareDropdown();
+ Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this, arguments);
+ }
+
+ if (this.opened) {
+ this._focusContent();
+ }
+ },
+
+ /**
+ * Overridden from `IronOverlayBehavior`.
+ */
+ _renderOpened: function() {
+ if (!this.allowOutsideScroll) {
+ Polymer.IronDropdownScrollManager.pushScrollLock(this);
+ }
+
+ if (!this.noAnimations && this.animationConfig && this.animationConfig.open) {
+ this.$.contentWrapper.classList.add('animating');
+ this.playAnimation('open');
+ } else {
+ Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this, arguments);
+ }
+ },
+
+ /**
+ * Overridden from `IronOverlayBehavior`.
+ */
+ _renderClosed: function() {
+ Polymer.IronDropdownScrollManager.removeScrollLock(this);
+ if (!this.noAnimations && this.animationConfig && this.animationConfig.close) {
+ this.$.contentWrapper.classList.add('animating');
+ this.playAnimation('close');
+ } else {
+ Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this, arguments);
+ }
+ },
+
+ /**
+ * Called when animation finishes on the dropdown (when opening or
+ * closing). Responsible for "completing" the process of opening or
+ * closing the dropdown by positioning it or setting its display to
+ * none.
+ */
+ _onNeonAnimationFinish: function() {
+ this.$.contentWrapper.classList.remove('animating');
+ if (this.opened) {
+ Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this);
+ } else {
+ Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this);
+ }
+ },
+
+ /**
+ * Called when an `iron-resize` event fires.
+ */
+ _onIronResize: function() {
+ var containedElement = this.containedElement;
+ var scrollTop;
+ var scrollLeft;
+
+ if (containedElement) {
+ scrollTop = containedElement.scrollTop;
+ scrollLeft = containedElement.scrollLeft;
+ }
+
+ if (this.opened) {
+ this._updateOverlayPosition();
+ }
+
+ Polymer.IronOverlayBehaviorImpl._onIronResize.apply(this, arguments);
+
+ if (containedElement) {
+ containedElement.scrollTop = scrollTop;
+ containedElement.scrollLeft = scrollLeft;
+ }
+ },
+
+ /**
+ * Called when the `positionTarget` property changes.
+ */
+ _positionTargetChanged: function() {
+ this._updateOverlayPosition();
+ },
+
+ /**
+ * Constructs the final animation config from different properties used
+ * to configure specific parts of the opening and closing animations.
+ */
+ _updateAnimationConfig: function() {
+ var animationConfig = {};
+ var animations = [];
+
+ if (this.openAnimationConfig) {
+ // NOTE(cdata): When making `display:none` elements visible in Safari,
+ // the element will paint once in a fully visible state, causing the
+ // dropdown to flash before it fades in. We prepend an
+ // `opaque-animation` to fix this problem:
+ animationConfig.open = [{
+ name: 'opaque-animation',
+ }].concat(this.openAnimationConfig);
+ animations = animations.concat(animationConfig.open);
+ }
+
+ if (this.closeAnimationConfig) {
+ animationConfig.close = this.closeAnimationConfig;
+ animations = animations.concat(animationConfig.close);
+ }
+
+ animations.forEach(function(animation) {
+ animation.node = this.containedElement;
+ }, this);
+
+ this.animationConfig = animationConfig;
+ },
+
+ /**
+ * Prepares the dropdown for opening by updating measured layout
+ * values.
+ */
+ _prepareDropdown: function() {
+ this.sizingTarget = this.containedElement || this.sizingTarget;
+ this._updateAnimationConfig();
+ this._updateOverlayPosition();
+ },
+
+ /**
+ * Updates the overlay position based on configured horizontal
+ * and vertical alignment, and re-memoizes these values for the sake
+ * of behavior in `IronFitBehavior`.
+ */
+ _updateOverlayPosition: function() {
+ this._positionRectMemo = null;
+
+ if (!this.positionTarget) {
+ return;
+ }
+
+ this.style[this.horizontalAlign] =
+ this._horizontalAlignTargetValue + 'px';
+
+ this.style[this.verticalAlign] =
+ this._verticalAlignTargetValue + 'px';
+
+ // NOTE(cdata): We re-memoize inline styles here, otherwise
+ // calling `refit` from `IronFitBehavior` will reset inline styles
+ // to whatever they were when the dropdown first opened.
+ if (this._fitInfo) {
+ this._fitInfo.inlineStyle[this.horizontalAlign] =
+ this.style[this.horizontalAlign];
+
+ this._fitInfo.inlineStyle[this.verticalAlign] =
+ this.style[this.verticalAlign];
+ }
+ },
+
+ /**
+ * Focuses the configured focus target.
+ */
+ _focusContent: function() {
+ // NOTE(cdata): This is async so that it can attempt the focus after
+ // `display: none` is removed from the element.
+ this.async(function() {
+ if (this._focusTarget) {
+ this._focusTarget.focus();
+ }
+ });
+ }
+ });
+ })();
+Polymer({
+
+ is: 'fade-in-animation',
+
+ behaviors: [
+ Polymer.NeonAnimationBehavior
+ ],
+
+ configure: function(config) {
+ var node = config.node;
+ this._effect = new KeyframeEffect(node, [
+ {'opacity': '0'},
+ {'opacity': '1'}
+ ], this.timingFromConfig(config));
+ return this._effect;
+ }
+
+ });
+Polymer({
+
+ is: 'fade-out-animation',
+
+ behaviors: [
+ Polymer.NeonAnimationBehavior
+ ],
+
+ configure: function(config) {
+ var node = config.node;
+ this._effect = new KeyframeEffect(node, [
+ {'opacity': '1'},
+ {'opacity': '0'}
+ ], this.timingFromConfig(config));
+ return this._effect;
+ }
+
+ });
+Polymer({
+ is: 'paper-menu-grow-height-animation',
+
+ behaviors: [
+ Polymer.NeonAnimationBehavior
+ ],
+
+ configure: function(config) {
+ var node = config.node;
+ var rect = node.getBoundingClientRect();
+ var height = rect.height;
+
+ this._effect = new KeyframeEffect(node, [{
+ height: (height / 2) + 'px'
+ }, {
+ height: height + 'px'
+ }], this.timingFromConfig(config));
+
+ return this._effect;
+ }
+ });
+
+ Polymer({
+ is: 'paper-menu-grow-width-animation',
+
+ behaviors: [
+ Polymer.NeonAnimationBehavior
+ ],
+
+ configure: function(config) {
+ var node = config.node;
+ var rect = node.getBoundingClientRect();
+ var width = rect.width;
+
+ this._effect = new KeyframeEffect(node, [{
+ width: (width / 2) + 'px'
+ }, {
+ width: width + 'px'
+ }], this.timingFromConfig(config));
+
+ return this._effect;
+ }
+ });
+
+ Polymer({
+ is: 'paper-menu-shrink-width-animation',
+
+ behaviors: [
+ Polymer.NeonAnimationBehavior
+ ],
+
+ configure: function(config) {
+ var node = config.node;
+ var rect = node.getBoundingClientRect();
+ var width = rect.width;
+
+ this._effect = new KeyframeEffect(node, [{
+ width: width + 'px'
+ }, {
+ width: width - (width / 20) + 'px'
+ }], this.timingFromConfig(config));
+
+ return this._effect;
+ }
+ });
+
+ Polymer({
+ is: 'paper-menu-shrink-height-animation',
+
+ behaviors: [
+ Polymer.NeonAnimationBehavior
+ ],
+
+ configure: function(config) {
+ var node = config.node;
+ var rect = node.getBoundingClientRect();
+ var height = rect.height;
+ var top = rect.top;
+
+ this.setPrefixedProperty(node, 'transformOrigin', '0 0');
+
+ this._effect = new KeyframeEffect(node, [{
+ height: height + 'px',
+ transform: 'translateY(0)'
+ }, {
+ height: height / 2 + 'px',
+ transform: 'translateY(-20px)'
+ }], this.timingFromConfig(config));
+
+ return this._effect;
+ }
+ });
+(function() {
+ 'use strict';
+
+ var PaperMenuButton = Polymer({
+ is: 'paper-menu-button',
+
+ /**
+ * Fired when the dropdown opens.
+ *
+ * @event paper-dropdown-open
+ */
+
+ /**
+ * Fired when the dropdown closes.
+ *
+ * @event paper-dropdown-close
+ */
+
+ behaviors: [
+ Polymer.IronA11yKeysBehavior,
+ Polymer.IronControlState
+ ],
+
+ properties: {
+
+ /**
+ * True if the content is currently displayed.
+ */
+ opened: {
+ type: Boolean,
+ value: false,
+ notify: true,
+ observer: '_openedChanged'
+ },
+
+ /**
+ * The orientation against which to align the menu dropdown
+ * horizontally relative to the dropdown trigger.
+ */
+ horizontalAlign: {
+ type: String,
+ value: 'left',
+ reflectToAttribute: true
+ },
+
+ /**
+ * The orientation against which to align the menu dropdown
+ * vertically relative to the dropdown trigger.
+ */
+ verticalAlign: {
+ type: String,
+ value: 'top',
+ reflectToAttribute: true
+ },
+
+ /**
+ * A pixel value that will be added to the position calculated for the
+ * given `horizontalAlign`. Use a negative value to offset to the
+ * left, or a positive value to offset to the right.
+ */
+ horizontalOffset: {
+ type: Number,
+ value: 0,
+ notify: true
+ },
+
+ /**
+ * A pixel value that will be added to the position calculated for the
+ * given `verticalAlign`. Use a negative value to offset towards the
+ * top, or a positive value to offset towards the bottom.
+ */
+ verticalOffset: {
+ type: Number,
+ value: 0,
+ notify: true
+ },
+
+ /**
+ * Set to true to disable animations when opening and closing the
+ * dropdown.
+ */
+ noAnimations: {
+ type: Boolean,
+ value: false
+ },
+
+ /**
+ * Set to true to disable automatically closing the dropdown after
+ * a selection has been made.
+ */
+ ignoreSelect: {
+ type: Boolean,
+ value: false
+ },
+
+ /**
+ * An animation config. If provided, this will be used to animate the
+ * opening of the dropdown.
+ */
+ openAnimationConfig: {
+ type: Object,
+ value: function() {
+ return [{
+ name: 'fade-in-animation',
+ timing: {
+ delay: 100,
+ duration: 200
+ }
+ }, {
+ name: 'paper-menu-grow-width-animation',
+ timing: {
+ delay: 100,
+ duration: 150,
+ easing: PaperMenuButton.ANIMATION_CUBIC_BEZIER
+ }
+ }, {
+ name: 'paper-menu-grow-height-animation',
+ timing: {
+ delay: 100,
+ duration: 275,
+ easing: PaperMenuButton.ANIMATION_CUBIC_BEZIER
+ }
+ }];
+ }
+ },
+
+ /**
+ * An animation config. If provided, this will be used to animate the
+ * closing of the dropdown.
+ */
+ closeAnimationConfig: {
+ type: Object,
+ value: function() {
+ return [{
+ name: 'fade-out-animation',
+ timing: {
+ duration: 150
+ }
+ }, {
+ name: 'paper-menu-shrink-width-animation',
+ timing: {
+ delay: 100,
+ duration: 50,
+ easing: PaperMenuButton.ANIMATION_CUBIC_BEZIER
+ }
+ }, {
+ name: 'paper-menu-shrink-height-animation',
+ timing: {
+ duration: 200,
+ easing: 'ease-in'
+ }
+ }];
+ }
+ },
+
+ /**
+ * This is the element intended to be bound as the focus target
+ * for the `iron-dropdown` contained by `paper-menu-button`.
+ */
+ _dropdownContent: {
+ type: Object
+ }
+ },
+
+ hostAttributes: {
+ role: 'group',
+ 'aria-haspopup': 'true'
+ },
+
+ listeners: {
+ 'iron-select': '_onIronSelect'
+ },
+
+ /**
+ * The content element that is contained by the menu button, if any.
+ */
+ get contentElement() {
+ return Polymer.dom(this.$.content).getDistributedNodes()[0];
+ },
+
+ /**
+ * Make the dropdown content appear as an overlay positioned relative
+ * to the dropdown trigger.
+ */
+ open: function() {
+ if (this.disabled) {
+ return;
+ }
+
+ this.$.dropdown.open();
+ },
+
+ /**
+ * Hide the dropdown content.
+ */
+ close: function() {
+ this.$.dropdown.close();
+ },
+
+ /**
+ * When an `iron-select` event is received, the dropdown should
+ * automatically close on the assumption that a value has been chosen.
+ *
+ * @param {CustomEvent} event A CustomEvent instance with type
+ * set to `"iron-select"`.
+ */
+ _onIronSelect: function(event) {
+ if (!this.ignoreSelect) {
+ this.close();
+ }
+ },
+
+ /**
+ * When the dropdown opens, the `paper-menu-button` fires `paper-open`.
+ * When the dropdown closes, the `paper-menu-button` fires `paper-close`.
+ *
+ * @param {boolean} opened True if the dropdown is opened, otherwise false.
+ * @param {boolean} oldOpened The previous value of `opened`.
+ */
+ _openedChanged: function(opened, oldOpened) {
+ if (opened) {
+ // TODO(cdata): Update this when we can measure changes in distributed
+ // children in an idiomatic way.
+ // We poke this property in case the element has changed. This will
+ // cause the focus target for the `iron-dropdown` to be updated as
+ // necessary:
+ this._dropdownContent = this.contentElement;
+ this.fire('paper-dropdown-open');
+ } else if (oldOpened != null) {
+ this.fire('paper-dropdown-close');
+ }
+ },
+
+ /**
+ * If the dropdown is open when disabled becomes true, close the
+ * dropdown.
+ *
+ * @param {boolean} disabled True if disabled, otherwise false.
+ */
+ _disabledChanged: function(disabled) {
+ Polymer.IronControlState._disabledChanged.apply(this, arguments);
+ if (disabled && this.opened) {
+ this.close();
+ }
+ }
+ });
+
+ PaperMenuButton.ANIMATION_CUBIC_BEZIER = 'cubic-bezier(.3,.95,.5,1)';
+ PaperMenuButton.MAX_ANIMATION_TIME_MS = 400;
+
+ Polymer.PaperMenuButton = PaperMenuButton;
+ })();
+/**
+ * `Polymer.PaperInkyFocusBehavior` implements a ripple when the element has keyboard focus.
+ *
+ * @polymerBehavior Polymer.PaperInkyFocusBehavior
+ */
+ Polymer.PaperInkyFocusBehaviorImpl = {
+
+ observers: [
+ '_focusedChanged(receivedFocusFromKeyboard)'
+ ],
+
+ _focusedChanged: function(receivedFocusFromKeyboard) {
+ if (!this.$.ink) {
+ return;
+ }
+
+ this.$.ink.holdDown = receivedFocusFromKeyboard;
+ }
+
+ };
+
+ /** @polymerBehavior Polymer.PaperInkyFocusBehavior */
+ Polymer.PaperInkyFocusBehavior = [
+ Polymer.IronButtonState,
+ Polymer.IronControlState,
+ Polymer.PaperInkyFocusBehaviorImpl
+ ];
+Polymer({
+ is: 'paper-icon-button',
+
+ hostAttributes: {
+ role: 'button',
+ tabindex: '0'
+ },
+
+ behaviors: [
+ Polymer.PaperInkyFocusBehavior
+ ],
+
+ properties: {
+ /**
+ * The URL of an image for the icon. If the src property is specified,
+ * the icon property should not be.
+ */
+ src: {
+ type: String
+ },
+
+ /**
+ * 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.
+ */
+ icon: {
+ type: String
+ },
+
+ /**
+ * Specifies the alternate text for the button, for accessibility.
+ */
+ alt: {
+ type: String,
+ observer: "_altChanged"
+ }
+ },
+
+ _altChanged: function(newValue, oldValue) {
+ var label = this.getAttribute('aria-label');
+
+ // Don't stomp over a user-set aria-label.
+ if (!label || oldValue == label) {
+ this.setAttribute('aria-label', newValue);
+ }
+ }
+ });
+/**
+ * Use `Polymer.IronValidatableBehavior` to implement an element that validates user input.
+ *
+ * ### Accessibility
+ *
+ * Changing the `invalid` property, either manually or by calling `validate()` will update the
+ * `aria-invalid` attribute.
+ *
+ * @demo demo/index.html
+ * @polymerBehavior
+ */
+ Polymer.IronValidatableBehavior = {
+
+ properties: {
+
+ /**
+ * Namespace for this validator.
+ */
+ validatorType: {
+ type: String,
+ value: 'validator'
+ },
+
+ /**
+ * Name of the validator to use.
+ */
+ validator: {
+ type: String
+ },
+
+ /**
+ * True if the last call to `validate` is invalid.
+ */
+ invalid: {
+ notify: true,
+ reflectToAttribute: true,
+ type: Boolean,
+ value: false
+ },
+
+ _validatorMeta: {
+ type: Object
+ }
+
+ },
+
+ observers: [
+ '_invalidChanged(invalid)'
+ ],
+
+ get _validator() {
+ return this._validatorMeta && this._validatorMeta.byKey(this.validator);
+ },
+
+ ready: function() {
+ this._validatorMeta = new Polymer.IronMeta({type: this.validatorType});
+ },
+
+ _invalidChanged: function() {
+ if (this.invalid) {
+ this.setAttribute('aria-invalid', 'true');
+ } else {
+ this.removeAttribute('aria-invalid');
+ }
+ },
+
+ /**
+ * @return {boolean} True if the validator `validator` exists.
+ */
+ hasValidator: function() {
+ return this._validator != null;
+ },
+
+ /**
+ * Returns true if the `value` is valid, and updates `invalid`. If you want
+ * your element to have custom validation logic, do not override this method;
+ * override `_getValidity(value)` instead.
+
+ * @param {Object} value The value to be validated. By default, it is passed
+ * to the validator's `validate()` function, if a validator is set.
+ * @return {boolean} True if `value` is valid.
+ */
+ validate: function(value) {
+ this.invalid = !this._getValidity(value);
+ return !this.invalid;
+ },
+
+ /**
+ * Returns true if `value` is valid. By default, it is passed
+ * to the validator's `validate()` function, if a validator is set. You
+ * should override this method if you want to implement custom validity
+ * logic for your element.
+ *
+ * @param {Object} value The value to be validated.
+ * @return {boolean} True if `value` is valid.
+ */
+
+ _getValidity: function(value) {
+ if (this.hasValidator()) {
+ return this._validator.validate(value);
+ }
+ return true;
+ }
+ };
+/*
+`<iron-input>` adds two-way binding and custom validators using `Polymer.IronValidatorBehavior`
+to `<input>`.
+
+### Two-way binding
+
+By default you can only get notified of changes to an `input`'s `value` due to user input:
+
+ <input value="{{myValue::input}}">
+
+`iron-input` adds the `bind-value` property that mirrors the `value` property, and can be used
+for two-way data binding. `bind-value` will notify if it is changed either by user input or by script.
+
+ <input is="iron-input" bind-value="{{myValue}}">
+
+### Custom validators
+
+You can use custom validators that implement `Polymer.IronValidatorBehavior` with `<iron-input>`.
+
+ <input is="iron-input" validator="my-custom-validator">
+
+### Stopping invalid input
+
+It may be desirable to only allow users to enter certain characters. You can use the
+`prevent-invalid-input` and `allowed-pattern` attributes together to accomplish this. This feature
+is separate from validation, and `allowed-pattern` does not affect how the input is validated.
+
+ <!-- only allow characters that match [0-9] -->
+ <input is="iron-input" prevent-invalid-input allowed-pattern="[0-9]">
+
+@hero hero.svg
+@demo demo/index.html
+*/
+
+ Polymer({
+
+ is: 'iron-input',
+
+ extends: 'input',
+
+ behaviors: [
+ Polymer.IronValidatableBehavior
+ ],
+
+ properties: {
+
+ /**
+ * Use this property instead of `value` for two-way data binding.
+ */
+ bindValue: {
+ observer: '_bindValueChanged',
+ type: String
+ },
+
+ /**
+ * Set to true to prevent the user from entering invalid input. The new input characters are
+ * matched with `allowedPattern` if it is set, otherwise it will use the `pattern` attribute if
+ * set, or the `type` attribute (only supported for `type=number`).
+ */
+ preventInvalidInput: {
+ type: Boolean
+ },
+
+ /**
+ * Regular expression to match valid input characters.
+ */
+ allowedPattern: {
+ type: String
+ },
+
+ _previousValidInput: {
+ type: String,
+ value: ''
+ },
+
+ _patternAlreadyChecked: {
+ type: Boolean,
+ value: false
+ }
+
+ },
+
+ listeners: {
+ 'input': '_onInput',
+ 'keypress': '_onKeypress'
+ },
+
+ get _patternRegExp() {
+ var pattern;
+ if (this.allowedPattern) {
+ pattern = new RegExp(this.allowedPattern);
+ } else if (this.pattern) {
+ pattern = new RegExp(this.pattern);
+ } else {
+ switch (this.type) {
+ case 'number':
+ pattern = /[0-9.,e-]/;
+ break;
+ }
+ }
+ return pattern;
+ },
+
+ ready: function() {
+ this.bindValue = this.value;
+ },
+
+ /**
+ * @suppress {checkTypes}
+ */
+ _bindValueChanged: function() {
+ if (this.value !== this.bindValue) {
+ this.value = !(this.bindValue || this.bindValue === 0) ? '' : this.bindValue;
+ }
+ // manually notify because we don't want to notify until after setting value
+ this.fire('bind-value-changed', {value: this.bindValue});
+ },
+
+ _onInput: function() {
+ // Need to validate each of the characters pasted if they haven't
+ // been validated inside `_onKeypress` already.
+ if (this.preventInvalidInput && !this._patternAlreadyChecked) {
+ var valid = this._checkPatternValidity();
+ if (!valid) {
+ this.value = this._previousValidInput;
+ }
+ }
+
+ this.bindValue = this.value;
+ this._previousValidInput = this.value;
+ this._patternAlreadyChecked = false;
+ },
+
+ _isPrintable: function(event) {
+ // What a control/printable character is varies wildly based on the browser.
+ // - most control characters (arrows, backspace) do not send a `keypress` event
+ // in Chrome, but the *do* on Firefox
+ // - in Firefox, when they do send a `keypress` event, control chars have
+ // a charCode = 0, keyCode = xx (for ex. 40 for down arrow)
+ // - printable characters always send a keypress event.
+ // - in Firefox, printable chars always have a keyCode = 0. In Chrome, the keyCode
+ // always matches the charCode.
+ // None of this makes any sense.
+
+ // For these keys, ASCII code == browser keycode.
+ var anyNonPrintable =
+ (event.keyCode == 8) || // backspace
+ (event.keyCode == 9) || // tab
+ (event.keyCode == 13) || // enter
+ (event.keyCode == 27); // escape
+
+ // For these keys, make sure it's a browser keycode and not an ASCII code.
+ var mozNonPrintable =
+ (event.keyCode == 19) || // pause
+ (event.keyCode == 20) || // caps lock
+ (event.keyCode == 45) || // insert
+ (event.keyCode == 46) || // delete
+ (event.keyCode == 144) || // num lock
+ (event.keyCode == 145) || // scroll lock
+ (event.keyCode > 32 && event.keyCode < 41) || // page up/down, end, home, arrows
+ (event.keyCode > 111 && event.keyCode < 124); // fn keys
+
+ return !anyNonPrintable && !(event.charCode == 0 && mozNonPrintable);
+ },
+
+ _onKeypress: function(event) {
+ if (!this.preventInvalidInput && this.type !== 'number') {
+ return;
+ }
+ var regexp = this._patternRegExp;
+ if (!regexp) {
+ return;
+ }
+
+ // Handle special keys and backspace
+ if (event.metaKey || event.ctrlKey || event.altKey)
+ return;
+
+ // Check the pattern either here or in `_onInput`, but not in both.
+ this._patternAlreadyChecked = true;
+
+ var thisChar = String.fromCharCode(event.charCode);
+ if (this._isPrintable(event) && !regexp.test(thisChar)) {
+ event.preventDefault();
+ }
+ },
+
+ _checkPatternValidity: function() {
+ var regexp = this._patternRegExp;
+ if (!regexp) {
+ return true;
+ }
+ for (var i = 0; i < this.value.length; i++) {
+ if (!regexp.test(this.value[i])) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ /**
+ * Returns true if `value` is valid. The validator provided in `validator` will be used first,
+ * then any constraints.
+ * @return {boolean} True if the value is valid.
+ */
+ validate: function() {
+ // Empty, non-required input is valid.
+ if (!this.required && this.value == '') {
+ this.invalid = false;
+ return true;
+ }
+
+ var valid;
+ if (this.hasValidator()) {
+ valid = Polymer.IronValidatableBehavior.validate.call(this, this.value);
+ } else {
+ this.invalid = !this.validity.valid;
+ valid = this.validity.valid;
+ }
+ this.fire('iron-input-validate');
+ return valid;
+ }
+
+ });
+
+ /*
+ The `iron-input-validate` event is fired whenever `validate()` is called.
+ @event iron-input-validate
+ */
+Polymer({
+ is: 'paper-input-container',
+
+ properties: {
+ /**
+ * Set to true to disable the floating label. The label disappears when the input value is
+ * not null.
+ */
+ noLabelFloat: {
+ type: Boolean,
+ value: false
+ },
+
+ /**
+ * Set to true to always float the floating label.
+ */
+ alwaysFloatLabel: {
+ type: Boolean,
+ value: false
+ },
+
+ /**
+ * The attribute to listen for value changes on.
+ */
+ attrForValue: {
+ type: String,
+ value: 'bind-value'
+ },
+
+ /**
+ * Set to true to auto-validate the input value when it changes.
+ */
+ autoValidate: {
+ type: Boolean,
+ value: false
+ },
+
+ /**
+ * True if the input is invalid. This property is set automatically when the input value
+ * changes if auto-validating, or when the `iron-input-validate` event is heard from a child.
+ */
+ invalid: {
+ observer: '_invalidChanged',
+ type: Boolean,
+ value: false
+ },
+
+ /**
+ * True if the input has focus.
+ */
+ focused: {
+ readOnly: true,
+ type: Boolean,
+ value: false,
+ notify: true
+ },
+
+ _addons: {
+ type: Array
+ // do not set a default value here intentionally - it will be initialized lazily when a
+ // distributed child is attached, which may occur before configuration for this element
+ // in polyfill.
+ },
+
+ _inputHasContent: {
+ type: Boolean,
+ value: false
+ },
+
+ _inputSelector: {
+ type: String,
+ value: 'input,textarea,.paper-input-input'
+ },
+
+ _boundOnFocus: {
+ type: Function,
+ value: function() {
+ return this._onFocus.bind(this);
+ }
+ },
+
+ _boundOnBlur: {
+ type: Function,
+ value: function() {
+ return this._onBlur.bind(this);
+ }
+ },
+
+ _boundOnInput: {
+ type: Function,
+ value: function() {
+ return this._onInput.bind(this);
+ }
+ },
+
+ _boundValueChanged: {
+ type: Function,
+ value: function() {
+ return this._onValueChanged.bind(this);
+ }
+ }
+ },
+
+ listeners: {
+ 'addon-attached': '_onAddonAttached',
+ 'iron-input-validate': '_onIronInputValidate'
+ },
+
+ get _valueChangedEvent() {
+ return this.attrForValue + '-changed';
+ },
+
+ get _propertyForValue() {
+ return Polymer.CaseMap.dashToCamelCase(this.attrForValue);
+ },
+
+ get _inputElement() {
+ return Polymer.dom(this).querySelector(this._inputSelector);
+ },
+
+ get _inputElementValue() {
+ return this._inputElement[this._propertyForValue] || this._inputElement.value;
+ },
+
+ ready: function() {
+ if (!this._addons) {
+ this._addons = [];
+ }
+ this.addEventListener('focus', this._boundOnFocus, true);
+ this.addEventListener('blur', this._boundOnBlur, true);
+ if (this.attrForValue) {
+ this._inputElement.addEventListener(this._valueChangedEvent, this._boundValueChanged);
+ } else {
+ this.addEventListener('input', this._onInput);
+ }
+ },
+
+ attached: function() {
+ // Only validate when attached if the input already has a value.
+ if (this._inputElementValue != '') {
+ this._handleValueAndAutoValidate(this._inputElement);
+ } else {
+ this._handleValue(this._inputElement);
+ }
+ },
+
+ _onAddonAttached: function(event) {
+ if (!this._addons) {
+ this._addons = [];
+ }
+ var target = event.target;
+ if (this._addons.indexOf(target) === -1) {
+ this._addons.push(target);
+ if (this.isAttached) {
+ this._handleValue(this._inputElement);
+ }
+ }
+ },
+
+ _onFocus: function() {
+ this._setFocused(true);
+ },
+
+ _onBlur: function() {
+ this._setFocused(false);
+ this._handleValueAndAutoValidate(this._inputElement);
+ },
+
+ _onInput: function(event) {
+ this._handleValueAndAutoValidate(event.target);
+ },
+
+ _onValueChanged: function(event) {
+ this._handleValueAndAutoValidate(event.target);
+ },
+
+ _handleValue: function(inputElement) {
+ var value = this._inputElementValue;
+
+ // type="number" hack needed because this.value is empty until it's valid
+ if (value || value === 0 || (inputElement.type === 'number' && !inputElement.checkValidity())) {
+ this._inputHasContent = true;
+ } else {
+ this._inputHasContent = false;
+ }
+
+ this.updateAddons({
+ inputElement: inputElement,
+ value: value,
+ invalid: this.invalid
+ });
+ },
+
+ _handleValueAndAutoValidate: function(inputElement) {
+ if (this.autoValidate) {
+ var valid;
+ if (inputElement.validate) {
+ valid = inputElement.validate(this._inputElementValue);
+ } else {
+ valid = inputElement.checkValidity();
+ }
+ this.invalid = !valid;
+ }
+
+ // Call this last to notify the add-ons.
+ this._handleValue(inputElement);
+ },
+
+ _onIronInputValidate: function(event) {
+ this.invalid = this._inputElement.invalid;
+ },
+
+ _invalidChanged: function() {
+ if (this._addons) {
+ this.updateAddons({invalid: this.invalid});
+ }
+ },
+
+ /**
+ * Call this to update the state of add-ons.
+ * @param {Object} state Add-on state.
+ */
+ updateAddons: function(state) {
+ for (var addon, index = 0; addon = this._addons[index]; index++) {
+ addon.update(state);
+ }
+ },
+
+ _computeInputContentClass: function(noLabelFloat, alwaysFloatLabel, focused, invalid, _inputHasContent) {
+ var cls = 'input-content';
+ if (!noLabelFloat) {
+ var label = this.querySelector('label');
+
+ if (alwaysFloatLabel || _inputHasContent) {
+ cls += ' label-is-floating';
+ if (invalid) {
+ cls += ' is-invalid';
+ } else if (focused) {
+ cls += " label-is-highlighted";
+ }
+ // The label might have a horizontal offset if a prefix element exists
+ // which needs to be undone when displayed as a floating label.
+ if (Polymer.dom(this.$.prefix).getDistributedNodes().length > 0 &&
+ label && label.offsetParent) {
+ label.style.left = -label.offsetParent.offsetLeft + 'px';
+ }
+ } else {
+ // When the label is not floating, it should overlap the input element.
+ if (label) {
+ label.style.left = 0;
+ }
+ }
+ } else {
+ if (_inputHasContent) {
+ cls += ' label-is-hidden';
+ }
+ }
+ return cls;
+ },
+
+ _computeUnderlineClass: function(focused, invalid) {
+ var cls = 'underline';
+ if (invalid) {
+ cls += ' is-invalid';
+ } else if (focused) {
+ cls += ' is-highlighted'
+ }
+ return cls;
+ },
+
+ _computeAddOnContentClass: function(focused, invalid) {
+ var cls = 'add-on-content';
+ if (invalid) {
+ cls += ' is-invalid';
+ } else if (focused) {
+ cls += ' is-highlighted'
+ }
+ return cls;
+ }
+ });
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/** @interface */
+var SearchFieldDelegate = function() {};
+
+SearchFieldDelegate.prototype = {
+ /**
+ * @param {string} value
+ */
+ onSearchTermSearch: assertNotReached,
+};
+
+var SearchField = Polymer({
+ is: 'cr-search-field',
+
+ properties: {
+ label: {
+ type: String,
+ value: '',
+ },
+
+ clearLabel: {
+ type: String,
+ value: '',
+ },
+
+ showingSearch_: {
+ type: Boolean,
+ value: false,
+ },
+ },
+
+ /** @param {SearchFieldDelegate} delegate */
+ setDelegate: function(delegate) {
+ this.delegate_ = delegate;
+ },
+
+ /**
+ * Returns the value of the search field.
+ * @return {string}
+ */
+ getValue: function() {
+ var searchInput = this.$$('#search-input');
+ return searchInput ? searchInput.value : '';
+ },
+
+ /** @private */
+ onSearchTermSearch_: function() {
+ if (this.delegate_)
+ this.delegate_.onSearchTermSearch(this.getValue());
+ },
+
+ /** @private */
+ onSearchTermKeydown_: function(e) {
+ assert(this.showingSearch_);
+ if (e.keyIdentifier == 'U+001B') // Escape.
+ this.toggleShowingSearch_();
+ },
+
+ /** @private */
+ toggleShowingSearch_: function() {
+ this.showingSearch_ = !this.showingSearch_;
+ this.async(function() {
+ var searchInput = this.$$('#search-input');
+ if (this.showingSearch_) {
+ searchInput.focus();
+ } else {
+ searchInput.value = '';
+ this.onSearchTermSearch_();
+ }
+ });
+ },
+});
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('downloads', function() {
+ var Toolbar = Polymer({
+ is: 'downloads-toolbar',
+
+ attached: function() {
+ /** @private {!SearchFieldDelegate} */
+ this.searchFieldDelegate_ = new ToolbarSearchFieldDelegate(this);
+ this.$['search-input'].setDelegate(this.searchFieldDelegate_);
+ },
+
+ properties: {
+ downloadsShowing: {
+ reflectToAttribute: true,
+ type: Boolean,
+ value: false,
+ observer: 'onDownloadsShowingChange_',
+ },
+ },
+
+ /** @return {boolean} Whether removal can be undone. */
+ canUndo: function() {
+ return this.$['search-input'] != this.shadowRoot.activeElement;
+ },
+
+ /** @return {boolean} Whether "Clear all" should be allowed. */
+ canClearAll: function() {
+ return !this.$['search-input'].getValue() && this.downloadsShowing;
+ },
+
+ /** @private */
+ onClearAllClick_: function() {
+ assert(this.canClearAll());
+ downloads.ActionService.getInstance().clearAll();
+ },
+
+ /** @private */
+ onDownloadsShowingChange_: function() {
+ this.updateClearAll_();
+ },
+
+ /** @param {string} searchTerm */
+ onSearchTermSearch: function(searchTerm) {
+ downloads.ActionService.getInstance().search(searchTerm);
+ this.updateClearAll_();
+ },
+
+ /** @private */
+ onOpenDownloadsFolderClick_: function() {
+ downloads.ActionService.getInstance().openDownloadsFolder();
+ },
+
+ /** @private */
+ updateClearAll_: function() {
+ this.$$('#actions .clear-all').hidden = !this.canClearAll();
+ this.$$('paper-menu .clear-all').hidden = !this.canClearAll();
+ },
+ });
+
+ /**
+ * @constructor
+ * @implements {SearchFieldDelegate}
+ */
+ // TODO(devlin): This is a bit excessive, and it would be better to just have
+ // Toolbar implement SearchFieldDelegate. But for now, we don't know how to
+ // make that happen with closure compiler.
+ function ToolbarSearchFieldDelegate(toolbar) {
+ this.toolbar_ = toolbar;
+ }
+
+ ToolbarSearchFieldDelegate.prototype = {
+ /** @override */
+ onSearchTermSearch: function(searchTerm) {
+ this.toolbar_.onSearchTermSearch(searchTerm);
+ }
+ };
+
+ return {Toolbar: Toolbar};
+});
+
+// TODO(dbeam): https://github.com/PolymerElements/iron-dropdown/pull/16/files
+/** @suppress {checkTypes} */
+(function() {
+Polymer.IronDropdownScrollManager.pushScrollLock = function() {};
+})();
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('downloads', function() {
+ var Manager = Polymer({
+ is: 'downloads-manager',
+
+ properties: {
+ hasDownloads_: {
+ type: Boolean,
+ value: false,
+ },
+ },
+
+ /**
+ * @return {number} A guess at how many items could be visible at once.
+ * @private
+ */
+ guesstimateNumberOfVisibleItems_: function() {
+ var toolbarHeight = this.$.toolbar.offsetHeight;
+ return Math.floor((window.innerHeight - toolbarHeight) / 46) + 1;
+ },
+
+ /**
+ * @param {Event} e
+ * @private
+ */
+ onCanExecute_: function(e) {
+ e = /** @type {cr.ui.CanExecuteEvent} */(e);
+ switch (e.command.id) {
+ case 'undo-command':
+ e.canExecute = this.$.toolbar.canUndo();
+ break;
+ case 'clear-all-command':
+ e.canExecute = this.$.toolbar.canClearAll();
+ break;
+ }
+ },
+
+ /**
+ * @param {Event} e
+ * @private
+ */
+ onCommand_: function(e) {
+ if (e.command.id == 'clear-all-command')
+ downloads.ActionService.getInstance().clearAll();
+ else if (e.command.id == 'undo-command')
+ downloads.ActionService.getInstance().undo();
+ },
+
+ /** @private */
+ onLoad_: function() {
+ cr.ui.decorate('command', cr.ui.Command);
+ document.addEventListener('canExecute', this.onCanExecute_.bind(this));
+ document.addEventListener('command', this.onCommand_.bind(this));
+
+ // Shows all downloads.
+ downloads.ActionService.getInstance().search('');
+ },
+
+ /** @private */
+ rebuildFocusGrid_: function() {
+ var activeElement = this.shadowRoot.activeElement;
+
+ var activeItem;
+ if (activeElement && activeElement.tagName == 'downloads-item')
+ activeItem = activeElement;
+
+ var activeControl = activeItem && activeItem.shadowRoot.activeElement;
+
+ /** @private {!cr.ui.FocusGrid} */
+ this.focusGrid_ = this.focusGrid_ || new cr.ui.FocusGrid;
+ this.focusGrid_.destroy();
+
+ var boundary = this.$['downloads-list'];
+
+ this.items_.forEach(function(item) {
+ var focusRow = new downloads.FocusRow(item.content, boundary);
+ this.focusGrid_.addRow(focusRow);
+
+ if (item == activeItem && !cr.ui.FocusRow.isFocusable(activeControl))
+ focusRow.getEquivalentElement(activeControl).focus();
+ }, this);
+
+ this.focusGrid_.ensureRowActive();
+ },
+
+ /**
+ * @return {number} The number of downloads shown on the page.
+ * @private
+ */
+ size_: function() {
+ return this.items_.length;
+ },
+
+ /**
+ * Called when all items need to be updated.
+ * @param {!Array<!downloads.Data>} list A list of new download data.
+ * @private
+ */
+ updateAll_: function(list) {
+ var oldIdMap = this.idMap_ || {};
+
+ /** @private {!Object<!downloads.Item>} */
+ this.idMap_ = {};
+
+ /** @private {!Array<!downloads.Item>} */
+ this.items_ = [];
+
+ if (!this.iconLoader_) {
+ var guesstimate = Math.max(this.guesstimateNumberOfVisibleItems_(), 1);
+ /** @private {downloads.ThrottledIconLoader} */
+ this.iconLoader_ = new downloads.ThrottledIconLoader(guesstimate);
+ }
+
+ for (var i = 0; i < list.length; ++i) {
+ var data = list[i];
+ var id = data.id;
+
+ // Re-use old items when possible (saves work, preserves focus).
+ var item = oldIdMap[id] || new downloads.Item(this.iconLoader_);
+
+ this.idMap_[id] = item; // Associated by ID for fast lookup.
+ this.items_.push(item); // Add to sorted list for order.
+
+ // Render |item| but don't actually add to the DOM yet. |this.items_|
+ // must be fully created to be able to find the right spot to insert.
+ item.update(data);
+
+ // Collapse redundant dates.
+ var prev = list[i - 1];
+ item.hideDate = !!prev && prev.date_string == data.date_string;
+
+ delete oldIdMap[id];
+ }
+
+ // Remove stale, previously rendered items from the DOM.
+ for (var id in oldIdMap) {
+ if (oldIdMap[id].parentNode)
+ oldIdMap[id].parentNode.removeChild(oldIdMap[id]);
+ delete oldIdMap[id];
+ }
+
+ for (var i = 0; i < this.items_.length; ++i) {
+ var item = this.items_[i];
+ if (item.parentNode) // Already in the DOM; skip.
+ continue;
+
+ var before = null;
+ // Find the next rendered item after this one, and insert before it.
+ for (var j = i + 1; !before && j < this.items_.length; ++j) {
+ if (this.items_[j].parentNode)
+ before = this.items_[j];
+ }
+ // If |before| is null, |item| will just get added at the end.
+ this.$['downloads-list'].insertBefore(item, before);
+ }
+
+ var hasDownloads = this.size_() > 0;
+ if (!hasDownloads) {
+ var isSearching = downloads.ActionService.getInstance().isSearching();
+ var messageToShow = isSearching ? 'noSearchResults' : 'noDownloads';
+ this.$['no-downloads'].querySelector('span').textContent =
+ loadTimeData.getString(messageToShow);
+ }
+ this.hasDownloads_ = hasDownloads;
+
+ if (loadTimeData.getBoolean('allowDeletingHistory'))
+ this.$.toolbar.downloadsShowing = this.hasDownloads_;
+
+ this.$.panel.classList.remove('loading');
+
+ var allReady = this.items_.map(function(i) { return i.readyPromise; });
+ Promise.all(allReady).then(this.rebuildFocusGrid_.bind(this));
+ },
+
+ /**
+ * @param {!downloads.Data} data
+ * @private
+ */
+ updateItem_: function(data) {
+ var item = this.idMap_[data.id];
+
+ var activeControl = this.shadowRoot.activeElement == item ?
+ item.shadowRoot.activeElement : null;
+
+ item.update(data);
+
+ this.async(function() {
+ if (activeControl && !cr.ui.FocusRow.isFocusable(activeControl)) {
+ var focusRow = this.focusGrid_.getRowForRoot(item.content);
+ focusRow.getEquivalentElement(activeControl).focus();
+ }
+ }.bind(this));
+ },
+ });
+
+ Manager.size = function() {
+ return document.querySelector('downloads-manager').size_();
+ };
+
+ Manager.updateAll = function(list) {
+ document.querySelector('downloads-manager').updateAll_(list);
+ };
+
+ Manager.updateItem = function(item) {
+ document.querySelector('downloads-manager').updateItem_(item);
+ };
+
+ Manager.onLoad = function() {
+ document.querySelector('downloads-manager').onLoad_();
+ };
+
+ return {Manager: Manager};
+});
+
+window.addEventListener('load', downloads.Manager.onLoad);
« no previous file with comments | « chrome/browser/browser_resources.grd ('k') | chrome/browser/resources/md_downloads/item.css » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698