diff options
Diffstat (limited to 'chromium/chrome/browser/resources/md_downloads/crisper.js')
-rw-r--r-- | chromium/chrome/browser/resources/md_downloads/crisper.js | 17142 |
1 files changed, 17142 insertions, 0 deletions
diff --git a/chromium/chrome/browser/resources/md_downloads/crisper.js b/chromium/chrome/browser/resources/md_downloads/crisper.js new file mode 100644 index 00000000000..4741e55d1f7 --- /dev/null +++ b/chromium/chrome/browser/resources/md_downloads/crisper.js @@ -0,0 +1,17142 @@ +// 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. + +if (typeof Polymer == 'undefined') + Polymer = {dom: 'shadow'}; +else + console.error('Polymer is already defined.'); +// 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) 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, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +/** + * 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 (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) { + if (!condition) { + var message = 'Assertion failed'; + if (opt_message) + message = message + ': ' + opt_message; + var error = new Error(message); + var global = function() { return this; }(); + if (global.traceAssertionsForTesting) + console.warn(error.stack); + throw error; + } + 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) { + assert(false, 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) { + assert(value instanceof type, + opt_message || value + ' is not a[n] ' + (type.name || typeof type)); + return value; +}; +// 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() { + /** @private {Array<string>} */ + this.searchTerms_ = []; + } + + /** + * @param {string} s + * @return {string} |s| without whitespace at the beginning or end. + */ + function trim(s) { return s.trim(); } + + /** + * @param {string|undefined} value + * @return {boolean} Whether |value| is truthy. + */ + function truthy(value) { return !!value; } + + /** + * @param {string} searchText Input typed by the user into a search box. + * @return {Array<string>} A list of terms extracted from |searchText|. + */ + ActionService.splitTerms = function(searchText) { + // Split quoted terms (e.g., 'The "lazy" dog' => ['The', 'lazy', 'dog']). + return searchText.split(/"([^"]*)"/).map(trim).filter(truthy); + }; + + 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'), + + /** Loads more downloads with the current search terms. */ + loadMore: function() { + chrome.send('getDownloads', this.searchTerms_); + }, + + /** + * @return {boolean} Whether the user is currently searching for downloads + * (i.e. has a non-empty search term). + */ + isSearching: function() { + return this.searchTerms_.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) { + var searchTerms = ActionService.splitTerms(searchText); + var sameTerms = searchTerms.length == this.searchTerms_.length; + + for (var i = 0; sameTerms && i < searchTerms.length; ++i) { + if (searchTerms[i] != this.searchTerms_[i]) + sameTerms = false; + } + + if (sameTerms) + return; + + this.searchTerms_ = searchTerms; + this.loadMore(); + }, + + /** + * 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. + +// 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 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 || {}; +var parts = location.search.slice(1).split('&'); +for (var i = 0, o; i < parts.length && (o = parts[i]); i++) { +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; +for (var i = 0; i < this._callbacks.length; i++) { +this._callbacks[i](); +} +this._callbacks = []; +}, +_catchFirstRender: function () { +requestAnimationFrame(function () { +Polymer.RenderStatus._makeReady(); +}); +}, +_afterNextRenderQueue: [], +_waitingNextRender: false, +afterNextRender: function (element, fn, args) { +this._watchNextRender(); +this._afterNextRenderQueue.push([ +element, +fn, +args +]); +}, +_watchNextRender: function () { +if (!this._waitingNextRender) { +this._waitingNextRender = true; +var fn = function () { +Polymer.RenderStatus._flushNextRender(); +}; +if (!this._ready) { +this.whenReady(fn); +} else { +requestAnimationFrame(fn); +} +} +}, +_flushNextRender: function () { +var self = this; +setTimeout(function () { +self._flushRenderCallbacks(self._afterNextRenderQueue); +self._afterNextRenderQueue = []; +self._waitingNextRender = false; +}); +}, +_flushRenderCallbacks: function (callbacks) { +for (var i = 0, h; i < callbacks.length; i++) { +h = callbacks[i]; +h[1].apply(h[0], h[2] || Polymer.nar); +} +; +} +}; +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 () { +var self = this; +Polymer.RenderStatus.whenReady(function () { +self.isAttached = true; +self._doBehavior('attached'); +}); +}, +detachedCallback: function () { +this.isAttached = false; +this._doBehavior('detached'); +}, +attributeChangedCallback: function (name, oldValue, newValue) { +this._attributeChangedImpl(name); +this._doBehavior('attributeChanged', [ +name, +oldValue, +newValue +]); +}, +_attributeChangedImpl: function (name) { +this._setAttributeToProperty(this, name); +}, +extend: function (prototype, api) { +if (prototype && api) { +var n$ = Object.getOwnPropertyNames(api); +for (var i = 0, n; i < n$.length && (n = n$[i]); i++) { +this.copyOwnProperty(n, api, prototype); +} +} +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) { +forceDomModulesUpgrade(); +m = findModule(id); +} +if (m && selector) { +m = m.querySelector(selector); +} +return m; +} +} +}); +var cePolyfill = window.CustomElements && !CustomElements.useNative; +document.registerElement('dom-module', DomModule); +function forceDomModulesUpgrade() { +if (cePolyfill) { +var script = document._currentScript || document.currentScript; +var doc = script && script.ownerDocument || document; +var modules = doc.querySelectorAll('dom-module'); +for (var i = modules.length - 1, m; i >= 0 && (m = modules[i]); i--) { +if (m.__upgraded__) { +return; +} else { +CustomElements.upgrade(m); +} +} +} +} +}()); +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 = []; +for (var i = 0; i < behaviors.length; i++) { +var b = behaviors[i]; +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')); +} +} +return flat; +}, +_mixinBehavior: function (b) { +var n$ = Object.getOwnPropertyNames(b); +for (var i = 0, n; i < n$.length && (n = n$[i]); i++) { +if (!Polymer.Base._behaviorProperties[n] && !this.hasOwnProperty(n)) { +this.copyOwnProperty(n, b, 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) { +for (var i = 0; i < this.behaviors.length; i++) { +this._invokeBehavior(this.behaviors[i], name, args); +} +this._invokeBehavior(this, name, args); +}, +_invokeBehavior: function (b, name, args) { +var fn = b[name]; +if (fn) { +fn.apply(this, args || Polymer.nar); +} +}, +_marshalBehaviors: function () { +for (var i = 0; i < this.behaviors.length; i++) { +this._marshalBehavior(this.behaviors[i]); +} +this._marshalBehavior(this); +} +}); +Polymer.Base._behaviorProperties = { +hostAttributes: true, +registered: true, +properties: true, +observers: true, +listeners: true, +created: true, +attached: true, +detached: true, +attributeChanged: true, +ready: true +}; +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) { +for (var i = 0; i < this.behaviors.length; i++) { +info = this._getPropertyInfo(property, this.behaviors[i].properties); +if (info) { +return info; +} +} +; +} +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; +}, +_prepPropertyInfo: function () { +this._propertyInfo = {}; +for (var i = 0, p; i < this.behaviors.length; i++) { +this._addPropertyInfo(this._propertyInfo, this.behaviors[i].properties); +} +this._addPropertyInfo(this._propertyInfo, this.properties); +this._addPropertyInfo(this._propertyInfo, this._propertyEffects); +}, +_addPropertyInfo: function (target, source) { +if (source) { +var t, s; +for (var i in source) { +t = target[i]; +s = source[i]; +if (i[0] === '_' && !s.readOnly) { +continue; +} +if (!target[i]) { +target[i] = { +type: typeof s === 'function' ? s : s.type, +readOnly: s.readOnly, +attribute: Polymer.CaseMap.camelToDashCase(i) +}; +} else { +if (!t.type) { +t.type = s.type; +} +if (!t.readOnly) { +t.readOnly = s.readOnly; +} +} +} +} +} +}); +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({ +_addHostAttributes: function (attributes) { +if (!this._aggregatedAttributes) { +this._aggregatedAttributes = {}; +} +if (attributes) { +this.mixin(this._aggregatedAttributes, attributes); +} +}, +_marshalHostAttributes: function () { +if (this._aggregatedAttributes) { +this._applyAttributes(this, this._aggregatedAttributes); +} +}, +_applyAttributes: function (node, attr$) { +for (var n in attr$) { +if (!this.hasAttribute(n) && n !== 'class') { +var v = attr$[n]; +this.serializeValueToAttribute(v, n, this); +} +} +}, +_marshalAttributes: function () { +this._takeAttributesToModel(this); +}, +_takeAttributesToModel: function (model) { +if (this.hasAttributes()) { +for (var i in this._propertyInfo) { +var info = this._propertyInfo[i]; +if (this.hasAttribute(info.attribute)) { +this._setAttributeToProperty(model, info.attribute, i, info); +} +} +} +}, +_setAttributeToProperty: function (model, attribute, property, info) { +if (!this._serializing) { +var property = property || Polymer.CaseMap.dashToCamelCase(attribute); +info = info || this._propertyInfo && this._propertyInfo[property]; +if (info && !info.readOnly) { +var v = this.getAttribute(attribute); +model[property] = this.deserialize(v, info.type); +} +} +}, +_serializing: false, +reflectPropertyToAttribute: function (property, attribute, value) { +this._serializing = true; +value = value === undefined ? this[property] : value; +this.serializeValueToAttribute(value, attribute || Polymer.CaseMap.camelToDashCase(property)); +this._serializing = false; +}, +serializeValueToAttribute: function (value, attribute, node) { +var str = this.serialize(value); +node = node || this; +if (str === undefined) { +node.removeAttribute(attribute); +} else { +node.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.2.3'; +Polymer.Base._addFeature({ +_registerFeatures: function () { +this._prepIs(); +this._prepBehaviors(); +this._prepConstructor(); +this._prepPropertyInfo(); +}, +_prepBehavior: function (b) { +this._addHostAttributes(b.hostAttributes); +}, +_marshalBehavior: function (b) { +}, +_initFeatures: function () { +this._marshalHostAttributes(); +this._setupDebouncers(); +this._marshalBehaviors(); +} +}); +Polymer.Base._addFeature({ +_prepTemplate: function () { +if (this._template === undefined) { +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 && window.HTMLTemplateElement && HTMLTemplateElement.decorate) { +HTMLTemplateElement.decorate(this._template); +} +}, +_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 () { +}, +_registerHost: function (host) { +this.dataHost = host = host || Polymer.Base._hostStack[Polymer.Base._hostStack.length - 1]; +if (host && host._clients) { +host._clients.push(this); +} +}, +_beginHosting: function () { +Polymer.Base._hostStack.push(this); +if (!this._clients) { +this._clients = []; +} +}, +_endHosting: function () { +Polymer.Base._hostStack.pop(); +}, +_tryReady: function () { +if (this._canReady()) { +this._ready(); +} +}, +_canReady: function () { +return !this.dataHost || this.dataHost._clientsReadied; +}, +_ready: function () { +this._beforeClientsReady(); +if (this._template) { +this._setupRoot(); +this._readyClients(); +} +this._clientsReadied = true; +this._clients = null; +this._afterClientsReady(); +this._readySelf(); +}, +_readyClients: function () { +this._beginDistribute(); +var c$ = this._clients; +if (c$) { +for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) { +c._ready(); +} +} +this._finishDistribute(); +}, +_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.domInnerHTML = function () { +var escapeAttrRegExp = /[&\u00A0"]/g; +var escapeDataRegExp = /[&\u00A0<>]/g; +function escapeReplace(c) { +switch (c) { +case '&': +return '&'; +case '<': +return '<'; +case '>': +return '>'; +case '"': +return '"'; +case '\xA0': +return ' '; +} +} +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 needsToWrap = Settings.hasShadow && !Settings.nativeShadow; +var wrap = window.wrap ? window.wrap : function (node) { +return node; +}; +var DomApi = function (node) { +this.node = needsToWrap ? wrap(node) : node; +if (this.patch) { +this.patch(); +} +}; +DomApi.prototype = { +flush: function () { +Polymer.dom.flush(); +}, +deepContains: function (node) { +if (this.node.contains(node)) { +return true; +} +var n = node; +var wrappedDocument = wrap(document); +while (n && n !== wrappedDocument && n !== this.node) { +n = Polymer.dom(n).parentNode || n.host; +} +return n === this.node; +}, +_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._removeNodeFromParent(node); +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); +} +this.notifyObserver(); +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); +} +} +this.notifyObserver(); +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); +}, +_removeNodeFromParent: function (node) { +var parent = node._lightParent || node.parentNode; +if (parent && hasDomApi(parent)) { +factory(parent).notifyObserver(); +} +this._removeNodeFromHost(node, true); +}, +_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, parent); +} +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$ = arrayCopyChildNodes(node); +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.getEffectiveChildNodes(); +var list = []; +for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) { +if (c.nodeType === Node.ELEMENT_NODE && matchesSelector.call(c, selector)) { +list.push(c); +} +} +return list; +}, +getEffectiveChildNodes: function () { +var list = []; +var c$ = this.childNodes; +for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) { +if (c.localName === CONTENT) { +var d$ = factory(c).getDistributedNodes(); +for (var j = 0; j < d$.length; j++) { +list.push(d$[j]); +} +} else { +list.push(c); +} +} +return list; +}, +_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; +}, +observeNodes: function (callback) { +if (callback) { +if (!this.observer) { +this.observer = this.node.localName === CONTENT ? new DomApi.DistributedNodesObserver(this) : new DomApi.EffectiveNodesObserver(this); +} +return this.observer.addListener(callback); +} +}, +unobserveNodes: function (handle) { +if (this.observer) { +this.observer.removeListener(handle); +} +}, +notifyObserver: function () { +if (this.observer) { +this.observer.notify(); +} +} +}; +if (!Settings.useShadow) { +Object.defineProperties(DomApi.prototype, { +childNodes: { +get: function () { +var c$ = getLightChildren(this.node); +return Array.isArray(c$) ? c$ : arrayCopyChildNodes(this.node); +}, +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$ = arrayCopyChildNodes(d); +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 = function (m$) { +for (var i = 0; i < m$.length; i++) { +forwardMethod(m$[i]); +} +}; +var forwardMethod = function (method) { +DomApi.prototype[method] = function () { +return this.node[method].apply(this.node, arguments); +}; +}; +forwardMethods([ +'cloneNode', +'appendChild', +'insertBefore', +'removeChild', +'replaceChild' +]); +DomApi.prototype.querySelectorAll = function (selector) { +return arrayCopy(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$ ? arrayCopy(n$) : []; +}; +DomApi.prototype.getDistributedNodes = function () { +var n$ = this.node.getDistributedNodes && this.node.getDistributedNodes(); +return n$ ? arrayCopy(n$) : []; +}; +DomApi.prototype._distributeParent = function () { +}; +Object.defineProperties(DomApi.prototype, { +childNodes: { +get: function () { +return arrayCopyChildNodes(this.node); +}, +configurable: true +}, +children: { +get: function () { +return arrayCopyChildren(this.node); +}, +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 = function (f$) { +for (var i = 0; i < f$.length; i++) { +forwardProperty(f$[i]); +} +}; +var forwardProperty = function (name) { +Object.defineProperty(DomApi.prototype, name, { +get: function () { +return this.node[name]; +}, +configurable: true +}); +}; +forwardProperties([ +'parentNode', +'firstChild', +'lastChild', +'nextSibling', +'previousSibling', +'firstElementChild', +'lastElementChild', +'nextElementSibling', +'previousElementSibling' +]); +} +var CONTENT = 'content'; +function factory(node, patch) { +node = node || document; +if (!node.__domApi) { +node.__domApi = new DomApi(node, patch); +} +return node.__domApi; +} +; +function hasDomApi(node) { +return Boolean(node.__domApi); +} +; +Polymer.dom = function (obj, patch) { +if (obj instanceof Event) { +return Polymer.EventApi.factory(obj); +} else { +return factory(obj, patch); +} +}; +function getLightChildren(node) { +var children = node._lightChildren; +return children ? children : node.childNodes; +} +function getComposedChildren(node) { +if (!node._composedChildren) { +node._composedChildren = arrayCopyChildNodes(node); +} +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$ = arrayCopyChildNodes(node); +for (var i = 0, l = c$.length, child; i < l && (child = c$[i]); i++) { +child._lightParent = child._lightParent || node; +} +node._lightChildren = c$; +} +} +function arrayCopyChildNodes(parent) { +var copy = [], i = 0; +for (var n = parent.firstChild; n; n = n.nextSibling) { +copy[i++] = n; +} +return copy; +} +function arrayCopyChildren(parent) { +var copy = [], i = 0; +for (var n = parent.firstElementChild; n; n = n.nextElementSibling) { +copy[i++] = n; +} +return copy; +} +function arrayCopy(a$) { +var l = a$.length; +var copy = new Array(l); +for (var i = 0; i < l; i++) { +copy[i] = a$[i]; +} +return copy; +} +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, +hasDomApi: hasDomApi, +arrayCopy: arrayCopy, +arrayCopyChildNodes: arrayCopyChildNodes, +arrayCopyChildren: arrayCopyChildren, +wrap: wrap +}; +}(); +Polymer.Base.extend(Polymer.dom, { +_flushGuard: 0, +_FLUSH_MAX: 100, +_needsTakeRecords: !Polymer.Settings.useNativeCustomElements, +_debouncers: [], +_staticFlushList: [], +_finishDebouncer: null, +flush: function () { +this._flushGuard = 0; +this._prepareFlush(); +while (this._debouncers.length && this._flushGuard < this._FLUSH_MAX) { +for (var i = 0; i < this._debouncers.length; i++) { +this._debouncers[i].complete(); +} +if (this._finishDebouncer) { +this._finishDebouncer.complete(); +} +this._prepareFlush(); +this._flushGuard++; +} +if (this._flushGuard >= this._FLUSH_MAX) { +console.warn('Polymer.dom.flush aborted. Flush may not be complete.'); +} +}, +_prepareFlush: function () { +if (this._needsTakeRecords) { +CustomElements.takeRecords(); +} +for (var i = 0; i < this._staticFlushList.length; i++) { +this._staticFlushList[i](); +} +}, +addStaticFlush: function (fn) { +this._staticFlushList.push(fn); +}, +removeStaticFlush: function (fn) { +var i = this._staticFlushList.indexOf(fn); +if (i >= 0) { +this._staticFlushList.splice(i, 1); +} +}, +addDebouncer: function (debouncer) { +this._debouncers.push(debouncer); +this._finishDebouncer = Polymer.Debounce(this._finishDebouncer, this._finishFlush); +}, +_finishFlush: function () { +Polymer.dom._debouncers = []; +} +}); +Polymer.EventApi = function () { +'use strict'; +var DomApi = Polymer.DomApi.ctor; +var Settings = Polymer.Settings; +DomApi.Event = function (event) { +this.event = event; +}; +if (Settings.useShadow) { +DomApi.Event.prototype = { +get rootTarget() { +return this.event.path[0]; +}, +get localTarget() { +return this.event.target; +}, +get path() { +return this.event.path; +} +}; +} else { +DomApi.Event.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 DomApi.Event(event); +} +return event.__eventApi; +}; +return { factory: factory }; +}(); +(function () { +'use strict'; +var DomApi = Polymer.DomApi.ctor; +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); +} +}; +}()); +(function () { +'use strict'; +var DomApi = Polymer.DomApi.ctor; +var Settings = Polymer.Settings; +var hasDomApi = Polymer.DomApi.hasDomApi; +DomApi.EffectiveNodesObserver = function (domApi) { +this.domApi = domApi; +this.node = this.domApi.node; +this._listeners = []; +}; +DomApi.EffectiveNodesObserver.prototype = { +addListener: function (callback) { +if (!this._isSetup) { +this._setup(); +this._isSetup = true; +} +var listener = { +fn: callback, +_nodes: [] +}; +this._listeners.push(listener); +this._scheduleNotify(); +return listener; +}, +removeListener: function (handle) { +var i = this._listeners.indexOf(handle); +if (i >= 0) { +this._listeners.splice(i, 1); +handle._nodes = []; +} +if (!this._hasListeners()) { +this._cleanup(); +this._isSetup = false; +} +}, +_setup: function () { +this._observeContentElements(this.domApi.childNodes); +}, +_cleanup: function () { +this._unobserveContentElements(this.domApi.childNodes); +}, +_hasListeners: function () { +return Boolean(this._listeners.length); +}, +_scheduleNotify: function () { +if (this._debouncer) { +this._debouncer.stop(); +} +this._debouncer = Polymer.Debounce(this._debouncer, this._notify); +this._debouncer.context = this; +Polymer.dom.addDebouncer(this._debouncer); +}, +notify: function () { +if (this._hasListeners()) { +this._scheduleNotify(); +} +}, +_notify: function (mxns) { +this._beforeCallListeners(); +this._callListeners(); +}, +_beforeCallListeners: function () { +this._updateContentElements(); +}, +_updateContentElements: function () { +this._observeContentElements(this.domApi.childNodes); +}, +_observeContentElements: function (elements) { +for (var i = 0, n; i < elements.length && (n = elements[i]); i++) { +if (this._isContent(n)) { +n.__observeNodesMap = n.__observeNodesMap || new WeakMap(); +if (!n.__observeNodesMap.has(this)) { +n.__observeNodesMap.set(this, this._observeContent(n)); +} +} +} +}, +_observeContent: function (content) { +var self = this; +var h = Polymer.dom(content).observeNodes(function () { +self._scheduleNotify(); +}); +h._avoidChangeCalculation = true; +return h; +}, +_unobserveContentElements: function (elements) { +for (var i = 0, n, h; i < elements.length && (n = elements[i]); i++) { +if (this._isContent(n)) { +h = n.__observeNodesMap.get(this); +if (h) { +Polymer.dom(n).unobserveNodes(h); +n.__observeNodesMap.delete(this); +} +} +} +}, +_isContent: function (node) { +return node.localName === 'content'; +}, +_callListeners: function () { +var o$ = this._listeners; +var nodes = this._getEffectiveNodes(); +for (var i = 0, o; i < o$.length && (o = o$[i]); i++) { +var info = this._generateListenerInfo(o, nodes); +if (info || o._alwaysNotify) { +this._callListener(o, info); +} +} +}, +_getEffectiveNodes: function () { +return this.domApi.getEffectiveChildNodes(); +}, +_generateListenerInfo: function (listener, newNodes) { +if (listener._avoidChangeCalculation) { +return true; +} +var oldNodes = listener._nodes; +var info = { +target: this.node, +addedNodes: [], +removedNodes: [] +}; +var splices = Polymer.ArraySplice.calculateSplices(newNodes, oldNodes); +for (var i = 0, s; i < splices.length && (s = splices[i]); i++) { +for (var j = 0, n; j < s.removed.length && (n = s.removed[j]); j++) { +info.removedNodes.push(n); +} +} +for (var i = 0, s; i < splices.length && (s = splices[i]); i++) { +for (var j = s.index; j < s.index + s.addedCount; j++) { +info.addedNodes.push(newNodes[j]); +} +} +listener._nodes = newNodes; +if (info.addedNodes.length || info.removedNodes.length) { +return info; +} +}, +_callListener: function (listener, info) { +return listener.fn.call(this.node, info); +}, +enableShadowAttributeTracking: function () { +} +}; +if (Settings.useShadow) { +var baseSetup = DomApi.EffectiveNodesObserver.prototype._setup; +var baseCleanup = DomApi.EffectiveNodesObserver.prototype._cleanup; +var beforeCallListeners = DomApi.EffectiveNodesObserver.prototype._beforeCallListeners; +Polymer.Base.extend(DomApi.EffectiveNodesObserver.prototype, { +_setup: function () { +if (!this._observer) { +var self = this; +this._mutationHandler = function (mxns) { +if (mxns && mxns.length) { +self._scheduleNotify(); +} +}; +this._observer = new MutationObserver(this._mutationHandler); +this._boundFlush = function () { +self._flush(); +}; +Polymer.dom.addStaticFlush(this._boundFlush); +this._observer.observe(this.node, { childList: true }); +} +baseSetup.call(this); +}, +_cleanup: function () { +this._observer.disconnect(); +this._observer = null; +this._mutationHandler = null; +Polymer.dom.removeStaticFlush(this._boundFlush); +baseCleanup.call(this); +}, +_flush: function () { +if (this._observer) { +this._mutationHandler(this._observer.takeRecords()); +} +}, +enableShadowAttributeTracking: function () { +if (this._observer) { +this._makeContentListenersAlwaysNotify(); +this._observer.disconnect(); +this._observer.observe(this.node, { +childList: true, +attributes: true, +subtree: true +}); +var root = this.domApi.getOwnerRoot(); +var host = root && root.host; +if (host && Polymer.dom(host).observer) { +Polymer.dom(host).observer.enableShadowAttributeTracking(); +} +} +}, +_makeContentListenersAlwaysNotify: function () { +for (var i = 0, h; i < this._listeners.length; i++) { +h = this._listeners[i]; +h._alwaysNotify = h._isContentListener; +} +} +}); +} +}()); +(function () { +'use strict'; +var DomApi = Polymer.DomApi.ctor; +var Settings = Polymer.Settings; +DomApi.DistributedNodesObserver = function (domApi) { +DomApi.EffectiveNodesObserver.call(this, domApi); +}; +DomApi.DistributedNodesObserver.prototype = Object.create(DomApi.EffectiveNodesObserver.prototype); +Polymer.Base.extend(DomApi.DistributedNodesObserver.prototype, { +_setup: function () { +}, +_cleanup: function () { +}, +_beforeCallListeners: function () { +}, +_getEffectiveNodes: function () { +return this.domApi.getDistributedNodes(); +} +}); +if (Settings.useShadow) { +Polymer.Base.extend(DomApi.DistributedNodesObserver.prototype, { +_setup: function () { +if (!this._observer) { +var root = this.domApi.getOwnerRoot(); +var host = root && root.host; +if (host) { +var self = this; +this._observer = Polymer.dom(host).observeNodes(function () { +self._scheduleNotify(); +}); +this._observer._isContentListener = true; +if (this._hasAttrSelect()) { +Polymer.dom(host).observer.enableShadowAttributeTracking(); +} +} +} +}, +_hasAttrSelect: function () { +var select = this.node.getAttribute('select'); +return select && select.match(/[[.]+/); +}, +_cleanup: function () { +var root = this.domApi.getOwnerRoot(); +var host = root && root.host; +if (host) { +Polymer.dom(host).unobserveNodes(this._observer); +} +this._observer = null; +} +}); +} +}()); +(function () { +var hasDomApi = Polymer.DomApi.hasDomApi; +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._hasDistributed = 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(); +notifyContentObservers(this.shadyRoot); +} else { +if (!this.shadyRoot._hasDistributed) { +this.textContent = ''; +this._composedChildren = null; +this.appendChild(this.shadyRoot); +} else { +var children = this._composeNode(this); +this._updateChildNodes(this, children); +} +} +if (!this.shadyRoot._hasDistributed) { +notifyInitialDistribution(this); +} +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; +} +} +} +function notifyContentObservers(root) { +for (var i = 0, c; i < root._insertionPoints.length; i++) { +c = root._insertionPoints[i]; +if (hasDomApi(c)) { +Polymer.dom(c).notifyObserver(); +} +} +} +function notifyInitialDistribution(host) { +if (hasDomApi(host)) { +Polymer.dom(host).notifyObserver(); +} +} +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._prepBehaviors(); +this._prepConstructor(); +this._prepTemplate(); +this._prepShady(); +this._prepPropertyInfo(); +}, +_prepBehavior: function (b) { +this._addHostAttributes(b.hostAttributes); +}, +_initFeatures: function () { +this._registerHost(); +if (this._template) { +this._poolContent(); +this._beginHosting(); +this._stampTemplate(); +this._endHosting(); +} +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, template.hasAttribute('strip-whitespace')); +return list; +}, +_parseNodeAnnotations: function (node, list, stripWhiteSpace) { +return node.nodeType === Node.TEXT_NODE ? this._parseTextNodeAnnotation(node, list) : this._parseElementAnnotations(node, list, stripWhiteSpace); +}, +_bindingRegex: /([^{[]*)(\{\{|\[\[)(?!\}\}|\]\])(.+?)(?:\]\]|\}\})/g, +_parseBindings: function (text) { +var re = this._bindingRegex; +var parts = []; +var m, lastIndex; +while ((m = re.exec(text)) !== null) { +if (m[1]) { +parts.push({ literal: m[1] }); +} +var mode = m[2][0]; +var value = m[3].trim(); +var negate = false; +if (value[0] == '!') { +negate = true; +value = value.substring(1).trim(); +} +var customEvent, notifyEvent, colon; +if (mode == '{' && (colon = value.indexOf('::')) > 0) { +notifyEvent = value.substring(colon + 2); +value = value.substring(0, colon); +customEvent = true; +} +parts.push({ +compoundIndex: parts.length, +value: value, +mode: mode, +negate: negate, +event: notifyEvent, +customEvent: customEvent +}); +lastIndex = re.lastIndex; +} +if (lastIndex && lastIndex < text.length) { +var literal = text.substring(lastIndex); +if (literal) { +parts.push({ literal: literal }); +} +} +if (parts.length) { +return parts; +} +}, +_literalFromParts: function (parts) { +var s = ''; +for (var i = 0; i < parts.length; i++) { +var literal = parts[i].literal; +s += literal || ''; +} +return s; +}, +_parseTextNodeAnnotation: function (node, list) { +var parts = this._parseBindings(node.textContent); +if (parts) { +node.textContent = this._literalFromParts(parts) || ' '; +var annote = { +bindings: [{ +kind: 'text', +name: 'textContent', +parts: parts, +isCompound: parts.length !== 1 +}] +}; +list.push(annote); +return annote; +} +}, +_parseElementAnnotations: function (element, list, stripWhiteSpace) { +var annote = { +bindings: [], +events: [] +}; +if (element.localName === 'content') { +list._hasContent = true; +} +this._parseChildNodesAnnotations(element, annote, list, stripWhiteSpace); +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, stripWhiteSpace) { +if (root.firstChild) { +var node = root.firstChild; +var i = 0; +while (node) { +var next = node.nextSibling; +if (node.localName === 'template' && !node.hasAttribute('preserve-content')) { +this._parseTemplate(node, i, list, annote); +} +if (node.nodeType === Node.TEXT_NODE) { +var n = next; +while (n && n.nodeType === Node.TEXT_NODE) { +node.textContent += n.textContent; +next = n.nextSibling; +root.removeChild(n); +n = next; +} +if (stripWhiteSpace && !node.textContent.trim()) { +root.removeChild(node); +i--; +} +} +if (node.parentNode) { +var childAnnotation = this._parseNodeAnnotations(node, list, stripWhiteSpace); +if (childAnnotation) { +childAnnotation.parent = annote; +childAnnotation.index = i; +} +} +node = next; +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) { +var attrs = Array.prototype.slice.call(node.attributes); +for (var i = attrs.length - 1, a; a = attrs[i]; i--) { +var n = a.name; +var v = a.value; +var b; +if (n.slice(0, 3) === 'on-') { +node.removeAttribute(n); +annotation.events.push({ +name: n.slice(3), +value: v +}); +} else if (b = this._parseNodeAttributeAnnotation(node, n, v)) { +annotation.bindings.push(b); +} else if (n === 'id') { +annotation.id = v; +} +} +}, +_parseNodeAttributeAnnotation: function (node, name, value) { +var parts = this._parseBindings(value); +if (parts) { +var origName = name; +var kind = 'property'; +if (name[name.length - 1] == '$') { +name = name.slice(0, -1); +kind = 'attribute'; +} +var literal = this._literalFromParts(parts); +if (literal && kind == 'attribute') { +node.setAttribute(name, literal); +} +if (node.localName == 'input' && name == 'value') { +node.setAttribute(origName, ''); +} +node.removeAttribute(origName); +if (kind === 'property') { +name = Polymer.CaseMap.dashToCamelCase(name); +} +return { +kind: kind, +name: name, +parts: parts, +literal: literal, +isCompound: parts.length !== 1 +}; +} +}, +_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 { +var self = this; +Polymer.Annotations.prepElement = function (element) { +self._prepElement(element); +}; +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]; +for (var k = 0; k < b.parts.length; k++) { +var p = b.parts[k]; +if (!p.literal) { +p.signature = this._parseMethod(p.value); +if (!p.signature) { +p.model = this._modelForPath(p.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', +name: '_parent_' + prop, +parts: [{ +mode: '{', +model: prop, +value: prop +}] +}); +} +note.bindings = note.bindings.concat(bindings); +} +} +}, +_discoverTemplateParentProps: function (notes) { +var pp = {}; +for (var i = 0, n; i < notes.length && (n = notes[i]); i++) { +for (var j = 0, b$ = n.bindings, b; j < b$.length && (b = b$[j]); j++) { +for (var k = 0, p$ = b.parts, p; k < p$.length && (p = p$[k]); k++) { +if (p.signature) { +var args = p.signature.args; +for (var kk = 0; kk < args.length; kk++) { +pp[args[kk].model] = true; +} +} else { +pp[p.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 (config) { +var notes = this._notes; +var nodes = this._nodes; +for (var i = 0; i < notes.length; i++) { +var note = notes[i]; +var node = nodes[i]; +this._configureTemplateContent(note, node); +this._configureCompoundBindings(note, node); +} +}, +_configureTemplateContent: function (note, node) { +if (note.templateContent) { +node._content = note.templateContent; +} +}, +_configureCompoundBindings: function (note, node) { +var bindings = note.bindings; +for (var i = 0; i < bindings.length; i++) { +var binding = bindings[i]; +if (binding.isCompound) { +var storage = node.__compoundStorage__ || (node.__compoundStorage__ = {}); +var parts = binding.parts; +var literals = new Array(parts.length); +for (var j = 0; j < parts.length; j++) { +literals[j] = parts[j].literal; +} +var name = binding.name; +storage[name] = literals; +if (binding.literal && binding.kind == 'property') { +if (node._configValue) { +node._configValue(name, binding.literal); +} else { +node[name] = binding.literal; +} +} +} +} +}, +_marshalIdNodes: function () { +this.$ = {}; +for (var i = 0, l = this._notes.length, a; i < l && (a = this._notes[i]); i++) { +if (a.id) { +this.$[a.id] = this._findAnnotatedNode(this.root, a); +} +} +}, +_marshalAnnotatedNodes: function () { +if (this._notes && this._notes.length) { +var r = new Array(this._notes.length); +for (var i = 0; i < this._notes.length; i++) { +r[i] = this._findAnnotatedNode(this.root, this._notes[i]); +} +this._nodes = r; +} +}, +_marshalAnnotatedListeners: function () { +for (var i = 0, l = this._notes.length, a; i < l && (a = this._notes[i]); i++) { +if (a.events && a.events.length) { +var node = this._findAnnotatedNode(this.root, a); +for (var j = 0, e$ = a.events, e; j < e$.length && (e = e$[j]); j++) { +this.listen(node, e.name, e.value); +} +} +} +} +}); +Polymer.Base._addFeature({ +listeners: {}, +_listenListeners: function (listeners) { +var node, name, eventName; +for (eventName in listeners) { +if (eventName.indexOf('.') < 0) { +node = this; +name = eventName; +} else { +name = eventName.split('.'); +node = this.$[name[0]]; +name = name[1]; +} +this.listen(node, name, listeners[eventName]); +} +}, +listen: function (node, eventName, methodName) { +var handler = this._recallEventHandler(this, eventName, node, methodName); +if (!handler) { +handler = this._createEventHandler(node, eventName, methodName); +} +if (handler._listening) { +return; +} +this._listen(node, eventName, handler); +handler._listening = true; +}, +_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')); +} +}; +handler._listening = false; +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); +handler._listening = false; +} +}, +_listen: function (node, eventName, handler) { +node.addEventListener(eventName, handler); +}, +_unlisten: function (node, eventName, handler) { +node.removeEventListener(eventName, handler); +} +}); +(function () { +'use strict'; +var wrap = Polymer.DomApi.wrap; +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 = wrap(ev.currentTarget); +var gobj = node[GESTURE_KEY]; +if (!gobj) { +return; +} +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) { +node = wrap(node); +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) { +node = wrap(node); +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: function (e) { +return Gestures.prevent(e); +} +}); +} +}); +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(function () { +Polymer.Async._atEndOfMicrotask(); +}).observe(Polymer.Async._twiddle, { characterData: true }); +Polymer.Debounce = function () { +var Async = Polymer.Async; +var Debouncer = function (context) { +this.context = context; +var self = this; +this.boundComplete = function () { +self.complete(); +}; +}; +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, ''); +} +}, +getEffectiveChildNodes: function () { +return Polymer.dom(this).getEffectiveChildNodes(); +}, +getEffectiveChildren: function () { +var list = Polymer.dom(this).getEffectiveChildNodes(); +return list.filter(function (n) { +return n.nodeType === Node.ELEMENT_NODE; +}); +}, +getEffectiveTextContent: function () { +var cn = this.getEffectiveChildNodes(); +var tc = []; +for (var i = 0, c; c = cn[i]; i++) { +if (c.nodeType !== Node.COMMENT_NODE) { +tc.push(Polymer.dom(c).textContent); +} +} +return tc.join(''); +}, +queryEffectiveChildren: function (slctr) { +var e$ = Polymer.dom(this).queryDistributedElements(slctr); +return e$ && e$[0]; +}, +queryAllEffectiveChildren: function (slctr) { +return Polymer.dom(this).queryDistributedElements(slctr); +}, +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 ? {} : detail; +var bubbles = options.bubbles === undefined ? true : options.bubbles; +var cancelable = Boolean(options.cancelable); +var useCache = options._useCache; +var event = this._getEvent(type, bubbles, cancelable, useCache); +event.detail = detail; +if (useCache) { +this.__eventCache[type] = null; +} +node.dispatchEvent(event); +if (useCache) { +this.__eventCache[type] = event; +} +return event; +}, +__eventCache: {}, +_getEvent: function (type, bubbles, cancelable, useCache) { +var event = useCache && this.__eventCache[type]; +if (!event || (event.bubbles != bubbles || event.cancelable != cancelable)) { +event = new Event(type, { +bubbles: Boolean(bubbles), +cancelable: cancelable +}); +} +return event; +}, +async: function (callback, waitTime) { +var self = this; +return Polymer.Async.run(function () { +callback.call(self); +}, 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; +var self = this; +if (onload) { +l.onload = function (e) { +return onload.call(self, e); +}; +} +if (onerror) { +l.onerror = function (e) { +return onerror.call(self, e); +}; +} +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; +}, +isLightDescendant: function (node) { +return this !== node && this.contains(node) && Polymer.dom(this).getOwnerRoot() === Polymer.dom(node).getOwnerRoot(); +}, +isLocalDescendant: function (node) { +return this.root === Polymer.dom(node).getOwnerRoot(); +} +}); +Polymer.Bind = { +_dataEventCache: {}, +prepareModel: function (model) { +Polymer.Base.mixin(model, this._modelApi); +}, +_modelApi: { +_notifyChange: function (source, event, value) { +value = value === undefined ? this[source] : value; +event = event || Polymer.CaseMap.camelToDashCase(source) + '-changed'; +this.fire(event, { value: value }, { +bubbles: false, +cancelable: false, +_useCache: true +}); +}, +_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) { +for (var i = 0, l = effects.length, fx; i < l && (fx = effects[i]); i++) { +fx.fn.call(this, property, value, fx.effect, old, fromAbove); +} +}, +_clearPath: function (path) { +for (var prop in this.__data__) { +if (prop.indexOf(path + '.') === 0) { +this.__data__[prop] = undefined; +} +} +} +}, +ensurePropertyEffects: function (model, property) { +if (!model._propertyEffects) { +model._propertyEffects = {}; +} +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); +var propEffect = { +kind: kind, +effect: effect, +fn: Polymer.Bind['_' + kind + 'Effect'] +}; +fx.push(propEffect); +return propEffect; +}, +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) { +if (!model._bindListeners) { +model._bindListeners = []; +} +var fn = this._notedListenerFactory(property, path, this._isStructured(path)); +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) { +return function (target, value, targetPath) { +if (targetPath) { +this._notifyPath(this._fixPath(path, property, targetPath), value); +} else { +value = target[property]; +if (!isStructured) { +this[path] = value; +} else { +if (this.__data__[path] != value) { +this.set(path, value); +} +} +} +}; +}, +prepareInstance: function (inst) { +inst.__data__ = Object.create(null); +}, +setupBindListeners: function (inst) { +var b$ = inst._bindListeners; +for (var i = 0, l = b$.length, info; i < l && (info = b$[i]); i++) { +var node = inst._nodes[info.index]; +this._addNotifyListener(node, inst, info.event, info.changedFn); +} +; +}, +_addNotifyListener: function (element, context, event, changedFn) { +element.addEventListener(event, function (e) { +return context._notifyListener(changedFn, e); +}); +} +}; +Polymer.Base.extend(Polymer.Bind, { +_shouldAddListener: function (effect) { +return effect.name && effect.kind != 'attribute' && effect.kind != 'text' && !effect.isCompound && effect.parts[0].mode === '{' && !effect.parts[0].negate; +}, +_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(effect, calc); +} +}, +_reflectEffect: function (source, value, effect) { +this.reflectPropertyToAttribute(source, effect.attribute, value); +}, +_notifyEffect: function (source, value, effect, old, fromAbove) { +if (!fromAbove) { +this._notifyChange(source, effect.event, value); +} +}, +_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.name, 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(effect, computedvalue); +} +} 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) { +var prop = Polymer.Bind.addPropertyEffect(this, property, kind, effect); +prop.pathFn = this['_' + prop.kind + 'PathEffect']; +}, +_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', { event: Polymer.CaseMap.camelToDashCase(p) + '-changed' }); +} +if (prop.reflectToAttribute) { +this._addPropertyEffect(p, 'reflect', { attribute: Polymer.CaseMap.camelToDashCase(p) }); +} +if (prop.readOnly) { +Polymer.Bind.ensurePropertyEffects(this, p); +} +} +} +}, +_addComputedEffect: function (name, expression) { +var sig = this._parseMethod(expression); +for (var i = 0, arg; i < sig.args.length && (arg = sig.args[i]); i++) { +this._addPropertyEffect(arg.model, 'compute', { +method: sig.method, +args: sig.args, +trigger: arg, +name: name +}); +} +}, +_addObserverEffect: function (property, observer) { +this._addPropertyEffect(property, 'observer', { +method: observer, +property: property +}); +}, +_addComplexObserverEffects: function (observers) { +if (observers) { +for (var i = 0, o; i < observers.length && (o = observers[i]); i++) { +this._addComplexObserverEffect(o); +} +} +}, +_addComplexObserverEffect: function (observer) { +var sig = this._parseMethod(observer); +for (var i = 0, arg; i < sig.args.length && (arg = sig.args[i]); i++) { +this._addPropertyEffect(arg.model, 'complexObserver', { +method: sig.method, +args: sig.args, +trigger: arg +}); +} +}, +_addAnnotationEffects: function (notes) { +for (var i = 0, note; i < notes.length && (note = notes[i]); i++) { +var b$ = note.bindings; +for (var j = 0, binding; j < b$.length && (binding = b$[j]); j++) { +this._addAnnotationEffect(binding, i); +} +} +}, +_addAnnotationEffect: function (note, index) { +if (Polymer.Bind._shouldAddListener(note)) { +Polymer.Bind._addAnnotatedListener(this, index, note.name, note.parts[0].value, note.parts[0].event); +} +for (var i = 0; i < note.parts.length; i++) { +var part = note.parts[i]; +if (part.signature) { +this._addAnnotatedComputationEffect(note, part, index); +} else if (!part.literal) { +this._addPropertyEffect(part.model, 'annotation', { +kind: note.kind, +index: index, +name: note.name, +value: part.value, +isCompound: note.isCompound, +compoundIndex: part.compoundIndex, +event: part.event, +customEvent: part.customEvent, +negate: part.negate +}); +} +} +}, +_addAnnotatedComputationEffect: function (note, part, index) { +var sig = part.signature; +if (sig.static) { +this.__addAnnotatedComputationEffect('__static__', index, note, part, null); +} else { +for (var i = 0, arg; i < sig.args.length && (arg = sig.args[i]); i++) { +if (!arg.literal) { +this.__addAnnotatedComputationEffect(arg.model, index, note, part, arg); +} +} +} +}, +__addAnnotatedComputationEffect: function (property, index, note, part, trigger) { +this._addPropertyEffect(property, 'annotatedComputation', { +index: index, +isCompound: note.isCompound, +compoundIndex: part.compoundIndex, +kind: note.kind, +name: note.name, +negate: part.negate, +method: part.signature.method, +args: part.signature.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, ',').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(/,/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); +if (this._bindListeners) { +Polymer.Bind.setupBindListeners(this); +} +}, +_applyEffectValue: function (info, value) { +var node = this._nodes[info.index]; +var property = info.name; +if (info.isCompound) { +var storage = node.__compoundStorage__[property]; +storage[info.compoundIndex] = value; +value = storage.join(''); +} +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; +} +var pinfo; +if (!node._propertyInfo || !(pinfo = node._propertyInfo[property]) || !pinfo.readOnly) { +this.__setProperty(property, value, true, node); +} +} +}, +_executeStaticEffects: function () { +if (this._propertyEffects && this._propertyEffects.__static__) { +this._effectEffects('__static__', null, this._propertyEffects.__static__); +} +} +}); +Polymer.Base._addFeature({ +_setupConfigure: function (initialConfig) { +this._config = {}; +this._handlers = []; +if (initialConfig) { +for (var i in initialConfig) { +if (initialConfig[i] !== undefined) { +this._config[i] = initialConfig[i]; +} +} +} +}, +_marshalAttributes: function () { +this._takeAttributesToModel(this._config); +}, +_attributeChangedImpl: function (name) { +var model = this._clientsReadied ? this : this._config; +this._setAttributeToProperty(model, name); +}, +_configValue: function (name, value) { +var info = this._propertyInfo[name]; +if (!info || !info.readOnly) { +this._config[name] = value; +} +}, +_beforeClientsReady: function () { +this._configure(); +}, +_configure: function () { +this._configureAnnotationReferences(); +this._aboveConfig = this.mixin({}, this._config); +var config = {}; +for (var i = 0; i < this.behaviors.length; i++) { +this._configureProperties(this.behaviors[i].properties, config); +} +this._configureProperties(this.properties, config); +this.mixin(config, this._aboveConfig); +this._config = config; +if (this._clients && this._clients.length) { +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; +} +} +}, +_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' && !x.isCompound) { +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 (!Polymer.Bind._isEventBogus(e, e.target)) { +var value, path; +if (e.detail) { +value = e.detail.value; +path = e.detail.path; +} +if (!this._clientsReadied) { +this._queueHandler([ +fn, +e.target, +value, +path +]); +} else { +return fn.call(this, e.target, value, path); +} +} +}, +_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], h[3]); +} +this._handlers = []; +} +}); +(function () { +'use strict'; +Polymer.Base._addFeature({ +notifyPath: function (path, value, fromAbove) { +var info = {}; +this._get(path, this, info); +this._notifyPath(info.path, value, fromAbove); +}, +_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._notifyPathUp(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]; +if (array && part[0] == '#') { +prop = Polymer.Collection.get(array).getItem(part); +} else { +prop = prop[part]; +if (array && parseInt(part, 10) == part) { +parts[i] = Polymer.Collection.get(array).getKey(prop); +} +} +if (!prop) { +return; +} +array = Array.isArray(prop) ? prop : null; +} +if (array) { +var coll = Polymer.Collection.get(array); +if (last[0] == '#') { +var key = last; +var old = coll.getItem(key); +last = array.indexOf(old); +coll.setItem(key, value); +} else if (parseInt(last, 10) == last) { +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) { +return this._get(path, root); +}, +_get: function (path, root, info) { +var prop = root || this; +var parts = this._getPathParts(path); +var array; +for (var i = 0; i < parts.length; i++) { +if (!prop) { +return; +} +var part = parts[i]; +if (array && part[0] == '#') { +prop = Polymer.Collection.get(array).getItem(part); +} else { +prop = prop[part]; +if (info && array && parseInt(part, 10) == part) { +parts[i] = Polymer.Collection.get(array).getKey(prop); +} +} +array = Array.isArray(prop) ? prop : null; +} +if (info) { +info.path = parts.join('.'); +} +return prop; +}, +_pathEffector: function (path, value) { +var model = this._modelForPath(path); +var fx$ = this._propertyEffects && this._propertyEffects[model]; +if (fx$) { +for (var i = 0, fx; i < fx$.length && (fx = fx$[i]); i++) { +var fxFn = fx.pathFn; +if (fxFn) { +fxFn.call(this, path, value, fx.effect); +} +} +} +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); +}, +_notifyPathUp: 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, +_useCache: true +}); +}, +_modelForPath: function (path) { +var dot = path.indexOf('.'); +return dot < 0 ? path : path.slice(0, dot); +}, +_EVENT_CHANGED: '-changed', +notifySplices: function (path, splices) { +var info = {}; +var array = this._get(path, this, info); +this._notifySplices(array, info.path, splices); +}, +_notifySplices: function (array, path, splices) { +var change = { +keySplices: Polymer.Collection.applySplices(array, splices), +indexSplices: splices +}; +if (!array.hasOwnProperty('splices')) { +Object.defineProperty(array, 'splices', { +configurable: true, +writable: true +}); +} +array.splices = change; +this._notifyPath(path + '.splices', change); +this._notifyPath(path + '.length', array.length); +change.keySplices = null; +change.indexSplices = null; +}, +_notifySplice: function (array, path, index, added, removed) { +this._notifySplices(array, path, [{ +index: index, +addedCount: added, +removed: removed, +object: array, +type: 'splice' +}]); +}, +push: function (path) { +var info = {}; +var array = this._get(path, this, info); +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, info.path, len, args.length, []); +} +return ret; +}, +pop: function (path) { +var info = {}; +var array = this._get(path, this, info); +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, info.path, array.length, 0, [ret]); +} +return ret; +}, +splice: function (path, start, deleteCount) { +var info = {}; +var array = this._get(path, this, info); +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, info.path, start, addedCount, ret); +} +return ret; +}, +shift: function (path) { +var info = {}; +var array = this._get(path, this, info); +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, info.path, 0, 0, [ret]); +} +return ret; +}, +unshift: function (path) { +var info = {}; +var array = this._get(path, this, info); +var args = Array.prototype.slice.call(arguments, 1); +var ret = array.unshift.apply(array, args); +if (args.length) { +this._notifySplice(array, info.path, 0, args.length, []); +} +return ret; +}, +prepareModelNotifyPath: function (model) { +this.mixin(model, { +fire: Polymer.Base.fire, +_getEvent: Polymer.Base._getEvent, +__eventCache: Polymer.Base.__eventCache, +notifyPath: Polymer.Base.notifyPath, +_get: Polymer.Base._get, +_EVENT_CHANGED: Polymer.Base._EVENT_CHANGED, +_notifyPath: Polymer.Base._notifyPath, +_notifyPathUp: Polymer.Base._notifyPathUp, +_pathEffector: Polymer.Base._pathEffector, +_annotationPathEffect: Polymer.Base._annotationPathEffect, +_complexObserverPathEffect: Polymer.Base._complexObserverPathEffect, +_annotatedComputationPathEffect: Polymer.Base._annotatedComputationPathEffect, +_computePathEffect: Polymer.Base._computePathEffect, +_modelForPath: Polymer.Base._modelForPath, +_pathMatchesEffect: Polymer.Base._pathMatchesEffect, +_notifyBoundPaths: Polymer.Base._notifyBoundPaths, +_getPathParts: Polymer.Base._getPathParts +}); +} +}); +}()); +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 = this._expandUnicodeEscapes(t); +t = t.replace(this._rx.multipleSpaces, ' '); +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; +}, +_expandUnicodeEscapes: function (s) { +return s.replace(/\\([0-9a-f]{1,6})\s/gi, function () { +var code = arguments[1], repeat = 6 - code.length; +while (repeat--) { +code = '0' + code; +} +return '\\' + code; +}); +}, +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/, +multipleSpaces: /\s+/g +}, +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$ = Polymer.DomApi.arrayCopy(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.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); +} +if (this._template) { +this._styles = this._collectStyles(); +var cssText = styleTransformer.elementStyles(this); +if (cssText) { +var style = styleUtil.applyCss(cssText, this.is, nativeShadow ? this._template.content : null); +if (!nativeShadow) { +this._scopeStyle = style; +} +} +} else { +this._styles = []; +} +}, +_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); +var p = this._template && this._template.parentNode; +if (this._template && (!p || p.id.toLowerCase() !== this.is)) { +cssText += styleUtil.cssFromElement(this._template); +} +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('*'); +for (var i = 0, n; i < n$.length && (n = n$[i]); i++) { +n.className = self._scopeElementClass(n, n.className); +} +} +}; +scopify(container); +if (shouldObserve) { +var mo = new MutationObserver(function (mxns) { +for (var i = 0, m; i < mxns.length && (m = mxns[i]); i++) { +if (m.addedNodes) { +for (var j = 0; j < m.addedNodes.length; j++) { +scopify(m.addedNodes[j]); +} +} +} +}); +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.filter(function (v) { +return v; +}).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: /(?:^|[;\s{]\s*)(--[\w-]*?)\s*:\s*(?:([^;{]*)|{([^}]*)})(?:(?=[;\s}])|$)/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) : null; +}, +customStyle: null, +getComputedStyleValue: function (property) { +return this._styleProperties && this._styleProperties[property] || getComputedStyle(this).getPropertyValue(property); +}, +_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 = this.shadyRoot && this.shadyRoot._hasDistributed ? Polymer.dom(node) : 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._prepConstructor(); +this._prepTemplate(); +this._prepStyles(); +this._prepStyleProperties(); +this._prepAnnotations(); +this._prepEffects(); +this._prepBehaviors(); +this._prepPropertyInfo(); +this._prepBindings(); +this._prepShady(); +}, +_prepBehavior: function (b) { +this._addPropertyEffects(b.properties); +this._addComplexObserverEffects(b.observers); +this._addHostAttributes(b.hostAttributes); +}, +_initFeatures: function () { +this._setupConfigure(); +this._setupStyleProperties(); +this._setupDebouncers(); +this._registerHost(); +if (this._template) { +this._poolContent(); +this._beginHosting(); +this._stampTemplate(); +this._endHosting(); +this._marshalAnnotationReferences(); +} +this._marshalInstanceEffects(); +this._marshalBehaviors(); +this._marshalHostAttributes(); +this._marshalAttributes(); +this._tryReady(); +}, +_marshalBehavior: function (b) { +if (b.listeners) { +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', +_template: null, +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(true); +} else { +var self = this; +var observer = new MutationObserver(function () { +observer.disconnect(); +self._apply(true); +}); +observer.observe(e, { childList: true }); +} +} +} +}, +_apply: function (deferProperties) { +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); +}); +var self = this; +function fn() { +self._applyCustomProperties(e); +} +if (this._pendingApplyProperties) { +cancelAnimationFrame(this._pendingApplyProperties); +this._pendingApplyProperties = null; +} +if (deferProperties) { +this._pendingApplyProperties = requestAnimationFrame(fn); +} else { +fn(); +} +} +}, +_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) { +this._templatized = 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); +this._prepParentProperties(archetype, template); +archetype._prepEffects(); +this._customPrepEffects(archetype); +archetype._prepBehaviors(); +archetype._prepPropertyInfo(); +archetype._prepBindings(); +archetype._notifyPathUp = this._notifyPathUpImpl; +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 = function () { +rootDataHost._prepElement(); +}; +} +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); +Polymer.Base.prepareModelNotifyPath(proto); +} +for (prop in parentProps) { +var parentProp = this._parentPropPrefix + prop; +var effects = [ +{ +kind: 'function', +effect: this._createForwardPropEffector(prop), +fn: Polymer.Bind._functionEffect +}, +{ +kind: 'notify', +fn: Polymer.Bind._notifyEffect, +effect: { event: Polymer.CaseMap.camelToDashCase(parentProp) + '-changed' } +} +]; +Polymer.Bind._createAccessors(proto, parentProp, effects); +} +} +var self = this; +if (template != this) { +Polymer.Bind.prepareInstance(template); +template._forwardParentProp = function (source, value) { +self._forwardParentProp(source, value); +}; +} +this._extendTemplate(template, proto); +template._pathEffector = function (path, value, fromAbove) { +return self._pathEffectorImpl(path, value, fromAbove); +}; +} +}, +_createForwardPropEffector: function (prop) { +return function (source, value) { +this._forwardParentProp(prop, value); +}; +}, +_createHostPropEffector: function (prop) { +var prefix = this._parentPropPrefix; +return function (source, value) { +this.dataHost._templatized[prefix + prop] = value; +}; +}, +_createInstancePropEffector: function (prop) { +return function (source, value, old, fromAbove) { +if (!fromAbove) { +this.dataHost._forwardInstanceProp(this, prop, value); +} +}; +}, +_extendTemplate: function (template, proto) { +var n$ = Object.getOwnPropertyNames(proto); +for (var i = 0, n; i < n$.length && (n = n$[i]); i++) { +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) { +}, +_notifyPathUpImpl: 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._templatized.notifyPath(dataHost._parentPropPrefix + path, value); +} +}, +_pathEffectorImpl: function (path, value, fromAbove) { +if (this._forwardParentPath) { +if (path.indexOf(this._parentPropPrefix) === 0) { +var subPath = path.substring(this._parentPropPrefix.length); +var model = this._modelForPath(subPath); +if (model in this._parentProps) { +this._forwardParentPath(subPath, value); +} +} +} +Polymer.Base._pathEffector.call(this._templatized, path, value, fromAbove); +}, +_constructorImpl: function (model, host) { +this._rootDataHost = host._getRootDataHost(); +this._setupConfigure(model); +this._registerHost(host); +this._beginHosting(); +this.root = this.instanceTemplate(this._template); +this.root.__noContent = !this._notes._hasContent; +this.root.__styleScoped = true; +this._endHosting(); +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) { +var templatized = this._templatized; +for (var prop in this._parentProps) { +model[prop] = templatized[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', +_template: null, +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) { +key = this._parseKey(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) { +var key; +if (item && typeof item == 'object') { +key = this.omap.get(item); +} else { +key = this.pmap[item]; +} +if (key != undefined) { +return '#' + key; +} +}, +getKeys: function () { +return Object.keys(this.store).map(function (key) { +return '#' + key; +}); +}, +_parseKey: function (key) { +if (key[0] == '#') { +return key.slice(1); +} +throw new Error('unexpected key ' + key); +}, +setItem: function (key, item) { +key = this._parseKey(key); +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) { +key = this._parseKey(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; +for (var i = 0, s; i < splices.length && (s = splices[i]); i++) { +s.addedKeys = []; +for (var j = 0; j < s.removed.length; j++) { +key = this.getKey(s.removed[j]); +keyMap[key] = keyMap[key] ? null : -1; +} +for (var j = 0; j < s.addedCount; j++) { +var item = this.userArray[s.index + j]; +key = this.getKey(item); +key = key === undefined ? this.add(item) : key; +keyMap[key] = keyMap[key] ? null : 1; +s.addedKeys.push(key); +} +} +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', +_template: null, +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, +initialCount: { +type: Number, +observer: '_initializeChunking' +}, +targetFramerate: { +type: Number, +value: 20 +}, +_targetFrameTime: { computed: '_computeFrameTime(targetFramerate)' } +}, +behaviors: [Polymer.Templatizer], +observers: ['_itemsChanged(items.*)'], +created: function () { +this._instances = []; +this._pool = []; +this._limit = Infinity; +var self = this; +this._boundRenderChunk = function () { +self._renderChunk(); +}; +}, +detached: function () { +for (var i = 0; i < this._instances.length; i++) { +this._detachInstance(i); +} +}, +attached: function () { +var parent = Polymer.dom(Polymer.dom(this).parentNode); +for (var i = 0; i < this._instances.length; i++) { +this._attachInstance(i, parent); +} +}, +ready: function () { +this._instanceProps = { __key__: true }; +this._instanceProps[this.as] = true; +this._instanceProps[this.indexAs] = true; +if (!this.ctor) { +this.templatize(this); +} +}, +_sortChanged: function (sort) { +var dataHost = this._getRootDataHost(); +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 (filter) { +var dataHost = this._getRootDataHost(); +this._filterFn = filter && (typeof filter == 'function' ? filter : function () { +return dataHost[filter].apply(dataHost, arguments); +}); +this._needFullRefresh = true; +if (this.items) { +this._debounceTemplate(this._render); +} +}, +_computeFrameTime: function (rate) { +return Math.ceil(1000 / rate); +}, +_initializeChunking: function () { +if (this.initialCount) { +this._limit = this.initialCount; +this._chunkCount = this.initialCount; +this._lastChunkTime = performance.now(); +} +}, +_tryRenderChunk: function () { +if (this.items && this._limit < this.items.length) { +this.debounce('renderChunk', this._requestRenderChunk); +} +}, +_requestRenderChunk: function () { +requestAnimationFrame(this._boundRenderChunk); +}, +_renderChunk: function () { +var currChunkTime = performance.now(); +var ratio = this._targetFrameTime / (currChunkTime - this._lastChunkTime); +this._chunkCount = Math.round(this._chunkCount * ratio) || 1; +this._limit += this._chunkCount; +this._lastChunkTime = currChunkTime; +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._initializeChunking(); +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._keySplices.length) { +if (this._sortFn) { +this._applySplicesUserSort(this._keySplices); +} else { +if (this._filterFn) { +this._applyFullRefresh(); +} else { +this._applySplicesArrayOrder(this._indexSplices); +} +} +} else { +} +this._keySplices = []; +this._indexSplices = []; +var keyToIdx = this._keyToInstIdx = {}; +for (var i = this._instances.length - 1; i >= 0; i--) { +var inst = this._instances[i]; +if (inst.isPlaceholder && i < this._limit) { +inst = this._insertInstance(i, inst.__key__); +} else if (!inst.isPlaceholder && i >= this._limit) { +inst = this._downgradeInstance(i, inst.__key__); +} +keyToIdx[inst.__key__] = i; +if (!inst.isPlaceholder) { +inst.__setProperty(this.indexAs, i, true); +} +} +this._pool.length = 0; +this.fire('dom-change'); +this._tryRenderChunk(); +}, +_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])); +} +} +} +var self = this; +if (this._filterFn) { +keys = keys.filter(function (a) { +return self._filterFn(c.getItem(a)); +}); +} +if (this._sortFn) { +keys.sort(function (a, b) { +return self._sortFn(c.getItem(a), c.getItem(b)); +}); +} +for (var i = 0; i < keys.length; i++) { +var key = keys[i]; +var inst = this._instances[i]; +if (inst) { +inst.__key__ = key; +if (!inst.isPlaceholder && i < this._limit) { +inst.__setProperty(this.as, c.getItem(key), true); +} +} else if (i < this._limit) { +this._insertInstance(i, key); +} else { +this._insertPlaceholder(i, key); +} +} +for (var j = this._instances.length - 1; j >= i; j--) { +this._detachAndRemoveInstance(j); +} +}, +_numericSort: function (a, b) { +return a - b; +}, +_applySplicesUserSort: function (splices) { +var c = this.collection; +var instances = this._instances; +var keyMap = {}; +for (var i = 0, s; i < splices.length && (s = splices[i]); i++) { +for (var j = 0; j < s.removed.length; j++) { +var key = s.removed[j]; +keyMap[key] = keyMap[key] ? null : -1; +} +for (var j = 0; j < s.added.length; j++) { +var key = s.added[j]; +keyMap[key] = keyMap[key] ? null : 1; +} +} +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) { +this._detachAndRemoveInstance(idx); +} +} +} +var self = this; +if (addedKeys.length) { +if (this._filterFn) { +addedKeys = addedKeys.filter(function (a) { +return self._filterFn(c.getItem(a)); +}); +} +addedKeys.sort(function (a, b) { +return self._sortFn(c.getItem(a), c.getItem(b)); +}); +var start = 0; +for (var i = 0; i < addedKeys.length; i++) { +start = this._insertRowUserSort(start, addedKeys[i]); +} +} +}, +_insertRowUserSort: function (start, key) { +var c = this.collection; +var item = c.getItem(key); +var end = this._instances.length - 1; +var idx = -1; +while (start <= end) { +var mid = start + end >> 1; +var midKey = this._instances[mid].__key__; +var cmp = this._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._insertPlaceholder(idx, key); +return idx; +}, +_applySplicesArrayOrder: function (splices) { +var c = this.collection; +for (var i = 0, s; i < splices.length && (s = splices[i]); i++) { +for (var j = 0; j < s.removed.length; j++) { +this._detachAndRemoveInstance(s.index); +} +for (var j = 0; j < s.addedKeys.length; j++) { +this._insertPlaceholder(s.index + j, s.addedKeys[j]); +} +} +}, +_detachInstance: function (idx) { +var inst = this._instances[idx]; +if (!inst.isPlaceholder) { +for (var i = 0; i < inst._children.length; i++) { +var el = inst._children[i]; +Polymer.dom(inst.root).appendChild(el); +} +return inst; +} +}, +_attachInstance: function (idx, parent) { +var inst = this._instances[idx]; +if (!inst.isPlaceholder) { +parent.insertBefore(inst.root, this); +} +}, +_detachAndRemoveInstance: function (idx) { +var inst = this._detachInstance(idx); +if (inst) { +this._pool.push(inst); +} +this._instances.splice(idx, 1); +}, +_insertPlaceholder: function (idx, key) { +this._instances.splice(idx, 0, { +isPlaceholder: true, +__key__: key +}); +}, +_stampInstance: function (idx, key) { +var model = { __key__: key }; +model[this.as] = this.collection.getItem(key); +model[this.indexAs] = idx; +return this.stamp(model); +}, +_insertInstance: function (idx, key) { +var inst = this._pool.pop(); +if (inst) { +inst.__setProperty(this.as, this.collection.getItem(key), true); +inst.__setProperty('__key__', key, true); +} else { +inst = this._stampInstance(idx, key); +} +var beforeRow = this._instances[idx + 1]; +var beforeNode = beforeRow && !beforeRow.isPlaceholder ? beforeRow._children[0] : this; +var parentNode = Polymer.dom(this).parentNode; +Polymer.dom(parentNode).insertBefore(inst.root, beforeNode); +this._instances[idx] = inst; +return inst; +}, +_downgradeInstance: function (idx, key) { +var inst = this._detachInstance(idx); +if (inst) { +this._pool.push(inst); +} +inst = { +isPlaceholder: true, +__key__: key +}; +this._instances[idx] = inst; +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) { +var i$ = this._instances; +for (var i = 0, inst; i < i$.length && (inst = i$[i]); i++) { +if (!inst.isPlaceholder) { +inst.__setProperty(prop, value, true); +} +} +}, +_forwardParentPath: function (path, value) { +var i$ = this._instances; +for (var i = 0, inst; i < i$.length && (inst = i$[i]); i++) { +if (!inst.isPlaceholder) { +inst._notifyPath(path, value, true); +} +} +}, +_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 && !inst.isPlaceholder) { +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', +_template: null, +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'); +this.unlinkPaths('selectedItem'); +} +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); +var 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', +_template: null, +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) { +var parentNode = Polymer.dom(this).parentNode; +if (parentNode) { +var parent = Polymer.dom(parentNode); +this._instance = this.stamp(); +var root = this._instance.root; +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); +for (var i = 0, n; i < c$.length && (n = c$[i]); i++) { +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', +_template: null, +created: function () { +var self = this; +Polymer.RenderStatus.whenReady(function () { +self._markImportsReady(); +}); +}, +_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]; +} +var setupConfigure = this._setupConfigure; +this._setupConfigure = function () { +setupConfigure.call(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(); +this._prepPropertyInfo(); +Polymer.Base._initFeatures.call(this); +this._children = Polymer.DomApi.arrayCopyChildNodes(this.root); +} +this._insertChildren(); +this.fire('dom-change'); +} +}); +/** + * `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; + } + }; +(function() { + + var IOS = navigator.userAgent.match(/iP(?:hone|ad;(?: U;)? CPU) OS (\d+)/); + var IOS_TOUCH_SCROLLING = IOS && IOS[1] >= 8; + var DEFAULT_PHYSICAL_COUNT = 3; + var MAX_PHYSICAL_COUNT = 500; + + Polymer({ + + is: 'iron-list', + + properties: { + + /** + * An array containing items determining how many instances of the template + * to stamp and that that each template instance should bind to. + */ + items: { + type: Array + }, + + /** + * The name of the variable to add to the binding scope for the array + * element associated with a given template instance. + */ + as: { + type: String, + value: 'item' + }, + + /** + * The name of the variable to add to the binding scope with the index + * for the row. + */ + indexAs: { + type: String, + value: 'index' + }, + + /** + * The name of the variable to add to the binding scope to indicate + * if the row is selected. + */ + selectedAs: { + type: String, + value: 'selected' + }, + + /** + * When true, tapping a row will select the item, placing its data model + * in the set of selected items retrievable via the selection property. + * + * Note that tapping focusable elements within the list item will not + * result in selection, since they are presumed to have their * own action. + */ + selectionEnabled: { + type: Boolean, + value: false + }, + + /** + * When `multiSelection` is false, this is the currently selected item, or `null` + * if no item is selected. + */ + selectedItem: { + type: Object, + notify: true + }, + + /** + * When `multiSelection` is true, this is an array that contains the selected items. + */ + selectedItems: { + type: Object, + notify: true + }, + + /** + * When `true`, multiple items may be selected at once (in this case, + * `selected` is an array of currently selected items). When `false`, + * only one item may be selected at a time. + */ + multiSelection: { + type: Boolean, + value: false + } + }, + + observers: [ + '_itemsChanged(items.*)', + '_selectionEnabledChanged(selectionEnabled)', + '_multiSelectionChanged(multiSelection)' + ], + + behaviors: [ + Polymer.Templatizer, + Polymer.IronResizableBehavior + ], + + listeners: { + 'iron-resize': '_resizeHandler' + }, + + /** + * The ratio of hidden tiles that should remain in the scroll direction. + * Recommended value ~0.5, so it will distribute tiles evely in both directions. + */ + _ratio: 0.5, + + /** + * The element that controls the scroll + * @type {?Element} + */ + _scroller: null, + + /** + * The padding-top value of the `scroller` element + */ + _scrollerPaddingTop: 0, + + /** + * This value is the same as `scrollTop`. + */ + _scrollPosition: 0, + + /** + * The number of tiles in the DOM. + */ + _physicalCount: 0, + + /** + * The k-th tile that is at the top of the scrolling list. + */ + _physicalStart: 0, + + /** + * The k-th tile that is at the bottom of the scrolling list. + */ + _physicalEnd: 0, + + /** + * The sum of the heights of all the tiles in the DOM. + */ + _physicalSize: 0, + + /** + * The average `offsetHeight` of the tiles observed till now. + */ + _physicalAverage: 0, + + /** + * The number of tiles which `offsetHeight` > 0 observed until now. + */ + _physicalAverageCount: 0, + + /** + * The Y position of the item rendered in the `_physicalStart` + * tile relative to the scrolling list. + */ + _physicalTop: 0, + + /** + * The number of items in the list. + */ + _virtualCount: 0, + + /** + * The n-th item rendered in the `_physicalStart` tile. + */ + _virtualStartVal: 0, + + /** + * A map between an item key and its physical item index + */ + _physicalIndexForKey: null, + + /** + * The estimated scroll height based on `_physicalAverage` + */ + _estScrollHeight: 0, + + /** + * The scroll height of the dom node + */ + _scrollHeight: 0, + + /** + * The height of the list. This is referred as the viewport in the context of list. + */ + _viewportSize: 0, + + /** + * An array of DOM nodes that are currently in the tree + * @type {?Array<!TemplatizerNode>} + */ + _physicalItems: null, + + /** + * An array of heights for each item in `_physicalItems` + * @type {?Array<number>} + */ + _physicalSizes: null, + + /** + * A cached value for the visible index. + * See `firstVisibleIndex` + * @type {?number} + */ + _firstVisibleIndexVal: null, + + /** + * A Polymer collection for the items. + * @type {?Polymer.Collection} + */ + _collection: null, + + /** + * True if the current item list was rendered for the first time + * after attached. + */ + _itemsRendered: false, + + /** + * The page that is currently rendered. + */ + _lastPage: null, + + /** + * The max number of pages to render. One page is equivalent to the height of the list. + */ + _maxPages: 3, + + /** + * The bottom of the physical content. + */ + get _physicalBottom() { + return this._physicalTop + this._physicalSize; + }, + + /** + * The bottom of the scroll. + */ + get _scrollBottom() { + return this._scrollPosition + this._viewportSize; + }, + + /** + * The n-th item rendered in the last physical item. + */ + get _virtualEnd() { + return this._virtualStartVal + this._physicalCount - 1; + }, + + /** + * The lowest n-th value for an item such that it can be rendered in `_physicalStart`. + */ + _minVirtualStart: 0, + + /** + * The largest n-th value for an item such that it can be rendered in `_physicalStart`. + */ + get _maxVirtualStart() { + return Math.max(0, this._virtualCount - this._physicalCount); + }, + + /** + * The height of the physical content that isn't on the screen. + */ + get _hiddenContentSize() { + return this._physicalSize - this._viewportSize; + }, + + /** + * The maximum scroll top value. + */ + get _maxScrollTop() { + return this._estScrollHeight - this._viewportSize; + }, + + /** + * Sets the n-th item rendered in `_physicalStart` + */ + set _virtualStart(val) { + // clamp the value so that _minVirtualStart <= val <= _maxVirtualStart + this._virtualStartVal = Math.min(this._maxVirtualStart, Math.max(this._minVirtualStart, val)); + this._physicalStart = this._virtualStartVal % this._physicalCount; + this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this._physicalCount; + }, + + /** + * Gets the n-th item rendered in `_physicalStart` + */ + get _virtualStart() { + return this._virtualStartVal; + }, + + /** + * An optimal physical size such that we will have enough physical items + * to fill up the viewport and recycle when the user scrolls. + * + * This default value assumes that we will at least have the equivalent + * to a viewport of physical items above and below the user's viewport. + */ + get _optPhysicalSize() { + return this._viewportSize * this._maxPages; + }, + + /** + * True if the current list is visible. + */ + get _isVisible() { + return this._scroller && Boolean(this._scroller.offsetWidth || this._scroller.offsetHeight); + }, + + /** + * Gets the index of the first visible item in the viewport. + * + * @type {number} + */ + get firstVisibleIndex() { + var physicalOffset; + + if (this._firstVisibleIndexVal === null) { + physicalOffset = this._physicalTop; + + this._firstVisibleIndexVal = this._iterateItems( + function(pidx, vidx) { + physicalOffset += this._physicalSizes[pidx]; + + if (physicalOffset > this._scrollPosition) { + return vidx; + } + }) || 0; + } + + return this._firstVisibleIndexVal; + }, + + ready: function() { + if (IOS_TOUCH_SCROLLING) { + this._scrollListener = function() { + requestAnimationFrame(this._scrollHandler.bind(this)); + }.bind(this); + } else { + this._scrollListener = this._scrollHandler.bind(this); + } + }, + + /** + * When the element has been attached to the DOM tree. + */ + attached: function() { + // delegate to the parent's scroller + // e.g. paper-scroll-header-panel + var el = Polymer.dom(this); + + var parentNode = /** @type {?{scroller: ?Element}} */ (el.parentNode); + if (parentNode && parentNode.scroller) { + this._scroller = parentNode.scroller; + } else { + this._scroller = this; + this.classList.add('has-scroller'); + } + + if (IOS_TOUCH_SCROLLING) { + this._scroller.style.webkitOverflowScrolling = 'touch'; + } + + this._scroller.addEventListener('scroll', this._scrollListener); + + this.updateViewportBoundaries(); + this._render(); + }, + + /** + * When the element has been removed from the DOM tree. + */ + detached: function() { + this._itemsRendered = false; + if (this._scroller) { + this._scroller.removeEventListener('scroll', this._scrollListener); + } + }, + + /** + * Invoke this method if you dynamically update the viewport's + * size or CSS padding. + * + * @method updateViewportBoundaries + */ + updateViewportBoundaries: function() { + var scrollerStyle = window.getComputedStyle(this._scroller); + this._scrollerPaddingTop = parseInt(scrollerStyle['padding-top'], 10); + this._viewportSize = this._scroller.offsetHeight; + }, + + /** + * Update the models, the position of the + * items in the viewport and recycle tiles as needed. + */ + _refresh: function() { + // clamp the `scrollTop` value + // IE 10|11 scrollTop may go above `_maxScrollTop` + // iOS `scrollTop` may go below 0 and above `_maxScrollTop` + var scrollTop = Math.max(0, Math.min(this._maxScrollTop, this._scroller.scrollTop)); + var tileHeight, tileTop, kth, recycledTileSet, scrollBottom, physicalBottom; + var ratio = this._ratio; + var delta = scrollTop - this._scrollPosition; + var recycledTiles = 0; + var hiddenContentSize = this._hiddenContentSize; + var currentRatio = ratio; + var movingUp = []; + + // track the last `scrollTop` + this._scrollPosition = scrollTop; + + // clear cached visible index + this._firstVisibleIndexVal = null; + + scrollBottom = this._scrollBottom; + physicalBottom = this._physicalBottom; + + // random access + if (Math.abs(delta) > this._physicalSize) { + this._physicalTop += delta; + recycledTiles = Math.round(delta / this._physicalAverage); + } + // scroll up + else if (delta < 0) { + var topSpace = scrollTop - this._physicalTop; + var virtualStart = this._virtualStart; + + recycledTileSet = []; + + kth = this._physicalEnd; + currentRatio = topSpace / hiddenContentSize; + + // move tiles from bottom to top + while ( + // approximate `currentRatio` to `ratio` + currentRatio < ratio && + // recycle less physical items than the total + recycledTiles < this._physicalCount && + // ensure that these recycled tiles are needed + virtualStart - recycledTiles > 0 && + // ensure that the tile is not visible + physicalBottom - this._physicalSizes[kth] > scrollBottom + ) { + + tileHeight = this._physicalSizes[kth]; + currentRatio += tileHeight / hiddenContentSize; + physicalBottom -= tileHeight; + recycledTileSet.push(kth); + recycledTiles++; + kth = (kth === 0) ? this._physicalCount - 1 : kth - 1; + } + + movingUp = recycledTileSet; + recycledTiles = -recycledTiles; + } + // scroll down + else if (delta > 0) { + var bottomSpace = physicalBottom - scrollBottom; + var virtualEnd = this._virtualEnd; + var lastVirtualItemIndex = this._virtualCount-1; + + recycledTileSet = []; + + kth = this._physicalStart; + currentRatio = bottomSpace / hiddenContentSize; + + // move tiles from top to bottom + while ( + // approximate `currentRatio` to `ratio` + currentRatio < ratio && + // recycle less physical items than the total + recycledTiles < this._physicalCount && + // ensure that these recycled tiles are needed + virtualEnd + recycledTiles < lastVirtualItemIndex && + // ensure that the tile is not visible + this._physicalTop + this._physicalSizes[kth] < scrollTop + ) { + + tileHeight = this._physicalSizes[kth]; + currentRatio += tileHeight / hiddenContentSize; + + this._physicalTop += tileHeight; + recycledTileSet.push(kth); + recycledTiles++; + kth = (kth + 1) % this._physicalCount; + } + } + + if (recycledTiles === 0) { + // If the list ever reach this case, the physical average is not significant enough + // to create all the items needed to cover the entire viewport. + // e.g. A few items have a height that differs from the average by serveral order of magnitude. + if (physicalBottom < scrollBottom || this._physicalTop > scrollTop) { + this.async(this._increasePool.bind(this, 1)); + } + } else { + this._virtualStart = this._virtualStart + recycledTiles; + this._update(recycledTileSet, movingUp); + } + }, + + /** + * Update the list of items, starting from the `_virtualStartVal` item. + * @param {!Array<number>=} itemSet + * @param {!Array<number>=} movingUp + */ + _update: function(itemSet, movingUp) { + // update models + this._assignModels(itemSet); + + // measure heights + this._updateMetrics(itemSet); + + // adjust offset after measuring + if (movingUp) { + while (movingUp.length) { + this._physicalTop -= this._physicalSizes[movingUp.pop()]; + } + } + // update the position of the items + this._positionItems(); + + // set the scroller size + this._updateScrollerSize(); + + // increase the pool of physical items + this._increasePoolIfNeeded(); + }, + + /** + * Creates a pool of DOM elements and attaches them to the local dom. + */ + _createPool: function(size) { + var physicalItems = new Array(size); + + this._ensureTemplatized(); + + for (var i = 0; i < size; i++) { + var inst = this.stamp(null); + // First element child is item; Safari doesn't support children[0] + // on a doc fragment + physicalItems[i] = inst.root.querySelector('*'); + Polymer.dom(this).appendChild(inst.root); + } + + return physicalItems; + }, + + /** + * Increases the pool of physical items only if needed. + * This function will allocate additional physical items + * if the physical size is shorter than `_optPhysicalSize` + */ + _increasePoolIfNeeded: function() { + if (this._viewportSize !== 0 && this._physicalSize < this._optPhysicalSize) { + // 0 <= `currentPage` <= `_maxPages` + var currentPage = Math.floor(this._physicalSize / this._viewportSize); + + if (currentPage === 0) { + // fill the first page + this.async(this._increasePool.bind(this, Math.round(this._physicalCount * 0.5))); + } else if (this._lastPage !== currentPage) { + // once a page is filled up, paint it and defer the next increase + requestAnimationFrame(this._increasePool.bind(this, 1)); + } else { + // fill the rest of the pages + this.async(this._increasePool.bind(this, 1)); + } + this._lastPage = currentPage; + return true; + } + return false; + }, + + /** + * Increases the pool size. + */ + _increasePool: function(missingItems) { + // limit the size + var nextPhysicalCount = Math.min( + this._physicalCount + missingItems, + this._virtualCount, + MAX_PHYSICAL_COUNT + ); + var prevPhysicalCount = this._physicalCount; + var delta = nextPhysicalCount - prevPhysicalCount; + + if (delta > 0) { + [].push.apply(this._physicalItems, this._createPool(delta)); + [].push.apply(this._physicalSizes, new Array(delta)); + + this._physicalCount = prevPhysicalCount + delta; + // tail call + return this._update(); + } + }, + + /** + * Render a new list of items. This method does exactly the same as `update`, + * but it also ensures that only one `update` cycle is created. + */ + _render: function() { + var requiresUpdate = this._virtualCount > 0 || this._physicalCount > 0; + + if (this.isAttached && !this._itemsRendered && this._isVisible && requiresUpdate) { + this._lastPage = 0; + this._update(); + this._itemsRendered = true; + } + }, + + /** + * Templetizes the user template. + */ + _ensureTemplatized: function() { + if (!this.ctor) { + // Template instance props that should be excluded from forwarding + var props = {}; + + props.__key__ = true; + props[this.as] = true; + props[this.indexAs] = true; + props[this.selectedAs] = true; + + this._instanceProps = props; + this._userTemplate = Polymer.dom(this).querySelector('template'); + + if (this._userTemplate) { + this.templatize(this._userTemplate); + } else { + console.warn('iron-list requires a template to be provided in light-dom'); + } + } + }, + + /** + * Implements extension point from Templatizer mixin. + */ + _getStampedChildren: function() { + return this._physicalItems; + }, + + /** + * Implements extension point from Templatizer + * Called as a side effect of a template instance path change, responsible + * for notifying items.<key-for-instance>.<path> change up to host. + */ + _forwardInstancePath: function(inst, path, value) { + if (path.indexOf(this.as + '.') === 0) { + this.notifyPath('items.' + inst.__key__ + '.' + + path.slice(this.as.length + 1), value); + } + }, + + /** + * Implements extension point from Templatizer mixin + * Called as side-effect of a host property change, responsible for + * notifying parent path change on each row. + */ + _forwardParentProp: function(prop, value) { + if (this._physicalItems) { + this._physicalItems.forEach(function(item) { + item._templateInstance[prop] = value; + }, this); + } + }, + + /** + * Implements extension point from Templatizer + * Called as side-effect of a host path change, responsible for + * notifying parent.<path> path change on each row. + */ + _forwardParentPath: function(path, value) { + if (this._physicalItems) { + this._physicalItems.forEach(function(item) { + item._templateInstance.notifyPath(path, value, true); + }, this); + } + }, + + /** + * Called as a side effect of a host items.<key>.<path> path change, + * responsible for notifying item.<path> changes to row for key. + */ + _forwardItemPath: function(path, value) { + if (this._physicalIndexForKey) { + var dot = path.indexOf('.'); + var key = path.substring(0, dot < 0 ? path.length : dot); + var idx = this._physicalIndexForKey[key]; + var row = this._physicalItems[idx]; + if (row) { + var inst = row._templateInstance; + if (dot >= 0) { + path = this.as + '.' + path.substring(dot+1); + inst.notifyPath(path, value, true); + } else { + inst[this.as] = value; + } + } + } + }, + + /** + * Called when the items have changed. That is, ressignments + * to `items`, splices or updates to a single item. + */ + _itemsChanged: function(change) { + if (change.path === 'items') { + // render the new set + this._itemsRendered = false; + + // update the whole set + this._virtualStartVal = 0; + this._physicalTop = 0; + this._virtualCount = this.items ? this.items.length : 0; + this._collection = this.items ? Polymer.Collection.get(this.items) : null; + this._physicalIndexForKey = {}; + + // scroll to the top + this._resetScrollPosition(0); + + // create the initial physical items + if (!this._physicalItems) { + this._physicalCount = Math.max(1, Math.min(DEFAULT_PHYSICAL_COUNT, this._virtualCount)); + this._physicalItems = this._createPool(this._physicalCount); + this._physicalSizes = new Array(this._physicalCount); + } + + this.debounce('refresh', this._render); + + } else if (change.path === 'items.splices') { + // render the new set + this._itemsRendered = false; + + this._adjustVirtualIndex(change.value.indexSplices); + this._virtualCount = this.items ? this.items.length : 0; + + this.debounce('refresh', this._render); + + } else { + // update a single item + this._forwardItemPath(change.path.split('.').slice(1).join('.'), change.value); + } + }, + + /** + * @param {!Array<!PolymerSplice>} splices + */ + _adjustVirtualIndex: function(splices) { + var i, splice, idx; + + for (i = 0; i < splices.length; i++) { + splice = splices[i]; + + // deselect removed items + splice.removed.forEach(this.$.selector.deselect, this.$.selector); + + idx = splice.index; + // We only need to care about changes happening above the current position + if (idx >= this._virtualStartVal) { + break; + } + + this._virtualStart = this._virtualStart + + Math.max(splice.addedCount - splice.removed.length, idx - this._virtualStartVal); + } + }, + + _scrollHandler: function() { + this._refresh(); + }, + + /** + * Executes a provided function per every physical index in `itemSet` + * `itemSet` default value is equivalent to the entire set of physical indexes. + * + * @param {!function(number, number)} fn + * @param {!Array<number>=} itemSet + */ + _iterateItems: function(fn, itemSet) { + var pidx, vidx, rtn, i; + + if (arguments.length === 2 && itemSet) { + for (i = 0; i < itemSet.length; i++) { + pidx = itemSet[i]; + if (pidx >= this._physicalStart) { + vidx = this._virtualStartVal + (pidx - this._physicalStart); + } else { + vidx = this._virtualStartVal + (this._physicalCount - this._physicalStart) + pidx; + } + if ((rtn = fn.call(this, pidx, vidx)) != null) { + return rtn; + } + } + } else { + pidx = this._physicalStart; + vidx = this._virtualStartVal; + + for (; pidx < this._physicalCount; pidx++, vidx++) { + if ((rtn = fn.call(this, pidx, vidx)) != null) { + return rtn; + } + } + + pidx = 0; + + for (; pidx < this._physicalStart; pidx++, vidx++) { + if ((rtn = fn.call(this, pidx, vidx)) != null) { + return rtn; + } + } + } + }, + + /** + * Assigns the data models to a given set of items. + * @param {!Array<number>=} itemSet + */ + _assignModels: function(itemSet) { + this._iterateItems(function(pidx, vidx) { + var el = this._physicalItems[pidx]; + var inst = el._templateInstance; + var item = this.items && this.items[vidx]; + + if (item) { + inst[this.as] = item; + inst.__key__ = this._collection.getKey(item); + inst[this.selectedAs] = + /** @type {!ArraySelectorElement} */ (this.$.selector).isSelected(item); + inst[this.indexAs] = vidx; + el.removeAttribute('hidden'); + this._physicalIndexForKey[inst.__key__] = pidx; + } else { + inst.__key__ = null; + el.setAttribute('hidden', ''); + } + + }, itemSet); + }, + + /** + * Updates the height for a given set of items. + * + * @param {!Array<number>=} itemSet + */ + _updateMetrics: function(itemSet) { + var newPhysicalSize = 0; + var oldPhysicalSize = 0; + var prevAvgCount = this._physicalAverageCount; + var prevPhysicalAvg = this._physicalAverage; + // Make sure we distributed all the physical items + // so we can measure them + Polymer.dom.flush(); + + this._iterateItems(function(pidx, vidx) { + oldPhysicalSize += this._physicalSizes[pidx] || 0; + this._physicalSizes[pidx] = this._physicalItems[pidx].offsetHeight; + newPhysicalSize += this._physicalSizes[pidx]; + this._physicalAverageCount += this._physicalSizes[pidx] ? 1 : 0; + }, itemSet); + + this._physicalSize = this._physicalSize + newPhysicalSize - oldPhysicalSize; + this._viewportSize = this._scroller.offsetHeight; + + // update the average if we measured something + if (this._physicalAverageCount !== prevAvgCount) { + this._physicalAverage = Math.round( + ((prevPhysicalAvg * prevAvgCount) + newPhysicalSize) / + this._physicalAverageCount); + } + }, + + /** + * Updates the position of the physical items. + */ + _positionItems: function() { + this._adjustScrollPosition(); + + var y = this._physicalTop; + + this._iterateItems(function(pidx) { + + this.transform('translate3d(0, ' + y + 'px, 0)', this._physicalItems[pidx]); + y += this._physicalSizes[pidx]; + + }); + }, + + /** + * Adjusts the scroll position when it was overestimated. + */ + _adjustScrollPosition: function() { + var deltaHeight = this._virtualStartVal === 0 ? this._physicalTop : + Math.min(this._scrollPosition + this._physicalTop, 0); + + if (deltaHeight) { + this._physicalTop = this._physicalTop - deltaHeight; + + // juking scroll position during interial scrolling on iOS is no bueno + if (!IOS_TOUCH_SCROLLING) { + this._resetScrollPosition(this._scroller.scrollTop - deltaHeight); + } + } + }, + + /** + * Sets the position of the scroll. + */ + _resetScrollPosition: function(pos) { + if (this._scroller) { + this._scroller.scrollTop = pos; + this._scrollPosition = this._scroller.scrollTop; + } + }, + + /** + * Sets the scroll height, that's the height of the content, + * + * @param {boolean=} forceUpdate If true, updates the height no matter what. + */ + _updateScrollerSize: function(forceUpdate) { + this._estScrollHeight = (this._physicalBottom + + Math.max(this._virtualCount - this._physicalCount - this._virtualStartVal, 0) * this._physicalAverage); + + forceUpdate = forceUpdate || this._scrollHeight === 0; + forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight - this._physicalSize; + + // amortize height adjustment, so it won't trigger repaints very often + if (forceUpdate || Math.abs(this._estScrollHeight - this._scrollHeight) >= this._optPhysicalSize) { + this.$.items.style.height = this._estScrollHeight + 'px'; + this._scrollHeight = this._estScrollHeight; + } + }, + + /** + * Scroll to a specific item in the virtual list regardless + * of the physical items in the DOM tree. + * + * @method scrollToIndex + * @param {number} idx The index of the item + */ + scrollToIndex: function(idx) { + if (typeof idx !== 'number') { + return; + } + + var firstVisible = this.firstVisibleIndex; + + idx = Math.min(Math.max(idx, 0), this._virtualCount-1); + + // start at the previous virtual item + // so we have a item above the first visible item + this._virtualStart = idx - 1; + + // assign new models + this._assignModels(); + + // measure the new sizes + this._updateMetrics(); + + // estimate new physical offset + this._physicalTop = this._virtualStart * this._physicalAverage; + + var currentTopItem = this._physicalStart; + var currentVirtualItem = this._virtualStart; + var targetOffsetTop = 0; + var hiddenContentSize = this._hiddenContentSize; + + // scroll to the item as much as we can + while (currentVirtualItem < idx && targetOffsetTop < hiddenContentSize) { + targetOffsetTop = targetOffsetTop + this._physicalSizes[currentTopItem]; + currentTopItem = (currentTopItem + 1) % this._physicalCount; + currentVirtualItem++; + } + + // update the scroller size + this._updateScrollerSize(true); + + // update the position of the items + this._positionItems(); + + // set the new scroll position + this._resetScrollPosition(this._physicalTop + targetOffsetTop + 1); + + // increase the pool of physical items if needed + this._increasePoolIfNeeded(); + + // clear cached visible index + this._firstVisibleIndexVal = null; + }, + + /** + * Reset the physical average and the average count. + */ + _resetAverage: function() { + this._physicalAverage = 0; + this._physicalAverageCount = 0; + }, + + /** + * A handler for the `iron-resize` event triggered by `IronResizableBehavior` + * when the element is resized. + */ + _resizeHandler: function() { + this.debounce('resize', function() { + this._render(); + if (this._itemsRendered && this._physicalItems && this._isVisible) { + this._resetAverage(); + this.updateViewportBoundaries(); + this.scrollToIndex(this.firstVisibleIndex); + } + }); + }, + + _getModelFromItem: function(item) { + var key = this._collection.getKey(item); + var pidx = this._physicalIndexForKey[key]; + + if (pidx !== undefined) { + return this._physicalItems[pidx]._templateInstance; + } + return null; + }, + + /** + * Gets a valid item instance from its index or the object value. + * + * @param {(Object|number)} item The item object or its index + */ + _getNormalizedItem: function(item) { + if (typeof item === 'number') { + item = this.items[item]; + if (!item) { + throw new RangeError('<item> not found'); + } + } else if (this._collection.getKey(item) === undefined) { + throw new TypeError('<item> should be a valid item'); + } + return item; + }, + + /** + * Select the list item at the given index. + * + * @method selectItem + * @param {(Object|number)} item The item object or its index + */ + selectItem: function(item) { + item = this._getNormalizedItem(item); + var model = this._getModelFromItem(item); + + if (!this.multiSelection && this.selectedItem) { + this.deselectItem(this.selectedItem); + } + if (model) { + model[this.selectedAs] = true; + } + this.$.selector.select(item); + }, + + /** + * Deselects the given item list if it is already selected. + * + + * @method deselect + * @param {(Object|number)} item The item object or its index + */ + deselectItem: function(item) { + item = this._getNormalizedItem(item); + var model = this._getModelFromItem(item); + + if (model) { + model[this.selectedAs] = false; + } + this.$.selector.deselect(item); + }, + + /** + * Select or deselect a given item depending on whether the item + * has already been selected. + * + * @method toggleSelectionForItem + * @param {(Object|number)} item The item object or its index + */ + toggleSelectionForItem: function(item) { + item = this._getNormalizedItem(item); + if (/** @type {!ArraySelectorElement} */ (this.$.selector).isSelected(item)) { + this.deselectItem(item); + } else { + this.selectItem(item); + } + }, + + /** + * Clears the current selection state of the list. + * + * @method clearSelection + */ + clearSelection: function() { + function unselect(item) { + var model = this._getModelFromItem(item); + if (model) { + model[this.selectedAs] = false; + } + } + + if (Array.isArray(this.selectedItems)) { + this.selectedItems.forEach(unselect, this); + } else if (this.selectedItem) { + unselect.call(this, this.selectedItem); + } + + /** @type {!ArraySelectorElement} */ (this.$.selector).clearSelection(); + }, + + /** + * Add an event listener to `tap` if `selectionEnabled` is true, + * it will remove the listener otherwise. + */ + _selectionEnabledChanged: function(selectionEnabled) { + if (selectionEnabled) { + this.listen(this, 'tap', '_selectionHandler'); + this.listen(this, 'keypress', '_selectionHandler'); + } else { + this.unlisten(this, 'tap', '_selectionHandler'); + this.unlisten(this, 'keypress', '_selectionHandler'); + } + }, + + /** + * Select an item from an event object. + */ + _selectionHandler: function(e) { + if (e.type !== 'keypress' || e.keyCode === 13) { + var model = this.modelForElement(e.target); + if (model) { + this.toggleSelectionForItem(model[this.as]); + } + } + }, + + _multiSelectionChanged: function(multiSelection) { + this.clearSelection(); + this.$.selector.multi = multiSelection; + }, + + /** + * Updates the size of an item. + * + * @method updateSizeForItem + * @param {(Object|number)} item The item object or its index + */ + updateSizeForItem: function(item) { + item = this._getNormalizedItem(item); + var key = this._collection.getKey(item); + var pidx = this._physicalIndexForKey[key]; + + if (pidx !== undefined) { + this._updateMetrics([pidx]); + this._positionItems(); + } + } + }); + +})(); +(function() { + + // monostate data + var metaDatas = {}; + var metaArrays = {}; + var singleton = null; + + 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 + } + + }, + + hostAttributes: { + hidden: true + }, + + /** + * Only runs if someone invokes the factory/constructor directly + * e.g. `new Polymer.IronMeta()` + * + * @param {{type: (string|undefined), key: (string|undefined), value}=} config + */ + 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); + } + } + } + + }); + + Polymer.IronMeta.getIronMeta = function getIronMeta() { + if (singleton === null) { + singleton = new Polymer.IronMeta(); + } + return singleton; + }; + + /** + `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()`); + * + * @param {{type: (string|undefined), key: (string|undefined)}=} config + */ + 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="12" y="0" width="12" height="24" /> + * <circle cx="12" cy="12" r="12" /> + * </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 + * @implements {Polymer.Iconset} + */ + Polymer({ + is: 'iron-iconset-svg', + + properties: { + + /** + * The name of the iconset. + */ + name: { + type: String, + observer: '_nameChanged' + }, + + /** + * The size of an individual icon. Note that icons must be square. + */ + size: { + type: Number, + value: 24 + } + + }, + + attached: function() { + this.style.display = 'none'; + }, + + /** + * 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 content = sourceSvg.cloneNode(true), + svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'), + viewBox = content.getAttribute('viewBox') || '0 0 ' + size + ' ' + size; + svg.setAttribute('viewBox', viewBox); + 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(content).removeAttribute('id'); + return svg; + } + return null; + } + + }); +(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+0008': 'backspace', + 'U+0009': 'tab', + 'U+001B': 'esc', + 'U+0020': 'space', + '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 = { + 8: 'backspace', + 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' + }; + + /** + * 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 === ' ' || SPACE_KEY.test(lKey)) { + validKey = 'space'; + } else if (lKey.length == 1) { + validKey = lKey; + } else if (ARROW_KEY.test(lKey)) { + validKey = lKey.replace('arrow', ''); + } 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 (keyIdent in KEY_IDENTIFIER) { + validKey = KEY_IDENTIFIER[keyIdent]; + } else if (IDENT_CHAR.test(keyIdent)) { + keyIdent = parseInt(keyIdent.replace('U+', '0x'), 16); + validKey = String.fromCharCode(keyIdent).toLowerCase(); + } 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, event, eventKey) { + return eventKey === keyCombo.key && + (!keyCombo.hasModifiers || ( + !!event.shiftKey === !!keyCombo.shiftKey && + !!event.ctrlKey === !!keyCombo.ctrlKey && + !!event.altKey === !!keyCombo.altKey && + !!event.metaKey === !!keyCombo.metaKey) + ); + } + + function parseKeyComboString(keyComboString) { + if (keyComboString.length === 1) { + return { + combo: keyComboString, + key: keyComboString, + event: 'keydown' + }; + } + 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; + parsedKeyCombo.hasModifiers = true; + } else { + parsedKeyCombo.key = keyName; + parsedKeyCombo.event = event || 'keydown'; + } + + return parsedKeyCombo; + }, { + combo: keyComboString.split(':').shift() + }); + } + + function parseEventString(eventString) { + return eventString.trim().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; + } + }, + + /** + * If true, this property will cause the implementing element to + * automatically stop propagation on any handled KeyboardEvents. + */ + stopKeyboardEventPropagation: { + type: Boolean, + value: false + }, + + _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 eventKey = normalizedKeyForEvent(event); + for (var i = 0; i < keyCombos.length; ++i) { + if (keyComboMatchesEvent(keyCombos[i], event, eventKey)) { + 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]); + } + + // Give precedence to combos with modifiers to be checked first. + for (var eventName in this._keyBindings) { + this._keyBindings[eventName].sort(function (kb1, kb2) { + var b1 = kb1[0].hasModifiers; + var b2 = kb2[0].hasModifiers; + return (b1 === b2) ? 0 : b1 ? -1 : 1; + }) + } + }, + + _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) { + if (this.stopKeyboardEventPropagation) { + event.stopPropagation(); + } + + // if event has been already prevented, don't do anything + if (event.defaultPrevented) { + return; + } + + var eventKey = normalizedKeyForEvent(event); + for (var i = 0; i < keyBindings.length; i++) { + var keyCombo = keyBindings[i][0]; + var handlerName = keyBindings[i][1]; + if (keyComboMatchesEvent(keyCombo, event, eventKey)) { + this._triggerKeyHandler(keyCombo, handlerName, event); + // exit the loop if eventDefault was prevented + if (event.defaultPrevented) { + return; + } + } + } + }, + + _triggerKeyHandler: function(keyCombo, handlerName, keyboardEvent) { + var detail = Object.create(keyCombo); + detail.keyboardEvent = keyboardEvent; + var event = new CustomEvent(keyCombo.event, { + detail: detail, + cancelable: true + }); + this[handlerName].call(this, event); + if (event.defaultPrevented) { + keyboardEvent.preventDefault(); + } + } + }; + })(); +/** + * @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) { + this._setFocused(event.type === 'focus'); + } else if (!this.shadowRoot && !this.isLightDescendant(event.target)) { + 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'); + } + }, + + _downHandler: function(event) { + this._setPointerDown(true); + this._setPressed(true); + this._setReceivedFocusFromKeyboard(false); + }, + + _upHandler: function() { + this._setPointerDown(false); + this._setPressed(false); + }, + + /** + * @param {!KeyboardEvent} event . + */ + _spaceKeyDownHandler: function(event) { + var keyboardEvent = event.detail.keyboardEvent; + var target = Polymer.dom(keyboardEvent).localTarget; + + // Ignore the event if this is coming from a focused light child, since that + // element will deal with it. + if (this.isLightDescendant(/** @type {Node} */(target))) + return; + + keyboardEvent.preventDefault(); + keyboardEvent.stopImmediatePropagation(); + this._setPressed(true); + }, + + /** + * @param {!KeyboardEvent} event . + */ + _spaceKeyUpHandler: function(event) { + var keyboardEvent = event.detail.keyboardEvent; + var target = Polymer.dom(keyboardEvent).localTarget; + + // Ignore the event if this is coming from a focused light child, since that + // element will deal with it. + if (this.isLightDescendant(/** @type {Node} */(target))) + return; + + 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 + ]; +(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' + }, + + /** + * If true, the ripple will not generate a ripple effect + * via pointer interaction. + * Calling ripple's imperative api like `simulatedRipple` will + * still generate the ripple effect. + */ + noink: { + type: Boolean, + value: false + }, + + _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() { + // Set up a11yKeysBehavior to listen to key events on the target, + // so that space and enter activate the ripple even if the target doesn't + // handle key events. The key handlers deal with `noink` themselves. + this.keyEventTarget = this.target; + this.listen(this.target, 'up', 'uiUpAction'); + this.listen(this.target, 'down', 'uiDownAction'); + }, + + detached: function() { + this.unlisten(this.target, 'up', 'uiUpAction'); + this.unlisten(this.target, 'down', 'uiDownAction'); + }, + + 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); + }, + + /** + * Provokes a ripple down effect via a UI event, + * respecting the `noink` property. + * @param {Event=} event + */ + uiDownAction: function(event) { + if (!this.noink) { + this.downAction(event); + } + }, + + /** + * Provokes a ripple down effect via a UI event, + * *not* respecting the `noink` property. + * @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(); + } + }, + + /** + * Provokes a ripple up effect via a UI event, + * respecting the `noink` property. + * @param {Event=} event + */ + uiUpAction: function(event) { + if (!this.noink) { + this.upAction(event); + } + }, + + /** + * Provokes a ripple up effect via a UI event, + * *not* respecting the `noink` property. + * @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.uiDownAction(); + this.async(this.uiUpAction, 1); + }, + + _onSpaceKeydown: function() { + this.uiDownAction(); + }, + + _onSpaceKeyup: function() { + this.uiUpAction(); + }, + + // note: holdDown does not respect noink since it can be a focus based + // effect. + _holdDownChanged: function(newVal, oldVal) { + if (oldVal === undefined) { + return; + } + if (newVal) { + this.downAction(); + } else { + this.upAction(); + } + } + }); + })(); +/** + * `Polymer.PaperRippleBehavior` dynamically implements a ripple + * when the element has focus via pointer or keyboard. + * + * NOTE: This behavior is intended to be used in conjunction with and after + * `Polymer.IronButtonState` and `Polymer.IronControlState`. + * + * @polymerBehavior Polymer.PaperRippleBehavior + */ + Polymer.PaperRippleBehavior = { + + properties: { + /** + * If true, the element will not produce a ripple effect when interacted + * with via the pointer. + */ + noink: { + type: Boolean, + observer: '_noinkChanged' + }, + + /** + * @type {Element|undefined} + */ + _rippleContainer: { + type: Object, + } + }, + + /** + * Ensures a `<paper-ripple>` element is available when the element is + * focused. + */ + _buttonStateChanged: function() { + if (this.focused) { + this.ensureRipple(); + } + }, + + /** + * In addition to the functionality provided in `IronButtonState`, ensures + * a ripple effect is created when the element is in a `pressed` state. + */ + _downHandler: function(event) { + Polymer.IronButtonStateImpl._downHandler.call(this, event); + if (this.pressed) { + this.ensureRipple(event); + } + }, + + /** + * Ensures this element contains a ripple effect. For startup efficiency + * the ripple effect is dynamically on demand when needed. + * @param {!Event=} optTriggeringEvent (optional) event that triggered the + * ripple. + */ + ensureRipple: function(optTriggeringEvent) { + if (!this.hasRipple()) { + this._ripple = this._createRipple(); + this._ripple.noink = this.noink; + var rippleContainer = this._rippleContainer || this.root; + if (rippleContainer) { + Polymer.dom(rippleContainer).appendChild(this._ripple); + } + if (optTriggeringEvent) { + // Check if the event happened inside of the ripple container + // Fall back to host instead of the root because distributed text + // nodes are not valid event targets + var domContainer = Polymer.dom(this._rippleContainer || this); + var target = Polymer.dom(optTriggeringEvent).rootTarget; + if (domContainer.deepContains( /** @type {Node} */(target))) { + this._ripple.uiDownAction(optTriggeringEvent); + } + } + } + }, + + /** + * Returns the `<paper-ripple>` element used by this element to create + * ripple effects. The element's ripple is created on demand, when + * necessary, and calling this method will force the + * ripple to be created. + */ + getRipple: function() { + this.ensureRipple(); + return this._ripple; + }, + + /** + * Returns true if this element currently contains a ripple effect. + * @return {boolean} + */ + hasRipple: function() { + return Boolean(this._ripple); + }, + + /** + * Create the element's ripple effect via creating a `<paper-ripple>`. + * Override this method to customize the ripple element. + * @return {!PaperRippleElement} Returns a `<paper-ripple>` element. + */ + _createRipple: function() { + return /** @type {!PaperRippleElement} */ ( + document.createElement('paper-ripple')); + }, + + _noinkChanged: function(noink) { + if (this.hasRipple()) { + this._ripple.noink = noink; + } + } + + }; +/** + * `Polymer.PaperInkyFocusBehavior` implements a ripple when the element has keyboard focus. + * + * @polymerBehavior Polymer.PaperInkyFocusBehavior + */ + Polymer.PaperInkyFocusBehaviorImpl = { + + observers: [ + '_focusedChanged(receivedFocusFromKeyboard)' + ], + + _focusedChanged: function(receivedFocusFromKeyboard) { + if (receivedFocusFromKeyboard) { + this.ensureRipple(); + } + if (this.hasRipple()) { + this._ripple.holdDown = receivedFocusFromKeyboard; + } + }, + + _createRipple: function() { + var ripple = Polymer.PaperRippleBehavior._createRipple(); + ripple.id = 'ink'; + ripple.setAttribute('center', ''); + ripple.classList.add('circle'); + return ripple; + } + + }; + + /** @polymerBehavior Polymer.PaperInkyFocusBehavior */ + Polymer.PaperInkyFocusBehavior = [ + Polymer.IronButtonState, + Polymer.IronControlState, + Polymer.PaperRippleBehavior, + Polymer.PaperInkyFocusBehaviorImpl + ]; +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 + } + } + }); +/** @polymerBehavior Polymer.PaperButtonBehavior */ + Polymer.PaperButtonBehaviorImpl = { + + 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, + readOnly: true + } + + }, + + observers: [ + '_calculateElevation(focused, disabled, active, pressed, receivedFocusFromKeyboard)', + '_computeKeyboardClass(receivedFocusFromKeyboard)' + ], + + hostAttributes: { + role: 'button', + tabindex: '0', + animated: true + }, + + _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._setElevation(e); + }, + + _computeKeyboardClass: function(receivedFocusFromKeyboard) { + this.classList.toggle('keyboard-focus', receivedFocusFromKeyboard); + }, + + /** + * In addition to `IronButtonState` behavior, when space key goes down, + * create a ripple down effect. + * + * @param {!KeyboardEvent} event . + */ + _spaceKeyDownHandler: function(event) { + Polymer.IronButtonStateImpl._spaceKeyDownHandler.call(this, event); + if (this.hasRipple()) { + this._ripple.uiDownAction(); + } + }, + + /** + * In addition to `IronButtonState` behavior, when space key goes up, + * create a ripple up effect. + * + * @param {!KeyboardEvent} event . + */ + _spaceKeyUpHandler: function(event) { + Polymer.IronButtonStateImpl._spaceKeyUpHandler.call(this, event); + if (this.hasRipple()) { + this._ripple.uiUpAction(); + } + } + + }; + + /** @polymerBehavior */ + Polymer.PaperButtonBehavior = [ + Polymer.IronButtonState, + Polymer.IronControlState, + Polymer.PaperRippleBehavior, + 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); + } + } + /** + + Fired when the animation finishes. + This is useful if you want to wait until + the ripple animation finishes to perform some action. + + @event transitionend + @param {{node: Object}} detail Contains the animated node. + */ + }); +/** + * `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) - + (this.min / this.step)) / (1 / this.step) : value; + }, + + _validateValue: function() { + var v = this._clampValue(this.value); + this.value = this.oldValue = isNaN(v) ? this.oldValue : v; + return this.value !== v; + }, + + _update: function() { + this._validateValue(); + this._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 InkyTextButton = Polymer({ + is: 'inky-text-button', + + behaviors: [ + Polymer.PaperInkyFocusBehavior + ], + + hostAttributes: { + role: 'button', + tabindex: 0, + }, + }); + + var Item = Polymer({ + is: 'downloads-item', + + properties: { + data: { + type: Object, + }, + + 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_)', + 'observeIsDangerous_(isDangerous_, data.file_path)', + ], + + ready: function() { + this.content = this.$.content; + }, + + /** @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 */ + computeDangerIcon_: function() { + if (!this.isDangerous_) + return ''; + + switch (this.data.danger_type) { + case downloads.DangerType.DANGEROUS_CONTENT: + case downloads.DangerType.DANGEROUS_HOST: + case downloads.DangerType.DANGEROUS_URL: + case downloads.DangerType.POTENTIALLY_UNWANTED: + case downloads.DangerType.UNCOMMON_CONTENT: + return 'remove-circle'; + default: + return 'warning'; + } + }, + + /** @private */ + computeDate_: function() { + assert(typeof this.data.hideDate == 'boolean'); + if (this.data.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 */ + observeIsDangerous_: function() { + if (this.data && !this.isDangerous_) { + var filePath = encodeURIComponent(this.data.file_path); + var scaleFactor = '?scale=' + window.devicePixelRatio + 'x'; + this.$['file-icon'].src = 'chrome://fileicon/' + filePath + scaleFactor; + } + }, + + /** @private */ + onCancelTap_: function() { + downloads.ActionService.getInstance().cancel(this.data.id); + }, + + /** @private */ + onDiscardDangerousTap_: 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 + */ + onFileLinkTap_: function(e) { + e.preventDefault(); + downloads.ActionService.getInstance().openFile(this.data.id); + }, + + /** @private */ + onPauseTap_: function() { + downloads.ActionService.getInstance().pause(this.data.id); + }, + + /** @private */ + onRemoveTap_: function() { + downloads.ActionService.getInstance().remove(this.data.id); + }, + + /** @private */ + onResumeTap_: function() { + downloads.ActionService.getInstance().resume(this.data.id); + }, + + /** @private */ + onRetryTap_: function() { + downloads.ActionService.getInstance().download(this.data.url); + }, + + /** @private */ + onSaveDangerousTap_: function() { + downloads.ActionService.getInstance().saveDangerous(this.data.id); + }, + + /** @private */ + onShowTap_: function() { + downloads.ActionService.getInstance().show(this.data.id); + }, + }); + + return { + InkyTextButton: InkyTextButton, + Item: Item, + }; +}); +/** @polymerBehavior Polymer.PaperItemBehavior */ + Polymer.PaperItemBehaviorImpl = { + hostAttributes: { + role: 'option', + tabindex: '0' + } + }; + + /** @polymerBehavior */ + Polymer.PaperItemBehavior = [ + Polymer.IronControlState, + Polymer.IronButtonState, + Polymer.PaperItemBehaviorImpl + ]; +Polymer({ + is: 'paper-item', + + behaviors: [ + Polymer.PaperItemBehavior + ] + }); +/** + * @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. + * + * @type {?Object} + */ + 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 list of items from which a selection can be made. + */ + items: { + type: Array, + readOnly: true, + value: function() { + return []; + } + }, + + /** + * The set of excluded elements where the key is the `localName` + * of the element that will be ignored from the item list. + * + * @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._updateItems(); + if (!this._shouldUpdateSelection) { + this._updateSelected(this.attrForSelected,this.selected) + } + this._addListener(this.activateEvent); + }, + + detached: function() { + if (this._observer) { + Polymer.dom(this).unobserveNodes(this._observer); + } + this._removeListener(this.activateEvent); + }, + + /** + * 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); + }, + + get _shouldUpdateSelection() { + return this.selected != null; + }, + + _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); + }, + + _updateItems: function() { + var nodes = Polymer.dom(this).queryDistributedElements(this.selectable || '*'); + nodes = Array.prototype.filter.call(nodes, this._bindFilterItem); + this._setItems(nodes); + }, + + _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 items change under the given node. + _observeItems: function(node) { + return Polymer.dom(node).observeNodes(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 + }); + + this._updateItems(); + + if (this._shouldUpdateSelection) { + this._updateSelected(); + } + }); + }, + + _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; + }, + + get _shouldUpdateSelection() { + return this.selected != null || + (this.selectedValues != null && this.selectedValues.length); + }, + + _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 = this.getAttribute('tabindex'); + + Polymer.IronMenuBehaviorImpl._shiftTabPressed = true; + + this._setFocusedItem(null); + + 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) { + // do not focus the menu itself + return; + } + + this.blur(); + + // clear the cached focus item + 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; + + this._setFocusedItem(null); + + if (selectedItem) { + this._setFocusedItem(selectedItem); + } else { + this._setFocusedItem(this.items[0]); + } + // async 1ms to wait for `select` to get called from `_itemActivate` + }, 1); + }, + + /** + * 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')) { + // all other keys focus the menu item starting with that character + this._focusWithKeyboardEvent(event); + } + event.stopPropagation(); + }, + + // override _activateHandler + _activateHandler: function(event) { + Polymer.IronSelectableBehavior._activateHandler.call(this, event); + event.stopPropagation(); + } + }; + + Polymer.IronMenuBehaviorImpl._shiftTabPressed = false; + + /** @polymerBehavior Polymer.IronMenuBehavior */ + Polymer.IronMenuBehavior = [ + Polymer.IronMultiSelectableBehavior, + Polymer.IronA11yKeysBehavior, + Polymer.IronMenuBehaviorImpl + ]; +(function() { + Polymer({ + is: 'paper-menu', + + behaviors: [ + Polymer.IronMenuBehavior + ] + }); + })(); +/** +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'; + } + if (!this._fitInfo.positionedBy.vertically || !this._fitInfo.positionedBy.horizontally) { + // need position:fixed to properly size the element + this.style.position = 'fixed'; + } + // 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 = { + + _overlays: [], + + // iframes have a default z-index of 100, so this default should be at least + // that. + _minimumZ: 101, + + _backdrops: [], + + _applyOverlayZ: function(overlay, aboveZ) { + this._setZ(overlay, aboveZ + 2); + }, + + _setZ: function(element, z) { + element.style.zIndex = z; + }, + + // track overlays for z-index and focus managemant + addOverlay: function(overlay) { + var minimumZ = Math.max(this.currentOverlayZ(), this._minimumZ); + this._overlays.push(overlay); + var newZ = this.currentOverlayZ(); + if (newZ <= minimumZ) { + this._applyOverlayZ(overlay, minimumZ); + } + }, + + removeOverlay: function(overlay) { + var i = this._overlays.indexOf(overlay); + if (i >= 0) { + this._overlays.splice(i, 1); + this._setZ(overlay, ''); + } + }, + + currentOverlay: function() { + var i = this._overlays.length - 1; + while (this._overlays[i] && !this._overlays[i].opened) { + --i; + } + return this._overlays[i]; + }, + + currentOverlayZ: function() { + var z = this._minimumZ; + var current = this.currentOverlay(); + if (current) { + var z1 = window.getComputedStyle(current).zIndex; + if (!isNaN(z1)) { + z = Number(z1); + } + } + return z; + }, + + /** + * Ensures that the minimum z-index of new overlays is at least `minimumZ`. + * This does not effect the z-index of any existing overlays. + * + * @param {number} minimumZ + */ + ensureMinimumZ: function(minimumZ) { + this._minimumZ = Math.max(this._minimumZ, minimumZ); + }, + + focusOverlay: function() { + var current = this.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(); + } + }, + + trackBackdrop: function(element) { + // backdrops contains the overlays with a backdrop that are currently + // visible + if (element.opened) { + this._backdrops.push(element); + } else { + var index = this._backdrops.indexOf(element); + if (index >= 0) { + this._backdrops.splice(index, 1); + } + } + }, + + getBackdrops: function() { + return this._backdrops; + } + + }; +(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. When the dialog is canceled, the overlay fires an 'iron-overlay-canceled' +event. Call `preventDefault` on this event to prevent the overlay from closing. + +### 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() { + var cancelEvent = this.fire('iron-overlay-canceled', undefined, {cancelable: true}); + if (cancelEvent.defaultPrevented) { + return; + } + + 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 when the `iron-overlay` is canceled, but before it is closed. + * Cancel the event to prevent the `iron-overlay` from closing. + * @event iron-overlay-canceled + */ + +/** + * 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({ + 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. + * Use the related `Polymer.IronValidatorBehavior` to add custom validation logic to an iron-input. + * + * By default, an `<iron-form>` element validates its fields when the user presses the submit button. + * To validate a form imperatively, call the form's `validate()` method, which in turn will + * call `validate()` on all its children. By using `Polymer.IronValidatableBehavior`, your + * custom element will get a public `validate()`, which + * will return the validity of the element, and a corresponding `invalid` attribute, + * which can be used for styling. + * + * To implement the custom validation logic of your element, you must override + * the protected `_getValidity()` method of this behaviour, rather than `validate()`. + * See [this](https://github.com/PolymerElements/iron-form/blob/master/demo/simple-element.html) + * for an example. + * + * ### 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, + observer: "_allowedPatternChanged" + }, + + _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}); + }, + + _allowedPatternChanged: function() { + // Force to prevent invalid input when an `allowed-pattern` is set + this.preventInvalidInput = this.allowedPattern ? true : false; + }, + + _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 the label is floating, ignore any offsets that may have been + // applied from a prefix element. + this.$.labelAndInputContainer.style.position = 'static'; + + if (invalid) { + cls += ' is-invalid'; + } else if (focused) { + cls += " label-is-highlighted"; + } + } else { + // When the label is not floating, it should overlap the input element. + if (label) { + this.$.labelAndInputContainer.style.position = 'relative'; + } + } + } 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() { + // isRTL() only works after i18n_template.js runs to set <html dir>. + this.overflowAlign_ = isRTL() ? 'left' : 'right'; + + /** @private {!SearchFieldDelegate} */ + this.searchFieldDelegate_ = new ToolbarSearchFieldDelegate(this); + this.$['search-input'].setDelegate(this.searchFieldDelegate_); + }, + + properties: { + downloadsShowing: { + reflectToAttribute: true, + type: Boolean, + value: false, + observer: 'onDownloadsShowingChange_', + }, + + overflowAlign_: { + type: String, + value: 'right', + }, + }, + + /** @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 */ + onClearAllTap_: 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 */ + onOpenDownloadsFolderTap_: 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_: { + observer: 'hasDownloadsChanged_', + type: Boolean, + }, + + items_: { + type: Array, + value: function() { return []; }, + }, + }, + + hostAttributes: { + loading: true, + }, + + listeners: { + 'downloads-list.scroll': 'onListScroll_', + }, + + observers: [ + 'itemsChanged_(items_.*)', + ], + + /** @private */ + clearAll_: function() { + this.set('items_', []); + }, + + /** @private */ + hasDownloadsChanged_: function() { + if (loadTimeData.getBoolean('allowDeletingHistory')) + this.$.toolbar.downloadsShowing = this.hasDownloads_; + + if (this.hasDownloads_) { + this.$['downloads-list'].fire('iron-resize'); + } else { + var isSearching = downloads.ActionService.getInstance().isSearching(); + var messageToShow = isSearching ? 'noSearchResults' : 'noDownloads'; + this.$['no-downloads'].querySelector('span').textContent = + loadTimeData.getString(messageToShow); + } + }, + + /** + * @param {number} index + * @param {!Array<!downloads.Data>} list + * @private + */ + insertItems_: function(index, list) { + this.splice.apply(this, ['items_', index, 0].concat(list)); + this.updateHideDates_(index, index + list.length); + this.removeAttribute('loading'); + }, + + /** @private */ + itemsChanged_: function() { + this.hasDownloads_ = this.items_.length > 0; + }, + + /** + * @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 */ + onListScroll_: function() { + var list = this.$['downloads-list']; + if (list.scrollHeight - list.scrollTop - list.offsetHeight <= 100) { + // Approaching the end of the scrollback. Attempt to load more items. + downloads.ActionService.getInstance().loadMore(); + } + }, + + /** @private */ + onLoad_: function() { + cr.ui.decorate('command', cr.ui.Command); + document.addEventListener('canExecute', this.onCanExecute_.bind(this)); + document.addEventListener('command', this.onCommand_.bind(this)); + + downloads.ActionService.getInstance().loadMore(); + }, + + /** + * @param {number} index + * @private + */ + removeItem_: function(index) { + this.splice('items_', index, 1); + this.updateHideDates_(index, index); + this.onListScroll_(); + }, + + /** + * @param {number} start + * @param {number} end + * @private + */ + updateHideDates_: function(start, end) { + for (var i = start; i <= end; ++i) { + var current = this.items_[i]; + if (!current) + continue; + var prev = this.items_[i - 1]; + current.hideDate = !!prev && prev.date_string == current.date_string; + } + }, + + /** + * @param {number} index + * @param {!downloads.Data} data + * @private + */ + updateItem_: function(index, data) { + this.set('items_.' + index, data); + this.updateHideDates_(index, index); + this.$['downloads-list'].updateSizeForItem(index); + }, + }); + + Manager.clearAll = function() { + Manager.get().clearAll_(); + }; + + /** @return {!downloads.Manager} */ + Manager.get = function() { + return /** @type {!downloads.Manager} */( + queryRequiredElement('downloads-manager')); + }; + + Manager.insertItems = function(index, list) { + Manager.get().insertItems_(index, list); + }; + + Manager.onLoad = function() { + Manager.get().onLoad_(); + }; + + Manager.removeItem = function(index) { + Manager.get().removeItem_(index); + }; + + Manager.updateItem = function(index, data) { + Manager.get().updateItem_(index, data); + }; + + return {Manager: Manager}; +}); +// 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. + +window.addEventListener('load', downloads.Manager.onLoad);
\ No newline at end of file |