| Index: chrome/browser/resources/md_downloads/crisper.js
|
| diff --git a/chrome/browser/resources/md_downloads/crisper.js b/chrome/browser/resources/md_downloads/crisper.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..cf7b15097e53722cb38366937f3b4a41dc1ef126
|
| --- /dev/null
|
| +++ b/chrome/browser/resources/md_downloads/crisper.js
|
| @@ -0,0 +1,15660 @@
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +Polymer = {dom: 'shadow'};
|
| +// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +/**
|
| + * The global object.
|
| + * @type {!Object}
|
| + * @const
|
| + */
|
| +var global = this;
|
| +
|
| +/** Platform, package, object property, and Event support. **/
|
| +var cr = function() {
|
| + 'use strict';
|
| +
|
| + /**
|
| + * Builds an object structure for the provided namespace path,
|
| + * ensuring that names that already exist are not overwritten. For
|
| + * example:
|
| + * "a.b.c" -> a = {};a.b={};a.b.c={};
|
| + * @param {string} name Name of the object that this file defines.
|
| + * @param {*=} opt_object The object to expose at the end of the path.
|
| + * @param {Object=} opt_objectToExportTo The object to add the path to;
|
| + * default is {@code global}.
|
| + * @private
|
| + */
|
| + function exportPath(name, opt_object, opt_objectToExportTo) {
|
| + var parts = name.split('.');
|
| + var cur = opt_objectToExportTo || global;
|
| +
|
| + for (var part; parts.length && (part = parts.shift());) {
|
| + if (!parts.length && opt_object !== undefined) {
|
| + // last part and we have an object; use it
|
| + cur[part] = opt_object;
|
| + } else if (part in cur) {
|
| + cur = cur[part];
|
| + } else {
|
| + cur = cur[part] = {};
|
| + }
|
| + }
|
| + return cur;
|
| + };
|
| +
|
| + /**
|
| + * Fires a property change event on the target.
|
| + * @param {EventTarget} target The target to dispatch the event on.
|
| + * @param {string} propertyName The name of the property that changed.
|
| + * @param {*} newValue The new value for the property.
|
| + * @param {*} oldValue The old value for the property.
|
| + */
|
| + function dispatchPropertyChange(target, propertyName, newValue, oldValue) {
|
| + var e = new Event(propertyName + 'Change');
|
| + e.propertyName = propertyName;
|
| + e.newValue = newValue;
|
| + e.oldValue = oldValue;
|
| + target.dispatchEvent(e);
|
| + }
|
| +
|
| + /**
|
| + * Converts a camelCase javascript property name to a hyphenated-lower-case
|
| + * attribute name.
|
| + * @param {string} jsName The javascript camelCase property name.
|
| + * @return {string} The equivalent hyphenated-lower-case attribute name.
|
| + */
|
| + function getAttributeName(jsName) {
|
| + return jsName.replace(/([A-Z])/g, '-$1').toLowerCase();
|
| + }
|
| +
|
| + /**
|
| + * The kind of property to define in {@code defineProperty}.
|
| + * @enum {string}
|
| + * @const
|
| + */
|
| + var PropertyKind = {
|
| + /**
|
| + * Plain old JS property where the backing data is stored as a "private"
|
| + * field on the object.
|
| + * Use for properties of any type. Type will not be checked.
|
| + */
|
| + JS: 'js',
|
| +
|
| + /**
|
| + * The property backing data is stored as an attribute on an element.
|
| + * Use only for properties of type {string}.
|
| + */
|
| + ATTR: 'attr',
|
| +
|
| + /**
|
| + * The property backing data is stored as an attribute on an element. If the
|
| + * element has the attribute then the value is true.
|
| + * Use only for properties of type {boolean}.
|
| + */
|
| + BOOL_ATTR: 'boolAttr'
|
| + };
|
| +
|
| + /**
|
| + * Helper function for defineProperty that returns the getter to use for the
|
| + * property.
|
| + * @param {string} name The name of the property.
|
| + * @param {PropertyKind} kind The kind of the property.
|
| + * @return {function():*} The getter for the property.
|
| + */
|
| + function getGetter(name, kind) {
|
| + switch (kind) {
|
| + case PropertyKind.JS:
|
| + var privateName = name + '_';
|
| + return function() {
|
| + return this[privateName];
|
| + };
|
| + case PropertyKind.ATTR:
|
| + var attributeName = getAttributeName(name);
|
| + return function() {
|
| + return this.getAttribute(attributeName);
|
| + };
|
| + case PropertyKind.BOOL_ATTR:
|
| + var attributeName = getAttributeName(name);
|
| + return function() {
|
| + return this.hasAttribute(attributeName);
|
| + };
|
| + }
|
| +
|
| + // TODO(dbeam): replace with assertNotReached() in assert.js when I can coax
|
| + // the browser/unit tests to preprocess this file through grit.
|
| + throw 'not reached';
|
| + }
|
| +
|
| + /**
|
| + * Helper function for defineProperty that returns the setter of the right
|
| + * kind.
|
| + * @param {string} name The name of the property we are defining the setter
|
| + * for.
|
| + * @param {PropertyKind} kind The kind of property we are getting the
|
| + * setter for.
|
| + * @param {function(*, *):void=} opt_setHook A function to run after the
|
| + * property is set, but before the propertyChange event is fired.
|
| + * @return {function(*):void} The function to use as a setter.
|
| + */
|
| + function getSetter(name, kind, opt_setHook) {
|
| + switch (kind) {
|
| + case PropertyKind.JS:
|
| + var privateName = name + '_';
|
| + return function(value) {
|
| + var oldValue = this[name];
|
| + if (value !== oldValue) {
|
| + this[privateName] = value;
|
| + if (opt_setHook)
|
| + opt_setHook.call(this, value, oldValue);
|
| + dispatchPropertyChange(this, name, value, oldValue);
|
| + }
|
| + };
|
| +
|
| + case PropertyKind.ATTR:
|
| + var attributeName = getAttributeName(name);
|
| + return function(value) {
|
| + var oldValue = this[name];
|
| + if (value !== oldValue) {
|
| + if (value == undefined)
|
| + this.removeAttribute(attributeName);
|
| + else
|
| + this.setAttribute(attributeName, value);
|
| + if (opt_setHook)
|
| + opt_setHook.call(this, value, oldValue);
|
| + dispatchPropertyChange(this, name, value, oldValue);
|
| + }
|
| + };
|
| +
|
| + case PropertyKind.BOOL_ATTR:
|
| + var attributeName = getAttributeName(name);
|
| + return function(value) {
|
| + var oldValue = this[name];
|
| + if (value !== oldValue) {
|
| + if (value)
|
| + this.setAttribute(attributeName, name);
|
| + else
|
| + this.removeAttribute(attributeName);
|
| + if (opt_setHook)
|
| + opt_setHook.call(this, value, oldValue);
|
| + dispatchPropertyChange(this, name, value, oldValue);
|
| + }
|
| + };
|
| + }
|
| +
|
| + // TODO(dbeam): replace with assertNotReached() in assert.js when I can coax
|
| + // the browser/unit tests to preprocess this file through grit.
|
| + throw 'not reached';
|
| + }
|
| +
|
| + /**
|
| + * Defines a property on an object. When the setter changes the value a
|
| + * property change event with the type {@code name + 'Change'} is fired.
|
| + * @param {!Object} obj The object to define the property for.
|
| + * @param {string} name The name of the property.
|
| + * @param {PropertyKind=} opt_kind What kind of underlying storage to use.
|
| + * @param {function(*, *):void=} opt_setHook A function to run after the
|
| + * property is set, but before the propertyChange event is fired.
|
| + */
|
| + function defineProperty(obj, name, opt_kind, opt_setHook) {
|
| + if (typeof obj == 'function')
|
| + obj = obj.prototype;
|
| +
|
| + var kind = /** @type {PropertyKind} */ (opt_kind || PropertyKind.JS);
|
| +
|
| + if (!obj.__lookupGetter__(name))
|
| + obj.__defineGetter__(name, getGetter(name, kind));
|
| +
|
| + if (!obj.__lookupSetter__(name))
|
| + obj.__defineSetter__(name, getSetter(name, kind, opt_setHook));
|
| + }
|
| +
|
| + /**
|
| + * Counter for use with createUid
|
| + */
|
| + var uidCounter = 1;
|
| +
|
| + /**
|
| + * @return {number} A new unique ID.
|
| + */
|
| + function createUid() {
|
| + return uidCounter++;
|
| + }
|
| +
|
| + /**
|
| + * Returns a unique ID for the item. This mutates the item so it needs to be
|
| + * an object
|
| + * @param {!Object} item The item to get the unique ID for.
|
| + * @return {number} The unique ID for the item.
|
| + */
|
| + function getUid(item) {
|
| + if (item.hasOwnProperty('uid'))
|
| + return item.uid;
|
| + return item.uid = createUid();
|
| + }
|
| +
|
| + /**
|
| + * Dispatches a simple event on an event target.
|
| + * @param {!EventTarget} target The event target to dispatch the event on.
|
| + * @param {string} type The type of the event.
|
| + * @param {boolean=} opt_bubbles Whether the event bubbles or not.
|
| + * @param {boolean=} opt_cancelable Whether the default action of the event
|
| + * can be prevented. Default is true.
|
| + * @return {boolean} If any of the listeners called {@code preventDefault}
|
| + * during the dispatch this will return false.
|
| + */
|
| + function dispatchSimpleEvent(target, type, opt_bubbles, opt_cancelable) {
|
| + var e = new Event(type, {
|
| + bubbles: opt_bubbles,
|
| + cancelable: opt_cancelable === undefined || opt_cancelable
|
| + });
|
| + return target.dispatchEvent(e);
|
| + }
|
| +
|
| + /**
|
| + * Calls |fun| and adds all the fields of the returned object to the object
|
| + * named by |name|. For example, cr.define('cr.ui', function() {
|
| + * function List() {
|
| + * ...
|
| + * }
|
| + * function ListItem() {
|
| + * ...
|
| + * }
|
| + * return {
|
| + * List: List,
|
| + * ListItem: ListItem,
|
| + * };
|
| + * });
|
| + * defines the functions cr.ui.List and cr.ui.ListItem.
|
| + * @param {string} name The name of the object that we are adding fields to.
|
| + * @param {!Function} fun The function that will return an object containing
|
| + * the names and values of the new fields.
|
| + */
|
| + function define(name, fun) {
|
| + var obj = exportPath(name);
|
| + var exports = fun();
|
| + for (var propertyName in exports) {
|
| + // Maybe we should check the prototype chain here? The current usage
|
| + // pattern is always using an object literal so we only care about own
|
| + // properties.
|
| + var propertyDescriptor = Object.getOwnPropertyDescriptor(exports,
|
| + propertyName);
|
| + if (propertyDescriptor)
|
| + Object.defineProperty(obj, propertyName, propertyDescriptor);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Adds a {@code getInstance} static method that always return the same
|
| + * instance object.
|
| + * @param {!Function} ctor The constructor for the class to add the static
|
| + * method to.
|
| + */
|
| + function addSingletonGetter(ctor) {
|
| + ctor.getInstance = function() {
|
| + return ctor.instance_ || (ctor.instance_ = new ctor());
|
| + };
|
| + }
|
| +
|
| + /**
|
| + * Forwards public APIs to private implementations.
|
| + * @param {Function} ctor Constructor that have private implementations in its
|
| + * prototype.
|
| + * @param {Array<string>} methods List of public method names that have their
|
| + * underscored counterparts in constructor's prototype.
|
| + * @param {string=} opt_target Selector for target node.
|
| + */
|
| + function makePublic(ctor, methods, opt_target) {
|
| + methods.forEach(function(method) {
|
| + ctor[method] = function() {
|
| + var target = opt_target ? document.getElementById(opt_target) :
|
| + ctor.getInstance();
|
| + return target[method + '_'].apply(target, arguments);
|
| + };
|
| + });
|
| + }
|
| +
|
| + /**
|
| + * The mapping used by the sendWithCallback mechanism to tie the callback
|
| + * supplied to an invocation of sendWithCallback with the WebUI response
|
| + * sent by the browser in response to the chrome.send call. The mapping is
|
| + * from ID to callback function; the ID is generated by sendWithCallback and
|
| + * is unique across all invocations of said method.
|
| + * @type {!Object<Function>}
|
| + */
|
| + var chromeSendCallbackMap = Object.create(null);
|
| +
|
| + /**
|
| + * The named method the WebUI handler calls directly in response to a
|
| + * chrome.send call that expects a callback. The handler requires no knowledge
|
| + * of the specific name of this method, as the name is passed to the handler
|
| + * as the first argument in the arguments list of chrome.send. The handler
|
| + * must pass the ID, also sent via the chrome.send arguments list, as the
|
| + * first argument of the JS invocation; additionally, the handler may
|
| + * supply any number of other arguments that will be forwarded to the
|
| + * callback.
|
| + * @param {string} id The unique ID identifying the callback method this
|
| + * response is tied to.
|
| + */
|
| + function webUIResponse(id) {
|
| + chromeSendCallbackMap[id].apply(
|
| + null, Array.prototype.slice.call(arguments, 1));
|
| + delete chromeSendCallbackMap[id];
|
| + }
|
| +
|
| + /**
|
| + * A variation of chrome.send which allows the client to receive a direct
|
| + * callback without requiring the handler to have specific knowledge of any
|
| + * JS internal method names or state. The callback will be removed from the
|
| + * mapping once it has fired.
|
| + * @param {string} methodName The name of the WebUI handler API.
|
| + * @param {Array|undefined} args Arguments for the method call sent to the
|
| + * WebUI handler. Pass undefined if no args should be sent to the handler.
|
| + * @param {Function} callback A callback function which is called (indirectly)
|
| + * by the WebUI handler.
|
| + */
|
| + function sendWithCallback(methodName, args, callback) {
|
| + var id = methodName + createUid();
|
| + chromeSendCallbackMap[id] = callback;
|
| + chrome.send(methodName, ['cr.webUIResponse', id].concat(args || []));
|
| + }
|
| +
|
| + /**
|
| + * A registry of callbacks keyed by event name. Used by addWebUIListener to
|
| + * register listeners.
|
| + * @type {!Object<Array<Function>>}
|
| + */
|
| + var webUIListenerMap = Object.create(null);
|
| +
|
| + /**
|
| + * The named method the WebUI handler calls directly when an event occurs.
|
| + * The WebUI handler must supply the name of the event as the first argument
|
| + * of the JS invocation; additionally, the handler may supply any number of
|
| + * other arguments that will be forwarded to the listener callbacks.
|
| + * @param {string} event The name of the event that has occurred.
|
| + */
|
| + function webUIListenerCallback(event) {
|
| + var listenerCallbacks = webUIListenerMap[event];
|
| + for (var i = 0; i < listenerCallbacks.length; i++) {
|
| + var callback = listenerCallbacks[i];
|
| + callback.apply(null, Array.prototype.slice.call(arguments, 1));
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Registers a listener for an event fired from WebUI handlers. Any number of
|
| + * listeners may register for a single event.
|
| + * @param {string} event The event to listen to.
|
| + * @param {Function} callback The callback run when the event is fired.
|
| + */
|
| + function addWebUIListener(event, callback) {
|
| + if (event in webUIListenerMap)
|
| + webUIListenerMap[event].push(callback);
|
| + else
|
| + webUIListenerMap[event] = [callback];
|
| + }
|
| +
|
| + return {
|
| + addSingletonGetter: addSingletonGetter,
|
| + createUid: createUid,
|
| + define: define,
|
| + defineProperty: defineProperty,
|
| + dispatchPropertyChange: dispatchPropertyChange,
|
| + dispatchSimpleEvent: dispatchSimpleEvent,
|
| + exportPath: exportPath,
|
| + getUid: getUid,
|
| + makePublic: makePublic,
|
| + webUIResponse: webUIResponse,
|
| + sendWithCallback: sendWithCallback,
|
| + webUIListenerCallback: webUIListenerCallback,
|
| + addWebUIListener: addWebUIListener,
|
| + PropertyKind: PropertyKind,
|
| +
|
| + get doc() {
|
| + return document;
|
| + },
|
| +
|
| + /** Whether we are using a Mac or not. */
|
| + get isMac() {
|
| + return /Mac/.test(navigator.platform);
|
| + },
|
| +
|
| + /** Whether this is on the Windows platform or not. */
|
| + get isWindows() {
|
| + return /Win/.test(navigator.platform);
|
| + },
|
| +
|
| + /** Whether this is on chromeOS or not. */
|
| + get isChromeOS() {
|
| + return /CrOS/.test(navigator.userAgent);
|
| + },
|
| +
|
| + /** Whether this is on vanilla Linux (not chromeOS). */
|
| + get isLinux() {
|
| + return /Linux/.test(navigator.userAgent);
|
| + },
|
| + };
|
| +}();
|
| +// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +cr.define('cr.ui', function() {
|
| +
|
| + /**
|
| + * Decorates elements as an instance of a class.
|
| + * @param {string|!Element} source The way to find the element(s) to decorate.
|
| + * If this is a string then {@code querySeletorAll} is used to find the
|
| + * elements to decorate.
|
| + * @param {!Function} constr The constructor to decorate with. The constr
|
| + * needs to have a {@code decorate} function.
|
| + */
|
| + function decorate(source, constr) {
|
| + var elements;
|
| + if (typeof source == 'string')
|
| + elements = cr.doc.querySelectorAll(source);
|
| + else
|
| + elements = [source];
|
| +
|
| + for (var i = 0, el; el = elements[i]; i++) {
|
| + if (!(el instanceof constr))
|
| + constr.decorate(el);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Helper function for creating new element for define.
|
| + */
|
| + function createElementHelper(tagName, opt_bag) {
|
| + // Allow passing in ownerDocument to create in a different document.
|
| + var doc;
|
| + if (opt_bag && opt_bag.ownerDocument)
|
| + doc = opt_bag.ownerDocument;
|
| + else
|
| + doc = cr.doc;
|
| + return doc.createElement(tagName);
|
| + }
|
| +
|
| + /**
|
| + * Creates the constructor for a UI element class.
|
| + *
|
| + * Usage:
|
| + * <pre>
|
| + * var List = cr.ui.define('list');
|
| + * List.prototype = {
|
| + * __proto__: HTMLUListElement.prototype,
|
| + * decorate: function() {
|
| + * ...
|
| + * },
|
| + * ...
|
| + * };
|
| + * </pre>
|
| + *
|
| + * @param {string|Function} tagNameOrFunction The tagName or
|
| + * function to use for newly created elements. If this is a function it
|
| + * needs to return a new element when called.
|
| + * @return {function(Object=):Element} The constructor function which takes
|
| + * an optional property bag. The function also has a static
|
| + * {@code decorate} method added to it.
|
| + */
|
| + function define(tagNameOrFunction) {
|
| + var createFunction, tagName;
|
| + if (typeof tagNameOrFunction == 'function') {
|
| + createFunction = tagNameOrFunction;
|
| + tagName = '';
|
| + } else {
|
| + createFunction = createElementHelper;
|
| + tagName = tagNameOrFunction;
|
| + }
|
| +
|
| + /**
|
| + * Creates a new UI element constructor.
|
| + * @param {Object=} opt_propertyBag Optional bag of properties to set on the
|
| + * object after created. The property {@code ownerDocument} is special
|
| + * cased and it allows you to create the element in a different
|
| + * document than the default.
|
| + * @constructor
|
| + */
|
| + function f(opt_propertyBag) {
|
| + var el = createFunction(tagName, opt_propertyBag);
|
| + f.decorate(el);
|
| + for (var propertyName in opt_propertyBag) {
|
| + el[propertyName] = opt_propertyBag[propertyName];
|
| + }
|
| + return el;
|
| + }
|
| +
|
| + /**
|
| + * Decorates an element as a UI element class.
|
| + * @param {!Element} el The element to decorate.
|
| + */
|
| + f.decorate = function(el) {
|
| + el.__proto__ = f.prototype;
|
| + el.decorate();
|
| + };
|
| +
|
| + return f;
|
| + }
|
| +
|
| + /**
|
| + * Input elements do not grow and shrink with their content. This is a simple
|
| + * (and not very efficient) way of handling shrinking to content with support
|
| + * for min width and limited by the width of the parent element.
|
| + * @param {!HTMLElement} el The element to limit the width for.
|
| + * @param {!HTMLElement} parentEl The parent element that should limit the
|
| + * size.
|
| + * @param {number} min The minimum width.
|
| + * @param {number=} opt_scale Optional scale factor to apply to the width.
|
| + */
|
| + function limitInputWidth(el, parentEl, min, opt_scale) {
|
| + // Needs a size larger than borders
|
| + el.style.width = '10px';
|
| + var doc = el.ownerDocument;
|
| + var win = doc.defaultView;
|
| + var computedStyle = win.getComputedStyle(el);
|
| + var parentComputedStyle = win.getComputedStyle(parentEl);
|
| + var rtl = computedStyle.direction == 'rtl';
|
| +
|
| + // To get the max width we get the width of the treeItem minus the position
|
| + // of the input.
|
| + var inputRect = el.getBoundingClientRect(); // box-sizing
|
| + var parentRect = parentEl.getBoundingClientRect();
|
| + var startPos = rtl ? parentRect.right - inputRect.right :
|
| + inputRect.left - parentRect.left;
|
| +
|
| + // Add up border and padding of the input.
|
| + var inner = parseInt(computedStyle.borderLeftWidth, 10) +
|
| + parseInt(computedStyle.paddingLeft, 10) +
|
| + parseInt(computedStyle.paddingRight, 10) +
|
| + parseInt(computedStyle.borderRightWidth, 10);
|
| +
|
| + // We also need to subtract the padding of parent to prevent it to overflow.
|
| + var parentPadding = rtl ? parseInt(parentComputedStyle.paddingLeft, 10) :
|
| + parseInt(parentComputedStyle.paddingRight, 10);
|
| +
|
| + var max = parentEl.clientWidth - startPos - inner - parentPadding;
|
| + if (opt_scale)
|
| + max *= opt_scale;
|
| +
|
| + function limit() {
|
| + if (el.scrollWidth > max) {
|
| + el.style.width = max + 'px';
|
| + } else {
|
| + el.style.width = 0;
|
| + var sw = el.scrollWidth;
|
| + if (sw < min) {
|
| + el.style.width = min + 'px';
|
| + } else {
|
| + el.style.width = sw + 'px';
|
| + }
|
| + }
|
| + }
|
| +
|
| + el.addEventListener('input', limit);
|
| + limit();
|
| + }
|
| +
|
| + /**
|
| + * Takes a number and spits out a value CSS will be happy with. To avoid
|
| + * subpixel layout issues, the value is rounded to the nearest integral value.
|
| + * @param {number} pixels The number of pixels.
|
| + * @return {string} e.g. '16px'.
|
| + */
|
| + function toCssPx(pixels) {
|
| + if (!window.isFinite(pixels))
|
| + console.error('Pixel value is not a number: ' + pixels);
|
| + return Math.round(pixels) + 'px';
|
| + }
|
| +
|
| + /**
|
| + * Users complain they occasionaly use doubleclicks instead of clicks
|
| + * (http://crbug.com/140364). To fix it we freeze click handling for
|
| + * the doubleclick time interval.
|
| + * @param {MouseEvent} e Initial click event.
|
| + */
|
| + function swallowDoubleClick(e) {
|
| + var doc = e.target.ownerDocument;
|
| + var counter = Math.min(1, e.detail);
|
| + function swallow(e) {
|
| + e.stopPropagation();
|
| + e.preventDefault();
|
| + }
|
| + function onclick(e) {
|
| + if (e.detail > counter) {
|
| + counter = e.detail;
|
| + // Swallow the click since it's a click inside the doubleclick timeout.
|
| + swallow(e);
|
| + } else {
|
| + // Stop tracking clicks and let regular handling.
|
| + doc.removeEventListener('dblclick', swallow, true);
|
| + doc.removeEventListener('click', onclick, true);
|
| + }
|
| + }
|
| + // The following 'click' event (if e.type == 'mouseup') mustn't be taken
|
| + // into account (it mustn't stop tracking clicks). Start event listening
|
| + // after zero timeout.
|
| + setTimeout(function() {
|
| + doc.addEventListener('click', onclick, true);
|
| + doc.addEventListener('dblclick', swallow, true);
|
| + }, 0);
|
| + }
|
| +
|
| + return {
|
| + decorate: decorate,
|
| + define: define,
|
| + limitInputWidth: limitInputWidth,
|
| + toCssPx: toCssPx,
|
| + swallowDoubleClick: swallowDoubleClick
|
| + };
|
| +});
|
| +// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +/**
|
| + * @fileoverview A command is an abstraction of an action a user can do in the
|
| + * UI.
|
| + *
|
| + * When the focus changes in the document for each command a canExecute event
|
| + * is dispatched on the active element. By listening to this event you can
|
| + * enable and disable the command by setting the event.canExecute property.
|
| + *
|
| + * When a command is executed a command event is dispatched on the active
|
| + * element. Note that you should stop the propagation after you have handled the
|
| + * command if there might be other command listeners higher up in the DOM tree.
|
| + */
|
| +
|
| +cr.define('cr.ui', function() {
|
| +
|
| + /**
|
| + * This is used to identify keyboard shortcuts.
|
| + * @param {string} shortcut The text used to describe the keys for this
|
| + * keyboard shortcut.
|
| + * @constructor
|
| + */
|
| + function KeyboardShortcut(shortcut) {
|
| + var mods = {};
|
| + var ident = '';
|
| + shortcut.split('-').forEach(function(part) {
|
| + var partLc = part.toLowerCase();
|
| + switch (partLc) {
|
| + case 'alt':
|
| + case 'ctrl':
|
| + case 'meta':
|
| + case 'shift':
|
| + mods[partLc + 'Key'] = true;
|
| + break;
|
| + default:
|
| + if (ident)
|
| + throw Error('Invalid shortcut');
|
| + ident = part;
|
| + }
|
| + });
|
| +
|
| + this.ident_ = ident;
|
| + this.mods_ = mods;
|
| + }
|
| +
|
| + KeyboardShortcut.prototype = {
|
| + /**
|
| + * Whether the keyboard shortcut object matches a keyboard event.
|
| + * @param {!Event} e The keyboard event object.
|
| + * @return {boolean} Whether we found a match or not.
|
| + */
|
| + matchesEvent: function(e) {
|
| + if (e.keyIdentifier == this.ident_) {
|
| + // All keyboard modifiers needs to match.
|
| + var mods = this.mods_;
|
| + return ['altKey', 'ctrlKey', 'metaKey', 'shiftKey'].every(function(k) {
|
| + return e[k] == !!mods[k];
|
| + });
|
| + }
|
| + return false;
|
| + }
|
| + };
|
| +
|
| + /**
|
| + * Creates a new command element.
|
| + * @constructor
|
| + * @extends {HTMLElement}
|
| + */
|
| + var Command = cr.ui.define('command');
|
| +
|
| + Command.prototype = {
|
| + __proto__: HTMLElement.prototype,
|
| +
|
| + /**
|
| + * Initializes the command.
|
| + */
|
| + decorate: function() {
|
| + CommandManager.init(assert(this.ownerDocument));
|
| +
|
| + if (this.hasAttribute('shortcut'))
|
| + this.shortcut = this.getAttribute('shortcut');
|
| + },
|
| +
|
| + /**
|
| + * Executes the command by dispatching a command event on the given element.
|
| + * If |element| isn't given, the active element is used instead.
|
| + * If the command is {@code disabled} this does nothing.
|
| + * @param {HTMLElement=} opt_element Optional element to dispatch event on.
|
| + */
|
| + execute: function(opt_element) {
|
| + if (this.disabled)
|
| + return;
|
| + var doc = this.ownerDocument;
|
| + if (doc.activeElement) {
|
| + var e = new Event('command', {bubbles: true});
|
| + e.command = this;
|
| +
|
| + (opt_element || doc.activeElement).dispatchEvent(e);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Call this when there have been changes that might change whether the
|
| + * command can be executed or not.
|
| + * @param {Node=} opt_node Node for which to actuate command state.
|
| + */
|
| + canExecuteChange: function(opt_node) {
|
| + dispatchCanExecuteEvent(this,
|
| + opt_node || this.ownerDocument.activeElement);
|
| + },
|
| +
|
| + /**
|
| + * The keyboard shortcut that triggers the command. This is a string
|
| + * consisting of a keyIdentifier (as reported by WebKit in keydown) as
|
| + * well as optional key modifiers joinded with a '-'.
|
| + *
|
| + * Multiple keyboard shortcuts can be provided by separating them by
|
| + * whitespace.
|
| + *
|
| + * For example:
|
| + * "F1"
|
| + * "U+0008-Meta" for Apple command backspace.
|
| + * "U+0041-Ctrl" for Control A
|
| + * "U+007F U+0008-Meta" for Delete and Command Backspace
|
| + *
|
| + * @type {string}
|
| + */
|
| + shortcut_: '',
|
| + get shortcut() {
|
| + return this.shortcut_;
|
| + },
|
| + set shortcut(shortcut) {
|
| + var oldShortcut = this.shortcut_;
|
| + if (shortcut !== oldShortcut) {
|
| + this.keyboardShortcuts_ = shortcut.split(/\s+/).map(function(shortcut) {
|
| + return new KeyboardShortcut(shortcut);
|
| + });
|
| +
|
| + // Set this after the keyboardShortcuts_ since that might throw.
|
| + this.shortcut_ = shortcut;
|
| + cr.dispatchPropertyChange(this, 'shortcut', this.shortcut_,
|
| + oldShortcut);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Whether the event object matches the shortcut for this command.
|
| + * @param {!Event} e The key event object.
|
| + * @return {boolean} Whether it matched or not.
|
| + */
|
| + matchesEvent: function(e) {
|
| + if (!this.keyboardShortcuts_)
|
| + return false;
|
| +
|
| + return this.keyboardShortcuts_.some(function(keyboardShortcut) {
|
| + return keyboardShortcut.matchesEvent(e);
|
| + });
|
| + },
|
| + };
|
| +
|
| + /**
|
| + * The label of the command.
|
| + */
|
| + cr.defineProperty(Command, 'label', cr.PropertyKind.ATTR);
|
| +
|
| + /**
|
| + * Whether the command is disabled or not.
|
| + */
|
| + cr.defineProperty(Command, 'disabled', cr.PropertyKind.BOOL_ATTR);
|
| +
|
| + /**
|
| + * Whether the command is hidden or not.
|
| + */
|
| + cr.defineProperty(Command, 'hidden', cr.PropertyKind.BOOL_ATTR);
|
| +
|
| + /**
|
| + * Whether the command is checked or not.
|
| + */
|
| + cr.defineProperty(Command, 'checked', cr.PropertyKind.BOOL_ATTR);
|
| +
|
| + /**
|
| + * The flag that prevents the shortcut text from being displayed on menu.
|
| + *
|
| + * If false, the keyboard shortcut text (eg. "Ctrl+X" for the cut command)
|
| + * is displayed in menu when the command is assosiated with a menu item.
|
| + * Otherwise, no text is displayed.
|
| + */
|
| + cr.defineProperty(Command, 'hideShortcutText', cr.PropertyKind.BOOL_ATTR);
|
| +
|
| + /**
|
| + * Dispatches a canExecute event on the target.
|
| + * @param {!cr.ui.Command} command The command that we are testing for.
|
| + * @param {EventTarget} target The target element to dispatch the event on.
|
| + */
|
| + function dispatchCanExecuteEvent(command, target) {
|
| + var e = new CanExecuteEvent(command);
|
| + target.dispatchEvent(e);
|
| + command.disabled = !e.canExecute;
|
| + }
|
| +
|
| + /**
|
| + * The command managers for different documents.
|
| + */
|
| + var commandManagers = {};
|
| +
|
| + /**
|
| + * Keeps track of the focused element and updates the commands when the focus
|
| + * changes.
|
| + * @param {!Document} doc The document that we are managing the commands for.
|
| + * @constructor
|
| + */
|
| + function CommandManager(doc) {
|
| + doc.addEventListener('focus', this.handleFocus_.bind(this), true);
|
| + // Make sure we add the listener to the bubbling phase so that elements can
|
| + // prevent the command.
|
| + doc.addEventListener('keydown', this.handleKeyDown_.bind(this), false);
|
| + }
|
| +
|
| + /**
|
| + * Initializes a command manager for the document as needed.
|
| + * @param {!Document} doc The document to manage the commands for.
|
| + */
|
| + CommandManager.init = function(doc) {
|
| + var uid = cr.getUid(doc);
|
| + if (!(uid in commandManagers)) {
|
| + commandManagers[uid] = new CommandManager(doc);
|
| + }
|
| + };
|
| +
|
| + CommandManager.prototype = {
|
| +
|
| + /**
|
| + * Handles focus changes on the document.
|
| + * @param {Event} e The focus event object.
|
| + * @private
|
| + * @suppress {checkTypes}
|
| + * TODO(vitalyp): remove the suppression.
|
| + */
|
| + handleFocus_: function(e) {
|
| + var target = e.target;
|
| +
|
| + // Ignore focus on a menu button or command item.
|
| + if (target.menu || target.command)
|
| + return;
|
| +
|
| + var commands = Array.prototype.slice.call(
|
| + target.ownerDocument.querySelectorAll('command'));
|
| +
|
| + commands.forEach(function(command) {
|
| + dispatchCanExecuteEvent(command, target);
|
| + });
|
| + },
|
| +
|
| + /**
|
| + * Handles the keydown event and routes it to the right command.
|
| + * @param {!Event} e The keydown event.
|
| + */
|
| + handleKeyDown_: function(e) {
|
| + var target = e.target;
|
| + var commands = Array.prototype.slice.call(
|
| + target.ownerDocument.querySelectorAll('command'));
|
| +
|
| + for (var i = 0, command; command = commands[i]; i++) {
|
| + if (command.matchesEvent(e)) {
|
| + // When invoking a command via a shortcut, we have to manually check
|
| + // if it can be executed, since focus might not have been changed
|
| + // what would have updated the command's state.
|
| + command.canExecuteChange();
|
| +
|
| + if (!command.disabled) {
|
| + e.preventDefault();
|
| + // We do not want any other element to handle this.
|
| + e.stopPropagation();
|
| + command.execute();
|
| + return;
|
| + }
|
| + }
|
| + }
|
| + }
|
| + };
|
| +
|
| + /**
|
| + * The event type used for canExecute events.
|
| + * @param {!cr.ui.Command} command The command that we are evaluating.
|
| + * @extends {Event}
|
| + * @constructor
|
| + * @class
|
| + */
|
| + function CanExecuteEvent(command) {
|
| + var e = new Event('canExecute', {bubbles: true, cancelable: true});
|
| + e.__proto__ = CanExecuteEvent.prototype;
|
| + e.command = command;
|
| + return e;
|
| + }
|
| +
|
| + CanExecuteEvent.prototype = {
|
| + __proto__: Event.prototype,
|
| +
|
| + /**
|
| + * The current command
|
| + * @type {cr.ui.Command}
|
| + */
|
| + command: null,
|
| +
|
| + /**
|
| + * Whether the target can execute the command. Setting this also stops the
|
| + * propagation and prevents the default. Callers can tell if an event has
|
| + * been handled via |this.defaultPrevented|.
|
| + * @type {boolean}
|
| + */
|
| + canExecute_: false,
|
| + get canExecute() {
|
| + return this.canExecute_;
|
| + },
|
| + set canExecute(canExecute) {
|
| + this.canExecute_ = !!canExecute;
|
| + this.stopPropagation();
|
| + this.preventDefault();
|
| + }
|
| + };
|
| +
|
| + // Export
|
| + return {
|
| + Command: Command,
|
| + CanExecuteEvent: CanExecuteEvent
|
| + };
|
| +});
|
| +// Copyright (c) 2013 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +/**
|
| + * @fileoverview Assertion support.
|
| + */
|
| +
|
| +/**
|
| + * Verify |condition| is truthy and return |condition| if so.
|
| + * @template T
|
| + * @param {T} condition A condition to check for truthiness. Note that this
|
| + * may be used to test whether a value is defined or not, and we don't want
|
| + * to force a cast to Boolean.
|
| + * @param {string=} opt_message A message to show on failure.
|
| + * @return {T} A non-null |condition|.
|
| + */
|
| +function assert(condition, opt_message) {
|
| + 'use strict';
|
| + if (!condition) {
|
| + var msg = 'Assertion failed';
|
| + if (opt_message)
|
| + msg = msg + ': ' + opt_message;
|
| + throw new Error(msg);
|
| + }
|
| + return condition;
|
| +}
|
| +
|
| +/**
|
| + * Call this from places in the code that should never be reached.
|
| + *
|
| + * For example, handling all the values of enum with a switch() like this:
|
| + *
|
| + * function getValueFromEnum(enum) {
|
| + * switch (enum) {
|
| + * case ENUM_FIRST_OF_TWO:
|
| + * return first
|
| + * case ENUM_LAST_OF_TWO:
|
| + * return last;
|
| + * }
|
| + * assertNotReached();
|
| + * return document;
|
| + * }
|
| + *
|
| + * This code should only be hit in the case of serious programmer error or
|
| + * unexpected input.
|
| + *
|
| + * @param {string=} opt_message A message to show when this is hit.
|
| + */
|
| +function assertNotReached(opt_message) {
|
| + throw new Error(opt_message || 'Unreachable code hit');
|
| +}
|
| +
|
| +/**
|
| + * @param {*} value The value to check.
|
| + * @param {function(new: T, ...)} type A user-defined constructor.
|
| + * @param {string=} opt_message A message to show when this is hit.
|
| + * @return {T}
|
| + * @template T
|
| + */
|
| +function assertInstanceof(value, type, opt_message) {
|
| + if (!(value instanceof type)) {
|
| + throw new Error(opt_message ||
|
| + value + ' is not a[n] ' + (type.name || typeof type));
|
| + }
|
| + return value;
|
| +};
|
| +// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +/**
|
| + * @fileoverview EventTracker is a simple class that manages the addition and
|
| + * removal of DOM event listeners. In particular, it keeps track of all
|
| + * listeners that have been added and makes it easy to remove some or all of
|
| + * them without requiring all the information again. This is particularly handy
|
| + * when the listener is a generated function such as a lambda or the result of
|
| + * calling Function.bind.
|
| + */
|
| +
|
| +/**
|
| + * The type of the internal tracking entry. TODO(dbeam): move this back to
|
| + * EventTracker.Entry when https://github.com/google/closure-compiler/issues/544
|
| + * is fixed.
|
| + * @typedef {{target: !EventTarget,
|
| + * eventType: string,
|
| + * listener: Function,
|
| + * capture: boolean}}
|
| + */
|
| +var EventTrackerEntry;
|
| +
|
| +/**
|
| + * Create an EventTracker to track a set of events.
|
| + * EventTracker instances are typically tied 1:1 with other objects or
|
| + * DOM elements whose listeners should be removed when the object is disposed
|
| + * or the corresponding elements are removed from the DOM.
|
| + * @constructor
|
| + */
|
| +function EventTracker() {
|
| + /**
|
| + * @type {Array<EventTrackerEntry>}
|
| + * @private
|
| + */
|
| + this.listeners_ = [];
|
| +}
|
| +
|
| +EventTracker.prototype = {
|
| + /**
|
| + * Add an event listener - replacement for EventTarget.addEventListener.
|
| + * @param {!EventTarget} target The DOM target to add a listener to.
|
| + * @param {string} eventType The type of event to subscribe to.
|
| + * @param {EventListener|Function} listener The listener to add.
|
| + * @param {boolean=} opt_capture Whether to invoke during the capture phase.
|
| + */
|
| + add: function(target, eventType, listener, opt_capture) {
|
| + var capture = !!opt_capture;
|
| + var h = {
|
| + target: target,
|
| + eventType: eventType,
|
| + listener: listener,
|
| + capture: capture,
|
| + };
|
| + this.listeners_.push(h);
|
| + target.addEventListener(eventType, listener, capture);
|
| + },
|
| +
|
| + /**
|
| + * Remove any specified event listeners added with this EventTracker.
|
| + * @param {!EventTarget} target The DOM target to remove a listener from.
|
| + * @param {?string} eventType The type of event to remove.
|
| + */
|
| + remove: function(target, eventType) {
|
| + this.listeners_ = this.listeners_.filter(function(h) {
|
| + if (h.target == target && (!eventType || (h.eventType == eventType))) {
|
| + EventTracker.removeEventListener_(h);
|
| + return false;
|
| + }
|
| + return true;
|
| + });
|
| + },
|
| +
|
| + /**
|
| + * Remove all event listeners added with this EventTracker.
|
| + */
|
| + removeAll: function() {
|
| + this.listeners_.forEach(EventTracker.removeEventListener_);
|
| + this.listeners_ = [];
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * Remove a single event listener given it's tracking entry. It's up to the
|
| + * caller to ensure the entry is removed from listeners_.
|
| + * @param {EventTrackerEntry} h The entry describing the listener to remove.
|
| + * @private
|
| + */
|
| +EventTracker.removeEventListener_ = function(h) {
|
| + h.target.removeEventListener(h.eventType, h.listener, h.capture);
|
| +};
|
| +// Copyright 2014 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +cr.define('cr.ui', function() {
|
| + /**
|
| + * A class to manage grid of focusable elements in a 2D grid. For example,
|
| + * given this grid:
|
| + *
|
| + * focusable [focused] focusable (row: 0, col: 1)
|
| + * focusable focusable focusable
|
| + * focusable focusable focusable
|
| + *
|
| + * Pressing the down arrow would result in the focus moving down 1 row and
|
| + * keeping the same column:
|
| + *
|
| + * focusable focusable focusable
|
| + * focusable [focused] focusable (row: 1, col: 1)
|
| + * focusable focusable focusable
|
| + *
|
| + * And pressing right or tab at this point would move the focus to:
|
| + *
|
| + * focusable focusable focusable
|
| + * focusable focusable [focused] (row: 1, col: 2)
|
| + * focusable focusable focusable
|
| + *
|
| + * @constructor
|
| + * @implements {cr.ui.FocusRow.Delegate}
|
| + */
|
| + function FocusGrid() {
|
| + /** @type {!Array<!cr.ui.FocusRow>} */
|
| + this.rows = [];
|
| + }
|
| +
|
| + FocusGrid.prototype = {
|
| + /** @private {boolean} */
|
| + ignoreFocusChange_: false,
|
| +
|
| + /** @override */
|
| + onFocus: function(row, e) {
|
| + if (this.ignoreFocusChange_)
|
| + this.ignoreFocusChange_ = false;
|
| + else
|
| + this.lastFocused_ = e.currentTarget;
|
| +
|
| + this.rows.forEach(function(r) { r.makeActive(r == row); });
|
| + },
|
| +
|
| + /** @override */
|
| + onKeydown: function(row, e) {
|
| + var rowIndex = this.rows.indexOf(row);
|
| + assert(rowIndex >= 0);
|
| +
|
| + var newRow = -1;
|
| +
|
| + if (e.keyIdentifier == 'Up')
|
| + newRow = rowIndex - 1;
|
| + else if (e.keyIdentifier == 'Down')
|
| + newRow = rowIndex + 1;
|
| + else if (e.keyIdentifier == 'PageUp')
|
| + newRow = 0;
|
| + else if (e.keyIdentifier == 'PageDown')
|
| + newRow = this.rows.length - 1;
|
| +
|
| + var rowToFocus = this.rows[newRow];
|
| + if (rowToFocus) {
|
| + this.ignoreFocusChange_ = true;
|
| + rowToFocus.getEquivalentElement(this.lastFocused_).focus();
|
| + e.preventDefault();
|
| + return true;
|
| + }
|
| +
|
| + return false;
|
| + },
|
| +
|
| + /**
|
| + * Unregisters event handlers and removes all |this.rows|.
|
| + */
|
| + destroy: function() {
|
| + this.rows.forEach(function(row) { row.destroy(); });
|
| + this.rows.length = 0;
|
| + },
|
| +
|
| + /**
|
| + * @param {Node} target A target item to find in this grid.
|
| + * @return {number} The row index. -1 if not found.
|
| + */
|
| + getRowIndexForTarget: function(target) {
|
| + for (var i = 0; i < this.rows.length; ++i) {
|
| + if (this.rows[i].getElements().indexOf(target) >= 0)
|
| + return i;
|
| + }
|
| + return -1;
|
| + },
|
| +
|
| + /**
|
| + * @param {Element} root An element to search for.
|
| + * @return {?cr.ui.FocusRow} The row with root of |root| or null.
|
| + */
|
| + getRowForRoot: function(root) {
|
| + for (var i = 0; i < this.rows.length; ++i) {
|
| + if (this.rows[i].root == root)
|
| + return this.rows[i];
|
| + }
|
| + return null;
|
| + },
|
| +
|
| + /**
|
| + * Adds |row| to the end of this list.
|
| + * @param {!cr.ui.FocusRow} row The row that needs to be added to this grid.
|
| + */
|
| + addRow: function(row) {
|
| + this.addRowBefore(row, null);
|
| + },
|
| +
|
| + /**
|
| + * Adds |row| before |nextRow|. If |nextRow| is not in the list or it's
|
| + * null, |row| is added to the end.
|
| + * @param {!cr.ui.FocusRow} row The row that needs to be added to this grid.
|
| + * @param {cr.ui.FocusRow} nextRow The row that should follow |row|.
|
| + */
|
| + addRowBefore: function(row, nextRow) {
|
| + row.delegate = row.delegate || this;
|
| +
|
| + var nextRowIndex = this.rows.indexOf(nextRow);
|
| + if (nextRowIndex == -1)
|
| + this.rows.push(row);
|
| + else
|
| + this.rows.splice(nextRowIndex, 0, row);
|
| + },
|
| +
|
| + /**
|
| + * Removes a row from the focus row. No-op if row is not in the grid.
|
| + * @param {cr.ui.FocusRow} row The row that needs to be removed.
|
| + */
|
| + removeRow: function(row) {
|
| + var nextRowIndex = this.rows.indexOf(row);
|
| + if (nextRowIndex > -1)
|
| + this.rows.splice(nextRowIndex, 1);
|
| + },
|
| +
|
| + /**
|
| + * Makes sure that at least one row is active. Should be called once, after
|
| + * adding all rows to FocusGrid.
|
| + */
|
| + ensureRowActive: function() {
|
| + if (this.rows.length == 0)
|
| + return;
|
| +
|
| + for (var i = 0; i < this.rows.length; ++i) {
|
| + if (this.rows[i].isActive())
|
| + return;
|
| + }
|
| +
|
| + this.rows[0].makeActive(true);
|
| + },
|
| + };
|
| +
|
| + return {
|
| + FocusGrid: FocusGrid,
|
| + };
|
| +});
|
| +// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +// <include src="../../../../ui/webui/resources/js/assert.js">
|
| +
|
| +/**
|
| + * Alias for document.getElementById.
|
| + * @param {string} id The ID of the element to find.
|
| + * @return {HTMLElement} The found element or null if not found.
|
| + */
|
| +function $(id) {
|
| + return document.getElementById(id);
|
| +}
|
| +
|
| +/**
|
| + * Add an accessible message to the page that will be announced to
|
| + * users who have spoken feedback on, but will be invisible to all
|
| + * other users. It's removed right away so it doesn't clutter the DOM.
|
| + * @param {string} msg The text to be pronounced.
|
| + */
|
| +function announceAccessibleMessage(msg) {
|
| + var element = document.createElement('div');
|
| + element.setAttribute('aria-live', 'polite');
|
| + element.style.position = 'relative';
|
| + element.style.left = '-9999px';
|
| + element.style.height = '0px';
|
| + element.innerText = msg;
|
| + document.body.appendChild(element);
|
| + window.setTimeout(function() {
|
| + document.body.removeChild(element);
|
| + }, 0);
|
| +}
|
| +
|
| +/**
|
| + * Calls chrome.send with a callback and restores the original afterwards.
|
| + * @param {string} name The name of the message to send.
|
| + * @param {!Array} params The parameters to send.
|
| + * @param {string} callbackName The name of the function that the backend calls.
|
| + * @param {!Function} callback The function to call.
|
| + */
|
| +function chromeSend(name, params, callbackName, callback) {
|
| + var old = global[callbackName];
|
| + global[callbackName] = function() {
|
| + // restore
|
| + global[callbackName] = old;
|
| +
|
| + var args = Array.prototype.slice.call(arguments);
|
| + return callback.apply(global, args);
|
| + };
|
| + chrome.send(name, params);
|
| +}
|
| +
|
| +/**
|
| + * Returns the scale factors supported by this platform for webui
|
| + * resources.
|
| + * @return {Array} The supported scale factors.
|
| + */
|
| +function getSupportedScaleFactors() {
|
| + var supportedScaleFactors = [];
|
| + if (cr.isMac || cr.isChromeOS || cr.isWindows || cr.isLinux) {
|
| + // All desktop platforms support zooming which also updates the
|
| + // renderer's device scale factors (a.k.a devicePixelRatio), and
|
| + // these platforms has high DPI assets for 2.0x. Use 1x and 2x in
|
| + // image-set on these platforms so that the renderer can pick the
|
| + // closest image for the current device scale factor.
|
| + supportedScaleFactors.push(1);
|
| + supportedScaleFactors.push(2);
|
| + } else {
|
| + // For other platforms that use fixed device scale factor, use
|
| + // the window's device pixel ratio.
|
| + // TODO(oshima): Investigate if Android/iOS need to use image-set.
|
| + supportedScaleFactors.push(window.devicePixelRatio);
|
| + }
|
| + return supportedScaleFactors;
|
| +}
|
| +
|
| +/**
|
| + * Generates a CSS url string.
|
| + * @param {string} s The URL to generate the CSS url for.
|
| + * @return {string} The CSS url string.
|
| + */
|
| +function url(s) {
|
| + // http://www.w3.org/TR/css3-values/#uris
|
| + // Parentheses, commas, whitespace characters, single quotes (') and double
|
| + // quotes (") appearing in a URI must be escaped with a backslash
|
| + var s2 = s.replace(/(\(|\)|\,|\s|\'|\"|\\)/g, '\\$1');
|
| + // WebKit has a bug when it comes to URLs that end with \
|
| + // https://bugs.webkit.org/show_bug.cgi?id=28885
|
| + if (/\\\\$/.test(s2)) {
|
| + // Add a space to work around the WebKit bug.
|
| + s2 += ' ';
|
| + }
|
| + return 'url("' + s2 + '")';
|
| +}
|
| +
|
| +/**
|
| + * Returns the URL of the image, or an image set of URLs for the profile avatar.
|
| + * Default avatars have resources available for multiple scalefactors, whereas
|
| + * the GAIA profile image only comes in one size.
|
| + *
|
| + * @param {string} path The path of the image.
|
| + * @return {string} The url, or an image set of URLs of the avatar image.
|
| + */
|
| +function getProfileAvatarIcon(path) {
|
| + var chromeThemePath = 'chrome://theme';
|
| + var isDefaultAvatar =
|
| + (path.slice(0, chromeThemePath.length) == chromeThemePath);
|
| + return isDefaultAvatar ? imageset(path + '@scalefactorx'): url(path);
|
| +}
|
| +
|
| +/**
|
| + * Generates a CSS -webkit-image-set for a chrome:// url.
|
| + * An entry in the image set is added for each of getSupportedScaleFactors().
|
| + * The scale-factor-specific url is generated by replacing the first instance of
|
| + * 'scalefactor' in |path| with the numeric scale factor.
|
| + * @param {string} path The URL to generate an image set for.
|
| + * 'scalefactor' should be a substring of |path|.
|
| + * @return {string} The CSS -webkit-image-set.
|
| + */
|
| +function imageset(path) {
|
| + var supportedScaleFactors = getSupportedScaleFactors();
|
| +
|
| + var replaceStartIndex = path.indexOf('scalefactor');
|
| + if (replaceStartIndex < 0)
|
| + return url(path);
|
| +
|
| + var s = '';
|
| + for (var i = 0; i < supportedScaleFactors.length; ++i) {
|
| + var scaleFactor = supportedScaleFactors[i];
|
| + var pathWithScaleFactor = path.substr(0, replaceStartIndex) + scaleFactor +
|
| + path.substr(replaceStartIndex + 'scalefactor'.length);
|
| +
|
| + s += url(pathWithScaleFactor) + ' ' + scaleFactor + 'x';
|
| +
|
| + if (i != supportedScaleFactors.length - 1)
|
| + s += ', ';
|
| + }
|
| + return '-webkit-image-set(' + s + ')';
|
| +}
|
| +
|
| +/**
|
| + * Parses query parameters from Location.
|
| + * @param {Location} location The URL to generate the CSS url for.
|
| + * @return {Object} Dictionary containing name value pairs for URL
|
| + */
|
| +function parseQueryParams(location) {
|
| + var params = {};
|
| + var query = unescape(location.search.substring(1));
|
| + var vars = query.split('&');
|
| + for (var i = 0; i < vars.length; i++) {
|
| + var pair = vars[i].split('=');
|
| + params[pair[0]] = pair[1];
|
| + }
|
| + return params;
|
| +}
|
| +
|
| +/**
|
| + * Creates a new URL by appending or replacing the given query key and value.
|
| + * Not supporting URL with username and password.
|
| + * @param {Location} location The original URL.
|
| + * @param {string} key The query parameter name.
|
| + * @param {string} value The query parameter value.
|
| + * @return {string} The constructed new URL.
|
| + */
|
| +function setQueryParam(location, key, value) {
|
| + var query = parseQueryParams(location);
|
| + query[encodeURIComponent(key)] = encodeURIComponent(value);
|
| +
|
| + var newQuery = '';
|
| + for (var q in query) {
|
| + newQuery += (newQuery ? '&' : '?') + q + '=' + query[q];
|
| + }
|
| +
|
| + return location.origin + location.pathname + newQuery + location.hash;
|
| +}
|
| +
|
| +/**
|
| + * @param {Node} el A node to search for ancestors with |className|.
|
| + * @param {string} className A class to search for.
|
| + * @return {Element} A node with class of |className| or null if none is found.
|
| + */
|
| +function findAncestorByClass(el, className) {
|
| + return /** @type {Element} */(findAncestor(el, function(el) {
|
| + return el.classList && el.classList.contains(className);
|
| + }));
|
| +}
|
| +
|
| +/**
|
| + * Return the first ancestor for which the {@code predicate} returns true.
|
| + * @param {Node} node The node to check.
|
| + * @param {function(Node):boolean} predicate The function that tests the
|
| + * nodes.
|
| + * @return {Node} The found ancestor or null if not found.
|
| + */
|
| +function findAncestor(node, predicate) {
|
| + var last = false;
|
| + while (node != null && !(last = predicate(node))) {
|
| + node = node.parentNode;
|
| + }
|
| + return last ? node : null;
|
| +}
|
| +
|
| +function swapDomNodes(a, b) {
|
| + var afterA = a.nextSibling;
|
| + if (afterA == b) {
|
| + swapDomNodes(b, a);
|
| + return;
|
| + }
|
| + var aParent = a.parentNode;
|
| + b.parentNode.replaceChild(a, b);
|
| + aParent.insertBefore(b, afterA);
|
| +}
|
| +
|
| +/**
|
| + * Disables text selection and dragging, with optional whitelist callbacks.
|
| + * @param {function(Event):boolean=} opt_allowSelectStart Unless this function
|
| + * is defined and returns true, the onselectionstart event will be
|
| + * surpressed.
|
| + * @param {function(Event):boolean=} opt_allowDragStart Unless this function
|
| + * is defined and returns true, the ondragstart event will be surpressed.
|
| + */
|
| +function disableTextSelectAndDrag(opt_allowSelectStart, opt_allowDragStart) {
|
| + // Disable text selection.
|
| + document.onselectstart = function(e) {
|
| + if (!(opt_allowSelectStart && opt_allowSelectStart.call(this, e)))
|
| + e.preventDefault();
|
| + };
|
| +
|
| + // Disable dragging.
|
| + document.ondragstart = function(e) {
|
| + if (!(opt_allowDragStart && opt_allowDragStart.call(this, e)))
|
| + e.preventDefault();
|
| + };
|
| +}
|
| +
|
| +/**
|
| + * TODO(dbeam): DO NOT USE. THIS IS DEPRECATED. Use an action-link instead.
|
| + * Call this to stop clicks on <a href="#"> links from scrolling to the top of
|
| + * the page (and possibly showing a # in the link).
|
| + */
|
| +function preventDefaultOnPoundLinkClicks() {
|
| + document.addEventListener('click', function(e) {
|
| + var anchor = findAncestor(/** @type {Node} */(e.target), function(el) {
|
| + return el.tagName == 'A';
|
| + });
|
| + // Use getAttribute() to prevent URL normalization.
|
| + if (anchor && anchor.getAttribute('href') == '#')
|
| + e.preventDefault();
|
| + });
|
| +}
|
| +
|
| +/**
|
| + * Check the directionality of the page.
|
| + * @return {boolean} True if Chrome is running an RTL UI.
|
| + */
|
| +function isRTL() {
|
| + return document.documentElement.dir == 'rtl';
|
| +}
|
| +
|
| +/**
|
| + * Get an element that's known to exist by its ID. We use this instead of just
|
| + * calling getElementById and not checking the result because this lets us
|
| + * satisfy the JSCompiler type system.
|
| + * @param {string} id The identifier name.
|
| + * @return {!HTMLElement} the Element.
|
| + */
|
| +function getRequiredElement(id) {
|
| + return assertInstanceof($(id), HTMLElement,
|
| + 'Missing required element: ' + id);
|
| +}
|
| +
|
| +/**
|
| + * Query an element that's known to exist by a selector. We use this instead of
|
| + * just calling querySelector and not checking the result because this lets us
|
| + * satisfy the JSCompiler type system.
|
| + * @param {string} selectors CSS selectors to query the element.
|
| + * @param {(!Document|!DocumentFragment|!Element)=} opt_context An optional
|
| + * context object for querySelector.
|
| + * @return {!HTMLElement} the Element.
|
| + */
|
| +function queryRequiredElement(selectors, opt_context) {
|
| + var element = (opt_context || document).querySelector(selectors);
|
| + return assertInstanceof(element, HTMLElement,
|
| + 'Missing required element: ' + selectors);
|
| +}
|
| +
|
| +// Handle click on a link. If the link points to a chrome: or file: url, then
|
| +// call into the browser to do the navigation.
|
| +document.addEventListener('click', function(e) {
|
| + if (e.defaultPrevented)
|
| + return;
|
| +
|
| + var el = e.target;
|
| + if (el.nodeType == Node.ELEMENT_NODE &&
|
| + el.webkitMatchesSelector('A, A *')) {
|
| + while (el.tagName != 'A') {
|
| + el = el.parentElement;
|
| + }
|
| +
|
| + if ((el.protocol == 'file:' || el.protocol == 'about:') &&
|
| + (e.button == 0 || e.button == 1)) {
|
| + chrome.send('navigateToUrl', [
|
| + el.href,
|
| + el.target,
|
| + e.button,
|
| + e.altKey,
|
| + e.ctrlKey,
|
| + e.metaKey,
|
| + e.shiftKey
|
| + ]);
|
| + e.preventDefault();
|
| + }
|
| + }
|
| +});
|
| +
|
| +/**
|
| + * Creates a new URL which is the old URL with a GET param of key=value.
|
| + * @param {string} url The base URL. There is not sanity checking on the URL so
|
| + * it must be passed in a proper format.
|
| + * @param {string} key The key of the param.
|
| + * @param {string} value The value of the param.
|
| + * @return {string} The new URL.
|
| + */
|
| +function appendParam(url, key, value) {
|
| + var param = encodeURIComponent(key) + '=' + encodeURIComponent(value);
|
| +
|
| + if (url.indexOf('?') == -1)
|
| + return url + '?' + param;
|
| + return url + '&' + param;
|
| +}
|
| +
|
| +/**
|
| + * Creates a CSS -webkit-image-set for a favicon request.
|
| + * @param {string} url The url for the favicon.
|
| + * @param {number=} opt_size Optional preferred size of the favicon.
|
| + * @param {string=} opt_type Optional type of favicon to request. Valid values
|
| + * are 'favicon' and 'touch-icon'. Default is 'favicon'.
|
| + * @return {string} -webkit-image-set for the favicon.
|
| + */
|
| +function getFaviconImageSet(url, opt_size, opt_type) {
|
| + var size = opt_size || 16;
|
| + var type = opt_type || 'favicon';
|
| + return imageset(
|
| + 'chrome://' + type + '/size/' + size + '@scalefactorx/' + url);
|
| +}
|
| +
|
| +/**
|
| + * Creates a new URL for a favicon request for the current device pixel ratio.
|
| + * The URL must be updated when the user moves the browser to a screen with a
|
| + * different device pixel ratio. Use getFaviconImageSet() for the updating to
|
| + * occur automatically.
|
| + * @param {string} url The url for the favicon.
|
| + * @param {number=} opt_size Optional preferred size of the favicon.
|
| + * @param {string=} opt_type Optional type of favicon to request. Valid values
|
| + * are 'favicon' and 'touch-icon'. Default is 'favicon'.
|
| + * @return {string} Updated URL for the favicon.
|
| + */
|
| +function getFaviconUrlForCurrentDevicePixelRatio(url, opt_size, opt_type) {
|
| + var size = opt_size || 16;
|
| + var type = opt_type || 'favicon';
|
| + return 'chrome://' + type + '/size/' + size + '@' +
|
| + window.devicePixelRatio + 'x/' + url;
|
| +}
|
| +
|
| +/**
|
| + * Creates an element of a specified type with a specified class name.
|
| + * @param {string} type The node type.
|
| + * @param {string} className The class name to use.
|
| + * @return {Element} The created element.
|
| + */
|
| +function createElementWithClassName(type, className) {
|
| + var elm = document.createElement(type);
|
| + elm.className = className;
|
| + return elm;
|
| +}
|
| +
|
| +/**
|
| + * webkitTransitionEnd does not always fire (e.g. when animation is aborted
|
| + * or when no paint happens during the animation). This function sets up
|
| + * a timer and emulate the event if it is not fired when the timer expires.
|
| + * @param {!HTMLElement} el The element to watch for webkitTransitionEnd.
|
| + * @param {number} timeOut The maximum wait time in milliseconds for the
|
| + * webkitTransitionEnd to happen.
|
| + */
|
| +function ensureTransitionEndEvent(el, timeOut) {
|
| + var fired = false;
|
| + el.addEventListener('webkitTransitionEnd', function f(e) {
|
| + el.removeEventListener('webkitTransitionEnd', f);
|
| + fired = true;
|
| + });
|
| + window.setTimeout(function() {
|
| + if (!fired)
|
| + cr.dispatchSimpleEvent(el, 'webkitTransitionEnd', true);
|
| + }, timeOut);
|
| +}
|
| +
|
| +/**
|
| + * Alias for document.scrollTop getter.
|
| + * @param {!HTMLDocument} doc The document node where information will be
|
| + * queried from.
|
| + * @return {number} The Y document scroll offset.
|
| + */
|
| +function scrollTopForDocument(doc) {
|
| + return doc.documentElement.scrollTop || doc.body.scrollTop;
|
| +}
|
| +
|
| +/**
|
| + * Alias for document.scrollTop setter.
|
| + * @param {!HTMLDocument} doc The document node where information will be
|
| + * queried from.
|
| + * @param {number} value The target Y scroll offset.
|
| + */
|
| +function setScrollTopForDocument(doc, value) {
|
| + doc.documentElement.scrollTop = doc.body.scrollTop = value;
|
| +}
|
| +
|
| +/**
|
| + * Alias for document.scrollLeft getter.
|
| + * @param {!HTMLDocument} doc The document node where information will be
|
| + * queried from.
|
| + * @return {number} The X document scroll offset.
|
| + */
|
| +function scrollLeftForDocument(doc) {
|
| + return doc.documentElement.scrollLeft || doc.body.scrollLeft;
|
| +}
|
| +
|
| +/**
|
| + * Alias for document.scrollLeft setter.
|
| + * @param {!HTMLDocument} doc The document node where information will be
|
| + * queried from.
|
| + * @param {number} value The target X scroll offset.
|
| + */
|
| +function setScrollLeftForDocument(doc, value) {
|
| + doc.documentElement.scrollLeft = doc.body.scrollLeft = value;
|
| +}
|
| +
|
| +/**
|
| + * Replaces '&', '<', '>', '"', and ''' characters with their HTML encoding.
|
| + * @param {string} original The original string.
|
| + * @return {string} The string with all the characters mentioned above replaced.
|
| + */
|
| +function HTMLEscape(original) {
|
| + return original.replace(/&/g, '&')
|
| + .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 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +cr.define('downloads', function() {
|
| + /**
|
| + * @param {string} chromeSendName
|
| + * @return {function(string):void} A chrome.send() callback with curried name.
|
| + */
|
| + function chromeSendWithId(chromeSendName) {
|
| + return function(id) { chrome.send(chromeSendName, [id]); };
|
| + }
|
| +
|
| + /** @constructor */
|
| + function ActionService() {}
|
| +
|
| + ActionService.prototype = {
|
| + /** @param {string} id ID of the download to cancel. */
|
| + cancel: chromeSendWithId('cancel'),
|
| +
|
| + /** Instructs the browser to clear all finished downloads. */
|
| + clearAll: function() {
|
| + if (loadTimeData.getBoolean('allowDeletingHistory')) {
|
| + chrome.send('clearAll');
|
| + this.search('');
|
| + }
|
| + },
|
| +
|
| + /** @param {string} id ID of the dangerous download to discard. */
|
| + discardDangerous: chromeSendWithId('discardDangerous'),
|
| +
|
| + /** @param {string} url URL of a file to download. */
|
| + download: function(url) {
|
| + var a = document.createElement('a');
|
| + a.href = url;
|
| + a.setAttribute('download', '');
|
| + a.click();
|
| + },
|
| +
|
| + /** @param {string} id ID of the download that the user started dragging. */
|
| + drag: chromeSendWithId('drag'),
|
| +
|
| + /**
|
| + * @return {boolean} Whether the user is currently searching for downloads
|
| + * (i.e. has a non-empty search term).
|
| + */
|
| + isSearching: function() {
|
| + return this.searchText_.length > 0;
|
| + },
|
| +
|
| + /** Opens the current local destination for downloads. */
|
| + openDownloadsFolder: chrome.send.bind(chrome, 'openDownloadsFolder'),
|
| +
|
| + /**
|
| + * @param {string} id ID of the download to run locally on the user's box.
|
| + */
|
| + openFile: chromeSendWithId('openFile'),
|
| +
|
| + /** @param {string} id ID the of the progressing download to pause. */
|
| + pause: chromeSendWithId('pause'),
|
| +
|
| + /** @param {string} id ID of the finished download to remove. */
|
| + remove: chromeSendWithId('remove'),
|
| +
|
| + /** @param {string} id ID of the paused download to resume. */
|
| + resume: chromeSendWithId('resume'),
|
| +
|
| + /**
|
| + * @param {string} id ID of the dangerous download to save despite
|
| + * warnings.
|
| + */
|
| + saveDangerous: chromeSendWithId('saveDangerous'),
|
| +
|
| + /** @param {string} searchText What to search for. */
|
| + search: function(searchText) {
|
| + if (this.searchText_ == searchText)
|
| + return;
|
| +
|
| + this.searchText_ = searchText;
|
| +
|
| + // Split quoted terms (e.g., 'The "lazy" dog' => ['The', 'lazy', 'dog']).
|
| + function trim(s) { return s.trim(); }
|
| + chrome.send('getDownloads', searchText.split(/"([^"]*)"/).map(trim));
|
| + },
|
| +
|
| + /**
|
| + * Shows the local folder a finished download resides in.
|
| + * @param {string} id ID of the download to show.
|
| + */
|
| + show: chromeSendWithId('show'),
|
| +
|
| + /** Undo download removal. */
|
| + undo: chrome.send.bind(chrome, 'undo'),
|
| + };
|
| +
|
| + return {ActionService: ActionService};
|
| +});
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +cr.define('downloads', function() {
|
| + /**
|
| + * Explains why a download is in DANGEROUS state.
|
| + * @enum {string}
|
| + */
|
| + var DangerType = {
|
| + NOT_DANGEROUS: 'NOT_DANGEROUS',
|
| + DANGEROUS_FILE: 'DANGEROUS_FILE',
|
| + DANGEROUS_URL: 'DANGEROUS_URL',
|
| + DANGEROUS_CONTENT: 'DANGEROUS_CONTENT',
|
| + UNCOMMON_CONTENT: 'UNCOMMON_CONTENT',
|
| + DANGEROUS_HOST: 'DANGEROUS_HOST',
|
| + POTENTIALLY_UNWANTED: 'POTENTIALLY_UNWANTED',
|
| + };
|
| +
|
| + /**
|
| + * The states a download can be in. These correspond to states defined in
|
| + * DownloadsDOMHandler::CreateDownloadItemValue
|
| + * @enum {string}
|
| + */
|
| + var States = {
|
| + IN_PROGRESS: 'IN_PROGRESS',
|
| + CANCELLED: 'CANCELLED',
|
| + COMPLETE: 'COMPLETE',
|
| + PAUSED: 'PAUSED',
|
| + DANGEROUS: 'DANGEROUS',
|
| + INTERRUPTED: 'INTERRUPTED',
|
| + };
|
| +
|
| + return {
|
| + DangerType: DangerType,
|
| + States: States,
|
| + };
|
| +});
|
| +// Copyright 2014 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +cr.define('cr.ui', function() {
|
| + /**
|
| + * A class to manage focus between given horizontally arranged elements.
|
| + *
|
| + * Pressing left cycles backward and pressing right cycles forward in item
|
| + * order. Pressing Home goes to the beginning of the list and End goes to the
|
| + * end of the list.
|
| + *
|
| + * If an item in this row is focused, it'll stay active (accessible via tab).
|
| + * If no items in this row are focused, the row can stay active until focus
|
| + * changes to a node inside |this.boundary_|. If |boundary| isn't specified,
|
| + * any focus change deactivates the row.
|
| + *
|
| + * @param {!Element} root The root of this focus row. Focus classes are
|
| + * applied to |root| and all added elements must live within |root|.
|
| + * @param {?Node} boundary Focus events are ignored outside of this node.
|
| + * @param {cr.ui.FocusRow.Delegate=} opt_delegate An optional event delegate.
|
| + * @constructor
|
| + */
|
| + function FocusRow(root, boundary, opt_delegate) {
|
| + /** @type {!Element} */
|
| + this.root = root;
|
| +
|
| + /** @private {!Node} */
|
| + this.boundary_ = boundary || document;
|
| +
|
| + /** @type {cr.ui.FocusRow.Delegate|undefined} */
|
| + this.delegate = opt_delegate;
|
| +
|
| + /** @protected {!EventTracker} */
|
| + this.eventTracker = new EventTracker;
|
| + }
|
| +
|
| + /** @interface */
|
| + FocusRow.Delegate = function() {};
|
| +
|
| + FocusRow.Delegate.prototype = {
|
| + /**
|
| + * Called when a key is pressed while on a FocusRow's item. If true is
|
| + * returned, further processing is skipped.
|
| + * @param {!cr.ui.FocusRow} row The row that detected a keydown.
|
| + * @param {!Event} e
|
| + * @return {boolean} Whether the event was handled.
|
| + */
|
| + onKeydown: assertNotReached,
|
| +
|
| + /**
|
| + * @param {!cr.ui.FocusRow} row
|
| + * @param {!Event} e
|
| + */
|
| + onFocus: assertNotReached,
|
| + };
|
| +
|
| + /** @const {string} */
|
| + FocusRow.ACTIVE_CLASS = 'focus-row-active';
|
| +
|
| + /**
|
| + * Whether it's possible that |element| can be focused.
|
| + * @param {Element} element
|
| + * @return {boolean} Whether the item is focusable.
|
| + */
|
| + FocusRow.isFocusable = function(element) {
|
| + if (!element || element.disabled)
|
| + return false;
|
| +
|
| + // We don't check that element.tabIndex >= 0 here because inactive rows set
|
| + // a tabIndex of -1.
|
| +
|
| + function isVisible(element) {
|
| + assertInstanceof(element, Element);
|
| +
|
| + var style = window.getComputedStyle(element);
|
| + if (style.visibility == 'hidden' || style.display == 'none')
|
| + return false;
|
| +
|
| + var parent = element.parentNode;
|
| + if (!parent)
|
| + return false;
|
| +
|
| + if (parent == element.ownerDocument || parent instanceof DocumentFragment)
|
| + return true;
|
| +
|
| + return isVisible(parent);
|
| + }
|
| +
|
| + return isVisible(element);
|
| + };
|
| +
|
| + FocusRow.prototype = {
|
| + /**
|
| + * Register a new type of focusable element (or add to an existing one).
|
| + *
|
| + * Example: an (X) button might be 'delete' or 'close'.
|
| + *
|
| + * When FocusRow is used within a FocusGrid, these types are used to
|
| + * determine equivalent controls when Up/Down are pressed to change rows.
|
| + *
|
| + * Another example: mutually exclusive controls that hide eachother on
|
| + * activation (i.e. Play/Pause) could use the same type (i.e. 'play-pause')
|
| + * to indicate they're equivalent.
|
| + *
|
| + * @param {string} type The type of element to track focus of.
|
| + * @param {string} query The selector of the element from this row's root.
|
| + * @return {boolean} Whether a new item was added.
|
| + */
|
| + addItem: function(type, query) {
|
| + assert(type);
|
| +
|
| + var element = this.root.querySelector(query);
|
| + if (!element)
|
| + return false;
|
| +
|
| + element.setAttribute('focus-type', type);
|
| + element.tabIndex = this.isActive() ? 0 : -1;
|
| +
|
| + this.eventTracker.add(element, 'blur', this.onBlur_.bind(this));
|
| + this.eventTracker.add(element, 'focus', this.onFocus_.bind(this));
|
| + this.eventTracker.add(element, 'keydown', this.onKeydown_.bind(this));
|
| + this.eventTracker.add(element, 'mousedown',
|
| + this.onMousedown_.bind(this));
|
| + return true;
|
| + },
|
| +
|
| + /** Dereferences nodes and removes event handlers. */
|
| + destroy: function() {
|
| + this.eventTracker.removeAll();
|
| + },
|
| +
|
| + /**
|
| + * @param {Element} sampleElement An element for to find an equivalent for.
|
| + * @return {!Element} An equivalent element to focus for |sampleElement|.
|
| + * @protected
|
| + */
|
| + getCustomEquivalent: function(sampleElement) {
|
| + return assert(this.getFirstFocusable());
|
| + },
|
| +
|
| + /**
|
| + * @return {!Array<!Element>} All registered elements (regardless of
|
| + * focusability).
|
| + */
|
| + getElements: function() {
|
| + var elements = this.root.querySelectorAll('[focus-type]');
|
| + return Array.prototype.slice.call(elements);
|
| + },
|
| +
|
| + /**
|
| + * Find the element that best matches |sampleElement|.
|
| + * @param {!Element} sampleElement An element from a row of the same type
|
| + * which previously held focus.
|
| + * @return {!Element} The element that best matches sampleElement.
|
| + */
|
| + getEquivalentElement: function(sampleElement) {
|
| + if (this.getFocusableElements().indexOf(sampleElement) >= 0)
|
| + return sampleElement;
|
| +
|
| + var sampleFocusType = this.getTypeForElement(sampleElement);
|
| + if (sampleFocusType) {
|
| + var sameType = this.getFirstFocusable(sampleFocusType);
|
| + if (sameType)
|
| + return sameType;
|
| + }
|
| +
|
| + return this.getCustomEquivalent(sampleElement);
|
| + },
|
| +
|
| + /**
|
| + * @param {string=} opt_type An optional type to search for.
|
| + * @return {?Element} The first focusable element with |type|.
|
| + */
|
| + getFirstFocusable: function(opt_type) {
|
| + var filter = opt_type ? '="' + opt_type + '"' : '';
|
| + var elements = this.root.querySelectorAll('[focus-type' + filter + ']');
|
| + for (var i = 0; i < elements.length; ++i) {
|
| + if (cr.ui.FocusRow.isFocusable(elements[i]))
|
| + return elements[i];
|
| + }
|
| + return null;
|
| + },
|
| +
|
| + /** @return {!Array<!Element>} Registered, focusable elements. */
|
| + getFocusableElements: function() {
|
| + return this.getElements().filter(cr.ui.FocusRow.isFocusable);
|
| + },
|
| +
|
| + /**
|
| + * @param {!Element} element An element to determine a focus type for.
|
| + * @return {string} The focus type for |element| or '' if none.
|
| + */
|
| + getTypeForElement: function(element) {
|
| + return element.getAttribute('focus-type') || '';
|
| + },
|
| +
|
| + /** @return {boolean} Whether this row is currently active. */
|
| + isActive: function() {
|
| + return this.root.classList.contains(FocusRow.ACTIVE_CLASS);
|
| + },
|
| +
|
| + /**
|
| + * Enables/disables the tabIndex of the focusable elements in the FocusRow.
|
| + * tabIndex can be set properly.
|
| + * @param {boolean} active True if tab is allowed for this row.
|
| + */
|
| + makeActive: function(active) {
|
| + if (active == this.isActive())
|
| + return;
|
| +
|
| + this.getElements().forEach(function(element) {
|
| + element.tabIndex = active ? 0 : -1;
|
| + });
|
| +
|
| + this.root.classList.toggle(FocusRow.ACTIVE_CLASS, active);
|
| + },
|
| +
|
| + /**
|
| + * @param {!Event} e
|
| + * @private
|
| + */
|
| + onBlur_: function(e) {
|
| + if (!this.boundary_.contains(/** @type {Node} */(e.relatedTarget)))
|
| + return;
|
| +
|
| + if (this.getFocusableElements().indexOf(e.currentTarget) >= 0)
|
| + this.makeActive(false);
|
| + },
|
| +
|
| + /**
|
| + * @param {!Event} e
|
| + * @private
|
| + */
|
| + onFocus_: function(e) {
|
| + if (this.delegate)
|
| + this.delegate.onFocus(this, e);
|
| + },
|
| +
|
| + /**
|
| + * @param {!Event} e A mousedown event.
|
| + * @private
|
| + */
|
| + onMousedown_: function(e) {
|
| + // Only accept left mouse clicks.
|
| + if (e.button)
|
| + return;
|
| +
|
| + // Allow the element under the mouse cursor to be focusable.
|
| + if (!e.currentTarget.disabled)
|
| + e.currentTarget.tabIndex = 0;
|
| + },
|
| +
|
| + /**
|
| + * @param {Event} e The keydown event.
|
| + * @private
|
| + */
|
| + onKeydown_: function(e) {
|
| + var elements = this.getFocusableElements();
|
| + var elementIndex = elements.indexOf(e.currentTarget);
|
| + assert(elementIndex >= 0);
|
| +
|
| + if (this.delegate && this.delegate.onKeydown(this, e))
|
| + return;
|
| +
|
| + if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)
|
| + return;
|
| +
|
| + var index = -1;
|
| +
|
| + if (e.keyIdentifier == 'Left')
|
| + index = elementIndex + (isRTL() ? 1 : -1);
|
| + else if (e.keyIdentifier == 'Right')
|
| + index = elementIndex + (isRTL() ? -1 : 1);
|
| + else if (e.keyIdentifier == 'Home')
|
| + index = 0;
|
| + else if (e.keyIdentifier == 'End')
|
| + index = elements.length - 1;
|
| +
|
| + var elementToFocus = elements[index];
|
| + if (elementToFocus) {
|
| + this.getEquivalentElement(elementToFocus).focus();
|
| + e.preventDefault();
|
| + }
|
| + },
|
| + };
|
| +
|
| + return {
|
| + FocusRow: FocusRow,
|
| + };
|
| +});
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +cr.define('downloads', function() {
|
| + /**
|
| + * @param {!Element} root
|
| + * @param {?Node} boundary
|
| + * @constructor
|
| + * @extends {cr.ui.FocusRow}
|
| + */
|
| + function FocusRow(root, boundary) {
|
| + cr.ui.FocusRow.call(this, root, boundary);
|
| + this.addItems();
|
| + }
|
| +
|
| + FocusRow.prototype = {
|
| + __proto__: cr.ui.FocusRow.prototype,
|
| +
|
| + addItems: function() {
|
| + this.destroy();
|
| +
|
| + this.addItem('name-file-link',
|
| + 'content.is-active:not(.show-progress):not(.dangerous) #name');
|
| + assert(this.addItem('name-file-link', '#file-link'));
|
| + assert(this.addItem('url', '#url'));
|
| + this.addItem('show-retry', '#show');
|
| + this.addItem('show-retry', '#retry');
|
| + this.addItem('pause-resume', '#pause');
|
| + this.addItem('pause-resume', '#resume');
|
| + this.addItem('cancel', '#cancel');
|
| + this.addItem('controlled-by', '#controlled-by a');
|
| + this.addItem('danger-remove-discard', '#discard');
|
| + this.addItem('restore-save', '#save');
|
| + this.addItem('danger-remove-discard', '#danger-remove');
|
| + this.addItem('restore-save', '#restore');
|
| + assert(this.addItem('remove', '#remove'));
|
| +
|
| + // TODO(dbeam): it would be nice to do this asynchronously (so if multiple
|
| + // templates get rendered we only re-add once), but Manager#updateItem_()
|
| + // relies on the DOM being re-rendered synchronously.
|
| + this.eventTracker.add(this.root, 'dom-change', this.addItems.bind(this));
|
| + },
|
| + };
|
| +
|
| + return {FocusRow: FocusRow};
|
| +});
|
| +// Copyright 2014 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +// Action links are elements that are used to perform an in-page navigation or
|
| +// action (e.g. showing a dialog).
|
| +//
|
| +// They look like normal anchor (<a>) tags as their text color is blue. However,
|
| +// they're subtly different as they're not initially underlined (giving users a
|
| +// clue that underlined links navigate while action links don't).
|
| +//
|
| +// Action links look very similar to normal links when hovered (hand cursor,
|
| +// underlined). This gives the user an idea that clicking this link will do
|
| +// something similar to navigation but in the same page.
|
| +//
|
| +// They can be created in JavaScript like this:
|
| +//
|
| +// var link = document.createElement('a', 'action-link'); // Note second arg.
|
| +//
|
| +// or with a constructor like this:
|
| +//
|
| +// var link = new ActionLink();
|
| +//
|
| +// They can be used easily from HTML as well, like so:
|
| +//
|
| +// <a is="action-link">Click me!</a>
|
| +//
|
| +// NOTE: <action-link> and document.createElement('action-link') don't work.
|
| +
|
| +/**
|
| + * @constructor
|
| + * @extends {HTMLAnchorElement}
|
| + */
|
| +var ActionLink = document.registerElement('action-link', {
|
| + prototype: {
|
| + __proto__: HTMLAnchorElement.prototype,
|
| +
|
| + /** @this {ActionLink} */
|
| + createdCallback: function() {
|
| + // Action links can start disabled (e.g. <a is="action-link" disabled>).
|
| + this.tabIndex = this.disabled ? -1 : 0;
|
| +
|
| + if (!this.hasAttribute('role'))
|
| + this.setAttribute('role', 'link');
|
| +
|
| + this.addEventListener('keydown', function(e) {
|
| + if (!this.disabled && e.keyIdentifier == 'Enter') {
|
| + // Schedule a click asynchronously because other 'keydown' handlers
|
| + // may still run later (e.g. document.addEventListener('keydown')).
|
| + // Specifically options dialogs break when this timeout isn't here.
|
| + // NOTE: this affects the "trusted" state of the ensuing click. I
|
| + // haven't found anything that breaks because of this (yet).
|
| + window.setTimeout(this.click.bind(this), 0);
|
| + }
|
| + });
|
| +
|
| + function preventDefault(e) {
|
| + e.preventDefault();
|
| + }
|
| +
|
| + function removePreventDefault() {
|
| + document.removeEventListener('selectstart', preventDefault);
|
| + document.removeEventListener('mouseup', removePreventDefault);
|
| + }
|
| +
|
| + this.addEventListener('mousedown', function() {
|
| + // This handlers strives to match the behavior of <a href="...">.
|
| +
|
| + // While the mouse is down, prevent text selection from dragging.
|
| + document.addEventListener('selectstart', preventDefault);
|
| + document.addEventListener('mouseup', removePreventDefault);
|
| +
|
| + // If focus started via mouse press, don't show an outline.
|
| + if (document.activeElement != this)
|
| + this.classList.add('no-outline');
|
| + });
|
| +
|
| + this.addEventListener('blur', function() {
|
| + this.classList.remove('no-outline');
|
| + });
|
| + },
|
| +
|
| + /** @type {boolean} */
|
| + set disabled(disabled) {
|
| + if (disabled)
|
| + HTMLAnchorElement.prototype.setAttribute.call(this, 'disabled', '');
|
| + else
|
| + HTMLAnchorElement.prototype.removeAttribute.call(this, 'disabled');
|
| + this.tabIndex = disabled ? -1 : 0;
|
| + },
|
| + get disabled() {
|
| + return this.hasAttribute('disabled');
|
| + },
|
| +
|
| + /** @override */
|
| + setAttribute: function(attr, val) {
|
| + if (attr.toLowerCase() == 'disabled')
|
| + this.disabled = true;
|
| + else
|
| + HTMLAnchorElement.prototype.setAttribute.apply(this, arguments);
|
| + },
|
| +
|
| + /** @override */
|
| + removeAttribute: function(attr) {
|
| + if (attr.toLowerCase() == 'disabled')
|
| + this.disabled = false;
|
| + else
|
| + HTMLAnchorElement.prototype.removeAttribute.apply(this, arguments);
|
| + },
|
| + },
|
| +
|
| + extends: 'a',
|
| +});
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +/** @typedef {{img: HTMLImageElement, url: string}} */
|
| +var LoadIconRequest;
|
| +
|
| +cr.define('downloads', function() {
|
| + /**
|
| + * @param {number} maxAllowed The maximum number of simultaneous downloads
|
| + * allowed.
|
| + * @constructor
|
| + */
|
| + function ThrottledIconLoader(maxAllowed) {
|
| + assert(maxAllowed > 0);
|
| +
|
| + /** @private {number} */
|
| + this.maxAllowed_ = maxAllowed;
|
| +
|
| + /** @private {!Array<!LoadIconRequest>} */
|
| + this.requests_ = [];
|
| + }
|
| +
|
| + ThrottledIconLoader.prototype = {
|
| + /** @private {number} */
|
| + loading_: 0,
|
| +
|
| + /**
|
| + * Load the provided |url| into |img.src| after appending ?scale=.
|
| + * @param {!HTMLImageElement} img An <img> to show the loaded image in.
|
| + * @param {string} url A remote image URL to load.
|
| + */
|
| + loadScaledIcon: function(img, url) {
|
| + var scaledUrl = url + '?scale=' + window.devicePixelRatio + 'x';
|
| + if (img.src == scaledUrl)
|
| + return;
|
| +
|
| + this.requests_.push({img: img, url: scaledUrl});
|
| + this.loadNextIcon_();
|
| + },
|
| +
|
| + /** @private */
|
| + loadNextIcon_: function() {
|
| + if (this.loading_ > this.maxAllowed_ || !this.requests_.length)
|
| + return;
|
| +
|
| + var request = this.requests_.shift();
|
| + var img = request.img;
|
| +
|
| + img.onabort = img.onerror = img.onload = function() {
|
| + this.loading_--;
|
| + this.loadNextIcon_();
|
| + }.bind(this);
|
| +
|
| + this.loading_++;
|
| + img.src = request.url;
|
| + },
|
| + };
|
| +
|
| + return {ThrottledIconLoader: ThrottledIconLoader};
|
| +});
|
| +// Copyright 2014 Google Inc. All rights reserved.
|
| +//
|
| +// Licensed under the Apache License, Version 2.0 (the "License");
|
| +// you may not use this file except in compliance with the License.
|
| +// You may obtain a copy of the License at
|
| +//
|
| +// http://www.apache.org/licenses/LICENSE-2.0
|
| +//
|
| +// Unless required by applicable law or agreed to in writing, software
|
| +// distributed under the License is distributed on an "AS IS" BASIS,
|
| +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| +// See the License for the specific language governing permissions and
|
| +// limitations under the License.
|
| +
|
| +!function(a,b){b["true"]=a;var c={},d={},e={},f=null;!function(a){function b(a){if("number"==typeof a)return a;var b={};for(var c in a)b[c]=a[c];return b}function c(){this._delay=0,this._endDelay=0,this._fill="none",this._iterationStart=0,this._iterations=1,this._duration=0,this._playbackRate=1,this._direction="normal",this._easing="linear"}function d(b,d){var e=new c;return d&&(e.fill="both",e.duration="auto"),"number"!=typeof b||isNaN(b)?void 0!==b&&Object.getOwnPropertyNames(b).forEach(function(c){if("auto"!=b[c]){if(("number"==typeof e[c]||"duration"==c)&&("number"!=typeof b[c]||isNaN(b[c])))return;if("fill"==c&&-1==s.indexOf(b[c]))return;if("direction"==c&&-1==t.indexOf(b[c]))return;if("playbackRate"==c&&1!==b[c]&&a.isDeprecated("AnimationEffectTiming.playbackRate","2014-11-28","Use Animation.playbackRate instead."))return;e[c]=b[c]}}):e.duration=b,e}function e(a){return"number"==typeof a&&(a=isNaN(a)?{duration:0}:{duration:a}),a}function f(b,c){b=a.numericTimingToObject(b);var e=d(b,c);return e._easing=i(e.easing),e}function g(a,b,c,d){return 0>a||a>1||0>c||c>1?B:function(e){function f(a,b,c){return 3*a*(1-c)*(1-c)*c+3*b*(1-c)*c*c+c*c*c}if(0==e||1==e)return e;for(var g=0,h=1;;){var i=(g+h)/2,j=f(a,c,i);if(Math.abs(e-j)<.001)return f(b,d,i);e>j?g=i:h=i}}}function h(a,b){return function(c){if(c>=1)return 1;var d=1/a;return c+=b*d,c-c%d}}function i(a){var b=z.exec(a);if(b)return g.apply(this,b.slice(1).map(Number));var c=A.exec(a);if(c)return h(Number(c[1]),{start:u,middle:v,end:w}[c[2]]);var d=x[a];return d?d:B}function j(a){return Math.abs(k(a)/a.playbackRate)}function k(a){return a.duration*a.iterations}function l(a,b,c){return null==b?C:b<c.delay?D:b>=c.delay+a?E:F}function m(a,b,c,d,e){switch(d){case D:return"backwards"==b||"both"==b?0:null;case F:return c-e;case E:return"forwards"==b||"both"==b?a:null;case C:return null}}function n(a,b,c,d){return(d.playbackRate<0?b-a:b)*d.playbackRate+c}function o(a,b,c,d,e){return 1/0===c||c===-1/0||c-d==b&&e.iterations&&(e.iterations+e.iterationStart)%1==0?a:c%a}function p(a,b,c,d){return 0===c?0:b==a?d.iterationStart+d.iterations-1:Math.floor(c/a)}function q(a,b,c,d){var e=a%2>=1,f="normal"==d.direction||d.direction==(e?"alternate-reverse":"alternate"),g=f?c:b-c,h=g/b;return b*d.easing(h)}function r(a,b,c){var d=l(a,b,c),e=m(a,c.fill,b,d,c.delay);if(null===e)return null;if(0===a)return d===D?0:1;var f=c.iterationStart*c.duration,g=n(a,e,f,c),h=o(c.duration,k(c),g,f,c),i=p(c.duration,h,g,c);return q(i,c.duration,h,c)/c.duration}var s="backwards|forwards|both|none".split("|"),t="reverse|alternate|alternate-reverse".split("|");c.prototype={_setMember:function(b,c){this["_"+b]=c,this._effect&&(this._effect._timingInput[b]=c,this._effect._timing=a.normalizeTimingInput(a.normalizeTimingInput(this._effect._timingInput)),this._effect.activeDuration=a.calculateActiveDuration(this._effect._timing),this._effect._animation&&this._effect._animation._rebuildUnderlyingAnimation())},get playbackRate(){return this._playbackRate},set delay(a){this._setMember("delay",a)},get delay(){return this._delay},set endDelay(a){this._setMember("endDelay",a)},get endDelay(){return this._endDelay},set fill(a){this._setMember("fill",a)},get fill(){return this._fill},set iterationStart(a){this._setMember("iterationStart",a)},get iterationStart(){return this._iterationStart},set duration(a){this._setMember("duration",a)},get duration(){return this._duration},set direction(a){this._setMember("direction",a)},get direction(){return this._direction},set easing(a){this._setMember("easing",a)},get easing(){return this._easing},set iterations(a){this._setMember("iterations",a)},get iterations(){return this._iterations}};var u=1,v=.5,w=0,x={ease:g(.25,.1,.25,1),"ease-in":g(.42,0,1,1),"ease-out":g(0,0,.58,1),"ease-in-out":g(.42,0,.58,1),"step-start":h(1,u),"step-middle":h(1,v),"step-end":h(1,w)},y="\\s*(-?\\d+\\.?\\d*|-?\\.\\d+)\\s*",z=new RegExp("cubic-bezier\\("+y+","+y+","+y+","+y+"\\)"),A=/steps\(\s*(\d+)\s*,\s*(start|middle|end)\s*\)/,B=function(a){return a},C=0,D=1,E=2,F=3;a.cloneTimingInput=b,a.makeTiming=d,a.numericTimingToObject=e,a.normalizeTimingInput=f,a.calculateActiveDuration=j,a.calculateTimeFraction=r,a.calculatePhase=l,a.toTimingFunction=i}(c,f),function(a){function b(a,b){return a in h?h[a][b]||b:b}function c(a,c,d){var g=e[a];if(g){f.style[a]=c;for(var h in g){var i=g[h],j=f.style[i];d[i]=b(i,j)}}else d[a]=b(a,c)}function d(b){function d(){var a=e.length;null==e[a-1].offset&&(e[a-1].offset=1),a>1&&null==e[0].offset&&(e[0].offset=0);for(var b=0,c=e[0].offset,d=1;a>d;d++){var f=e[d].offset;if(null!=f){for(var g=1;d-b>g;g++)e[b+g].offset=c+(f-c)*g/(d-b);b=d,c=f}}}if(!Array.isArray(b)&&null!==b)throw new TypeError("Keyframes must be null or an array of keyframes");if(null==b)return[];for(var e=b.map(function(b){var d={};for(var e in b){var f=b[e];if("offset"==e){if(null!=f&&(f=Number(f),!isFinite(f)))throw new TypeError("keyframe offsets must be numbers.")}else{if("composite"==e)throw{type:DOMException.NOT_SUPPORTED_ERR,name:"NotSupportedError",message:"add compositing is not supported"};f="easing"==e?a.toTimingFunction(f):""+f}c(e,f,d)}return void 0==d.offset&&(d.offset=null),void 0==d.easing&&(d.easing=a.toTimingFunction("linear")),d}),f=!0,g=-1/0,h=0;h<e.length;h++){var i=e[h].offset;if(null!=i){if(g>i)throw{code:DOMException.INVALID_MODIFICATION_ERR,name:"InvalidModificationError",message:"Keyframes are not loosely sorted by offset. Sort or specify offsets."};g=i}else f=!1}return e=e.filter(function(a){return a.offset>=0&&a.offset<=1}),f||d(),e}var e={background:["backgroundImage","backgroundPosition","backgroundSize","backgroundRepeat","backgroundAttachment","backgroundOrigin","backgroundClip","backgroundColor"],border:["borderTopColor","borderTopStyle","borderTopWidth","borderRightColor","borderRightStyle","borderRightWidth","borderBottomColor","borderBottomStyle","borderBottomWidth","borderLeftColor","borderLeftStyle","borderLeftWidth"],borderBottom:["borderBottomWidth","borderBottomStyle","borderBottomColor"],borderColor:["borderTopColor","borderRightColor","borderBottomColor","borderLeftColor"],borderLeft:["borderLeftWidth","borderLeftStyle","borderLeftColor"],borderRadius:["borderTopLeftRadius","borderTopRightRadius","borderBottomRightRadius","borderBottomLeftRadius"],borderRight:["borderRightWidth","borderRightStyle","borderRightColor"],borderTop:["borderTopWidth","borderTopStyle","borderTopColor"],borderWidth:["borderTopWidth","borderRightWidth","borderBottomWidth","borderLeftWidth"],flex:["flexGrow","flexShrink","flexBasis"],font:["fontFamily","fontSize","fontStyle","fontVariant","fontWeight","lineHeight"],margin:["marginTop","marginRight","marginBottom","marginLeft"],outline:["outlineColor","outlineStyle","outlineWidth"],padding:["paddingTop","paddingRight","paddingBottom","paddingLeft"]},f=document.createElementNS("http://www.w3.org/1999/xhtml","div"),g={thin:"1px",medium:"3px",thick:"5px"},h={borderBottomWidth:g,borderLeftWidth:g,borderRightWidth:g,borderTopWidth:g,fontSize:{"xx-small":"60%","x-small":"75%",small:"89%",medium:"100%",large:"120%","x-large":"150%","xx-large":"200%"},fontWeight:{normal:"400",bold:"700"},outlineWidth:g,textShadow:{none:"0px 0px 0px transparent"},boxShadow:{none:"0px 0px 0px 0px transparent"}};a.normalizeKeyframes=d}(c,f),function(a){var b={};a.isDeprecated=function(a,c,d,e){var f=e?"are":"is",g=new Date,h=new Date(c);return h.setMonth(h.getMonth()+3),h>g?(a in b||console.warn("Web Animations: "+a+" "+f+" deprecated and will stop working on "+h.toDateString()+". "+d),b[a]=!0,!1):!0},a.deprecated=function(b,c,d,e){var f=e?"are":"is";if(a.isDeprecated(b,c,d,e))throw new Error(b+" "+f+" no longer supported. "+d)}}(c),function(){if(document.documentElement.animate){var a=document.documentElement.animate([],0),b=!0;if(a&&(b=!1,"play|currentTime|pause|reverse|playbackRate|cancel|finish|startTime|playState".split("|").forEach(function(c){void 0===a[c]&&(b=!0)})),!b)return}!function(a,b){function c(a){for(var b={},c=0;c<a.length;c++)for(var d in a[c])if("offset"!=d&&"easing"!=d&&"composite"!=d){var e={offset:a[c].offset,easing:a[c].easing,value:a[c][d]};b[d]=b[d]||[],b[d].push(e)}for(var f in b){var g=b[f];if(0!=g[0].offset||1!=g[g.length-1].offset)throw{type:DOMException.NOT_SUPPORTED_ERR,name:"NotSupportedError",message:"Partial keyframes are not supported"}}return b}function d(a){var c=[];for(var d in a)for(var e=a[d],f=0;f<e.length-1;f++){var g=e[f].offset,h=e[f+1].offset,i=e[f].value,j=e[f+1].value;g==h&&(1==h?i=j:j=i),c.push({startTime:g,endTime:h,easing:e[f].easing,property:d,interpolation:b.propertyInterpolation(d,i,j)})}return c.sort(function(a,b){return a.startTime-b.startTime}),c}b.convertEffectInput=function(e){var f=a.normalizeKeyframes(e),g=c(f),h=d(g);return function(a,c){if(null!=c)h.filter(function(a){return 0>=c&&0==a.startTime||c>=1&&1==a.endTime||c>=a.startTime&&c<=a.endTime}).forEach(function(d){var e=c-d.startTime,f=d.endTime-d.startTime,g=0==f?0:d.easing(e/f);b.apply(a,d.property,d.interpolation(g))});else for(var d in g)"offset"!=d&&"easing"!=d&&"composite"!=d&&b.clear(a,d)}}}(c,d,f),function(a){function b(a,b,c){e[c]=e[c]||[],e[c].push([a,b])}function c(a,c,d){for(var e=0;e<d.length;e++){var f=d[e];b(a,c,f),/-/.test(f)&&b(a,c,f.replace(/-(.)/g,function(a,b){return b.toUpperCase()}))}}function d(b,c,d){if("initial"==c||"initial"==d){var g=b.replace(/-(.)/g,function(a,b){return b.toUpperCase()});"initial"==c&&(c=f[g]),"initial"==d&&(d=f[g])}for(var h=c==d?[]:e[b],i=0;h&&i<h.length;i++){var j=h[i][0](c),k=h[i][0](d);if(void 0!==j&&void 0!==k){var l=h[i][1](j,k);if(l){var m=a.Interpolation.apply(null,l);return function(a){return 0==a?c:1==a?d:m(a)}}}}return a.Interpolation(!1,!0,function(a){return a?d:c})}var e={};a.addPropertiesHandler=c;var f={backgroundColor:"transparent",backgroundPosition:"0% 0%",borderBottomColor:"currentColor",borderBottomLeftRadius:"0px",borderBottomRightRadius:"0px",borderBottomWidth:"3px",borderLeftColor:"currentColor",borderLeftWidth:"3px",borderRightColor:"currentColor",borderRightWidth:"3px",borderSpacing:"2px",borderTopColor:"currentColor",borderTopLeftRadius:"0px",borderTopRightRadius:"0px",borderTopWidth:"3px",bottom:"auto",clip:"rect(0px, 0px, 0px, 0px)",color:"black",fontSize:"100%",fontWeight:"400",height:"auto",left:"auto",letterSpacing:"normal",lineHeight:"120%",marginBottom:"0px",marginLeft:"0px",marginRight:"0px",marginTop:"0px",maxHeight:"none",maxWidth:"none",minHeight:"0px",minWidth:"0px",opacity:"1.0",outlineColor:"invert",outlineOffset:"0px",outlineWidth:"3px",paddingBottom:"0px",paddingLeft:"0px",paddingRight:"0px",paddingTop:"0px",right:"auto",textIndent:"0px",textShadow:"0px 0px 0px transparent",top:"auto",transform:"",verticalAlign:"0px",visibility:"visible",width:"auto",wordSpacing:"normal",zIndex:"auto"};a.propertyInterpolation=d}(d,f),function(a,b){function c(b){var c=a.calculateActiveDuration(b),d=function(d){return a.calculateTimeFraction(c,d,b)};return d._totalDuration=b.delay+c+b.endDelay,d._isCurrent=function(d){var e=a.calculatePhase(c,d,b);return e===PhaseActive||e===PhaseBefore},d}b.KeyframeEffect=function(d,e,f){var g,h=c(a.normalizeTimingInput(f)),i=b.convertEffectInput(e),j=function(){i(d,g)};return j._update=function(a){return g=h(a),null!==g},j._clear=function(){i(d,null)},j._hasSameTarget=function(a){return d===a},j._isCurrent=h._isCurrent,j._totalDuration=h._totalDuration,j},b.NullEffect=function(a){var b=function(){a&&(a(),a=null)};return b._update=function(){return null},b._totalDuration=0,b._isCurrent=function(){return!1},b._hasSameTarget=function(){return!1},b}}(c,d,f),function(a){a.apply=function(b,c,d){b.style[a.propertyName(c)]=d},a.clear=function(b,c){b.style[a.propertyName(c)]=""}}(d,f),function(a){window.Element.prototype.animate=function(b,c){return a.timeline._play(a.KeyframeEffect(this,b,c))}}(d),function(a){function b(a,c,d){if("number"==typeof a&&"number"==typeof c)return a*(1-d)+c*d;if("boolean"==typeof a&&"boolean"==typeof c)return.5>d?a:c;if(a.length==c.length){for(var e=[],f=0;f<a.length;f++)e.push(b(a[f],c[f],d));return e}throw"Mismatched interpolation arguments "+a+":"+c}a.Interpolation=function(a,c,d){return function(e){return d(b(a,c,e))}}}(d,f),function(a,b){a.sequenceNumber=0;var c=function(a,b,c){this.target=a,this.currentTime=b,this.timelineTime=c,this.type="finish",this.bubbles=!1,this.cancelable=!1,this.currentTarget=a,this.defaultPrevented=!1,this.eventPhase=Event.AT_TARGET,this.timeStamp=Date.now()};b.Animation=function(b){this._sequenceNumber=a.sequenceNumber++,this._currentTime=0,this._startTime=null,this._paused=!1,this._playbackRate=1,this._inTimeline=!0,this._finishedFlag=!1,this.onfinish=null,this._finishHandlers=[],this._effect=b,this._inEffect=this._effect._update(0),this._idle=!0,this._currentTimePending=!1},b.Animation.prototype={_ensureAlive:function(){this._inEffect=this._effect._update(this.playbackRate<0&&0===this.currentTime?-1:this.currentTime),this._inTimeline||!this._inEffect&&this._finishedFlag||(this._inTimeline=!0,b.timeline._animations.push(this))},_tickCurrentTime:function(a,b){a!=this._currentTime&&(this._currentTime=a,this._isFinished&&!b&&(this._currentTime=this._playbackRate>0?this._totalDuration:0),this._ensureAlive())},get currentTime(){return this._idle||this._currentTimePending?null:this._currentTime},set currentTime(a){a=+a,isNaN(a)||(b.restart(),this._paused||null==this._startTime||(this._startTime=this._timeline.currentTime-a/this._playbackRate),this._currentTimePending=!1,this._currentTime!=a&&(this._tickCurrentTime(a,!0),b.invalidateEffects()))},get startTime(){return this._startTime},set startTime(a){a=+a,isNaN(a)||this._paused||this._idle||(this._startTime=a,this._tickCurrentTime((this._timeline.currentTime-this._startTime)*this.playbackRate),b.invalidateEffects())},get playbackRate(){return this._playbackRate},set playbackRate(a){if(a!=this._playbackRate){var b=this.currentTime;this._playbackRate=a,this._startTime=null,"paused"!=this.playState&&"idle"!=this.playState&&this.play(),null!=b&&(this.currentTime=b)}},get _isFinished(){return!this._idle&&(this._playbackRate>0&&this._currentTime>=this._totalDuration||this._playbackRate<0&&this._currentTime<=0)},get _totalDuration(){return this._effect._totalDuration},get playState(){return this._idle?"idle":null==this._startTime&&!this._paused&&0!=this.playbackRate||this._currentTimePending?"pending":this._paused?"paused":this._isFinished?"finished":"running"},play:function(){this._paused=!1,(this._isFinished||this._idle)&&(this._currentTime=this._playbackRate>0?0:this._totalDuration,this._startTime=null,b.invalidateEffects()),this._finishedFlag=!1,b.restart(),this._idle=!1,this._ensureAlive()},pause:function(){this._isFinished||this._paused||this._idle||(this._currentTimePending=!0),this._startTime=null,this._paused=!0},finish:function(){this._idle||(this.currentTime=this._playbackRate>0?this._totalDuration:0,this._startTime=this._totalDuration-this.currentTime,this._currentTimePending=!1)},cancel:function(){this._inEffect&&(this._inEffect=!1,this._idle=!0,this.currentTime=0,this._startTime=null,this._effect._update(null),b.invalidateEffects(),b.restart())},reverse:function(){this.playbackRate*=-1,this.play()},addEventListener:function(a,b){"function"==typeof b&&"finish"==a&&this._finishHandlers.push(b)},removeEventListener:function(a,b){if("finish"==a){var c=this._finishHandlers.indexOf(b);c>=0&&this._finishHandlers.splice(c,1)}},_fireEvents:function(a){var b=this._isFinished;if((b||this._idle)&&!this._finishedFlag){var d=new c(this,this._currentTime,a),e=this._finishHandlers.concat(this.onfinish?[this.onfinish]:[]);setTimeout(function(){e.forEach(function(a){a.call(d.target,d)})},0)}this._finishedFlag=b},_tick:function(a){return this._idle||this._paused||(null==this._startTime?this.startTime=a-this._currentTime/this.playbackRate:this._isFinished||this._tickCurrentTime((a-this._startTime)*this.playbackRate)),this._currentTimePending=!1,this._fireEvents(a),!this._idle&&(this._inEffect||!this._finishedFlag)}}}(c,d,f),function(a,b){function c(a){var b=i;i=[],a<s.currentTime&&(a=s.currentTime),g(a),b.forEach(function(b){b[1](a)}),o&&g(a),f(),l=void 0}function d(a,b){return a._sequenceNumber-b._sequenceNumber}function e(){this._animations=[],this.currentTime=window.performance&&performance.now?performance.now():0}function f(){p.forEach(function(a){a()}),p.length=0}function g(a){n=!1;var c=b.timeline;c.currentTime=a,c._animations.sort(d),m=!1;var e=c._animations;c._animations=[];var f=[],g=[];e=e.filter(function(b){return b._inTimeline=b._tick(a),b._inEffect?g.push(b._effect):f.push(b._effect),b._isFinished||b._paused||b._idle||(m=!0),b._inTimeline}),p.push.apply(p,f),p.push.apply(p,g),c._animations.push.apply(c._animations,e),o=!1,m&&requestAnimationFrame(function(){})}var h=window.requestAnimationFrame,i=[],j=0;window.requestAnimationFrame=function(a){var b=j++;return 0==i.length&&h(c),i.push([b,a]),b},window.cancelAnimationFrame=function(a){i.forEach(function(b){b[0]==a&&(b[1]=function(){})})},e.prototype={_play:function(c){c._timing=a.normalizeTimingInput(c.timing);var d=new b.Animation(c);return d._idle=!1,d._timeline=this,this._animations.push(d),b.restart(),b.invalidateEffects(),d}};var k,l=void 0,k=function(){return void 0==l&&(l=performance.now()),l},m=!1,n=!1;b.restart=function(){return m||(m=!0,requestAnimationFrame(function(){}),n=!0),n};var o=!1;b.invalidateEffects=function(){o=!0};var p=[],q=1e3/60,r=window.getComputedStyle;Object.defineProperty(window,"getComputedStyle",{configurable:!0,enumerable:!0,value:function(){if(o){var a=k();a-s.currentTime>0&&(s.currentTime+=q*(Math.floor((a-s.currentTime)/q)+1)),g(s.currentTime)}return f(),r.apply(this,arguments)}});var s=new e;b.timeline=s}(c,d,f),function(a){function b(a,b){var c=a.exec(b);return c?(c=a.ignoreCase?c[0].toLowerCase():c[0],[c,b.substr(c.length)]):void 0}function c(a,b){b=b.replace(/^\s*/,"");var c=a(b);return c?[c[0],c[1].replace(/^\s*/,"")]:void 0}function d(a,d,e){a=c.bind(null,a);for(var f=[];;){var g=a(e);if(!g)return[f,e];if(f.push(g[0]),e=g[1],g=b(d,e),!g||""==g[1])return[f,e];e=g[1]}}function e(a,b){for(var c=0,d=0;d<b.length&&(!/\s|,/.test(b[d])||0!=c);d++)if("("==b[d])c++;else if(")"==b[d]&&(c--,0==c&&d++,0>=c))break;var e=a(b.substr(0,d));return void 0==e?void 0:[e,b.substr(d)]}function f(a,b){for(var c=a,d=b;c&&d;)c>d?c%=d:d%=c;return c=a*b/(c+d)}function g(a){return function(b){var c=a(b);return c&&(c[0]=void 0),c}}function h(a,b){return function(c){var d=a(c);return d?d:[b,c]}}function i(b,c){for(var d=[],e=0;e<b.length;e++){var f=a.consumeTrimmed(b[e],c);if(!f||""==f[0])return;void 0!==f[0]&&d.push(f[0]),c=f[1]}return""==c?d:void 0}function j(a,b,c,d,e){for(var g=[],h=[],i=[],j=f(d.length,e.length),k=0;j>k;k++){var l=b(d[k%d.length],e[k%e.length]);if(!l)return;g.push(l[0]),h.push(l[1]),i.push(l[2])}return[g,h,function(b){var d=b.map(function(a,b){return i[b](a)}).join(c);return a?a(d):d}]}function k(a,b,c){for(var d=[],e=[],f=[],g=0,h=0;h<c.length;h++)if("function"==typeof c[h]){var i=c[h](a[g],b[g++]);d.push(i[0]),e.push(i[1]),f.push(i[2])}else!function(a){d.push(!1),e.push(!1),f.push(function(){return c[a]})}(h);return[d,e,function(a){for(var b="",c=0;c<a.length;c++)b+=f[c](a[c]);return b}]}a.consumeToken=b,a.consumeTrimmed=c,a.consumeRepeated=d,a.consumeParenthesised=e,a.ignore=g,a.optional=h,a.consumeList=i,a.mergeNestedRepeated=j.bind(null,null),a.mergeWrappedNestedRepeated=j,a.mergeList=k}(d),function(a){function b(b){function c(b){var c=a.consumeToken(/^inset/i,b);if(c)return d.inset=!0,c;var c=a.consumeLengthOrPercent(b);if(c)return d.lengths.push(c[0]),c;var c=a.consumeColor(b);return c?(d.color=c[0],c):void 0}var d={inset:!1,lengths:[],color:null},e=a.consumeRepeated(c,/^/,b);return e&&e[0].length?[d,e[1]]:void 0}function c(c){var d=a.consumeRepeated(b,/^,/,c);return d&&""==d[1]?d[0]:void 0}function d(b,c){for(;b.lengths.length<Math.max(b.lengths.length,c.lengths.length);)b.lengths.push({px:0});for(;c.lengths.length<Math.max(b.lengths.length,c.lengths.length);)c.lengths.push({px:0});if(b.inset==c.inset&&!!b.color==!!c.color){for(var d,e=[],f=[[],0],g=[[],0],h=0;h<b.lengths.length;h++){var i=a.mergeDimensions(b.lengths[h],c.lengths[h],2==h);f[0].push(i[0]),g[0].push(i[1]),e.push(i[2])}if(b.color&&c.color){var j=a.mergeColors(b.color,c.color);f[1]=j[0],g[1]=j[1],d=j[2]}return[f,g,function(a){for(var c=b.inset?"inset ":" ",f=0;f<e.length;f++)c+=e[f](a[0][f])+" ";return d&&(c+=d(a[1])),c}]}}function e(b,c,d,e){function f(a){return{inset:a,color:[0,0,0,0],lengths:[{px:0},{px:0},{px:0},{px:0}]}}for(var g=[],h=[],i=0;i<d.length||i<e.length;i++){var j=d[i]||f(e[i].inset),k=e[i]||f(d[i].inset);g.push(j),h.push(k)}return a.mergeNestedRepeated(b,c,g,h)}var f=e.bind(null,d,", ");a.addPropertiesHandler(c,f,["box-shadow","text-shadow"])}(d),function(a){function b(a){return a.toFixed(3).replace(".000","")}function c(a,b,c){return Math.min(b,Math.max(a,c))}function d(a){return/^\s*[-+]?(\d*\.)?\d+\s*$/.test(a)?Number(a):void 0}function e(a,c){return[a,c,b]}function f(a,b){return 0!=a?h(0,1/0)(a,b):void 0}function g(a,b){return[a,b,function(a){return Math.round(c(1,1/0,a))}]}function h(a,d){return function(e,f){return[e,f,function(e){return b(c(a,d,e))}]}}function i(a,b){return[a,b,Math.round]}a.clamp=c,a.addPropertiesHandler(d,h(0,1/0),["border-image-width","line-height"]),a.addPropertiesHandler(d,h(0,1),["opacity","shape-image-threshold"]),a.addPropertiesHandler(d,f,["flex-grow","flex-shrink"]),a.addPropertiesHandler(d,g,["orphans","widows"]),a.addPropertiesHandler(d,i,["z-index"]),a.parseNumber=d,a.mergeNumbers=e,a.numberToString=b}(d,f),function(a){function b(a,b){return"visible"==a||"visible"==b?[0,1,function(c){return 0>=c?a:c>=1?b:"visible"}]:void 0}a.addPropertiesHandler(String,b,["visibility"])}(d),function(a){function b(a){a=a.trim(),e.fillStyle="#000",e.fillStyle=a;var b=e.fillStyle;if(e.fillStyle="#fff",e.fillStyle=a,b==e.fillStyle){e.fillRect(0,0,1,1);var c=e.getImageData(0,0,1,1).data;e.clearRect(0,0,1,1);var d=c[3]/255;return[c[0]*d,c[1]*d,c[2]*d,d]}}function c(b,c){return[b,c,function(b){function c(a){return Math.max(0,Math.min(255,a))}if(b[3])for(var d=0;3>d;d++)b[d]=Math.round(c(b[d]/b[3]));return b[3]=a.numberToString(a.clamp(0,1,b[3])),"rgba("+b.join(",")+")"}]}var d=document.createElementNS("http://www.w3.org/1999/xhtml","canvas");d.width=d.height=1;var e=d.getContext("2d");a.addPropertiesHandler(b,c,["background-color","border-bottom-color","border-left-color","border-right-color","border-top-color","color","outline-color","text-decoration-color"]),a.consumeColor=a.consumeParenthesised.bind(null,b),a.mergeColors=c}(d,f),function(a,b){function c(a,b){if(b=b.trim().toLowerCase(),"0"==b&&"px".search(a)>=0)return{px:0};if(/^[^(]*$|^calc/.test(b)){b=b.replace(/calc\(/g,"(");var c={};b=b.replace(a,function(a){return c[a]=null,"U"+a});for(var d="U("+a.source+")",e=b.replace(/[-+]?(\d*\.)?\d+/g,"N").replace(new RegExp("N"+d,"g"),"D").replace(/\s[+-]\s/g,"O").replace(/\s/g,""),f=[/N\*(D)/g,/(N|D)[*/]N/g,/(N|D)O\1/g,/\((N|D)\)/g],g=0;g<f.length;)f[g].test(e)?(e=e.replace(f[g],"$1"),g=0):g++;if("D"==e){for(var h in c){var i=eval(b.replace(new RegExp("U"+h,"g"),"").replace(new RegExp(d,"g"),"*0"));if(!isFinite(i))return;c[h]=i}return c}}}function d(a,b){return e(a,b,!0)}function e(b,c,d){var e,f=[];for(e in b)f.push(e);for(e in c)f.indexOf(e)<0&&f.push(e);return b=f.map(function(a){return b[a]||0}),c=f.map(function(a){return c[a]||0}),[b,c,function(b){var c=b.map(function(c,e){return 1==b.length&&d&&(c=Math.max(c,0)),a.numberToString(c)+f[e]}).join(" + ");return b.length>1?"calc("+c+")":c}]}var f="px|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc",g=c.bind(null,new RegExp(f,"g")),h=c.bind(null,new RegExp(f+"|%","g")),i=c.bind(null,/deg|rad|grad|turn/g);a.parseLength=g,a.parseLengthOrPercent=h,a.consumeLengthOrPercent=a.consumeParenthesised.bind(null,h),a.parseAngle=i,a.mergeDimensions=e;var j=a.consumeParenthesised.bind(null,g),k=a.consumeRepeated.bind(void 0,j,/^/),l=a.consumeRepeated.bind(void 0,k,/^,/);a.consumeSizePairList=l;var m=function(a){var b=l(a);return b&&""==b[1]?b[0]:void 0},n=a.mergeNestedRepeated.bind(void 0,d," "),o=a.mergeNestedRepeated.bind(void 0,n,",");a.mergeNonNegativeSizePair=n,a.addPropertiesHandler(m,o,["background-size"]),a.addPropertiesHandler(h,d,["border-bottom-width","border-image-width","border-left-width","border-right-width","border-top-width","flex-basis","font-size","height","line-height","max-height","max-width","outline-width","width"]),a.addPropertiesHandler(h,e,["border-bottom-left-radius","border-bottom-right-radius","border-top-left-radius","border-top-right-radius","bottom","left","letter-spacing","margin-bottom","margin-left","margin-right","margin-top","min-height","min-width","outline-offset","padding-bottom","padding-left","padding-right","padding-top","perspective","right","shape-margin","text-indent","top","vertical-align","word-spacing"])}(d,f),function(a){function b(b){return a.consumeLengthOrPercent(b)||a.consumeToken(/^auto/,b)}function c(c){var d=a.consumeList([a.ignore(a.consumeToken.bind(null,/^rect/)),a.ignore(a.consumeToken.bind(null,/^\(/)),a.consumeRepeated.bind(null,b,/^,/),a.ignore(a.consumeToken.bind(null,/^\)/))],c);return d&&4==d[0].length?d[0]:void 0}function d(b,c){return"auto"==b||"auto"==c?[!0,!1,function(d){var e=d?b:c;if("auto"==e)return"auto";var f=a.mergeDimensions(e,e);return f[2](f[0])}]:a.mergeDimensions(b,c)}function e(a){return"rect("+a+")"}var f=a.mergeWrappedNestedRepeated.bind(null,e,d,", ");a.parseBox=c,a.mergeBoxes=f,a.addPropertiesHandler(c,f,["clip"])}(d,f),function(a){function b(a){return function(b){var c=0;return a.map(function(a){return a===j?b[c++]:a})}}function c(a){return a}function d(b){if(b=b.toLowerCase().trim(),"none"==b)return[];for(var c,d=/\s*(\w+)\(([^)]*)\)/g,e=[],f=0;c=d.exec(b);){if(c.index!=f)return;f=c.index+c[0].length;var g=c[1],h=m[g];if(!h)return;var i=c[2].split(","),j=h[0];if(j.length<i.length)return;for(var n=[],o=0;o<j.length;o++){var p,q=i[o],r=j[o];if(p=q?{A:function(b){return"0"==b.trim()?l:a.parseAngle(b)},N:a.parseNumber,T:a.parseLengthOrPercent,L:a.parseLength}[r.toUpperCase()](q):{a:l,n:n[0],t:k}[r],void 0===p)return;n.push(p)}if(e.push({t:g,d:n}),d.lastIndex==b.length)return e}}function e(a){return a.toFixed(6).replace(".000000","")}function f(b,c){if(b.decompositionPair!==c){b.decompositionPair=c;var d=a.makeMatrixDecomposition(b)}if(c.decompositionPair!==b){c.decompositionPair=b;var f=a.makeMatrixDecomposition(c)}return null==d[0]||null==f[0]?[[!1],[!0],function(a){return a?c[0].d:b[0].d}]:(d[0].push(0),f[0].push(1),[d,f,function(b){var c=a.quat(d[0][3],f[0][3],b[5]),g=a.composeMatrix(b[0],b[1],b[2],c,b[4]),h=g.map(e).join(",");return h}])}function g(a){return a.replace(/[xy]/,"")}function h(a){return a.replace(/(x|y|z|3d)?$/,"3d")}function i(b,c){var d=a.makeMatrixDecomposition&&!0,e=!1;if(!b.length||!c.length){b.length||(e=!0,b=c,c=[]);for(var i=0;i<b.length;i++){var j=b[i].t,k=b[i].d,l="scale"==j.substr(0,5)?1:0;c.push({t:j,d:k.map(function(a){if("number"==typeof a)return l;var b={};for(var c in a)b[c]=l;return b})})}}var n=function(a,b){return"perspective"==a&&"perspective"==b||("matrix"==a||"matrix3d"==a)&&("matrix"==b||"matrix3d"==b)},o=[],p=[],q=[];if(b.length!=c.length){if(!d)return;var r=f(b,c);o=[r[0]],p=[r[1]],q=[["matrix",[r[2]]]]}else for(var i=0;i<b.length;i++){var j,s=b[i].t,t=c[i].t,u=b[i].d,v=c[i].d,w=m[s],x=m[t];if(n(s,t)){if(!d)return;var r=f([b[i]],[c[i]]);o.push(r[0]),p.push(r[1]),q.push(["matrix",[r[2]]])}else{if(s==t)j=s;else if(w[2]&&x[2]&&g(s)==g(t))j=g(s),u=w[2](u),v=x[2](v);else{if(!w[1]||!x[1]||h(s)!=h(t)){if(!d)return;var r=f(b,c);o=[r[0]],p=[r[1]],q=[["matrix",[r[2]]]];break}j=h(s),u=w[1](u),v=x[1](v)}for(var y=[],z=[],A=[],B=0;B<u.length;B++){var C="number"==typeof u[B]?a.mergeNumbers:a.mergeDimensions,r=C(u[B],v[B]);y[B]=r[0],z[B]=r[1],A.push(r[2])}o.push(y),p.push(z),q.push([j,A])}}if(e){var D=o;o=p,p=D}return[o,p,function(a){return a.map(function(a,b){var c=a.map(function(a,c){return q[b][1][c](a)}).join(",");return"matrix"==q[b][0]&&16==c.split(",").length&&(q[b][0]="matrix3d"),q[b][0]+"("+c+")"}).join(" ")}]}var j=null,k={px:0},l={deg:0},m={matrix:["NNNNNN",[j,j,0,0,j,j,0,0,0,0,1,0,j,j,0,1],c],matrix3d:["NNNNNNNNNNNNNNNN",c],rotate:["A"],rotatex:["A"],rotatey:["A"],rotatez:["A"],rotate3d:["NNNA"],perspective:["L"],scale:["Nn",b([j,j,1]),c],scalex:["N",b([j,1,1]),b([j,1])],scaley:["N",b([1,j,1]),b([1,j])],scalez:["N",b([1,1,j])],scale3d:["NNN",c],skew:["Aa",null,c],skewx:["A",null,b([j,l])],skewy:["A",null,b([l,j])],translate:["Tt",b([j,j,k]),c],translatex:["T",b([j,k,k]),b([j,k])],translatey:["T",b([k,j,k]),b([k,j])],translatez:["L",b([k,k,j])],translate3d:["TTL",c]};a.addPropertiesHandler(d,i,["transform"])}(d,f),function(a){function b(a,b){b.concat([a]).forEach(function(b){b in document.documentElement.style&&(c[a]=b)})}var c={};b("transform",["webkitTransform","msTransform"]),b("transformOrigin",["webkitTransformOrigin"]),b("perspective",["webkitPerspective"]),b("perspectiveOrigin",["webkitPerspectiveOrigin"]),a.propertyName=function(a){return c[a]||a}}(d,f)}(),!function(a,b){function c(a){var b=window.document.timeline;b.currentTime=a,b._discardAnimations(),0==b._animations.length?e=!1:requestAnimationFrame(c)}var d=window.requestAnimationFrame;window.requestAnimationFrame=function(a){return d(function(b){window.document.timeline._updateAnimationsPromises(),a(b),window.document.timeline._updateAnimationsPromises()})},b.AnimationTimeline=function(){this._animations=[],this.currentTime=void 0},b.AnimationTimeline.prototype={getAnimations:function(){return this._discardAnimations(),this._animations.slice()},_updateAnimationsPromises:function(){b.animationsWithPromises=b.animationsWithPromises.filter(function(a){return a._updatePromises()})},_discardAnimations:function(){this._updateAnimationsPromises(),this._animations=this._animations.filter(function(a){return"finished"!=a.playState&&"idle"!=a.playState})},_play:function(a){var c=new b.Animation(a,this);return this._animations.push(c),b.restartWebAnimationsNextTick(),c._updatePromises(),c._animation.play(),c._updatePromises(),c},play:function(a){return a&&a.remove(),this._play(a)}};var e=!1;b.restartWebAnimationsNextTick=function(){e||(e=!0,requestAnimationFrame(c))};var f=new b.AnimationTimeline;b.timeline=f;try{Object.defineProperty(window.document,"timeline",{configurable:!0,get:function(){return f}})}catch(g){}try{window.document.timeline=f}catch(g){}}(c,e,f),function(a,b){b.animationsWithPromises=[],b.Animation=function(b,c){if(this.effect=b,b&&(b._animation=this),!c)throw new Error("Animation with null timeline is not supported");this._timeline=c,this._sequenceNumber=a.sequenceNumber++,this._holdTime=0,this._paused=!1,this._isGroup=!1,this._animation=null,this._childAnimations=[],this._callback=null,this._oldPlayState="idle",this._rebuildUnderlyingAnimation(),this._animation.cancel(),this._updatePromises()},b.Animation.prototype={_updatePromises:function(){var a=this._oldPlayState,b=this.playState;return this._readyPromise&&b!==a&&("idle"==b?(this._rejectReadyPromise(),this._readyPromise=void 0):"pending"==a?this._resolveReadyPromise():"pending"==b&&(this._readyPromise=void 0)),this._finishedPromise&&b!==a&&("idle"==b?(this._rejectFinishedPromise(),this._finishedPromise=void 0):"finished"==b?this._resolveFinishedPromise():"finished"==a&&(this._finishedPromise=void 0)),this._oldPlayState=this.playState,this._readyPromise||this._finishedPromise},_rebuildUnderlyingAnimation:function(){this._updatePromises();var a,c,d,e,f=this._animation?!0:!1;f&&(a=this.playbackRate,c=this._paused,d=this.startTime,e=this.currentTime,this._animation.cancel(),this._animation._wrapper=null,this._animation=null),(!this.effect||this.effect instanceof window.KeyframeEffect)&&(this._animation=b.newUnderlyingAnimationForKeyframeEffect(this.effect),b.bindAnimationForKeyframeEffect(this)),(this.effect instanceof window.SequenceEffect||this.effect instanceof window.GroupEffect)&&(this._animation=b.newUnderlyingAnimationForGroup(this.effect),b.bindAnimationForGroup(this)),this.effect&&this.effect._onsample&&b.bindAnimationForCustomEffect(this),f&&(1!=a&&(this.playbackRate=a),null!==d?this.startTime=d:null!==e?this.currentTime=e:null!==this._holdTime&&(this.currentTime=this._holdTime),c&&this.pause()),this._updatePromises()
|
| +},_updateChildren:function(){if(this.effect&&"idle"!=this.playState){var a=this.effect._timing.delay;this._childAnimations.forEach(function(c){this._arrangeChildren(c,a),this.effect instanceof window.SequenceEffect&&(a+=b.groupChildDuration(c.effect))}.bind(this))}},_setExternalAnimation:function(a){if(this.effect&&this._isGroup)for(var b=0;b<this.effect.children.length;b++)this.effect.children[b]._animation=a,this._childAnimations[b]._setExternalAnimation(a)},_constructChildAnimations:function(){if(this.effect&&this._isGroup){var a=this.effect._timing.delay;this._removeChildAnimations(),this.effect.children.forEach(function(c){var d=window.document.timeline._play(c);this._childAnimations.push(d),d.playbackRate=this.playbackRate,this._paused&&d.pause(),c._animation=this.effect._animation,this._arrangeChildren(d,a),this.effect instanceof window.SequenceEffect&&(a+=b.groupChildDuration(c))}.bind(this))}},_arrangeChildren:function(a,b){null===this.startTime?a.currentTime=this.currentTime-b/this.playbackRate:a.startTime!==this.startTime+b/this.playbackRate&&(a.startTime=this.startTime+b/this.playbackRate)},get timeline(){return this._timeline},get playState(){return this._animation?this._animation.playState:"idle"},get finished(){return window.Promise?(this._finishedPromise||(-1==b.animationsWithPromises.indexOf(this)&&b.animationsWithPromises.push(this),this._finishedPromise=new Promise(function(a,b){this._resolveFinishedPromise=function(){a(this)},this._rejectFinishedPromise=function(){b({type:DOMException.ABORT_ERR,name:"AbortError"})}}.bind(this)),"finished"==this.playState&&this._resolveFinishedPromise()),this._finishedPromise):(console.warn("Animation Promises require JavaScript Promise constructor"),null)},get ready(){return window.Promise?(this._readyPromise||(-1==b.animationsWithPromises.indexOf(this)&&b.animationsWithPromises.push(this),this._readyPromise=new Promise(function(a,b){this._resolveReadyPromise=function(){a(this)},this._rejectReadyPromise=function(){b({type:DOMException.ABORT_ERR,name:"AbortError"})}}.bind(this)),"pending"!==this.playState&&this._resolveReadyPromise()),this._readyPromise):(console.warn("Animation Promises require JavaScript Promise constructor"),null)},get onfinish(){return this._onfinish},set onfinish(a){"function"==typeof a?(this._onfinish=a,this._animation.onfinish=function(b){b.target=this,a.call(this,b)}.bind(this)):(this._animation.onfinish=a,this.onfinish=this._animation.onfinish)},get currentTime(){this._updatePromises();var a=this._animation.currentTime;return this._updatePromises(),a},set currentTime(a){this._updatePromises(),this._animation.currentTime=isFinite(a)?a:Math.sign(a)*Number.MAX_VALUE,this._register(),this._forEachChild(function(b,c){b.currentTime=a-c}),this._updatePromises()},get startTime(){return this._animation.startTime},set startTime(a){this._updatePromises(),this._animation.startTime=isFinite(a)?a:Math.sign(a)*Number.MAX_VALUE,this._register(),this._forEachChild(function(b,c){b.startTime=a+c}),this._updatePromises()},get playbackRate(){return this._animation.playbackRate},set playbackRate(a){this._updatePromises();var b=this.currentTime;this._animation.playbackRate=a,this._forEachChild(function(b){b.playbackRate=a}),"paused"!=this.playState&&"idle"!=this.playState&&this.play(),null!==b&&(this.currentTime=b),this._updatePromises()},play:function(){this._updatePromises(),this._paused=!1,this._animation.play(),-1==this._timeline._animations.indexOf(this)&&this._timeline._animations.push(this),this._register(),b.awaitStartTime(this),this._forEachChild(function(a){var b=a.currentTime;a.play(),a.currentTime=b}),this._updatePromises()},pause:function(){this._updatePromises(),this.currentTime&&(this._holdTime=this.currentTime),this._animation.pause(),this._register(),this._forEachChild(function(a){a.pause()}),this._paused=!0,this._updatePromises()},finish:function(){this._updatePromises(),this._animation.finish(),this._register(),this._updatePromises()},cancel:function(){this._updatePromises(),this._animation.cancel(),this._register(),this._removeChildAnimations(),this._updatePromises()},reverse:function(){this._updatePromises();var a=this.currentTime;this._animation.reverse(),this._forEachChild(function(a){a.reverse()}),null!==a&&(this.currentTime=a),this._updatePromises()},addEventListener:function(a,b){var c=b;"function"==typeof b&&(c=function(a){a.target=this,b.call(this,a)}.bind(this),b._wrapper=c),this._animation.addEventListener(a,c)},removeEventListener:function(a,b){this._animation.removeEventListener(a,b&&b._wrapper||b)},_removeChildAnimations:function(){for(;this._childAnimations.length;)this._childAnimations.pop().cancel()},_forEachChild:function(b){var c=0;if(this.effect.children&&this._childAnimations.length<this.effect.children.length&&this._constructChildAnimations(),this._childAnimations.forEach(function(a){b.call(this,a,c),this.effect instanceof window.SequenceEffect&&(c+=a.effect.activeDuration)}.bind(this)),"pending"!=this.playState){var d=this.effect._timing,e=this.currentTime;null!==e&&(e=a.calculateTimeFraction(a.calculateActiveDuration(d),e,d)),(null==e||isNaN(e))&&this._removeChildAnimations()}}},window.Animation=b.Animation}(c,e,f),function(a,b){function c(b){this._frames=a.normalizeKeyframes(b)}function d(){for(var a=!1;h.length;){var b=h.shift();b._updateChildren(),a=!0}return a}var e=function(a){if(a._animation=void 0,a instanceof window.SequenceEffect||a instanceof window.GroupEffect)for(var b=0;b<a.children.length;b++)e(a.children[b])};b.removeMulti=function(a){for(var b=[],c=0;c<a.length;c++){var d=a[c];d._parent?(-1==b.indexOf(d._parent)&&b.push(d._parent),d._parent.children.splice(d._parent.children.indexOf(d),1),d._parent=null,e(d)):d._animation&&d._animation.effect==d&&(d._animation.cancel(),d._animation.effect=new KeyframeEffect(null,[]),d._animation._callback&&(d._animation._callback._animation=null),d._animation._rebuildUnderlyingAnimation(),e(d))}for(c=0;c<b.length;c++)b[c]._rebuild()},b.KeyframeEffect=function(b,d,e){return this.target=b,this._parent=null,e=a.numericTimingToObject(e),this._timingInput=a.cloneTimingInput(e),this._timing=a.normalizeTimingInput(e),this.timing=a.makeTiming(e,!1,this),this.timing._effect=this,"function"==typeof d?(a.deprecated("Custom KeyframeEffect","2015-06-22","Use KeyframeEffect.onsample instead."),this._normalizedKeyframes=d):this._normalizedKeyframes=new c(d),this._keyframes=d,this.activeDuration=a.calculateActiveDuration(this._timing),this},b.KeyframeEffect.prototype={getFrames:function(){return"function"==typeof this._normalizedKeyframes?this._normalizedKeyframes:this._normalizedKeyframes._frames},set onsample(a){if("function"==typeof this.getFrames())throw new Error("Setting onsample on custom effect KeyframeEffect is not supported.");this._onsample=a,this._animation&&this._animation._rebuildUnderlyingAnimation()},get parent(){return this._parent},clone:function(){if("function"==typeof this.getFrames())throw new Error("Cloning custom effects is not supported.");var b=new KeyframeEffect(this.target,[],a.cloneTimingInput(this._timingInput));return b._normalizedKeyframes=this._normalizedKeyframes,b._keyframes=this._keyframes,b},remove:function(){b.removeMulti([this])}};var f=Element.prototype.animate;Element.prototype.animate=function(a,c){return b.timeline._play(new b.KeyframeEffect(this,a,c))};var g=document.createElementNS("http://www.w3.org/1999/xhtml","div");b.newUnderlyingAnimationForKeyframeEffect=function(a){if(a){var b=a.target||g,c=a._keyframes;"function"==typeof c&&(c=[]);var d=a._timingInput}else var b=g,c=[],d=0;return f.apply(b,[c,d])},b.bindAnimationForKeyframeEffect=function(a){a.effect&&"function"==typeof a.effect._normalizedKeyframes&&b.bindAnimationForCustomEffect(a)};var h=[];b.awaitStartTime=function(a){null===a.startTime&&a._isGroup&&(0==h.length&&requestAnimationFrame(d),h.push(a))};var i=window.getComputedStyle;Object.defineProperty(window,"getComputedStyle",{configurable:!0,enumerable:!0,value:function(){window.document.timeline._updateAnimationsPromises();var a=i.apply(this,arguments);return d()&&(a=i.apply(this,arguments)),window.document.timeline._updateAnimationsPromises(),a}}),window.KeyframeEffect=b.KeyframeEffect,window.Element.prototype.getAnimations=function(){return document.timeline.getAnimations().filter(function(a){return null!==a.effect&&a.effect.target==this}.bind(this))}}(c,e,f),function(a,b){function c(a){a._registered||(a._registered=!0,f.push(a),g||(g=!0,requestAnimationFrame(d)))}function d(){var a=f;f=[],a.sort(function(a,b){return a._sequenceNumber-b._sequenceNumber}),a=a.filter(function(a){a();var b=a._animation?a._animation.playState:"idle";return"running"!=b&&"pending"!=b&&(a._registered=!1),a._registered}),f.push.apply(f,a),f.length?(g=!0,requestAnimationFrame(d)):g=!1}var e=(document.createElementNS("http://www.w3.org/1999/xhtml","div"),0);b.bindAnimationForCustomEffect=function(b){var d,f=b.effect.target,g="function"==typeof b.effect.getFrames();d=g?b.effect.getFrames():b.effect._onsample;var h=b.effect.timing,i=null;h=a.normalizeTimingInput(h);var j=function(){var c=j._animation?j._animation.currentTime:null;null!==c&&(c=a.calculateTimeFraction(a.calculateActiveDuration(h),c,h),isNaN(c)&&(c=null)),c!==i&&(g?d(c,f,b.effect):d(c,b.effect,b.effect._animation)),i=c};j._animation=b,j._registered=!1,j._sequenceNumber=e++,b._callback=j,c(j)};var f=[],g=!1;b.Animation.prototype._register=function(){this._callback&&c(this._callback)}}(c,e,f),function(a,b){function c(a){return a._timing.delay+a.activeDuration+a._timing.endDelay}function d(b,c){this._parent=null,this.children=b||[],this._reparent(this.children),c=a.numericTimingToObject(c),this._timingInput=a.cloneTimingInput(c),this._timing=a.normalizeTimingInput(c,!0),this.timing=a.makeTiming(c,!0,this),this.timing._effect=this,"auto"===this._timing.duration&&(this._timing.duration=this.activeDuration)}window.SequenceEffect=function(){d.apply(this,arguments)},window.GroupEffect=function(){d.apply(this,arguments)},d.prototype={_isAncestor:function(a){for(var b=this;null!==b;){if(b==a)return!0;b=b._parent}return!1},_rebuild:function(){for(var a=this;a;)"auto"===a.timing.duration&&(a._timing.duration=a.activeDuration),a=a._parent;this._animation&&this._animation._rebuildUnderlyingAnimation()},_reparent:function(a){b.removeMulti(a);for(var c=0;c<a.length;c++)a[c]._parent=this},_putChild:function(a,b){for(var c=b?"Cannot append an ancestor or self":"Cannot prepend an ancestor or self",d=0;d<a.length;d++)if(this._isAncestor(a[d]))throw{type:DOMException.HIERARCHY_REQUEST_ERR,name:"HierarchyRequestError",message:c};for(var d=0;d<a.length;d++)b?this.children.push(a[d]):this.children.unshift(a[d]);this._reparent(a),this._rebuild()},append:function(){this._putChild(arguments,!0)},prepend:function(){this._putChild(arguments,!1)},get parent(){return this._parent},get firstChild(){return this.children.length?this.children[0]:null},get lastChild(){return this.children.length?this.children[this.children.length-1]:null},clone:function(){for(var b=a.cloneTimingInput(this._timingInput),c=[],d=0;d<this.children.length;d++)c.push(this.children[d].clone());return this instanceof GroupEffect?new GroupEffect(c,b):new SequenceEffect(c,b)},remove:function(){b.removeMulti([this])}},window.SequenceEffect.prototype=Object.create(d.prototype),Object.defineProperty(window.SequenceEffect.prototype,"activeDuration",{get:function(){var a=0;return this.children.forEach(function(b){a+=c(b)}),Math.max(a,0)}}),window.GroupEffect.prototype=Object.create(d.prototype),Object.defineProperty(window.GroupEffect.prototype,"activeDuration",{get:function(){var a=0;return this.children.forEach(function(b){a=Math.max(a,c(b))}),a}}),b.newUnderlyingAnimationForGroup=function(c){var d,e=null,f=function(b){var c=d._wrapper;return c&&"pending"!=c.playState&&c.effect?null==b?void c._removeChildAnimations():0==b&&c.playbackRate<0&&(e||(e=a.normalizeTimingInput(c.effect.timing)),b=a.calculateTimeFraction(a.calculateActiveDuration(e),-1,e),isNaN(b)||null==b)?(c._forEachChild(function(a){a.currentTime=-1}),void c._removeChildAnimations()):void 0:void 0},g=new KeyframeEffect(null,[],c._timing);return g.onsample=f,d=b.timeline._play(g)},b.bindAnimationForGroup=function(a){a._animation._wrapper=a,a._isGroup=!0,b.awaitStartTime(a),a._constructChildAnimations(),a._setExternalAnimation(a)},b.groupChildDuration=c}(c,e,f)}({},function(){return this}());
|
| +// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +// <include src="../../../../ui/webui/resources/js/i18n_template_no_process.js">
|
| +
|
| +i18nTemplate.process(document, loadTimeData);
|
| +(function () {
|
| +function resolve() {
|
| +document.body.removeAttribute('unresolved');
|
| +}
|
| +if (window.WebComponents) {
|
| +addEventListener('WebComponentsReady', resolve);
|
| +} else {
|
| +if (document.readyState === 'interactive' || document.readyState === 'complete') {
|
| +resolve();
|
| +} else {
|
| +addEventListener('DOMContentLoaded', resolve);
|
| +}
|
| +}
|
| +}());
|
| +window.Polymer = {
|
| +Settings: function () {
|
| +var user = window.Polymer || {};
|
| +location.search.slice(1).split('&').forEach(function (o) {
|
| +o = o.split('=');
|
| +o[0] && (user[o[0]] = o[1] || true);
|
| +});
|
| +var wantShadow = user.dom === 'shadow';
|
| +var hasShadow = Boolean(Element.prototype.createShadowRoot);
|
| +var nativeShadow = hasShadow && !window.ShadowDOMPolyfill;
|
| +var useShadow = wantShadow && hasShadow;
|
| +var hasNativeImports = Boolean('import' in document.createElement('link'));
|
| +var useNativeImports = hasNativeImports;
|
| +var useNativeCustomElements = !window.CustomElements || window.CustomElements.useNative;
|
| +return {
|
| +wantShadow: wantShadow,
|
| +hasShadow: hasShadow,
|
| +nativeShadow: nativeShadow,
|
| +useShadow: useShadow,
|
| +useNativeShadow: useShadow && nativeShadow,
|
| +useNativeImports: useNativeImports,
|
| +useNativeCustomElements: useNativeCustomElements
|
| +};
|
| +}()
|
| +};
|
| +(function () {
|
| +var userPolymer = window.Polymer;
|
| +window.Polymer = function (prototype) {
|
| +if (typeof prototype === 'function') {
|
| +prototype = prototype.prototype;
|
| +}
|
| +if (!prototype) {
|
| +prototype = {};
|
| +}
|
| +var factory = desugar(prototype);
|
| +prototype = factory.prototype;
|
| +var options = { prototype: prototype };
|
| +if (prototype.extends) {
|
| +options.extends = prototype.extends;
|
| +}
|
| +Polymer.telemetry._registrate(prototype);
|
| +document.registerElement(prototype.is, options);
|
| +return factory;
|
| +};
|
| +var desugar = function (prototype) {
|
| +var base = Polymer.Base;
|
| +if (prototype.extends) {
|
| +base = Polymer.Base._getExtendedPrototype(prototype.extends);
|
| +}
|
| +prototype = Polymer.Base.chainObject(prototype, base);
|
| +prototype.registerCallback();
|
| +return prototype.constructor;
|
| +};
|
| +window.Polymer = Polymer;
|
| +if (userPolymer) {
|
| +for (var i in userPolymer) {
|
| +Polymer[i] = userPolymer[i];
|
| +}
|
| +}
|
| +Polymer.Class = desugar;
|
| +}());
|
| +Polymer.telemetry = {
|
| +registrations: [],
|
| +_regLog: function (prototype) {
|
| +console.log('[' + prototype.is + ']: registered');
|
| +},
|
| +_registrate: function (prototype) {
|
| +this.registrations.push(prototype);
|
| +Polymer.log && this._regLog(prototype);
|
| +},
|
| +dumpRegistrations: function () {
|
| +this.registrations.forEach(this._regLog);
|
| +}
|
| +};
|
| +Object.defineProperty(window, 'currentImport', {
|
| +enumerable: true,
|
| +configurable: true,
|
| +get: function () {
|
| +return (document._currentScript || document.currentScript).ownerDocument;
|
| +}
|
| +});
|
| +Polymer.RenderStatus = {
|
| +_ready: false,
|
| +_callbacks: [],
|
| +whenReady: function (cb) {
|
| +if (this._ready) {
|
| +cb();
|
| +} else {
|
| +this._callbacks.push(cb);
|
| +}
|
| +},
|
| +_makeReady: function () {
|
| +this._ready = true;
|
| +this._callbacks.forEach(function (cb) {
|
| +cb();
|
| +});
|
| +this._callbacks = [];
|
| +},
|
| +_catchFirstRender: function () {
|
| +requestAnimationFrame(function () {
|
| +Polymer.RenderStatus._makeReady();
|
| +});
|
| +}
|
| +};
|
| +if (window.HTMLImports) {
|
| +HTMLImports.whenReady(function () {
|
| +Polymer.RenderStatus._catchFirstRender();
|
| +});
|
| +} else {
|
| +Polymer.RenderStatus._catchFirstRender();
|
| +}
|
| +Polymer.ImportStatus = Polymer.RenderStatus;
|
| +Polymer.ImportStatus.whenLoaded = Polymer.ImportStatus.whenReady;
|
| +Polymer.Base = {
|
| +__isPolymerInstance__: true,
|
| +_addFeature: function (feature) {
|
| +this.extend(this, feature);
|
| +},
|
| +registerCallback: function () {
|
| +this._desugarBehaviors();
|
| +this._doBehavior('beforeRegister');
|
| +this._registerFeatures();
|
| +this._doBehavior('registered');
|
| +},
|
| +createdCallback: function () {
|
| +Polymer.telemetry.instanceCount++;
|
| +this.root = this;
|
| +this._doBehavior('created');
|
| +this._initFeatures();
|
| +},
|
| +attachedCallback: function () {
|
| +Polymer.RenderStatus.whenReady(function () {
|
| +this.isAttached = true;
|
| +this._doBehavior('attached');
|
| +}.bind(this));
|
| +},
|
| +detachedCallback: function () {
|
| +this.isAttached = false;
|
| +this._doBehavior('detached');
|
| +},
|
| +attributeChangedCallback: function (name) {
|
| +this._attributeChangedImpl(name);
|
| +this._doBehavior('attributeChanged', arguments);
|
| +},
|
| +_attributeChangedImpl: function (name) {
|
| +this._setAttributeToProperty(this, name);
|
| +},
|
| +extend: function (prototype, api) {
|
| +if (prototype && api) {
|
| +Object.getOwnPropertyNames(api).forEach(function (n) {
|
| +this.copyOwnProperty(n, api, prototype);
|
| +}, this);
|
| +}
|
| +return prototype || api;
|
| +},
|
| +mixin: function (target, source) {
|
| +for (var i in source) {
|
| +target[i] = source[i];
|
| +}
|
| +return target;
|
| +},
|
| +copyOwnProperty: function (name, source, target) {
|
| +var pd = Object.getOwnPropertyDescriptor(source, name);
|
| +if (pd) {
|
| +Object.defineProperty(target, name, pd);
|
| +}
|
| +},
|
| +_log: console.log.apply.bind(console.log, console),
|
| +_warn: console.warn.apply.bind(console.warn, console),
|
| +_error: console.error.apply.bind(console.error, console),
|
| +_logf: function () {
|
| +return this._logPrefix.concat([this.is]).concat(Array.prototype.slice.call(arguments, 0));
|
| +}
|
| +};
|
| +Polymer.Base._logPrefix = function () {
|
| +var color = window.chrome || /firefox/i.test(navigator.userAgent);
|
| +return color ? [
|
| +'%c[%s::%s]:',
|
| +'font-weight: bold; background-color:#EEEE00;'
|
| +] : ['[%s::%s]:'];
|
| +}();
|
| +Polymer.Base.chainObject = function (object, inherited) {
|
| +if (object && inherited && object !== inherited) {
|
| +if (!Object.__proto__) {
|
| +object = Polymer.Base.extend(Object.create(inherited), object);
|
| +}
|
| +object.__proto__ = inherited;
|
| +}
|
| +return object;
|
| +};
|
| +Polymer.Base = Polymer.Base.chainObject(Polymer.Base, HTMLElement.prototype);
|
| +if (window.CustomElements) {
|
| +Polymer.instanceof = CustomElements.instanceof;
|
| +} else {
|
| +Polymer.instanceof = function (obj, ctor) {
|
| +return obj instanceof ctor;
|
| +};
|
| +}
|
| +Polymer.isInstance = function (obj) {
|
| +return Boolean(obj && obj.__isPolymerInstance__);
|
| +};
|
| +Polymer.telemetry.instanceCount = 0;
|
| +(function () {
|
| +var modules = {};
|
| +var lcModules = {};
|
| +var findModule = function (id) {
|
| +return modules[id] || lcModules[id.toLowerCase()];
|
| +};
|
| +var DomModule = function () {
|
| +return document.createElement('dom-module');
|
| +};
|
| +DomModule.prototype = Object.create(HTMLElement.prototype);
|
| +Polymer.Base.extend(DomModule.prototype, {
|
| +constructor: DomModule,
|
| +createdCallback: function () {
|
| +this.register();
|
| +},
|
| +register: function (id) {
|
| +var id = id || this.id || this.getAttribute('name') || this.getAttribute('is');
|
| +if (id) {
|
| +this.id = id;
|
| +modules[id] = this;
|
| +lcModules[id.toLowerCase()] = this;
|
| +}
|
| +},
|
| +import: function (id, selector) {
|
| +if (id) {
|
| +var m = findModule(id);
|
| +if (!m) {
|
| +forceDocumentUpgrade();
|
| +m = findModule(id);
|
| +}
|
| +if (m && selector) {
|
| +m = m.querySelector(selector);
|
| +}
|
| +return m;
|
| +}
|
| +}
|
| +});
|
| +var cePolyfill = window.CustomElements && !CustomElements.useNative;
|
| +document.registerElement('dom-module', DomModule);
|
| +function forceDocumentUpgrade() {
|
| +if (cePolyfill) {
|
| +var script = document._currentScript || document.currentScript;
|
| +var doc = script && script.ownerDocument;
|
| +if (doc) {
|
| +CustomElements.upgradeAll(doc);
|
| +}
|
| +}
|
| +}
|
| +}());
|
| +Polymer.Base._addFeature({
|
| +_prepIs: function () {
|
| +if (!this.is) {
|
| +var module = (document._currentScript || document.currentScript).parentNode;
|
| +if (module.localName === 'dom-module') {
|
| +var id = module.id || module.getAttribute('name') || module.getAttribute('is');
|
| +this.is = id;
|
| +}
|
| +}
|
| +if (this.is) {
|
| +this.is = this.is.toLowerCase();
|
| +}
|
| +}
|
| +});
|
| +Polymer.Base._addFeature({
|
| +behaviors: [],
|
| +_desugarBehaviors: function () {
|
| +if (this.behaviors.length) {
|
| +this.behaviors = this._desugarSomeBehaviors(this.behaviors);
|
| +}
|
| +},
|
| +_desugarSomeBehaviors: function (behaviors) {
|
| +behaviors = this._flattenBehaviorsList(behaviors);
|
| +for (var i = behaviors.length - 1; i >= 0; i--) {
|
| +this._mixinBehavior(behaviors[i]);
|
| +}
|
| +return behaviors;
|
| +},
|
| +_flattenBehaviorsList: function (behaviors) {
|
| +var flat = [];
|
| +behaviors.forEach(function (b) {
|
| +if (b instanceof Array) {
|
| +flat = flat.concat(this._flattenBehaviorsList(b));
|
| +} else if (b) {
|
| +flat.push(b);
|
| +} else {
|
| +this._warn(this._logf('_flattenBehaviorsList', 'behavior is null, check for missing or 404 import'));
|
| +}
|
| +}, this);
|
| +return flat;
|
| +},
|
| +_mixinBehavior: function (b) {
|
| +Object.getOwnPropertyNames(b).forEach(function (n) {
|
| +switch (n) {
|
| +case 'hostAttributes':
|
| +case 'registered':
|
| +case 'properties':
|
| +case 'observers':
|
| +case 'listeners':
|
| +case 'created':
|
| +case 'attached':
|
| +case 'detached':
|
| +case 'attributeChanged':
|
| +case 'configure':
|
| +case 'ready':
|
| +break;
|
| +default:
|
| +if (!this.hasOwnProperty(n)) {
|
| +this.copyOwnProperty(n, b, this);
|
| +}
|
| +break;
|
| +}
|
| +}, this);
|
| +},
|
| +_prepBehaviors: function () {
|
| +this._prepFlattenedBehaviors(this.behaviors);
|
| +},
|
| +_prepFlattenedBehaviors: function (behaviors) {
|
| +for (var i = 0, l = behaviors.length; i < l; i++) {
|
| +this._prepBehavior(behaviors[i]);
|
| +}
|
| +this._prepBehavior(this);
|
| +},
|
| +_doBehavior: function (name, args) {
|
| +this.behaviors.forEach(function (b) {
|
| +this._invokeBehavior(b, name, args);
|
| +}, this);
|
| +this._invokeBehavior(this, name, args);
|
| +},
|
| +_invokeBehavior: function (b, name, args) {
|
| +var fn = b[name];
|
| +if (fn) {
|
| +fn.apply(this, args || Polymer.nar);
|
| +}
|
| +},
|
| +_marshalBehaviors: function () {
|
| +this.behaviors.forEach(function (b) {
|
| +this._marshalBehavior(b);
|
| +}, this);
|
| +this._marshalBehavior(this);
|
| +}
|
| +});
|
| +Polymer.Base._addFeature({
|
| +_getExtendedPrototype: function (tag) {
|
| +return this._getExtendedNativePrototype(tag);
|
| +},
|
| +_nativePrototypes: {},
|
| +_getExtendedNativePrototype: function (tag) {
|
| +var p = this._nativePrototypes[tag];
|
| +if (!p) {
|
| +var np = this.getNativePrototype(tag);
|
| +p = this.extend(Object.create(np), Polymer.Base);
|
| +this._nativePrototypes[tag] = p;
|
| +}
|
| +return p;
|
| +},
|
| +getNativePrototype: function (tag) {
|
| +return Object.getPrototypeOf(document.createElement(tag));
|
| +}
|
| +});
|
| +Polymer.Base._addFeature({
|
| +_prepConstructor: function () {
|
| +this._factoryArgs = this.extends ? [
|
| +this.extends,
|
| +this.is
|
| +] : [this.is];
|
| +var ctor = function () {
|
| +return this._factory(arguments);
|
| +};
|
| +if (this.hasOwnProperty('extends')) {
|
| +ctor.extends = this.extends;
|
| +}
|
| +Object.defineProperty(this, 'constructor', {
|
| +value: ctor,
|
| +writable: true,
|
| +configurable: true
|
| +});
|
| +ctor.prototype = this;
|
| +},
|
| +_factory: function (args) {
|
| +var elt = document.createElement.apply(document, this._factoryArgs);
|
| +if (this.factoryImpl) {
|
| +this.factoryImpl.apply(elt, args);
|
| +}
|
| +return elt;
|
| +}
|
| +});
|
| +Polymer.nob = Object.create(null);
|
| +Polymer.Base._addFeature({
|
| +properties: {},
|
| +getPropertyInfo: function (property) {
|
| +var info = this._getPropertyInfo(property, this.properties);
|
| +if (!info) {
|
| +this.behaviors.some(function (b) {
|
| +return info = this._getPropertyInfo(property, b.properties);
|
| +}, this);
|
| +}
|
| +return info || Polymer.nob;
|
| +},
|
| +_getPropertyInfo: function (property, properties) {
|
| +var p = properties && properties[property];
|
| +if (typeof p === 'function') {
|
| +p = properties[property] = { type: p };
|
| +}
|
| +if (p) {
|
| +p.defined = true;
|
| +}
|
| +return p;
|
| +}
|
| +});
|
| +Polymer.CaseMap = {
|
| +_caseMap: {},
|
| +dashToCamelCase: function (dash) {
|
| +var mapped = Polymer.CaseMap._caseMap[dash];
|
| +if (mapped) {
|
| +return mapped;
|
| +}
|
| +if (dash.indexOf('-') < 0) {
|
| +return Polymer.CaseMap._caseMap[dash] = dash;
|
| +}
|
| +return Polymer.CaseMap._caseMap[dash] = dash.replace(/-([a-z])/g, function (m) {
|
| +return m[1].toUpperCase();
|
| +});
|
| +},
|
| +camelToDashCase: function (camel) {
|
| +var mapped = Polymer.CaseMap._caseMap[camel];
|
| +if (mapped) {
|
| +return mapped;
|
| +}
|
| +return Polymer.CaseMap._caseMap[camel] = camel.replace(/([a-z][A-Z])/g, function (g) {
|
| +return g[0] + '-' + g[1].toLowerCase();
|
| +});
|
| +}
|
| +};
|
| +Polymer.Base._addFeature({
|
| +_prepAttributes: function () {
|
| +this._aggregatedAttributes = {};
|
| +},
|
| +_addHostAttributes: function (attributes) {
|
| +if (attributes) {
|
| +this.mixin(this._aggregatedAttributes, attributes);
|
| +}
|
| +},
|
| +_marshalHostAttributes: function () {
|
| +this._applyAttributes(this, this._aggregatedAttributes);
|
| +},
|
| +_applyAttributes: function (node, attr$) {
|
| +for (var n in attr$) {
|
| +if (!this.hasAttribute(n) && n !== 'class') {
|
| +this.serializeValueToAttribute(attr$[n], n, this);
|
| +}
|
| +}
|
| +},
|
| +_marshalAttributes: function () {
|
| +this._takeAttributesToModel(this);
|
| +},
|
| +_takeAttributesToModel: function (model) {
|
| +for (var i = 0, l = this.attributes.length; i < l; i++) {
|
| +this._setAttributeToProperty(model, this.attributes[i].name);
|
| +}
|
| +},
|
| +_setAttributeToProperty: function (model, attrName) {
|
| +if (!this._serializing) {
|
| +var propName = Polymer.CaseMap.dashToCamelCase(attrName);
|
| +var info = this.getPropertyInfo(propName);
|
| +if (info.defined || this._propertyEffects && this._propertyEffects[propName]) {
|
| +var val = this.getAttribute(attrName);
|
| +model[propName] = this.deserialize(val, info.type);
|
| +}
|
| +}
|
| +},
|
| +_serializing: false,
|
| +reflectPropertyToAttribute: function (name) {
|
| +this._serializing = true;
|
| +this.serializeValueToAttribute(this[name], Polymer.CaseMap.camelToDashCase(name));
|
| +this._serializing = false;
|
| +},
|
| +serializeValueToAttribute: function (value, attribute, node) {
|
| +var str = this.serialize(value);
|
| +(node || this)[str === undefined ? 'removeAttribute' : 'setAttribute'](attribute, str);
|
| +},
|
| +deserialize: function (value, type) {
|
| +switch (type) {
|
| +case Number:
|
| +value = Number(value);
|
| +break;
|
| +case Boolean:
|
| +value = value !== null;
|
| +break;
|
| +case Object:
|
| +try {
|
| +value = JSON.parse(value);
|
| +} catch (x) {
|
| +}
|
| +break;
|
| +case Array:
|
| +try {
|
| +value = JSON.parse(value);
|
| +} catch (x) {
|
| +value = null;
|
| +console.warn('Polymer::Attributes: couldn`t decode Array as JSON');
|
| +}
|
| +break;
|
| +case Date:
|
| +value = new Date(value);
|
| +break;
|
| +case String:
|
| +default:
|
| +break;
|
| +}
|
| +return value;
|
| +},
|
| +serialize: function (value) {
|
| +switch (typeof value) {
|
| +case 'boolean':
|
| +return value ? '' : undefined;
|
| +case 'object':
|
| +if (value instanceof Date) {
|
| +return value;
|
| +} else if (value) {
|
| +try {
|
| +return JSON.stringify(value);
|
| +} catch (x) {
|
| +return '';
|
| +}
|
| +}
|
| +default:
|
| +return value != null ? value : undefined;
|
| +}
|
| +}
|
| +});
|
| +Polymer.Base._addFeature({
|
| +_setupDebouncers: function () {
|
| +this._debouncers = {};
|
| +},
|
| +debounce: function (jobName, callback, wait) {
|
| +return this._debouncers[jobName] = Polymer.Debounce.call(this, this._debouncers[jobName], callback, wait);
|
| +},
|
| +isDebouncerActive: function (jobName) {
|
| +var debouncer = this._debouncers[jobName];
|
| +return debouncer && debouncer.finish;
|
| +},
|
| +flushDebouncer: function (jobName) {
|
| +var debouncer = this._debouncers[jobName];
|
| +if (debouncer) {
|
| +debouncer.complete();
|
| +}
|
| +},
|
| +cancelDebouncer: function (jobName) {
|
| +var debouncer = this._debouncers[jobName];
|
| +if (debouncer) {
|
| +debouncer.stop();
|
| +}
|
| +}
|
| +});
|
| +Polymer.version = '1.1.4';
|
| +Polymer.Base._addFeature({
|
| +_registerFeatures: function () {
|
| +this._prepIs();
|
| +this._prepAttributes();
|
| +this._prepBehaviors();
|
| +this._prepConstructor();
|
| +},
|
| +_prepBehavior: function (b) {
|
| +this._addHostAttributes(b.hostAttributes);
|
| +},
|
| +_marshalBehavior: function (b) {
|
| +},
|
| +_initFeatures: function () {
|
| +this._marshalHostAttributes();
|
| +this._setupDebouncers();
|
| +this._marshalBehaviors();
|
| +}
|
| +});
|
| +Polymer.Base._addFeature({
|
| +_prepTemplate: function () {
|
| +this._template = this._template || Polymer.DomModule.import(this.is, 'template');
|
| +if (this._template && this._template.hasAttribute('is')) {
|
| +this._warn(this._logf('_prepTemplate', 'top-level Polymer template ' + 'must not be a type-extension, found', this._template, 'Move inside simple <template>.'));
|
| +}
|
| +if (this._template && !this._template.content && HTMLTemplateElement.bootstrap) {
|
| +HTMLTemplateElement.decorate(this._template);
|
| +HTMLTemplateElement.bootstrap(this._template.content);
|
| +}
|
| +},
|
| +_stampTemplate: function () {
|
| +if (this._template) {
|
| +this.root = this.instanceTemplate(this._template);
|
| +}
|
| +},
|
| +instanceTemplate: function (template) {
|
| +var dom = document.importNode(template._content || template.content, true);
|
| +return dom;
|
| +}
|
| +});
|
| +(function () {
|
| +var baseAttachedCallback = Polymer.Base.attachedCallback;
|
| +Polymer.Base._addFeature({
|
| +_hostStack: [],
|
| +ready: function () {
|
| +},
|
| +_pushHost: function (host) {
|
| +this.dataHost = host = host || Polymer.Base._hostStack[Polymer.Base._hostStack.length - 1];
|
| +if (host && host._clients) {
|
| +host._clients.push(this);
|
| +}
|
| +this._beginHost();
|
| +},
|
| +_beginHost: function () {
|
| +Polymer.Base._hostStack.push(this);
|
| +if (!this._clients) {
|
| +this._clients = [];
|
| +}
|
| +},
|
| +_popHost: function () {
|
| +Polymer.Base._hostStack.pop();
|
| +},
|
| +_tryReady: function () {
|
| +if (this._canReady()) {
|
| +this._ready();
|
| +}
|
| +},
|
| +_canReady: function () {
|
| +return !this.dataHost || this.dataHost._clientsReadied;
|
| +},
|
| +_ready: function () {
|
| +this._beforeClientsReady();
|
| +this._setupRoot();
|
| +this._readyClients();
|
| +this._afterClientsReady();
|
| +this._readySelf();
|
| +},
|
| +_readyClients: function () {
|
| +this._beginDistribute();
|
| +var c$ = this._clients;
|
| +for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) {
|
| +c._ready();
|
| +}
|
| +this._finishDistribute();
|
| +this._clientsReadied = true;
|
| +this._clients = null;
|
| +},
|
| +_readySelf: function () {
|
| +this._doBehavior('ready');
|
| +this._readied = true;
|
| +if (this._attachedPending) {
|
| +this._attachedPending = false;
|
| +this.attachedCallback();
|
| +}
|
| +},
|
| +_beforeClientsReady: function () {
|
| +},
|
| +_afterClientsReady: function () {
|
| +},
|
| +_beforeAttached: function () {
|
| +},
|
| +attachedCallback: function () {
|
| +if (this._readied) {
|
| +this._beforeAttached();
|
| +baseAttachedCallback.call(this);
|
| +} else {
|
| +this._attachedPending = true;
|
| +}
|
| +}
|
| +});
|
| +}());
|
| +Polymer.ArraySplice = function () {
|
| +function newSplice(index, removed, addedCount) {
|
| +return {
|
| +index: index,
|
| +removed: removed,
|
| +addedCount: addedCount
|
| +};
|
| +}
|
| +var EDIT_LEAVE = 0;
|
| +var EDIT_UPDATE = 1;
|
| +var EDIT_ADD = 2;
|
| +var EDIT_DELETE = 3;
|
| +function ArraySplice() {
|
| +}
|
| +ArraySplice.prototype = {
|
| +calcEditDistances: function (current, currentStart, currentEnd, old, oldStart, oldEnd) {
|
| +var rowCount = oldEnd - oldStart + 1;
|
| +var columnCount = currentEnd - currentStart + 1;
|
| +var distances = new Array(rowCount);
|
| +for (var i = 0; i < rowCount; i++) {
|
| +distances[i] = new Array(columnCount);
|
| +distances[i][0] = i;
|
| +}
|
| +for (var j = 0; j < columnCount; j++)
|
| +distances[0][j] = j;
|
| +for (var i = 1; i < rowCount; i++) {
|
| +for (var j = 1; j < columnCount; j++) {
|
| +if (this.equals(current[currentStart + j - 1], old[oldStart + i - 1]))
|
| +distances[i][j] = distances[i - 1][j - 1];
|
| +else {
|
| +var north = distances[i - 1][j] + 1;
|
| +var west = distances[i][j - 1] + 1;
|
| +distances[i][j] = north < west ? north : west;
|
| +}
|
| +}
|
| +}
|
| +return distances;
|
| +},
|
| +spliceOperationsFromEditDistances: function (distances) {
|
| +var i = distances.length - 1;
|
| +var j = distances[0].length - 1;
|
| +var current = distances[i][j];
|
| +var edits = [];
|
| +while (i > 0 || j > 0) {
|
| +if (i == 0) {
|
| +edits.push(EDIT_ADD);
|
| +j--;
|
| +continue;
|
| +}
|
| +if (j == 0) {
|
| +edits.push(EDIT_DELETE);
|
| +i--;
|
| +continue;
|
| +}
|
| +var northWest = distances[i - 1][j - 1];
|
| +var west = distances[i - 1][j];
|
| +var north = distances[i][j - 1];
|
| +var min;
|
| +if (west < north)
|
| +min = west < northWest ? west : northWest;
|
| +else
|
| +min = north < northWest ? north : northWest;
|
| +if (min == northWest) {
|
| +if (northWest == current) {
|
| +edits.push(EDIT_LEAVE);
|
| +} else {
|
| +edits.push(EDIT_UPDATE);
|
| +current = northWest;
|
| +}
|
| +i--;
|
| +j--;
|
| +} else if (min == west) {
|
| +edits.push(EDIT_DELETE);
|
| +i--;
|
| +current = west;
|
| +} else {
|
| +edits.push(EDIT_ADD);
|
| +j--;
|
| +current = north;
|
| +}
|
| +}
|
| +edits.reverse();
|
| +return edits;
|
| +},
|
| +calcSplices: function (current, currentStart, currentEnd, old, oldStart, oldEnd) {
|
| +var prefixCount = 0;
|
| +var suffixCount = 0;
|
| +var minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart);
|
| +if (currentStart == 0 && oldStart == 0)
|
| +prefixCount = this.sharedPrefix(current, old, minLength);
|
| +if (currentEnd == current.length && oldEnd == old.length)
|
| +suffixCount = this.sharedSuffix(current, old, minLength - prefixCount);
|
| +currentStart += prefixCount;
|
| +oldStart += prefixCount;
|
| +currentEnd -= suffixCount;
|
| +oldEnd -= suffixCount;
|
| +if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0)
|
| +return [];
|
| +if (currentStart == currentEnd) {
|
| +var splice = newSplice(currentStart, [], 0);
|
| +while (oldStart < oldEnd)
|
| +splice.removed.push(old[oldStart++]);
|
| +return [splice];
|
| +} else if (oldStart == oldEnd)
|
| +return [newSplice(currentStart, [], currentEnd - currentStart)];
|
| +var ops = this.spliceOperationsFromEditDistances(this.calcEditDistances(current, currentStart, currentEnd, old, oldStart, oldEnd));
|
| +var splice = undefined;
|
| +var splices = [];
|
| +var index = currentStart;
|
| +var oldIndex = oldStart;
|
| +for (var i = 0; i < ops.length; i++) {
|
| +switch (ops[i]) {
|
| +case EDIT_LEAVE:
|
| +if (splice) {
|
| +splices.push(splice);
|
| +splice = undefined;
|
| +}
|
| +index++;
|
| +oldIndex++;
|
| +break;
|
| +case EDIT_UPDATE:
|
| +if (!splice)
|
| +splice = newSplice(index, [], 0);
|
| +splice.addedCount++;
|
| +index++;
|
| +splice.removed.push(old[oldIndex]);
|
| +oldIndex++;
|
| +break;
|
| +case EDIT_ADD:
|
| +if (!splice)
|
| +splice = newSplice(index, [], 0);
|
| +splice.addedCount++;
|
| +index++;
|
| +break;
|
| +case EDIT_DELETE:
|
| +if (!splice)
|
| +splice = newSplice(index, [], 0);
|
| +splice.removed.push(old[oldIndex]);
|
| +oldIndex++;
|
| +break;
|
| +}
|
| +}
|
| +if (splice) {
|
| +splices.push(splice);
|
| +}
|
| +return splices;
|
| +},
|
| +sharedPrefix: function (current, old, searchLength) {
|
| +for (var i = 0; i < searchLength; i++)
|
| +if (!this.equals(current[i], old[i]))
|
| +return i;
|
| +return searchLength;
|
| +},
|
| +sharedSuffix: function (current, old, searchLength) {
|
| +var index1 = current.length;
|
| +var index2 = old.length;
|
| +var count = 0;
|
| +while (count < searchLength && this.equals(current[--index1], old[--index2]))
|
| +count++;
|
| +return count;
|
| +},
|
| +calculateSplices: function (current, previous) {
|
| +return this.calcSplices(current, 0, current.length, previous, 0, previous.length);
|
| +},
|
| +equals: function (currentValue, previousValue) {
|
| +return currentValue === previousValue;
|
| +}
|
| +};
|
| +return new ArraySplice();
|
| +}();
|
| +Polymer.EventApi = function () {
|
| +var Settings = Polymer.Settings;
|
| +var EventApi = function (event) {
|
| +this.event = event;
|
| +};
|
| +if (Settings.useShadow) {
|
| +EventApi.prototype = {
|
| +get rootTarget() {
|
| +return this.event.path[0];
|
| +},
|
| +get localTarget() {
|
| +return this.event.target;
|
| +},
|
| +get path() {
|
| +return this.event.path;
|
| +}
|
| +};
|
| +} else {
|
| +EventApi.prototype = {
|
| +get rootTarget() {
|
| +return this.event.target;
|
| +},
|
| +get localTarget() {
|
| +var current = this.event.currentTarget;
|
| +var currentRoot = current && Polymer.dom(current).getOwnerRoot();
|
| +var p$ = this.path;
|
| +for (var i = 0; i < p$.length; i++) {
|
| +if (Polymer.dom(p$[i]).getOwnerRoot() === currentRoot) {
|
| +return p$[i];
|
| +}
|
| +}
|
| +},
|
| +get path() {
|
| +if (!this.event._path) {
|
| +var path = [];
|
| +var o = this.rootTarget;
|
| +while (o) {
|
| +path.push(o);
|
| +o = Polymer.dom(o).parentNode || o.host;
|
| +}
|
| +path.push(window);
|
| +this.event._path = path;
|
| +}
|
| +return this.event._path;
|
| +}
|
| +};
|
| +}
|
| +var factory = function (event) {
|
| +if (!event.__eventApi) {
|
| +event.__eventApi = new EventApi(event);
|
| +}
|
| +return event.__eventApi;
|
| +};
|
| +return { factory: factory };
|
| +}();
|
| +Polymer.domInnerHTML = function () {
|
| +var escapeAttrRegExp = /[&\u00A0"]/g;
|
| +var escapeDataRegExp = /[&\u00A0<>]/g;
|
| +function escapeReplace(c) {
|
| +switch (c) {
|
| +case '&':
|
| +return '&';
|
| +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 DomApi = function (node) {
|
| +this.node = node;
|
| +if (this.patch) {
|
| +this.patch();
|
| +}
|
| +};
|
| +if (window.wrap && Settings.useShadow && !Settings.useNativeShadow) {
|
| +DomApi = function (node) {
|
| +this.node = wrap(node);
|
| +if (this.patch) {
|
| +this.patch();
|
| +}
|
| +};
|
| +}
|
| +DomApi.prototype = {
|
| +flush: function () {
|
| +Polymer.dom.flush();
|
| +},
|
| +_lazyDistribute: function (host) {
|
| +if (host.shadyRoot && host.shadyRoot._distributionClean) {
|
| +host.shadyRoot._distributionClean = false;
|
| +Polymer.dom.addDebouncer(host.debounce('_distribute', host._distributeContent));
|
| +}
|
| +},
|
| +appendChild: function (node) {
|
| +return this._addNode(node);
|
| +},
|
| +insertBefore: function (node, ref_node) {
|
| +return this._addNode(node, ref_node);
|
| +},
|
| +_addNode: function (node, ref_node) {
|
| +this._removeNodeFromHost(node, true);
|
| +var addedInsertionPoint;
|
| +var root = this.getOwnerRoot();
|
| +if (root) {
|
| +addedInsertionPoint = this._maybeAddInsertionPoint(node, this.node);
|
| +}
|
| +if (this._nodeHasLogicalChildren(this.node)) {
|
| +if (ref_node) {
|
| +var children = this.childNodes;
|
| +var index = children.indexOf(ref_node);
|
| +if (index < 0) {
|
| +throw Error('The ref_node to be inserted before is not a child ' + 'of this node');
|
| +}
|
| +}
|
| +this._addLogicalInfo(node, this.node, index);
|
| +}
|
| +this._addNodeToHost(node);
|
| +if (!this._maybeDistribute(node, this.node) && !this._tryRemoveUndistributedNode(node)) {
|
| +if (ref_node) {
|
| +ref_node = ref_node.localName === CONTENT ? this._firstComposedNode(ref_node) : ref_node;
|
| +}
|
| +var container = this.node._isShadyRoot ? this.node.host : this.node;
|
| +addToComposedParent(container, node, ref_node);
|
| +if (ref_node) {
|
| +nativeInsertBefore.call(container, node, ref_node);
|
| +} else {
|
| +nativeAppendChild.call(container, node);
|
| +}
|
| +}
|
| +if (addedInsertionPoint) {
|
| +this._updateInsertionPoints(root.host);
|
| +}
|
| +return node;
|
| +},
|
| +removeChild: function (node) {
|
| +if (factory(node).parentNode !== this.node) {
|
| +console.warn('The node to be removed is not a child of this node', node);
|
| +}
|
| +this._removeNodeFromHost(node);
|
| +if (!this._maybeDistribute(node, this.node)) {
|
| +var container = this.node._isShadyRoot ? this.node.host : this.node;
|
| +if (container === node.parentNode) {
|
| +removeFromComposedParent(container, node);
|
| +nativeRemoveChild.call(container, node);
|
| +}
|
| +}
|
| +return node;
|
| +},
|
| +replaceChild: function (node, ref_node) {
|
| +this.insertBefore(node, ref_node);
|
| +this.removeChild(ref_node);
|
| +return node;
|
| +},
|
| +_hasCachedOwnerRoot: function (node) {
|
| +return Boolean(node._ownerShadyRoot !== undefined);
|
| +},
|
| +getOwnerRoot: function () {
|
| +return this._ownerShadyRootForNode(this.node);
|
| +},
|
| +_ownerShadyRootForNode: function (node) {
|
| +if (!node) {
|
| +return;
|
| +}
|
| +if (node._ownerShadyRoot === undefined) {
|
| +var root;
|
| +if (node._isShadyRoot) {
|
| +root = node;
|
| +} else {
|
| +var parent = Polymer.dom(node).parentNode;
|
| +if (parent) {
|
| +root = parent._isShadyRoot ? parent : this._ownerShadyRootForNode(parent);
|
| +} else {
|
| +root = null;
|
| +}
|
| +}
|
| +node._ownerShadyRoot = root;
|
| +}
|
| +return node._ownerShadyRoot;
|
| +},
|
| +_maybeDistribute: function (node, parent) {
|
| +var fragContent = node.nodeType === Node.DOCUMENT_FRAGMENT_NODE && !node.__noContent && Polymer.dom(node).querySelector(CONTENT);
|
| +var wrappedContent = fragContent && Polymer.dom(fragContent).parentNode.nodeType !== Node.DOCUMENT_FRAGMENT_NODE;
|
| +var hasContent = fragContent || node.localName === CONTENT;
|
| +if (hasContent) {
|
| +var root = this._ownerShadyRootForNode(parent);
|
| +if (root) {
|
| +var host = root.host;
|
| +this._lazyDistribute(host);
|
| +}
|
| +}
|
| +var parentNeedsDist = this._parentNeedsDistribution(parent);
|
| +if (parentNeedsDist) {
|
| +this._lazyDistribute(parent);
|
| +}
|
| +return parentNeedsDist || hasContent && !wrappedContent;
|
| +},
|
| +_maybeAddInsertionPoint: function (node, parent) {
|
| +var added;
|
| +if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE && !node.__noContent) {
|
| +var c$ = factory(node).querySelectorAll(CONTENT);
|
| +for (var i = 0, n, np, na; i < c$.length && (n = c$[i]); i++) {
|
| +np = factory(n).parentNode;
|
| +if (np === node) {
|
| +np = parent;
|
| +}
|
| +na = this._maybeAddInsertionPoint(n, np);
|
| +added = added || na;
|
| +}
|
| +} else if (node.localName === CONTENT) {
|
| +saveLightChildrenIfNeeded(parent);
|
| +saveLightChildrenIfNeeded(node);
|
| +added = true;
|
| +}
|
| +return added;
|
| +},
|
| +_tryRemoveUndistributedNode: function (node) {
|
| +if (this.node.shadyRoot) {
|
| +var parent = getComposedParent(node);
|
| +if (parent) {
|
| +nativeRemoveChild.call(parent, node);
|
| +}
|
| +return true;
|
| +}
|
| +},
|
| +_updateInsertionPoints: function (host) {
|
| +var i$ = host.shadyRoot._insertionPoints = factory(host.shadyRoot).querySelectorAll(CONTENT);
|
| +for (var i = 0, c; i < i$.length; i++) {
|
| +c = i$[i];
|
| +saveLightChildrenIfNeeded(c);
|
| +saveLightChildrenIfNeeded(factory(c).parentNode);
|
| +}
|
| +},
|
| +_nodeHasLogicalChildren: function (node) {
|
| +return Boolean(node._lightChildren !== undefined);
|
| +},
|
| +_parentNeedsDistribution: function (parent) {
|
| +return parent && parent.shadyRoot && hasInsertionPoint(parent.shadyRoot);
|
| +},
|
| +_removeNodeFromHost: function (node, ensureComposedRemoval) {
|
| +var hostNeedsDist;
|
| +var root;
|
| +var parent = node._lightParent;
|
| +if (parent) {
|
| +factory(node)._distributeParent();
|
| +root = this._ownerShadyRootForNode(node);
|
| +if (root) {
|
| +root.host._elementRemove(node);
|
| +hostNeedsDist = this._removeDistributedChildren(root, node);
|
| +}
|
| +this._removeLogicalInfo(node, node._lightParent);
|
| +}
|
| +this._removeOwnerShadyRoot(node);
|
| +if (root && hostNeedsDist) {
|
| +this._updateInsertionPoints(root.host);
|
| +this._lazyDistribute(root.host);
|
| +} else if (ensureComposedRemoval) {
|
| +removeFromComposedParent(getComposedParent(node), node);
|
| +}
|
| +},
|
| +_removeDistributedChildren: function (root, container) {
|
| +var hostNeedsDist;
|
| +var ip$ = root._insertionPoints;
|
| +for (var i = 0; i < ip$.length; i++) {
|
| +var content = ip$[i];
|
| +if (this._contains(container, content)) {
|
| +var dc$ = factory(content).getDistributedNodes();
|
| +for (var j = 0; j < dc$.length; j++) {
|
| +hostNeedsDist = true;
|
| +var node = dc$[j];
|
| +var parent = node.parentNode;
|
| +if (parent) {
|
| +removeFromComposedParent(parent, node);
|
| +nativeRemoveChild.call(parent, node);
|
| +}
|
| +}
|
| +}
|
| +}
|
| +return hostNeedsDist;
|
| +},
|
| +_contains: function (container, node) {
|
| +while (node) {
|
| +if (node == container) {
|
| +return true;
|
| +}
|
| +node = factory(node).parentNode;
|
| +}
|
| +},
|
| +_addNodeToHost: function (node) {
|
| +var root = this.getOwnerRoot();
|
| +if (root) {
|
| +root.host._elementAdd(node);
|
| +}
|
| +},
|
| +_addLogicalInfo: function (node, container, index) {
|
| +var children = factory(container).childNodes;
|
| +index = index === undefined ? children.length : index;
|
| +if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
| +var c$ = Array.prototype.slice.call(node.childNodes);
|
| +for (var i = 0, n; i < c$.length && (n = c$[i]); i++) {
|
| +children.splice(index++, 0, n);
|
| +n._lightParent = container;
|
| +}
|
| +} else {
|
| +children.splice(index, 0, node);
|
| +node._lightParent = container;
|
| +}
|
| +},
|
| +_removeLogicalInfo: function (node, container) {
|
| +var children = factory(container).childNodes;
|
| +var index = children.indexOf(node);
|
| +if (index < 0 || container !== node._lightParent) {
|
| +throw Error('The node to be removed is not a child of this node');
|
| +}
|
| +children.splice(index, 1);
|
| +node._lightParent = null;
|
| +},
|
| +_removeOwnerShadyRoot: function (node) {
|
| +if (this._hasCachedOwnerRoot(node)) {
|
| +var c$ = factory(node).childNodes;
|
| +for (var i = 0, l = c$.length, n; i < l && (n = c$[i]); i++) {
|
| +this._removeOwnerShadyRoot(n);
|
| +}
|
| +}
|
| +node._ownerShadyRoot = undefined;
|
| +},
|
| +_firstComposedNode: function (content) {
|
| +var n$ = factory(content).getDistributedNodes();
|
| +for (var i = 0, l = n$.length, n, p$; i < l && (n = n$[i]); i++) {
|
| +p$ = factory(n).getDestinationInsertionPoints();
|
| +if (p$[p$.length - 1] === content) {
|
| +return n;
|
| +}
|
| +}
|
| +},
|
| +querySelector: function (selector) {
|
| +return this.querySelectorAll(selector)[0];
|
| +},
|
| +querySelectorAll: function (selector) {
|
| +return this._query(function (n) {
|
| +return matchesSelector.call(n, selector);
|
| +}, this.node);
|
| +},
|
| +_query: function (matcher, node) {
|
| +node = node || this.node;
|
| +var list = [];
|
| +this._queryElements(factory(node).childNodes, matcher, list);
|
| +return list;
|
| +},
|
| +_queryElements: function (elements, matcher, list) {
|
| +for (var i = 0, l = elements.length, c; i < l && (c = elements[i]); i++) {
|
| +if (c.nodeType === Node.ELEMENT_NODE) {
|
| +this._queryElement(c, matcher, list);
|
| +}
|
| +}
|
| +},
|
| +_queryElement: function (node, matcher, list) {
|
| +if (matcher(node)) {
|
| +list.push(node);
|
| +}
|
| +this._queryElements(factory(node).childNodes, matcher, list);
|
| +},
|
| +getDestinationInsertionPoints: function () {
|
| +return this.node._destinationInsertionPoints || [];
|
| +},
|
| +getDistributedNodes: function () {
|
| +return this.node._distributedNodes || [];
|
| +},
|
| +queryDistributedElements: function (selector) {
|
| +var c$ = this.childNodes;
|
| +var list = [];
|
| +this._distributedFilter(selector, c$, list);
|
| +for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) {
|
| +if (c.localName === CONTENT) {
|
| +this._distributedFilter(selector, factory(c).getDistributedNodes(), list);
|
| +}
|
| +}
|
| +return list;
|
| +},
|
| +_distributedFilter: function (selector, list, results) {
|
| +results = results || [];
|
| +for (var i = 0, l = list.length, d; i < l && (d = list[i]); i++) {
|
| +if (d.nodeType === Node.ELEMENT_NODE && d.localName !== CONTENT && matchesSelector.call(d, selector)) {
|
| +results.push(d);
|
| +}
|
| +}
|
| +return results;
|
| +},
|
| +_clear: function () {
|
| +while (this.childNodes.length) {
|
| +this.removeChild(this.childNodes[0]);
|
| +}
|
| +},
|
| +setAttribute: function (name, value) {
|
| +this.node.setAttribute(name, value);
|
| +this._distributeParent();
|
| +},
|
| +removeAttribute: function (name) {
|
| +this.node.removeAttribute(name);
|
| +this._distributeParent();
|
| +},
|
| +_distributeParent: function () {
|
| +if (this._parentNeedsDistribution(this.parentNode)) {
|
| +this._lazyDistribute(this.parentNode);
|
| +}
|
| +},
|
| +cloneNode: function (deep) {
|
| +var n = nativeCloneNode.call(this.node, false);
|
| +if (deep) {
|
| +var c$ = this.childNodes;
|
| +var d = factory(n);
|
| +for (var i = 0, nc; i < c$.length; i++) {
|
| +nc = factory(c$[i]).cloneNode(true);
|
| +d.appendChild(nc);
|
| +}
|
| +}
|
| +return n;
|
| +},
|
| +importNode: function (externalNode, deep) {
|
| +var doc = this.node instanceof Document ? this.node : this.node.ownerDocument;
|
| +var n = nativeImportNode.call(doc, externalNode, false);
|
| +if (deep) {
|
| +var c$ = factory(externalNode).childNodes;
|
| +var d = factory(n);
|
| +for (var i = 0, nc; i < c$.length; i++) {
|
| +nc = factory(doc).importNode(c$[i], true);
|
| +d.appendChild(nc);
|
| +}
|
| +}
|
| +return n;
|
| +}
|
| +};
|
| +Object.defineProperty(DomApi.prototype, 'classList', {
|
| +get: function () {
|
| +if (!this._classList) {
|
| +this._classList = new DomApi.ClassList(this);
|
| +}
|
| +return this._classList;
|
| +},
|
| +configurable: true
|
| +});
|
| +DomApi.ClassList = function (host) {
|
| +this.domApi = host;
|
| +this.node = host.node;
|
| +};
|
| +DomApi.ClassList.prototype = {
|
| +add: function () {
|
| +this.node.classList.add.apply(this.node.classList, arguments);
|
| +this.domApi._distributeParent();
|
| +},
|
| +remove: function () {
|
| +this.node.classList.remove.apply(this.node.classList, arguments);
|
| +this.domApi._distributeParent();
|
| +},
|
| +toggle: function () {
|
| +this.node.classList.toggle.apply(this.node.classList, arguments);
|
| +this.domApi._distributeParent();
|
| +},
|
| +contains: function () {
|
| +return this.node.classList.contains.apply(this.node.classList, arguments);
|
| +}
|
| +};
|
| +if (!Settings.useShadow) {
|
| +Object.defineProperties(DomApi.prototype, {
|
| +childNodes: {
|
| +get: function () {
|
| +var c$ = getLightChildren(this.node);
|
| +return Array.isArray(c$) ? c$ : Array.prototype.slice.call(c$);
|
| +},
|
| +configurable: true
|
| +},
|
| +children: {
|
| +get: function () {
|
| +return Array.prototype.filter.call(this.childNodes, function (n) {
|
| +return n.nodeType === Node.ELEMENT_NODE;
|
| +});
|
| +},
|
| +configurable: true
|
| +},
|
| +parentNode: {
|
| +get: function () {
|
| +return this.node._lightParent || getComposedParent(this.node);
|
| +},
|
| +configurable: true
|
| +},
|
| +firstChild: {
|
| +get: function () {
|
| +return this.childNodes[0];
|
| +},
|
| +configurable: true
|
| +},
|
| +lastChild: {
|
| +get: function () {
|
| +var c$ = this.childNodes;
|
| +return c$[c$.length - 1];
|
| +},
|
| +configurable: true
|
| +},
|
| +nextSibling: {
|
| +get: function () {
|
| +var c$ = this.parentNode && factory(this.parentNode).childNodes;
|
| +if (c$) {
|
| +return c$[Array.prototype.indexOf.call(c$, this.node) + 1];
|
| +}
|
| +},
|
| +configurable: true
|
| +},
|
| +previousSibling: {
|
| +get: function () {
|
| +var c$ = this.parentNode && factory(this.parentNode).childNodes;
|
| +if (c$) {
|
| +return c$[Array.prototype.indexOf.call(c$, this.node) - 1];
|
| +}
|
| +},
|
| +configurable: true
|
| +},
|
| +firstElementChild: {
|
| +get: function () {
|
| +return this.children[0];
|
| +},
|
| +configurable: true
|
| +},
|
| +lastElementChild: {
|
| +get: function () {
|
| +var c$ = this.children;
|
| +return c$[c$.length - 1];
|
| +},
|
| +configurable: true
|
| +},
|
| +nextElementSibling: {
|
| +get: function () {
|
| +var c$ = this.parentNode && factory(this.parentNode).children;
|
| +if (c$) {
|
| +return c$[Array.prototype.indexOf.call(c$, this.node) + 1];
|
| +}
|
| +},
|
| +configurable: true
|
| +},
|
| +previousElementSibling: {
|
| +get: function () {
|
| +var c$ = this.parentNode && factory(this.parentNode).children;
|
| +if (c$) {
|
| +return c$[Array.prototype.indexOf.call(c$, this.node) - 1];
|
| +}
|
| +},
|
| +configurable: true
|
| +},
|
| +textContent: {
|
| +get: function () {
|
| +var nt = this.node.nodeType;
|
| +if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) {
|
| +return this.node.textContent;
|
| +} else {
|
| +var tc = [];
|
| +for (var i = 0, cn = this.childNodes, c; c = cn[i]; i++) {
|
| +if (c.nodeType !== Node.COMMENT_NODE) {
|
| +tc.push(c.textContent);
|
| +}
|
| +}
|
| +return tc.join('');
|
| +}
|
| +},
|
| +set: function (text) {
|
| +var nt = this.node.nodeType;
|
| +if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) {
|
| +this.node.textContent = text;
|
| +} else {
|
| +this._clear();
|
| +if (text) {
|
| +this.appendChild(document.createTextNode(text));
|
| +}
|
| +}
|
| +},
|
| +configurable: true
|
| +},
|
| +innerHTML: {
|
| +get: function () {
|
| +var nt = this.node.nodeType;
|
| +if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) {
|
| +return null;
|
| +} else {
|
| +return getInnerHTML(this.node);
|
| +}
|
| +},
|
| +set: function (text) {
|
| +var nt = this.node.nodeType;
|
| +if (nt !== Node.TEXT_NODE || nt !== Node.COMMENT_NODE) {
|
| +this._clear();
|
| +var d = document.createElement('div');
|
| +d.innerHTML = text;
|
| +var c$ = Array.prototype.slice.call(d.childNodes);
|
| +for (var i = 0; i < c$.length; i++) {
|
| +this.appendChild(c$[i]);
|
| +}
|
| +}
|
| +},
|
| +configurable: true
|
| +}
|
| +});
|
| +DomApi.prototype._getComposedInnerHTML = function () {
|
| +return getInnerHTML(this.node, true);
|
| +};
|
| +} else {
|
| +var forwardMethods = [
|
| +'cloneNode',
|
| +'appendChild',
|
| +'insertBefore',
|
| +'removeChild',
|
| +'replaceChild'
|
| +];
|
| +forwardMethods.forEach(function (name) {
|
| +DomApi.prototype[name] = function () {
|
| +return this.node[name].apply(this.node, arguments);
|
| +};
|
| +});
|
| +DomApi.prototype.querySelectorAll = function (selector) {
|
| +return Array.prototype.slice.call(this.node.querySelectorAll(selector));
|
| +};
|
| +DomApi.prototype.getOwnerRoot = function () {
|
| +var n = this.node;
|
| +while (n) {
|
| +if (n.nodeType === Node.DOCUMENT_FRAGMENT_NODE && n.host) {
|
| +return n;
|
| +}
|
| +n = n.parentNode;
|
| +}
|
| +};
|
| +DomApi.prototype.importNode = function (externalNode, deep) {
|
| +var doc = this.node instanceof Document ? this.node : this.node.ownerDocument;
|
| +return doc.importNode(externalNode, deep);
|
| +};
|
| +DomApi.prototype.getDestinationInsertionPoints = function () {
|
| +var n$ = this.node.getDestinationInsertionPoints && this.node.getDestinationInsertionPoints();
|
| +return n$ ? Array.prototype.slice.call(n$) : [];
|
| +};
|
| +DomApi.prototype.getDistributedNodes = function () {
|
| +var n$ = this.node.getDistributedNodes && this.node.getDistributedNodes();
|
| +return n$ ? Array.prototype.slice.call(n$) : [];
|
| +};
|
| +DomApi.prototype._distributeParent = function () {
|
| +};
|
| +Object.defineProperties(DomApi.prototype, {
|
| +childNodes: {
|
| +get: function () {
|
| +return Array.prototype.slice.call(this.node.childNodes);
|
| +},
|
| +configurable: true
|
| +},
|
| +children: {
|
| +get: function () {
|
| +return Array.prototype.slice.call(this.node.children);
|
| +},
|
| +configurable: true
|
| +},
|
| +textContent: {
|
| +get: function () {
|
| +return this.node.textContent;
|
| +},
|
| +set: function (value) {
|
| +return this.node.textContent = value;
|
| +},
|
| +configurable: true
|
| +},
|
| +innerHTML: {
|
| +get: function () {
|
| +return this.node.innerHTML;
|
| +},
|
| +set: function (value) {
|
| +return this.node.innerHTML = value;
|
| +},
|
| +configurable: true
|
| +}
|
| +});
|
| +var forwardProperties = [
|
| +'parentNode',
|
| +'firstChild',
|
| +'lastChild',
|
| +'nextSibling',
|
| +'previousSibling',
|
| +'firstElementChild',
|
| +'lastElementChild',
|
| +'nextElementSibling',
|
| +'previousElementSibling'
|
| +];
|
| +forwardProperties.forEach(function (name) {
|
| +Object.defineProperty(DomApi.prototype, name, {
|
| +get: function () {
|
| +return this.node[name];
|
| +},
|
| +configurable: true
|
| +});
|
| +});
|
| +}
|
| +var CONTENT = 'content';
|
| +var factory = function (node, patch) {
|
| +node = node || document;
|
| +if (!node.__domApi) {
|
| +node.__domApi = new DomApi(node, patch);
|
| +}
|
| +return node.__domApi;
|
| +};
|
| +Polymer.dom = function (obj, patch) {
|
| +if (obj instanceof Event) {
|
| +return Polymer.EventApi.factory(obj);
|
| +} else {
|
| +return factory(obj, patch);
|
| +}
|
| +};
|
| +Polymer.Base.extend(Polymer.dom, {
|
| +_flushGuard: 0,
|
| +_FLUSH_MAX: 100,
|
| +_needsTakeRecords: !Polymer.Settings.useNativeCustomElements,
|
| +_debouncers: [],
|
| +_finishDebouncer: null,
|
| +flush: function () {
|
| +for (var i = 0; i < this._debouncers.length; i++) {
|
| +this._debouncers[i].complete();
|
| +}
|
| +if (this._finishDebouncer) {
|
| +this._finishDebouncer.complete();
|
| +}
|
| +this._flushPolyfills();
|
| +if (this._debouncers.length && this._flushGuard < this._FLUSH_MAX) {
|
| +this._flushGuard++;
|
| +this.flush();
|
| +} else {
|
| +if (this._flushGuard >= this._FLUSH_MAX) {
|
| +console.warn('Polymer.dom.flush aborted. Flush may not be complete.');
|
| +}
|
| +this._flushGuard = 0;
|
| +}
|
| +},
|
| +_flushPolyfills: function () {
|
| +if (this._needsTakeRecords) {
|
| +CustomElements.takeRecords();
|
| +}
|
| +},
|
| +addDebouncer: function (debouncer) {
|
| +this._debouncers.push(debouncer);
|
| +this._finishDebouncer = Polymer.Debounce(this._finishDebouncer, this._finishFlush);
|
| +},
|
| +_finishFlush: function () {
|
| +Polymer.dom._debouncers = [];
|
| +}
|
| +});
|
| +function getLightChildren(node) {
|
| +var children = node._lightChildren;
|
| +return children ? children : node.childNodes;
|
| +}
|
| +function getComposedChildren(node) {
|
| +if (!node._composedChildren) {
|
| +node._composedChildren = Array.prototype.slice.call(node.childNodes);
|
| +}
|
| +return node._composedChildren;
|
| +}
|
| +function addToComposedParent(parent, node, ref_node) {
|
| +var children = getComposedChildren(parent);
|
| +var i = ref_node ? children.indexOf(ref_node) : -1;
|
| +if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
| +var fragChildren = getComposedChildren(node);
|
| +for (var j = 0; j < fragChildren.length; j++) {
|
| +addNodeToComposedChildren(fragChildren[j], parent, children, i + j);
|
| +}
|
| +node._composedChildren = null;
|
| +} else {
|
| +addNodeToComposedChildren(node, parent, children, i);
|
| +}
|
| +}
|
| +function getComposedParent(node) {
|
| +return node.__patched ? node._composedParent : node.parentNode;
|
| +}
|
| +function addNodeToComposedChildren(node, parent, children, i) {
|
| +node._composedParent = parent;
|
| +children.splice(i >= 0 ? i : children.length, 0, node);
|
| +}
|
| +function removeFromComposedParent(parent, node) {
|
| +node._composedParent = null;
|
| +if (parent) {
|
| +var children = getComposedChildren(parent);
|
| +var i = children.indexOf(node);
|
| +if (i >= 0) {
|
| +children.splice(i, 1);
|
| +}
|
| +}
|
| +}
|
| +function saveLightChildrenIfNeeded(node) {
|
| +if (!node._lightChildren) {
|
| +var c$ = Array.prototype.slice.call(node.childNodes);
|
| +for (var i = 0, l = c$.length, child; i < l && (child = c$[i]); i++) {
|
| +child._lightParent = child._lightParent || node;
|
| +}
|
| +node._lightChildren = c$;
|
| +}
|
| +}
|
| +function hasInsertionPoint(root) {
|
| +return Boolean(root && root._insertionPoints.length);
|
| +}
|
| +var p = Element.prototype;
|
| +var matchesSelector = p.matches || p.matchesSelector || p.mozMatchesSelector || p.msMatchesSelector || p.oMatchesSelector || p.webkitMatchesSelector;
|
| +return {
|
| +getLightChildren: getLightChildren,
|
| +getComposedParent: getComposedParent,
|
| +getComposedChildren: getComposedChildren,
|
| +removeFromComposedParent: removeFromComposedParent,
|
| +saveLightChildrenIfNeeded: saveLightChildrenIfNeeded,
|
| +matchesSelector: matchesSelector,
|
| +hasInsertionPoint: hasInsertionPoint,
|
| +ctor: DomApi,
|
| +factory: factory
|
| +};
|
| +}();
|
| +(function () {
|
| +Polymer.Base._addFeature({
|
| +_prepShady: function () {
|
| +this._useContent = this._useContent || Boolean(this._template);
|
| +},
|
| +_poolContent: function () {
|
| +if (this._useContent) {
|
| +saveLightChildrenIfNeeded(this);
|
| +}
|
| +},
|
| +_setupRoot: function () {
|
| +if (this._useContent) {
|
| +this._createLocalRoot();
|
| +if (!this.dataHost) {
|
| +upgradeLightChildren(this._lightChildren);
|
| +}
|
| +}
|
| +},
|
| +_createLocalRoot: function () {
|
| +this.shadyRoot = this.root;
|
| +this.shadyRoot._distributionClean = false;
|
| +this.shadyRoot._isShadyRoot = true;
|
| +this.shadyRoot._dirtyRoots = [];
|
| +var i$ = this.shadyRoot._insertionPoints = !this._notes || this._notes._hasContent ? this.shadyRoot.querySelectorAll('content') : [];
|
| +saveLightChildrenIfNeeded(this.shadyRoot);
|
| +for (var i = 0, c; i < i$.length; i++) {
|
| +c = i$[i];
|
| +saveLightChildrenIfNeeded(c);
|
| +saveLightChildrenIfNeeded(c.parentNode);
|
| +}
|
| +this.shadyRoot.host = this;
|
| +},
|
| +get domHost() {
|
| +var root = Polymer.dom(this).getOwnerRoot();
|
| +return root && root.host;
|
| +},
|
| +distributeContent: function (updateInsertionPoints) {
|
| +if (this.shadyRoot) {
|
| +var dom = Polymer.dom(this);
|
| +if (updateInsertionPoints) {
|
| +dom._updateInsertionPoints(this);
|
| +}
|
| +var host = getTopDistributingHost(this);
|
| +dom._lazyDistribute(host);
|
| +}
|
| +},
|
| +_distributeContent: function () {
|
| +if (this._useContent && !this.shadyRoot._distributionClean) {
|
| +this._beginDistribute();
|
| +this._distributeDirtyRoots();
|
| +this._finishDistribute();
|
| +}
|
| +},
|
| +_beginDistribute: function () {
|
| +if (this._useContent && hasInsertionPoint(this.shadyRoot)) {
|
| +this._resetDistribution();
|
| +this._distributePool(this.shadyRoot, this._collectPool());
|
| +}
|
| +},
|
| +_distributeDirtyRoots: function () {
|
| +var c$ = this.shadyRoot._dirtyRoots;
|
| +for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) {
|
| +c._distributeContent();
|
| +}
|
| +this.shadyRoot._dirtyRoots = [];
|
| +},
|
| +_finishDistribute: function () {
|
| +if (this._useContent) {
|
| +this.shadyRoot._distributionClean = true;
|
| +if (hasInsertionPoint(this.shadyRoot)) {
|
| +this._composeTree();
|
| +} else {
|
| +if (!this.shadyRoot._hasDistributed) {
|
| +this.textContent = '';
|
| +this._composedChildren = null;
|
| +this.appendChild(this.shadyRoot);
|
| +} else {
|
| +var children = this._composeNode(this);
|
| +this._updateChildNodes(this, children);
|
| +}
|
| +}
|
| +this.shadyRoot._hasDistributed = true;
|
| +}
|
| +},
|
| +elementMatches: function (selector, node) {
|
| +node = node || this;
|
| +return matchesSelector.call(node, selector);
|
| +},
|
| +_resetDistribution: function () {
|
| +var children = getLightChildren(this);
|
| +for (var i = 0; i < children.length; i++) {
|
| +var child = children[i];
|
| +if (child._destinationInsertionPoints) {
|
| +child._destinationInsertionPoints = undefined;
|
| +}
|
| +if (isInsertionPoint(child)) {
|
| +clearDistributedDestinationInsertionPoints(child);
|
| +}
|
| +}
|
| +var root = this.shadyRoot;
|
| +var p$ = root._insertionPoints;
|
| +for (var j = 0; j < p$.length; j++) {
|
| +p$[j]._distributedNodes = [];
|
| +}
|
| +},
|
| +_collectPool: function () {
|
| +var pool = [];
|
| +var children = getLightChildren(this);
|
| +for (var i = 0; i < children.length; i++) {
|
| +var child = children[i];
|
| +if (isInsertionPoint(child)) {
|
| +pool.push.apply(pool, child._distributedNodes);
|
| +} else {
|
| +pool.push(child);
|
| +}
|
| +}
|
| +return pool;
|
| +},
|
| +_distributePool: function (node, pool) {
|
| +var p$ = node._insertionPoints;
|
| +for (var i = 0, l = p$.length, p; i < l && (p = p$[i]); i++) {
|
| +this._distributeInsertionPoint(p, pool);
|
| +maybeRedistributeParent(p, this);
|
| +}
|
| +},
|
| +_distributeInsertionPoint: function (content, pool) {
|
| +var anyDistributed = false;
|
| +for (var i = 0, l = pool.length, node; i < l; i++) {
|
| +node = pool[i];
|
| +if (!node) {
|
| +continue;
|
| +}
|
| +if (this._matchesContentSelect(node, content)) {
|
| +distributeNodeInto(node, content);
|
| +pool[i] = undefined;
|
| +anyDistributed = true;
|
| +}
|
| +}
|
| +if (!anyDistributed) {
|
| +var children = getLightChildren(content);
|
| +for (var j = 0; j < children.length; j++) {
|
| +distributeNodeInto(children[j], content);
|
| +}
|
| +}
|
| +},
|
| +_composeTree: function () {
|
| +this._updateChildNodes(this, this._composeNode(this));
|
| +var p$ = this.shadyRoot._insertionPoints;
|
| +for (var i = 0, l = p$.length, p, parent; i < l && (p = p$[i]); i++) {
|
| +parent = p._lightParent || p.parentNode;
|
| +if (!parent._useContent && parent !== this && parent !== this.shadyRoot) {
|
| +this._updateChildNodes(parent, this._composeNode(parent));
|
| +}
|
| +}
|
| +},
|
| +_composeNode: function (node) {
|
| +var children = [];
|
| +var c$ = getLightChildren(node.shadyRoot || node);
|
| +for (var i = 0; i < c$.length; i++) {
|
| +var child = c$[i];
|
| +if (isInsertionPoint(child)) {
|
| +var distributedNodes = child._distributedNodes;
|
| +for (var j = 0; j < distributedNodes.length; j++) {
|
| +var distributedNode = distributedNodes[j];
|
| +if (isFinalDestination(child, distributedNode)) {
|
| +children.push(distributedNode);
|
| +}
|
| +}
|
| +} else {
|
| +children.push(child);
|
| +}
|
| +}
|
| +return children;
|
| +},
|
| +_updateChildNodes: function (container, children) {
|
| +var composed = getComposedChildren(container);
|
| +var splices = Polymer.ArraySplice.calculateSplices(children, composed);
|
| +for (var i = 0, d = 0, s; i < splices.length && (s = splices[i]); i++) {
|
| +for (var j = 0, n; j < s.removed.length && (n = s.removed[j]); j++) {
|
| +if (getComposedParent(n) === container) {
|
| +remove(n);
|
| +}
|
| +composed.splice(s.index + d, 1);
|
| +}
|
| +d -= s.addedCount;
|
| +}
|
| +for (var i = 0, s, next; i < splices.length && (s = splices[i]); i++) {
|
| +next = composed[s.index];
|
| +for (var j = s.index, n; j < s.index + s.addedCount; j++) {
|
| +n = children[j];
|
| +insertBefore(container, n, next);
|
| +composed.splice(j, 0, n);
|
| +}
|
| +}
|
| +ensureComposedParent(container, children);
|
| +},
|
| +_matchesContentSelect: function (node, contentElement) {
|
| +var select = contentElement.getAttribute('select');
|
| +if (!select) {
|
| +return true;
|
| +}
|
| +select = select.trim();
|
| +if (!select) {
|
| +return true;
|
| +}
|
| +if (!(node instanceof Element)) {
|
| +return false;
|
| +}
|
| +var validSelectors = /^(:not\()?[*.#[a-zA-Z_|]/;
|
| +if (!validSelectors.test(select)) {
|
| +return false;
|
| +}
|
| +return this.elementMatches(select, node);
|
| +},
|
| +_elementAdd: function () {
|
| +},
|
| +_elementRemove: function () {
|
| +}
|
| +});
|
| +var saveLightChildrenIfNeeded = Polymer.DomApi.saveLightChildrenIfNeeded;
|
| +var getLightChildren = Polymer.DomApi.getLightChildren;
|
| +var matchesSelector = Polymer.DomApi.matchesSelector;
|
| +var hasInsertionPoint = Polymer.DomApi.hasInsertionPoint;
|
| +var getComposedChildren = Polymer.DomApi.getComposedChildren;
|
| +var getComposedParent = Polymer.DomApi.getComposedParent;
|
| +var removeFromComposedParent = Polymer.DomApi.removeFromComposedParent;
|
| +function distributeNodeInto(child, insertionPoint) {
|
| +insertionPoint._distributedNodes.push(child);
|
| +var points = child._destinationInsertionPoints;
|
| +if (!points) {
|
| +child._destinationInsertionPoints = [insertionPoint];
|
| +} else {
|
| +points.push(insertionPoint);
|
| +}
|
| +}
|
| +function clearDistributedDestinationInsertionPoints(content) {
|
| +var e$ = content._distributedNodes;
|
| +if (e$) {
|
| +for (var i = 0; i < e$.length; i++) {
|
| +var d = e$[i]._destinationInsertionPoints;
|
| +if (d) {
|
| +d.splice(d.indexOf(content) + 1, d.length);
|
| +}
|
| +}
|
| +}
|
| +}
|
| +function maybeRedistributeParent(content, host) {
|
| +var parent = content._lightParent;
|
| +if (parent && parent.shadyRoot && hasInsertionPoint(parent.shadyRoot) && parent.shadyRoot._distributionClean) {
|
| +parent.shadyRoot._distributionClean = false;
|
| +host.shadyRoot._dirtyRoots.push(parent);
|
| +}
|
| +}
|
| +function isFinalDestination(insertionPoint, node) {
|
| +var points = node._destinationInsertionPoints;
|
| +return points && points[points.length - 1] === insertionPoint;
|
| +}
|
| +function isInsertionPoint(node) {
|
| +return node.localName == 'content';
|
| +}
|
| +var nativeInsertBefore = Element.prototype.insertBefore;
|
| +var nativeRemoveChild = Element.prototype.removeChild;
|
| +function insertBefore(parentNode, newChild, refChild) {
|
| +var newChildParent = getComposedParent(newChild);
|
| +if (newChildParent !== parentNode) {
|
| +removeFromComposedParent(newChildParent, newChild);
|
| +}
|
| +remove(newChild);
|
| +nativeInsertBefore.call(parentNode, newChild, refChild || null);
|
| +newChild._composedParent = parentNode;
|
| +}
|
| +function remove(node) {
|
| +var parentNode = getComposedParent(node);
|
| +if (parentNode) {
|
| +node._composedParent = null;
|
| +nativeRemoveChild.call(parentNode, node);
|
| +}
|
| +}
|
| +function ensureComposedParent(parent, children) {
|
| +for (var i = 0, n; i < children.length; i++) {
|
| +children[i]._composedParent = parent;
|
| +}
|
| +}
|
| +function getTopDistributingHost(host) {
|
| +while (host && hostNeedsRedistribution(host)) {
|
| +host = host.domHost;
|
| +}
|
| +return host;
|
| +}
|
| +function hostNeedsRedistribution(host) {
|
| +var c$ = Polymer.dom(host).children;
|
| +for (var i = 0, c; i < c$.length; i++) {
|
| +c = c$[i];
|
| +if (c.localName === 'content') {
|
| +return host.domHost;
|
| +}
|
| +}
|
| +}
|
| +var needsUpgrade = window.CustomElements && !CustomElements.useNative;
|
| +function upgradeLightChildren(children) {
|
| +if (needsUpgrade && children) {
|
| +for (var i = 0; i < children.length; i++) {
|
| +CustomElements.upgrade(children[i]);
|
| +}
|
| +}
|
| +}
|
| +}());
|
| +if (Polymer.Settings.useShadow) {
|
| +Polymer.Base._addFeature({
|
| +_poolContent: function () {
|
| +},
|
| +_beginDistribute: function () {
|
| +},
|
| +distributeContent: function () {
|
| +},
|
| +_distributeContent: function () {
|
| +},
|
| +_finishDistribute: function () {
|
| +},
|
| +_createLocalRoot: function () {
|
| +this.createShadowRoot();
|
| +this.shadowRoot.appendChild(this.root);
|
| +this.root = this.shadowRoot;
|
| +}
|
| +});
|
| +}
|
| +Polymer.DomModule = document.createElement('dom-module');
|
| +Polymer.Base._addFeature({
|
| +_registerFeatures: function () {
|
| +this._prepIs();
|
| +this._prepAttributes();
|
| +this._prepBehaviors();
|
| +this._prepConstructor();
|
| +this._prepTemplate();
|
| +this._prepShady();
|
| +},
|
| +_prepBehavior: function (b) {
|
| +this._addHostAttributes(b.hostAttributes);
|
| +},
|
| +_initFeatures: function () {
|
| +this._poolContent();
|
| +this._pushHost();
|
| +this._stampTemplate();
|
| +this._popHost();
|
| +this._marshalHostAttributes();
|
| +this._setupDebouncers();
|
| +this._marshalBehaviors();
|
| +this._tryReady();
|
| +},
|
| +_marshalBehavior: function (b) {
|
| +}
|
| +});
|
| +Polymer.nar = [];
|
| +Polymer.Annotations = {
|
| +parseAnnotations: function (template) {
|
| +var list = [];
|
| +var content = template._content || template.content;
|
| +this._parseNodeAnnotations(content, list);
|
| +return list;
|
| +},
|
| +_parseNodeAnnotations: function (node, list) {
|
| +return node.nodeType === Node.TEXT_NODE ? this._parseTextNodeAnnotation(node, list) : this._parseElementAnnotations(node, list);
|
| +},
|
| +_testEscape: function (value) {
|
| +var escape = value.slice(0, 2);
|
| +if (escape === '{{' || escape === '[[') {
|
| +return escape;
|
| +}
|
| +},
|
| +_parseTextNodeAnnotation: function (node, list) {
|
| +var v = node.textContent;
|
| +var escape = this._testEscape(v);
|
| +if (escape) {
|
| +node.textContent = ' ';
|
| +var annote = {
|
| +bindings: [{
|
| +kind: 'text',
|
| +mode: escape[0],
|
| +value: v.slice(2, -2).trim()
|
| +}]
|
| +};
|
| +list.push(annote);
|
| +return annote;
|
| +}
|
| +},
|
| +_parseElementAnnotations: function (element, list) {
|
| +var annote = {
|
| +bindings: [],
|
| +events: []
|
| +};
|
| +if (element.localName === 'content') {
|
| +list._hasContent = true;
|
| +}
|
| +this._parseChildNodesAnnotations(element, annote, list);
|
| +if (element.attributes) {
|
| +this._parseNodeAttributeAnnotations(element, annote, list);
|
| +if (this.prepElement) {
|
| +this.prepElement(element);
|
| +}
|
| +}
|
| +if (annote.bindings.length || annote.events.length || annote.id) {
|
| +list.push(annote);
|
| +}
|
| +return annote;
|
| +},
|
| +_parseChildNodesAnnotations: function (root, annote, list, callback) {
|
| +if (root.firstChild) {
|
| +for (var i = 0, node = root.firstChild; node; node = node.nextSibling, i++) {
|
| +if (node.localName === 'template' && !node.hasAttribute('preserve-content')) {
|
| +this._parseTemplate(node, i, list, annote);
|
| +}
|
| +if (node.nodeType === Node.TEXT_NODE) {
|
| +var n = node.nextSibling;
|
| +while (n && n.nodeType === Node.TEXT_NODE) {
|
| +node.textContent += n.textContent;
|
| +root.removeChild(n);
|
| +n = n.nextSibling;
|
| +}
|
| +}
|
| +var childAnnotation = this._parseNodeAnnotations(node, list, callback);
|
| +if (childAnnotation) {
|
| +childAnnotation.parent = annote;
|
| +childAnnotation.index = i;
|
| +}
|
| +}
|
| +}
|
| +},
|
| +_parseTemplate: function (node, index, list, parent) {
|
| +var content = document.createDocumentFragment();
|
| +content._notes = this.parseAnnotations(node);
|
| +content.appendChild(node.content);
|
| +list.push({
|
| +bindings: Polymer.nar,
|
| +events: Polymer.nar,
|
| +templateContent: content,
|
| +parent: parent,
|
| +index: index
|
| +});
|
| +},
|
| +_parseNodeAttributeAnnotations: function (node, annotation) {
|
| +for (var i = node.attributes.length - 1, a; a = node.attributes[i]; i--) {
|
| +var n = a.name, v = a.value;
|
| +if (n === 'id' && !this._testEscape(v)) {
|
| +annotation.id = v;
|
| +} else if (n.slice(0, 3) === 'on-') {
|
| +node.removeAttribute(n);
|
| +annotation.events.push({
|
| +name: n.slice(3),
|
| +value: v
|
| +});
|
| +} else {
|
| +var b = this._parseNodeAttributeAnnotation(node, n, v);
|
| +if (b) {
|
| +annotation.bindings.push(b);
|
| +}
|
| +}
|
| +}
|
| +},
|
| +_parseNodeAttributeAnnotation: function (node, n, v) {
|
| +var escape = this._testEscape(v);
|
| +if (escape) {
|
| +var customEvent;
|
| +var name = n;
|
| +var mode = escape[0];
|
| +v = v.slice(2, -2).trim();
|
| +var not = false;
|
| +if (v[0] == '!') {
|
| +v = v.substring(1);
|
| +not = true;
|
| +}
|
| +var kind = 'property';
|
| +if (n[n.length - 1] == '$') {
|
| +name = n.slice(0, -1);
|
| +kind = 'attribute';
|
| +}
|
| +var notifyEvent, colon;
|
| +if (mode == '{' && (colon = v.indexOf('::')) > 0) {
|
| +notifyEvent = v.substring(colon + 2);
|
| +v = v.substring(0, colon);
|
| +customEvent = true;
|
| +}
|
| +if (node.localName == 'input' && n == 'value') {
|
| +node.setAttribute(n, '');
|
| +}
|
| +node.removeAttribute(n);
|
| +if (kind === 'property') {
|
| +name = Polymer.CaseMap.dashToCamelCase(name);
|
| +}
|
| +return {
|
| +kind: kind,
|
| +mode: mode,
|
| +name: name,
|
| +value: v,
|
| +negate: not,
|
| +event: notifyEvent,
|
| +customEvent: customEvent
|
| +};
|
| +}
|
| +},
|
| +_localSubTree: function (node, host) {
|
| +return node === host ? node.childNodes : node._lightChildren || node.childNodes;
|
| +},
|
| +findAnnotatedNode: function (root, annote) {
|
| +var parent = annote.parent && Polymer.Annotations.findAnnotatedNode(root, annote.parent);
|
| +return !parent ? root : Polymer.Annotations._localSubTree(parent, root)[annote.index];
|
| +}
|
| +};
|
| +(function () {
|
| +function resolveCss(cssText, ownerDocument) {
|
| +return cssText.replace(CSS_URL_RX, function (m, pre, url, post) {
|
| +return pre + '\'' + resolve(url.replace(/["']/g, ''), ownerDocument) + '\'' + post;
|
| +});
|
| +}
|
| +function resolveAttrs(element, ownerDocument) {
|
| +for (var name in URL_ATTRS) {
|
| +var a$ = URL_ATTRS[name];
|
| +for (var i = 0, l = a$.length, a, at, v; i < l && (a = a$[i]); i++) {
|
| +if (name === '*' || element.localName === name) {
|
| +at = element.attributes[a];
|
| +v = at && at.value;
|
| +if (v && v.search(BINDING_RX) < 0) {
|
| +at.value = a === 'style' ? resolveCss(v, ownerDocument) : resolve(v, ownerDocument);
|
| +}
|
| +}
|
| +}
|
| +}
|
| +}
|
| +function resolve(url, ownerDocument) {
|
| +if (url && url[0] === '#') {
|
| +return url;
|
| +}
|
| +var resolver = getUrlResolver(ownerDocument);
|
| +resolver.href = url;
|
| +return resolver.href || url;
|
| +}
|
| +var tempDoc;
|
| +var tempDocBase;
|
| +function resolveUrl(url, baseUri) {
|
| +if (!tempDoc) {
|
| +tempDoc = document.implementation.createHTMLDocument('temp');
|
| +tempDocBase = tempDoc.createElement('base');
|
| +tempDoc.head.appendChild(tempDocBase);
|
| +}
|
| +tempDocBase.href = baseUri;
|
| +return resolve(url, tempDoc);
|
| +}
|
| +function getUrlResolver(ownerDocument) {
|
| +return ownerDocument.__urlResolver || (ownerDocument.__urlResolver = ownerDocument.createElement('a'));
|
| +}
|
| +var CSS_URL_RX = /(url\()([^)]*)(\))/g;
|
| +var URL_ATTRS = {
|
| +'*': [
|
| +'href',
|
| +'src',
|
| +'style',
|
| +'url'
|
| +],
|
| +form: ['action']
|
| +};
|
| +var BINDING_RX = /\{\{|\[\[/;
|
| +Polymer.ResolveUrl = {
|
| +resolveCss: resolveCss,
|
| +resolveAttrs: resolveAttrs,
|
| +resolveUrl: resolveUrl
|
| +};
|
| +}());
|
| +Polymer.Base._addFeature({
|
| +_prepAnnotations: function () {
|
| +if (!this._template) {
|
| +this._notes = [];
|
| +} else {
|
| +Polymer.Annotations.prepElement = this._prepElement.bind(this);
|
| +if (this._template._content && this._template._content._notes) {
|
| +this._notes = this._template._content._notes;
|
| +} else {
|
| +this._notes = Polymer.Annotations.parseAnnotations(this._template);
|
| +}
|
| +this._processAnnotations(this._notes);
|
| +Polymer.Annotations.prepElement = null;
|
| +}
|
| +},
|
| +_processAnnotations: function (notes) {
|
| +for (var i = 0; i < notes.length; i++) {
|
| +var note = notes[i];
|
| +for (var j = 0; j < note.bindings.length; j++) {
|
| +var b = note.bindings[j];
|
| +b.signature = this._parseMethod(b.value);
|
| +if (!b.signature) {
|
| +b.model = this._modelForPath(b.value);
|
| +}
|
| +}
|
| +if (note.templateContent) {
|
| +this._processAnnotations(note.templateContent._notes);
|
| +var pp = note.templateContent._parentProps = this._discoverTemplateParentProps(note.templateContent._notes);
|
| +var bindings = [];
|
| +for (var prop in pp) {
|
| +bindings.push({
|
| +index: note.index,
|
| +kind: 'property',
|
| +mode: '{',
|
| +name: '_parent_' + prop,
|
| +model: prop,
|
| +value: prop
|
| +});
|
| +}
|
| +note.bindings = note.bindings.concat(bindings);
|
| +}
|
| +}
|
| +},
|
| +_discoverTemplateParentProps: function (notes) {
|
| +var pp = {};
|
| +notes.forEach(function (n) {
|
| +n.bindings.forEach(function (b) {
|
| +if (b.signature) {
|
| +var args = b.signature.args;
|
| +for (var k = 0; k < args.length; k++) {
|
| +pp[args[k].model] = true;
|
| +}
|
| +} else {
|
| +pp[b.model] = true;
|
| +}
|
| +});
|
| +if (n.templateContent) {
|
| +var tpp = n.templateContent._parentProps;
|
| +Polymer.Base.mixin(pp, tpp);
|
| +}
|
| +});
|
| +return pp;
|
| +},
|
| +_prepElement: function (element) {
|
| +Polymer.ResolveUrl.resolveAttrs(element, this._template.ownerDocument);
|
| +},
|
| +_findAnnotatedNode: Polymer.Annotations.findAnnotatedNode,
|
| +_marshalAnnotationReferences: function () {
|
| +if (this._template) {
|
| +this._marshalIdNodes();
|
| +this._marshalAnnotatedNodes();
|
| +this._marshalAnnotatedListeners();
|
| +}
|
| +},
|
| +_configureAnnotationReferences: function () {
|
| +this._configureTemplateContent();
|
| +},
|
| +_configureTemplateContent: function () {
|
| +this._notes.forEach(function (note, i) {
|
| +if (note.templateContent) {
|
| +this._nodes[i]._content = note.templateContent;
|
| +}
|
| +}, this);
|
| +},
|
| +_marshalIdNodes: function () {
|
| +this.$ = {};
|
| +this._notes.forEach(function (a) {
|
| +if (a.id) {
|
| +this.$[a.id] = this._findAnnotatedNode(this.root, a);
|
| +}
|
| +}, this);
|
| +},
|
| +_marshalAnnotatedNodes: function () {
|
| +if (this._nodes) {
|
| +this._nodes = this._nodes.map(function (a) {
|
| +return this._findAnnotatedNode(this.root, a);
|
| +}, this);
|
| +}
|
| +},
|
| +_marshalAnnotatedListeners: function () {
|
| +this._notes.forEach(function (a) {
|
| +if (a.events && a.events.length) {
|
| +var node = this._findAnnotatedNode(this.root, a);
|
| +a.events.forEach(function (e) {
|
| +this.listen(node, e.name, e.value);
|
| +}, this);
|
| +}
|
| +}, this);
|
| +}
|
| +});
|
| +Polymer.Base._addFeature({
|
| +listeners: {},
|
| +_listenListeners: function (listeners) {
|
| +var node, name, key;
|
| +for (key in listeners) {
|
| +if (key.indexOf('.') < 0) {
|
| +node = this;
|
| +name = key;
|
| +} else {
|
| +name = key.split('.');
|
| +node = this.$[name[0]];
|
| +name = name[1];
|
| +}
|
| +this.listen(node, name, listeners[key]);
|
| +}
|
| +},
|
| +listen: function (node, eventName, methodName) {
|
| +this._listen(node, eventName, this._createEventHandler(node, eventName, methodName));
|
| +},
|
| +_boundListenerKey: function (eventName, methodName) {
|
| +return eventName + ':' + methodName;
|
| +},
|
| +_recordEventHandler: function (host, eventName, target, methodName, handler) {
|
| +var hbl = host.__boundListeners;
|
| +if (!hbl) {
|
| +hbl = host.__boundListeners = new WeakMap();
|
| +}
|
| +var bl = hbl.get(target);
|
| +if (!bl) {
|
| +bl = {};
|
| +hbl.set(target, bl);
|
| +}
|
| +var key = this._boundListenerKey(eventName, methodName);
|
| +bl[key] = handler;
|
| +},
|
| +_recallEventHandler: function (host, eventName, target, methodName) {
|
| +var hbl = host.__boundListeners;
|
| +if (!hbl) {
|
| +return;
|
| +}
|
| +var bl = hbl.get(target);
|
| +if (!bl) {
|
| +return;
|
| +}
|
| +var key = this._boundListenerKey(eventName, methodName);
|
| +return bl[key];
|
| +},
|
| +_createEventHandler: function (node, eventName, methodName) {
|
| +var host = this;
|
| +var handler = function (e) {
|
| +if (host[methodName]) {
|
| +host[methodName](e, e.detail);
|
| +} else {
|
| +host._warn(host._logf('_createEventHandler', 'listener method `' + methodName + '` not defined'));
|
| +}
|
| +};
|
| +this._recordEventHandler(host, eventName, node, methodName, handler);
|
| +return handler;
|
| +},
|
| +unlisten: function (node, eventName, methodName) {
|
| +var handler = this._recallEventHandler(this, eventName, node, methodName);
|
| +if (handler) {
|
| +this._unlisten(node, eventName, handler);
|
| +}
|
| +},
|
| +_listen: function (node, eventName, handler) {
|
| +node.addEventListener(eventName, handler);
|
| +},
|
| +_unlisten: function (node, eventName, handler) {
|
| +node.removeEventListener(eventName, handler);
|
| +}
|
| +});
|
| +(function () {
|
| +'use strict';
|
| +var HAS_NATIVE_TA = typeof document.head.style.touchAction === 'string';
|
| +var GESTURE_KEY = '__polymerGestures';
|
| +var HANDLED_OBJ = '__polymerGesturesHandled';
|
| +var TOUCH_ACTION = '__polymerGesturesTouchAction';
|
| +var TAP_DISTANCE = 25;
|
| +var TRACK_DISTANCE = 5;
|
| +var TRACK_LENGTH = 2;
|
| +var MOUSE_TIMEOUT = 2500;
|
| +var MOUSE_EVENTS = [
|
| +'mousedown',
|
| +'mousemove',
|
| +'mouseup',
|
| +'click'
|
| +];
|
| +var MOUSE_WHICH_TO_BUTTONS = [
|
| +0,
|
| +1,
|
| +4,
|
| +2
|
| +];
|
| +var MOUSE_HAS_BUTTONS = function () {
|
| +try {
|
| +return new MouseEvent('test', { buttons: 1 }).buttons === 1;
|
| +} catch (e) {
|
| +return false;
|
| +}
|
| +}();
|
| +var IS_TOUCH_ONLY = navigator.userAgent.match(/iP(?:[oa]d|hone)|Android/);
|
| +var mouseCanceller = function (mouseEvent) {
|
| +mouseEvent[HANDLED_OBJ] = { skip: true };
|
| +if (mouseEvent.type === 'click') {
|
| +var path = Polymer.dom(mouseEvent).path;
|
| +for (var i = 0; i < path.length; i++) {
|
| +if (path[i] === POINTERSTATE.mouse.target) {
|
| +return;
|
| +}
|
| +}
|
| +mouseEvent.preventDefault();
|
| +mouseEvent.stopPropagation();
|
| +}
|
| +};
|
| +function setupTeardownMouseCanceller(setup) {
|
| +for (var i = 0, en; i < MOUSE_EVENTS.length; i++) {
|
| +en = MOUSE_EVENTS[i];
|
| +if (setup) {
|
| +document.addEventListener(en, mouseCanceller, true);
|
| +} else {
|
| +document.removeEventListener(en, mouseCanceller, true);
|
| +}
|
| +}
|
| +}
|
| +function ignoreMouse() {
|
| +if (IS_TOUCH_ONLY) {
|
| +return;
|
| +}
|
| +if (!POINTERSTATE.mouse.mouseIgnoreJob) {
|
| +setupTeardownMouseCanceller(true);
|
| +}
|
| +var unset = function () {
|
| +setupTeardownMouseCanceller();
|
| +POINTERSTATE.mouse.target = null;
|
| +POINTERSTATE.mouse.mouseIgnoreJob = null;
|
| +};
|
| +POINTERSTATE.mouse.mouseIgnoreJob = Polymer.Debounce(POINTERSTATE.mouse.mouseIgnoreJob, unset, MOUSE_TIMEOUT);
|
| +}
|
| +function hasLeftMouseButton(ev) {
|
| +var type = ev.type;
|
| +if (MOUSE_EVENTS.indexOf(type) === -1) {
|
| +return false;
|
| +}
|
| +if (type === 'mousemove') {
|
| +var buttons = ev.buttons === undefined ? 1 : ev.buttons;
|
| +if (ev instanceof window.MouseEvent && !MOUSE_HAS_BUTTONS) {
|
| +buttons = MOUSE_WHICH_TO_BUTTONS[ev.which] || 0;
|
| +}
|
| +return Boolean(buttons & 1);
|
| +} else {
|
| +var button = ev.button === undefined ? 0 : ev.button;
|
| +return button === 0;
|
| +}
|
| +}
|
| +function isSyntheticClick(ev) {
|
| +if (ev.type === 'click') {
|
| +if (ev.detail === 0) {
|
| +return true;
|
| +}
|
| +var t = Gestures.findOriginalTarget(ev);
|
| +var bcr = t.getBoundingClientRect();
|
| +var x = ev.pageX, y = ev.pageY;
|
| +return !(x >= bcr.left && x <= bcr.right && (y >= bcr.top && y <= bcr.bottom));
|
| +}
|
| +return false;
|
| +}
|
| +var POINTERSTATE = {
|
| +mouse: {
|
| +target: null,
|
| +mouseIgnoreJob: null
|
| +},
|
| +touch: {
|
| +x: 0,
|
| +y: 0,
|
| +id: -1,
|
| +scrollDecided: false
|
| +}
|
| +};
|
| +function firstTouchAction(ev) {
|
| +var path = Polymer.dom(ev).path;
|
| +var ta = 'auto';
|
| +for (var i = 0, n; i < path.length; i++) {
|
| +n = path[i];
|
| +if (n[TOUCH_ACTION]) {
|
| +ta = n[TOUCH_ACTION];
|
| +break;
|
| +}
|
| +}
|
| +return ta;
|
| +}
|
| +function trackDocument(stateObj, movefn, upfn) {
|
| +stateObj.movefn = movefn;
|
| +stateObj.upfn = upfn;
|
| +document.addEventListener('mousemove', movefn);
|
| +document.addEventListener('mouseup', upfn);
|
| +}
|
| +function untrackDocument(stateObj) {
|
| +document.removeEventListener('mousemove', stateObj.movefn);
|
| +document.removeEventListener('mouseup', stateObj.upfn);
|
| +}
|
| +var Gestures = {
|
| +gestures: {},
|
| +recognizers: [],
|
| +deepTargetFind: function (x, y) {
|
| +var node = document.elementFromPoint(x, y);
|
| +var next = node;
|
| +while (next && next.shadowRoot) {
|
| +next = next.shadowRoot.elementFromPoint(x, y);
|
| +if (next) {
|
| +node = next;
|
| +}
|
| +}
|
| +return node;
|
| +},
|
| +findOriginalTarget: function (ev) {
|
| +if (ev.path) {
|
| +return ev.path[0];
|
| +}
|
| +return ev.target;
|
| +},
|
| +handleNative: function (ev) {
|
| +var handled;
|
| +var type = ev.type;
|
| +var node = ev.currentTarget;
|
| +var gobj = node[GESTURE_KEY];
|
| +var gs = gobj[type];
|
| +if (!gs) {
|
| +return;
|
| +}
|
| +if (!ev[HANDLED_OBJ]) {
|
| +ev[HANDLED_OBJ] = {};
|
| +if (type.slice(0, 5) === 'touch') {
|
| +var t = ev.changedTouches[0];
|
| +if (type === 'touchstart') {
|
| +if (ev.touches.length === 1) {
|
| +POINTERSTATE.touch.id = t.identifier;
|
| +}
|
| +}
|
| +if (POINTERSTATE.touch.id !== t.identifier) {
|
| +return;
|
| +}
|
| +if (!HAS_NATIVE_TA) {
|
| +if (type === 'touchstart' || type === 'touchmove') {
|
| +Gestures.handleTouchAction(ev);
|
| +}
|
| +}
|
| +if (type === 'touchend') {
|
| +POINTERSTATE.mouse.target = Polymer.dom(ev).rootTarget;
|
| +ignoreMouse(true);
|
| +}
|
| +}
|
| +}
|
| +handled = ev[HANDLED_OBJ];
|
| +if (handled.skip) {
|
| +return;
|
| +}
|
| +var recognizers = Gestures.recognizers;
|
| +for (var i = 0, r; i < recognizers.length; i++) {
|
| +r = recognizers[i];
|
| +if (gs[r.name] && !handled[r.name]) {
|
| +if (r.flow && r.flow.start.indexOf(ev.type) > -1) {
|
| +if (r.reset) {
|
| +r.reset();
|
| +}
|
| +}
|
| +}
|
| +}
|
| +for (var i = 0, r; i < recognizers.length; i++) {
|
| +r = recognizers[i];
|
| +if (gs[r.name] && !handled[r.name]) {
|
| +handled[r.name] = true;
|
| +r[type](ev);
|
| +}
|
| +}
|
| +},
|
| +handleTouchAction: function (ev) {
|
| +var t = ev.changedTouches[0];
|
| +var type = ev.type;
|
| +if (type === 'touchstart') {
|
| +POINTERSTATE.touch.x = t.clientX;
|
| +POINTERSTATE.touch.y = t.clientY;
|
| +POINTERSTATE.touch.scrollDecided = false;
|
| +} else if (type === 'touchmove') {
|
| +if (POINTERSTATE.touch.scrollDecided) {
|
| +return;
|
| +}
|
| +POINTERSTATE.touch.scrollDecided = true;
|
| +var ta = firstTouchAction(ev);
|
| +var prevent = false;
|
| +var dx = Math.abs(POINTERSTATE.touch.x - t.clientX);
|
| +var dy = Math.abs(POINTERSTATE.touch.y - t.clientY);
|
| +if (!ev.cancelable) {
|
| +} else if (ta === 'none') {
|
| +prevent = true;
|
| +} else if (ta === 'pan-x') {
|
| +prevent = dy > dx;
|
| +} else if (ta === 'pan-y') {
|
| +prevent = dx > dy;
|
| +}
|
| +if (prevent) {
|
| +ev.preventDefault();
|
| +} else {
|
| +Gestures.prevent('track');
|
| +}
|
| +}
|
| +},
|
| +add: function (node, evType, handler) {
|
| +var recognizer = this.gestures[evType];
|
| +var deps = recognizer.deps;
|
| +var name = recognizer.name;
|
| +var gobj = node[GESTURE_KEY];
|
| +if (!gobj) {
|
| +node[GESTURE_KEY] = gobj = {};
|
| +}
|
| +for (var i = 0, dep, gd; i < deps.length; i++) {
|
| +dep = deps[i];
|
| +if (IS_TOUCH_ONLY && MOUSE_EVENTS.indexOf(dep) > -1) {
|
| +continue;
|
| +}
|
| +gd = gobj[dep];
|
| +if (!gd) {
|
| +gobj[dep] = gd = { _count: 0 };
|
| +}
|
| +if (gd._count === 0) {
|
| +node.addEventListener(dep, this.handleNative);
|
| +}
|
| +gd[name] = (gd[name] || 0) + 1;
|
| +gd._count = (gd._count || 0) + 1;
|
| +}
|
| +node.addEventListener(evType, handler);
|
| +if (recognizer.touchAction) {
|
| +this.setTouchAction(node, recognizer.touchAction);
|
| +}
|
| +},
|
| +remove: function (node, evType, handler) {
|
| +var recognizer = this.gestures[evType];
|
| +var deps = recognizer.deps;
|
| +var name = recognizer.name;
|
| +var gobj = node[GESTURE_KEY];
|
| +if (gobj) {
|
| +for (var i = 0, dep, gd; i < deps.length; i++) {
|
| +dep = deps[i];
|
| +gd = gobj[dep];
|
| +if (gd && gd[name]) {
|
| +gd[name] = (gd[name] || 1) - 1;
|
| +gd._count = (gd._count || 1) - 1;
|
| +if (gd._count === 0) {
|
| +node.removeEventListener(dep, this.handleNative);
|
| +}
|
| +}
|
| +}
|
| +}
|
| +node.removeEventListener(evType, handler);
|
| +},
|
| +register: function (recog) {
|
| +this.recognizers.push(recog);
|
| +for (var i = 0; i < recog.emits.length; i++) {
|
| +this.gestures[recog.emits[i]] = recog;
|
| +}
|
| +},
|
| +findRecognizerByEvent: function (evName) {
|
| +for (var i = 0, r; i < this.recognizers.length; i++) {
|
| +r = this.recognizers[i];
|
| +for (var j = 0, n; j < r.emits.length; j++) {
|
| +n = r.emits[j];
|
| +if (n === evName) {
|
| +return r;
|
| +}
|
| +}
|
| +}
|
| +return null;
|
| +},
|
| +setTouchAction: function (node, value) {
|
| +if (HAS_NATIVE_TA) {
|
| +node.style.touchAction = value;
|
| +}
|
| +node[TOUCH_ACTION] = value;
|
| +},
|
| +fire: function (target, type, detail) {
|
| +var ev = Polymer.Base.fire(type, detail, {
|
| +node: target,
|
| +bubbles: true,
|
| +cancelable: true
|
| +});
|
| +if (ev.defaultPrevented) {
|
| +var se = detail.sourceEvent;
|
| +if (se && se.preventDefault) {
|
| +se.preventDefault();
|
| +}
|
| +}
|
| +},
|
| +prevent: function (evName) {
|
| +var recognizer = this.findRecognizerByEvent(evName);
|
| +if (recognizer.info) {
|
| +recognizer.info.prevent = true;
|
| +}
|
| +}
|
| +};
|
| +Gestures.register({
|
| +name: 'downup',
|
| +deps: [
|
| +'mousedown',
|
| +'touchstart',
|
| +'touchend'
|
| +],
|
| +flow: {
|
| +start: [
|
| +'mousedown',
|
| +'touchstart'
|
| +],
|
| +end: [
|
| +'mouseup',
|
| +'touchend'
|
| +]
|
| +},
|
| +emits: [
|
| +'down',
|
| +'up'
|
| +],
|
| +info: {
|
| +movefn: function () {
|
| +},
|
| +upfn: function () {
|
| +}
|
| +},
|
| +reset: function () {
|
| +untrackDocument(this.info);
|
| +},
|
| +mousedown: function (e) {
|
| +if (!hasLeftMouseButton(e)) {
|
| +return;
|
| +}
|
| +var t = Gestures.findOriginalTarget(e);
|
| +var self = this;
|
| +var movefn = function movefn(e) {
|
| +if (!hasLeftMouseButton(e)) {
|
| +self.fire('up', t, e);
|
| +untrackDocument(self.info);
|
| +}
|
| +};
|
| +var upfn = function upfn(e) {
|
| +if (hasLeftMouseButton(e)) {
|
| +self.fire('up', t, e);
|
| +}
|
| +untrackDocument(self.info);
|
| +};
|
| +trackDocument(this.info, movefn, upfn);
|
| +this.fire('down', t, e);
|
| +},
|
| +touchstart: function (e) {
|
| +this.fire('down', Gestures.findOriginalTarget(e), e.changedTouches[0]);
|
| +},
|
| +touchend: function (e) {
|
| +this.fire('up', Gestures.findOriginalTarget(e), e.changedTouches[0]);
|
| +},
|
| +fire: function (type, target, event) {
|
| +var self = this;
|
| +Gestures.fire(target, type, {
|
| +x: event.clientX,
|
| +y: event.clientY,
|
| +sourceEvent: event,
|
| +prevent: Gestures.prevent.bind(Gestures)
|
| +});
|
| +}
|
| +});
|
| +Gestures.register({
|
| +name: 'track',
|
| +touchAction: 'none',
|
| +deps: [
|
| +'mousedown',
|
| +'touchstart',
|
| +'touchmove',
|
| +'touchend'
|
| +],
|
| +flow: {
|
| +start: [
|
| +'mousedown',
|
| +'touchstart'
|
| +],
|
| +end: [
|
| +'mouseup',
|
| +'touchend'
|
| +]
|
| +},
|
| +emits: ['track'],
|
| +info: {
|
| +x: 0,
|
| +y: 0,
|
| +state: 'start',
|
| +started: false,
|
| +moves: [],
|
| +addMove: function (move) {
|
| +if (this.moves.length > TRACK_LENGTH) {
|
| +this.moves.shift();
|
| +}
|
| +this.moves.push(move);
|
| +},
|
| +movefn: function () {
|
| +},
|
| +upfn: function () {
|
| +},
|
| +prevent: false
|
| +},
|
| +reset: function () {
|
| +this.info.state = 'start';
|
| +this.info.started = false;
|
| +this.info.moves = [];
|
| +this.info.x = 0;
|
| +this.info.y = 0;
|
| +this.info.prevent = false;
|
| +untrackDocument(this.info);
|
| +},
|
| +hasMovedEnough: function (x, y) {
|
| +if (this.info.prevent) {
|
| +return false;
|
| +}
|
| +if (this.info.started) {
|
| +return true;
|
| +}
|
| +var dx = Math.abs(this.info.x - x);
|
| +var dy = Math.abs(this.info.y - y);
|
| +return dx >= TRACK_DISTANCE || dy >= TRACK_DISTANCE;
|
| +},
|
| +mousedown: function (e) {
|
| +if (!hasLeftMouseButton(e)) {
|
| +return;
|
| +}
|
| +var t = Gestures.findOriginalTarget(e);
|
| +var self = this;
|
| +var movefn = function movefn(e) {
|
| +var x = e.clientX, y = e.clientY;
|
| +if (self.hasMovedEnough(x, y)) {
|
| +self.info.state = self.info.started ? e.type === 'mouseup' ? 'end' : 'track' : 'start';
|
| +self.info.addMove({
|
| +x: x,
|
| +y: y
|
| +});
|
| +if (!hasLeftMouseButton(e)) {
|
| +self.info.state = 'end';
|
| +untrackDocument(self.info);
|
| +}
|
| +self.fire(t, e);
|
| +self.info.started = true;
|
| +}
|
| +};
|
| +var upfn = function upfn(e) {
|
| +if (self.info.started) {
|
| +Gestures.prevent('tap');
|
| +movefn(e);
|
| +}
|
| +untrackDocument(self.info);
|
| +};
|
| +trackDocument(this.info, movefn, upfn);
|
| +this.info.x = e.clientX;
|
| +this.info.y = e.clientY;
|
| +},
|
| +touchstart: function (e) {
|
| +var ct = e.changedTouches[0];
|
| +this.info.x = ct.clientX;
|
| +this.info.y = ct.clientY;
|
| +},
|
| +touchmove: function (e) {
|
| +var t = Gestures.findOriginalTarget(e);
|
| +var ct = e.changedTouches[0];
|
| +var x = ct.clientX, y = ct.clientY;
|
| +if (this.hasMovedEnough(x, y)) {
|
| +this.info.addMove({
|
| +x: x,
|
| +y: y
|
| +});
|
| +this.fire(t, ct);
|
| +this.info.state = 'track';
|
| +this.info.started = true;
|
| +}
|
| +},
|
| +touchend: function (e) {
|
| +var t = Gestures.findOriginalTarget(e);
|
| +var ct = e.changedTouches[0];
|
| +if (this.info.started) {
|
| +Gestures.prevent('tap');
|
| +this.info.state = 'end';
|
| +this.info.addMove({
|
| +x: ct.clientX,
|
| +y: ct.clientY
|
| +});
|
| +this.fire(t, ct);
|
| +}
|
| +},
|
| +fire: function (target, touch) {
|
| +var secondlast = this.info.moves[this.info.moves.length - 2];
|
| +var lastmove = this.info.moves[this.info.moves.length - 1];
|
| +var dx = lastmove.x - this.info.x;
|
| +var dy = lastmove.y - this.info.y;
|
| +var ddx, ddy = 0;
|
| +if (secondlast) {
|
| +ddx = lastmove.x - secondlast.x;
|
| +ddy = lastmove.y - secondlast.y;
|
| +}
|
| +return Gestures.fire(target, 'track', {
|
| +state: this.info.state,
|
| +x: touch.clientX,
|
| +y: touch.clientY,
|
| +dx: dx,
|
| +dy: dy,
|
| +ddx: ddx,
|
| +ddy: ddy,
|
| +sourceEvent: touch,
|
| +hover: function () {
|
| +return Gestures.deepTargetFind(touch.clientX, touch.clientY);
|
| +}
|
| +});
|
| +}
|
| +});
|
| +Gestures.register({
|
| +name: 'tap',
|
| +deps: [
|
| +'mousedown',
|
| +'click',
|
| +'touchstart',
|
| +'touchend'
|
| +],
|
| +flow: {
|
| +start: [
|
| +'mousedown',
|
| +'touchstart'
|
| +],
|
| +end: [
|
| +'click',
|
| +'touchend'
|
| +]
|
| +},
|
| +emits: ['tap'],
|
| +info: {
|
| +x: NaN,
|
| +y: NaN,
|
| +prevent: false
|
| +},
|
| +reset: function () {
|
| +this.info.x = NaN;
|
| +this.info.y = NaN;
|
| +this.info.prevent = false;
|
| +},
|
| +save: function (e) {
|
| +this.info.x = e.clientX;
|
| +this.info.y = e.clientY;
|
| +},
|
| +mousedown: function (e) {
|
| +if (hasLeftMouseButton(e)) {
|
| +this.save(e);
|
| +}
|
| +},
|
| +click: function (e) {
|
| +if (hasLeftMouseButton(e)) {
|
| +this.forward(e);
|
| +}
|
| +},
|
| +touchstart: function (e) {
|
| +this.save(e.changedTouches[0]);
|
| +},
|
| +touchend: function (e) {
|
| +this.forward(e.changedTouches[0]);
|
| +},
|
| +forward: function (e) {
|
| +var dx = Math.abs(e.clientX - this.info.x);
|
| +var dy = Math.abs(e.clientY - this.info.y);
|
| +var t = Gestures.findOriginalTarget(e);
|
| +if (isNaN(dx) || isNaN(dy) || dx <= TAP_DISTANCE && dy <= TAP_DISTANCE || isSyntheticClick(e)) {
|
| +if (!this.info.prevent) {
|
| +Gestures.fire(t, 'tap', {
|
| +x: e.clientX,
|
| +y: e.clientY,
|
| +sourceEvent: e
|
| +});
|
| +}
|
| +}
|
| +}
|
| +});
|
| +var DIRECTION_MAP = {
|
| +x: 'pan-x',
|
| +y: 'pan-y',
|
| +none: 'none',
|
| +all: 'auto'
|
| +};
|
| +Polymer.Base._addFeature({
|
| +_listen: function (node, eventName, handler) {
|
| +if (Gestures.gestures[eventName]) {
|
| +Gestures.add(node, eventName, handler);
|
| +} else {
|
| +node.addEventListener(eventName, handler);
|
| +}
|
| +},
|
| +_unlisten: function (node, eventName, handler) {
|
| +if (Gestures.gestures[eventName]) {
|
| +Gestures.remove(node, eventName, handler);
|
| +} else {
|
| +node.removeEventListener(eventName, handler);
|
| +}
|
| +},
|
| +setScrollDirection: function (direction, node) {
|
| +node = node || this;
|
| +Gestures.setTouchAction(node, DIRECTION_MAP[direction] || 'auto');
|
| +}
|
| +});
|
| +Polymer.Gestures = Gestures;
|
| +}());
|
| +Polymer.Async = {
|
| +_currVal: 0,
|
| +_lastVal: 0,
|
| +_callbacks: [],
|
| +_twiddleContent: 0,
|
| +_twiddle: document.createTextNode(''),
|
| +run: function (callback, waitTime) {
|
| +if (waitTime > 0) {
|
| +return ~setTimeout(callback, waitTime);
|
| +} else {
|
| +this._twiddle.textContent = this._twiddleContent++;
|
| +this._callbacks.push(callback);
|
| +return this._currVal++;
|
| +}
|
| +},
|
| +cancel: function (handle) {
|
| +if (handle < 0) {
|
| +clearTimeout(~handle);
|
| +} else {
|
| +var idx = handle - this._lastVal;
|
| +if (idx >= 0) {
|
| +if (!this._callbacks[idx]) {
|
| +throw 'invalid async handle: ' + handle;
|
| +}
|
| +this._callbacks[idx] = null;
|
| +}
|
| +}
|
| +},
|
| +_atEndOfMicrotask: function () {
|
| +var len = this._callbacks.length;
|
| +for (var i = 0; i < len; i++) {
|
| +var cb = this._callbacks[i];
|
| +if (cb) {
|
| +try {
|
| +cb();
|
| +} catch (e) {
|
| +i++;
|
| +this._callbacks.splice(0, i);
|
| +this._lastVal += i;
|
| +this._twiddle.textContent = this._twiddleContent++;
|
| +throw e;
|
| +}
|
| +}
|
| +}
|
| +this._callbacks.splice(0, len);
|
| +this._lastVal += len;
|
| +}
|
| +};
|
| +new (window.MutationObserver || JsMutationObserver)(Polymer.Async._atEndOfMicrotask.bind(Polymer.Async)).observe(Polymer.Async._twiddle, { characterData: true });
|
| +Polymer.Debounce = function () {
|
| +var Async = Polymer.Async;
|
| +var Debouncer = function (context) {
|
| +this.context = context;
|
| +this.boundComplete = this.complete.bind(this);
|
| +};
|
| +Debouncer.prototype = {
|
| +go: function (callback, wait) {
|
| +var h;
|
| +this.finish = function () {
|
| +Async.cancel(h);
|
| +};
|
| +h = Async.run(this.boundComplete, wait);
|
| +this.callback = callback;
|
| +},
|
| +stop: function () {
|
| +if (this.finish) {
|
| +this.finish();
|
| +this.finish = null;
|
| +}
|
| +},
|
| +complete: function () {
|
| +if (this.finish) {
|
| +this.stop();
|
| +this.callback.call(this.context);
|
| +}
|
| +}
|
| +};
|
| +function debounce(debouncer, callback, wait) {
|
| +if (debouncer) {
|
| +debouncer.stop();
|
| +} else {
|
| +debouncer = new Debouncer(this);
|
| +}
|
| +debouncer.go(callback, wait);
|
| +return debouncer;
|
| +}
|
| +return debounce;
|
| +}();
|
| +Polymer.Base._addFeature({
|
| +$$: function (slctr) {
|
| +return Polymer.dom(this.root).querySelector(slctr);
|
| +},
|
| +toggleClass: function (name, bool, node) {
|
| +node = node || this;
|
| +if (arguments.length == 1) {
|
| +bool = !node.classList.contains(name);
|
| +}
|
| +if (bool) {
|
| +Polymer.dom(node).classList.add(name);
|
| +} else {
|
| +Polymer.dom(node).classList.remove(name);
|
| +}
|
| +},
|
| +toggleAttribute: function (name, bool, node) {
|
| +node = node || this;
|
| +if (arguments.length == 1) {
|
| +bool = !node.hasAttribute(name);
|
| +}
|
| +if (bool) {
|
| +Polymer.dom(node).setAttribute(name, '');
|
| +} else {
|
| +Polymer.dom(node).removeAttribute(name);
|
| +}
|
| +},
|
| +classFollows: function (name, toElement, fromElement) {
|
| +if (fromElement) {
|
| +Polymer.dom(fromElement).classList.remove(name);
|
| +}
|
| +if (toElement) {
|
| +Polymer.dom(toElement).classList.add(name);
|
| +}
|
| +},
|
| +attributeFollows: function (name, toElement, fromElement) {
|
| +if (fromElement) {
|
| +Polymer.dom(fromElement).removeAttribute(name);
|
| +}
|
| +if (toElement) {
|
| +Polymer.dom(toElement).setAttribute(name, '');
|
| +}
|
| +},
|
| +getContentChildNodes: function (slctr) {
|
| +var content = Polymer.dom(this.root).querySelector(slctr || 'content');
|
| +return content ? Polymer.dom(content).getDistributedNodes() : [];
|
| +},
|
| +getContentChildren: function (slctr) {
|
| +return this.getContentChildNodes(slctr).filter(function (n) {
|
| +return n.nodeType === Node.ELEMENT_NODE;
|
| +});
|
| +},
|
| +fire: function (type, detail, options) {
|
| +options = options || Polymer.nob;
|
| +var node = options.node || this;
|
| +var detail = detail === null || detail === undefined ? Polymer.nob : detail;
|
| +var bubbles = options.bubbles === undefined ? true : options.bubbles;
|
| +var cancelable = Boolean(options.cancelable);
|
| +var event = new CustomEvent(type, {
|
| +bubbles: Boolean(bubbles),
|
| +cancelable: cancelable,
|
| +detail: detail
|
| +});
|
| +node.dispatchEvent(event);
|
| +return event;
|
| +},
|
| +async: function (callback, waitTime) {
|
| +return Polymer.Async.run(callback.bind(this), waitTime);
|
| +},
|
| +cancelAsync: function (handle) {
|
| +Polymer.Async.cancel(handle);
|
| +},
|
| +arrayDelete: function (path, item) {
|
| +var index;
|
| +if (Array.isArray(path)) {
|
| +index = path.indexOf(item);
|
| +if (index >= 0) {
|
| +return path.splice(index, 1);
|
| +}
|
| +} else {
|
| +var arr = this.get(path);
|
| +index = arr.indexOf(item);
|
| +if (index >= 0) {
|
| +return this.splice(path, index, 1);
|
| +}
|
| +}
|
| +},
|
| +transform: function (transform, node) {
|
| +node = node || this;
|
| +node.style.webkitTransform = transform;
|
| +node.style.transform = transform;
|
| +},
|
| +translate3d: function (x, y, z, node) {
|
| +node = node || this;
|
| +this.transform('translate3d(' + x + ',' + y + ',' + z + ')', node);
|
| +},
|
| +importHref: function (href, onload, onerror) {
|
| +var l = document.createElement('link');
|
| +l.rel = 'import';
|
| +l.href = href;
|
| +if (onload) {
|
| +l.onload = onload.bind(this);
|
| +}
|
| +if (onerror) {
|
| +l.onerror = onerror.bind(this);
|
| +}
|
| +document.head.appendChild(l);
|
| +return l;
|
| +},
|
| +create: function (tag, props) {
|
| +var elt = document.createElement(tag);
|
| +if (props) {
|
| +for (var n in props) {
|
| +elt[n] = props[n];
|
| +}
|
| +}
|
| +return elt;
|
| +}
|
| +});
|
| +Polymer.Bind = {
|
| +prepareModel: function (model) {
|
| +model._propertyEffects = {};
|
| +model._bindListeners = [];
|
| +Polymer.Base.mixin(model, this._modelApi);
|
| +},
|
| +_modelApi: {
|
| +_notifyChange: function (property) {
|
| +var eventName = Polymer.CaseMap.camelToDashCase(property) + '-changed';
|
| +Polymer.Base.fire(eventName, { value: this[property] }, {
|
| +bubbles: false,
|
| +node: this
|
| +});
|
| +},
|
| +_propertySetter: function (property, value, effects, fromAbove) {
|
| +var old = this.__data__[property];
|
| +if (old !== value && (old === old || value === value)) {
|
| +this.__data__[property] = value;
|
| +if (typeof value == 'object') {
|
| +this._clearPath(property);
|
| +}
|
| +if (this._propertyChanged) {
|
| +this._propertyChanged(property, value, old);
|
| +}
|
| +if (effects) {
|
| +this._effectEffects(property, value, effects, old, fromAbove);
|
| +}
|
| +}
|
| +return old;
|
| +},
|
| +__setProperty: function (property, value, quiet, node) {
|
| +node = node || this;
|
| +var effects = node._propertyEffects && node._propertyEffects[property];
|
| +if (effects) {
|
| +node._propertySetter(property, value, effects, quiet);
|
| +} else {
|
| +node[property] = value;
|
| +}
|
| +},
|
| +_effectEffects: function (property, value, effects, old, fromAbove) {
|
| +effects.forEach(function (fx) {
|
| +var fn = Polymer.Bind['_' + fx.kind + 'Effect'];
|
| +if (fn) {
|
| +fn.call(this, property, value, fx.effect, old, fromAbove);
|
| +}
|
| +}, this);
|
| +},
|
| +_clearPath: function (path) {
|
| +for (var prop in this.__data__) {
|
| +if (prop.indexOf(path + '.') === 0) {
|
| +this.__data__[prop] = undefined;
|
| +}
|
| +}
|
| +}
|
| +},
|
| +ensurePropertyEffects: function (model, property) {
|
| +var fx = model._propertyEffects[property];
|
| +if (!fx) {
|
| +fx = model._propertyEffects[property] = [];
|
| +}
|
| +return fx;
|
| +},
|
| +addPropertyEffect: function (model, property, kind, effect) {
|
| +var fx = this.ensurePropertyEffects(model, property);
|
| +fx.push({
|
| +kind: kind,
|
| +effect: effect
|
| +});
|
| +},
|
| +createBindings: function (model) {
|
| +var fx$ = model._propertyEffects;
|
| +if (fx$) {
|
| +for (var n in fx$) {
|
| +var fx = fx$[n];
|
| +fx.sort(this._sortPropertyEffects);
|
| +this._createAccessors(model, n, fx);
|
| +}
|
| +}
|
| +},
|
| +_sortPropertyEffects: function () {
|
| +var EFFECT_ORDER = {
|
| +'compute': 0,
|
| +'annotation': 1,
|
| +'computedAnnotation': 2,
|
| +'reflect': 3,
|
| +'notify': 4,
|
| +'observer': 5,
|
| +'complexObserver': 6,
|
| +'function': 7
|
| +};
|
| +return function (a, b) {
|
| +return EFFECT_ORDER[a.kind] - EFFECT_ORDER[b.kind];
|
| +};
|
| +}(),
|
| +_createAccessors: function (model, property, effects) {
|
| +var defun = {
|
| +get: function () {
|
| +return this.__data__[property];
|
| +}
|
| +};
|
| +var setter = function (value) {
|
| +this._propertySetter(property, value, effects);
|
| +};
|
| +var info = model.getPropertyInfo && model.getPropertyInfo(property);
|
| +if (info && info.readOnly) {
|
| +if (!info.computed) {
|
| +model['_set' + this.upper(property)] = setter;
|
| +}
|
| +} else {
|
| +defun.set = setter;
|
| +}
|
| +Object.defineProperty(model, property, defun);
|
| +},
|
| +upper: function (name) {
|
| +return name[0].toUpperCase() + name.substring(1);
|
| +},
|
| +_addAnnotatedListener: function (model, index, property, path, event) {
|
| +var fn = this._notedListenerFactory(property, path, this._isStructured(path), this._isEventBogus);
|
| +var eventName = event || Polymer.CaseMap.camelToDashCase(property) + '-changed';
|
| +model._bindListeners.push({
|
| +index: index,
|
| +property: property,
|
| +path: path,
|
| +changedFn: fn,
|
| +event: eventName
|
| +});
|
| +},
|
| +_isStructured: function (path) {
|
| +return path.indexOf('.') > 0;
|
| +},
|
| +_isEventBogus: function (e, target) {
|
| +return e.path && e.path[0] !== target;
|
| +},
|
| +_notedListenerFactory: function (property, path, isStructured, bogusTest) {
|
| +return function (e, target) {
|
| +if (!bogusTest(e, target)) {
|
| +if (e.detail && e.detail.path) {
|
| +this.notifyPath(this._fixPath(path, property, e.detail.path), e.detail.value);
|
| +} else {
|
| +var value = target[property];
|
| +if (!isStructured) {
|
| +this[path] = target[property];
|
| +} else {
|
| +if (this.__data__[path] != value) {
|
| +this.set(path, value);
|
| +}
|
| +}
|
| +}
|
| +}
|
| +};
|
| +},
|
| +prepareInstance: function (inst) {
|
| +inst.__data__ = Object.create(null);
|
| +},
|
| +setupBindListeners: function (inst) {
|
| +inst._bindListeners.forEach(function (info) {
|
| +var node = inst._nodes[info.index];
|
| +node.addEventListener(info.event, inst._notifyListener.bind(inst, info.changedFn));
|
| +});
|
| +}
|
| +};
|
| +Polymer.Base.extend(Polymer.Bind, {
|
| +_shouldAddListener: function (effect) {
|
| +return effect.name && effect.mode === '{' && !effect.negate && effect.kind != 'attribute';
|
| +},
|
| +_annotationEffect: function (source, value, effect) {
|
| +if (source != effect.value) {
|
| +value = this.get(effect.value);
|
| +this.__data__[effect.value] = value;
|
| +}
|
| +var calc = effect.negate ? !value : value;
|
| +if (!effect.customEvent || this._nodes[effect.index][effect.name] !== calc) {
|
| +return this._applyEffectValue(calc, effect);
|
| +}
|
| +},
|
| +_reflectEffect: function (source) {
|
| +this.reflectPropertyToAttribute(source);
|
| +},
|
| +_notifyEffect: function (source, value, effect, old, fromAbove) {
|
| +if (!fromAbove) {
|
| +this._notifyChange(source);
|
| +}
|
| +},
|
| +_functionEffect: function (source, value, fn, old, fromAbove) {
|
| +fn.call(this, source, value, old, fromAbove);
|
| +},
|
| +_observerEffect: function (source, value, effect, old) {
|
| +var fn = this[effect.method];
|
| +if (fn) {
|
| +fn.call(this, value, old);
|
| +} else {
|
| +this._warn(this._logf('_observerEffect', 'observer method `' + effect.method + '` not defined'));
|
| +}
|
| +},
|
| +_complexObserverEffect: function (source, value, effect) {
|
| +var fn = this[effect.method];
|
| +if (fn) {
|
| +var args = Polymer.Bind._marshalArgs(this.__data__, effect, source, value);
|
| +if (args) {
|
| +fn.apply(this, args);
|
| +}
|
| +} else {
|
| +this._warn(this._logf('_complexObserverEffect', 'observer method `' + effect.method + '` not defined'));
|
| +}
|
| +},
|
| +_computeEffect: function (source, value, effect) {
|
| +var args = Polymer.Bind._marshalArgs(this.__data__, effect, source, value);
|
| +if (args) {
|
| +var fn = this[effect.method];
|
| +if (fn) {
|
| +this.__setProperty(effect.property, fn.apply(this, args));
|
| +} else {
|
| +this._warn(this._logf('_computeEffect', 'compute method `' + effect.method + '` not defined'));
|
| +}
|
| +}
|
| +},
|
| +_annotatedComputationEffect: function (source, value, effect) {
|
| +var computedHost = this._rootDataHost || this;
|
| +var fn = computedHost[effect.method];
|
| +if (fn) {
|
| +var args = Polymer.Bind._marshalArgs(this.__data__, effect, source, value);
|
| +if (args) {
|
| +var computedvalue = fn.apply(computedHost, args);
|
| +if (effect.negate) {
|
| +computedvalue = !computedvalue;
|
| +}
|
| +this._applyEffectValue(computedvalue, effect);
|
| +}
|
| +} else {
|
| +computedHost._warn(computedHost._logf('_annotatedComputationEffect', 'compute method `' + effect.method + '` not defined'));
|
| +}
|
| +},
|
| +_marshalArgs: function (model, effect, path, value) {
|
| +var values = [];
|
| +var args = effect.args;
|
| +for (var i = 0, l = args.length; i < l; i++) {
|
| +var arg = args[i];
|
| +var name = arg.name;
|
| +var v;
|
| +if (arg.literal) {
|
| +v = arg.value;
|
| +} else if (arg.structured) {
|
| +v = Polymer.Base.get(name, model);
|
| +} else {
|
| +v = model[name];
|
| +}
|
| +if (args.length > 1 && v === undefined) {
|
| +return;
|
| +}
|
| +if (arg.wildcard) {
|
| +var baseChanged = name.indexOf(path + '.') === 0;
|
| +var matches = effect.trigger.name.indexOf(name) === 0 && !baseChanged;
|
| +values[i] = {
|
| +path: matches ? path : name,
|
| +value: matches ? value : v,
|
| +base: v
|
| +};
|
| +} else {
|
| +values[i] = v;
|
| +}
|
| +}
|
| +return values;
|
| +}
|
| +});
|
| +Polymer.Base._addFeature({
|
| +_addPropertyEffect: function (property, kind, effect) {
|
| +Polymer.Bind.addPropertyEffect(this, property, kind, effect);
|
| +},
|
| +_prepEffects: function () {
|
| +Polymer.Bind.prepareModel(this);
|
| +this._addAnnotationEffects(this._notes);
|
| +},
|
| +_prepBindings: function () {
|
| +Polymer.Bind.createBindings(this);
|
| +},
|
| +_addPropertyEffects: function (properties) {
|
| +if (properties) {
|
| +for (var p in properties) {
|
| +var prop = properties[p];
|
| +if (prop.observer) {
|
| +this._addObserverEffect(p, prop.observer);
|
| +}
|
| +if (prop.computed) {
|
| +prop.readOnly = true;
|
| +this._addComputedEffect(p, prop.computed);
|
| +}
|
| +if (prop.notify) {
|
| +this._addPropertyEffect(p, 'notify');
|
| +}
|
| +if (prop.reflectToAttribute) {
|
| +this._addPropertyEffect(p, 'reflect');
|
| +}
|
| +if (prop.readOnly) {
|
| +Polymer.Bind.ensurePropertyEffects(this, p);
|
| +}
|
| +}
|
| +}
|
| +},
|
| +_addComputedEffect: function (name, expression) {
|
| +var sig = this._parseMethod(expression);
|
| +sig.args.forEach(function (arg) {
|
| +this._addPropertyEffect(arg.model, 'compute', {
|
| +method: sig.method,
|
| +args: sig.args,
|
| +trigger: arg,
|
| +property: name
|
| +});
|
| +}, this);
|
| +},
|
| +_addObserverEffect: function (property, observer) {
|
| +this._addPropertyEffect(property, 'observer', {
|
| +method: observer,
|
| +property: property
|
| +});
|
| +},
|
| +_addComplexObserverEffects: function (observers) {
|
| +if (observers) {
|
| +observers.forEach(function (observer) {
|
| +this._addComplexObserverEffect(observer);
|
| +}, this);
|
| +}
|
| +},
|
| +_addComplexObserverEffect: function (observer) {
|
| +var sig = this._parseMethod(observer);
|
| +sig.args.forEach(function (arg) {
|
| +this._addPropertyEffect(arg.model, 'complexObserver', {
|
| +method: sig.method,
|
| +args: sig.args,
|
| +trigger: arg
|
| +});
|
| +}, this);
|
| +},
|
| +_addAnnotationEffects: function (notes) {
|
| +this._nodes = [];
|
| +notes.forEach(function (note) {
|
| +var index = this._nodes.push(note) - 1;
|
| +note.bindings.forEach(function (binding) {
|
| +this._addAnnotationEffect(binding, index);
|
| +}, this);
|
| +}, this);
|
| +},
|
| +_addAnnotationEffect: function (note, index) {
|
| +if (Polymer.Bind._shouldAddListener(note)) {
|
| +Polymer.Bind._addAnnotatedListener(this, index, note.name, note.value, note.event);
|
| +}
|
| +if (note.signature) {
|
| +this._addAnnotatedComputationEffect(note, index);
|
| +} else {
|
| +note.index = index;
|
| +this._addPropertyEffect(note.model, 'annotation', note);
|
| +}
|
| +},
|
| +_addAnnotatedComputationEffect: function (note, index) {
|
| +var sig = note.signature;
|
| +if (sig.static) {
|
| +this.__addAnnotatedComputationEffect('__static__', index, note, sig, null);
|
| +} else {
|
| +sig.args.forEach(function (arg) {
|
| +if (!arg.literal) {
|
| +this.__addAnnotatedComputationEffect(arg.model, index, note, sig, arg);
|
| +}
|
| +}, this);
|
| +}
|
| +},
|
| +__addAnnotatedComputationEffect: function (property, index, note, sig, trigger) {
|
| +this._addPropertyEffect(property, 'annotatedComputation', {
|
| +index: index,
|
| +kind: note.kind,
|
| +property: note.name,
|
| +negate: note.negate,
|
| +method: sig.method,
|
| +args: sig.args,
|
| +trigger: trigger
|
| +});
|
| +},
|
| +_parseMethod: function (expression) {
|
| +var m = expression.match(/([^\s]+)\((.*)\)/);
|
| +if (m) {
|
| +var sig = {
|
| +method: m[1],
|
| +static: true
|
| +};
|
| +if (m[2].trim()) {
|
| +var args = m[2].replace(/\\,/g, ',').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);
|
| +Polymer.Bind.setupBindListeners(this);
|
| +},
|
| +_applyEffectValue: function (value, info) {
|
| +var node = this._nodes[info.index];
|
| +var property = info.property || info.name || 'textContent';
|
| +if (info.kind == 'attribute') {
|
| +this.serializeValueToAttribute(value, property, node);
|
| +} else {
|
| +if (property === 'className') {
|
| +value = this._scopeElementClass(node, value);
|
| +}
|
| +if (property === 'textContent' || node.localName == 'input' && property == 'value') {
|
| +value = value == undefined ? '' : value;
|
| +}
|
| +return node[property] = value;
|
| +}
|
| +},
|
| +_executeStaticEffects: function () {
|
| +if (this._propertyEffects.__static__) {
|
| +this._effectEffects('__static__', null, this._propertyEffects.__static__);
|
| +}
|
| +}
|
| +});
|
| +Polymer.Base._addFeature({
|
| +_setupConfigure: function (initialConfig) {
|
| +this._config = {};
|
| +for (var i in initialConfig) {
|
| +if (initialConfig[i] !== undefined) {
|
| +this._config[i] = initialConfig[i];
|
| +}
|
| +}
|
| +this._handlers = [];
|
| +},
|
| +_marshalAttributes: function () {
|
| +this._takeAttributesToModel(this._config);
|
| +},
|
| +_attributeChangedImpl: function (name) {
|
| +var model = this._clientsReadied ? this : this._config;
|
| +this._setAttributeToProperty(model, name);
|
| +},
|
| +_configValue: function (name, value) {
|
| +this._config[name] = value;
|
| +},
|
| +_beforeClientsReady: function () {
|
| +this._configure();
|
| +},
|
| +_configure: function () {
|
| +this._configureAnnotationReferences();
|
| +this._aboveConfig = this.mixin({}, this._config);
|
| +var config = {};
|
| +this.behaviors.forEach(function (b) {
|
| +this._configureProperties(b.properties, config);
|
| +}, this);
|
| +this._configureProperties(this.properties, config);
|
| +this._mixinConfigure(config, this._aboveConfig);
|
| +this._config = config;
|
| +this._distributeConfig(this._config);
|
| +},
|
| +_configureProperties: function (properties, config) {
|
| +for (var i in properties) {
|
| +var c = properties[i];
|
| +if (c.value !== undefined) {
|
| +var value = c.value;
|
| +if (typeof value == 'function') {
|
| +value = value.call(this, this._config);
|
| +}
|
| +config[i] = value;
|
| +}
|
| +}
|
| +},
|
| +_mixinConfigure: function (a, b) {
|
| +for (var prop in b) {
|
| +if (!this.getPropertyInfo(prop).readOnly) {
|
| +a[prop] = b[prop];
|
| +}
|
| +}
|
| +},
|
| +_distributeConfig: function (config) {
|
| +var fx$ = this._propertyEffects;
|
| +if (fx$) {
|
| +for (var p in config) {
|
| +var fx = fx$[p];
|
| +if (fx) {
|
| +for (var i = 0, l = fx.length, x; i < l && (x = fx[i]); i++) {
|
| +if (x.kind === 'annotation') {
|
| +var node = this._nodes[x.effect.index];
|
| +if (node._configValue) {
|
| +var value = p === x.effect.value ? config[p] : this.get(x.effect.value, config);
|
| +node._configValue(x.effect.name, value);
|
| +}
|
| +}
|
| +}
|
| +}
|
| +}
|
| +}
|
| +},
|
| +_afterClientsReady: function () {
|
| +this._executeStaticEffects();
|
| +this._applyConfig(this._config, this._aboveConfig);
|
| +this._flushHandlers();
|
| +},
|
| +_applyConfig: function (config, aboveConfig) {
|
| +for (var n in config) {
|
| +if (this[n] === undefined) {
|
| +this.__setProperty(n, config[n], n in aboveConfig);
|
| +}
|
| +}
|
| +},
|
| +_notifyListener: function (fn, e) {
|
| +if (!this._clientsReadied) {
|
| +this._queueHandler([
|
| +fn,
|
| +e,
|
| +e.target
|
| +]);
|
| +} else {
|
| +return fn.call(this, e, e.target);
|
| +}
|
| +},
|
| +_queueHandler: function (args) {
|
| +this._handlers.push(args);
|
| +},
|
| +_flushHandlers: function () {
|
| +var h$ = this._handlers;
|
| +for (var i = 0, l = h$.length, h; i < l && (h = h$[i]); i++) {
|
| +h[0].call(this, h[1], h[2]);
|
| +}
|
| +this._handlers = [];
|
| +}
|
| +});
|
| +(function () {
|
| +'use strict';
|
| +Polymer.Base._addFeature({
|
| +notifyPath: function (path, value, fromAbove) {
|
| +var old = this._propertySetter(path, value);
|
| +if (old !== value && (old === old || value === value)) {
|
| +this._pathEffector(path, value);
|
| +if (!fromAbove) {
|
| +this._notifyPath(path, value);
|
| +}
|
| +return true;
|
| +}
|
| +},
|
| +_getPathParts: function (path) {
|
| +if (Array.isArray(path)) {
|
| +var parts = [];
|
| +for (var i = 0; i < path.length; i++) {
|
| +var args = path[i].toString().split('.');
|
| +for (var j = 0; j < args.length; j++) {
|
| +parts.push(args[j]);
|
| +}
|
| +}
|
| +return parts;
|
| +} else {
|
| +return path.toString().split('.');
|
| +}
|
| +},
|
| +set: function (path, value, root) {
|
| +var prop = root || this;
|
| +var parts = this._getPathParts(path);
|
| +var array;
|
| +var last = parts[parts.length - 1];
|
| +if (parts.length > 1) {
|
| +for (var i = 0; i < parts.length - 1; i++) {
|
| +var part = parts[i];
|
| +prop = prop[part];
|
| +if (array && parseInt(part) == part) {
|
| +parts[i] = Polymer.Collection.get(array).getKey(prop);
|
| +}
|
| +if (!prop) {
|
| +return;
|
| +}
|
| +array = Array.isArray(prop) ? prop : null;
|
| +}
|
| +if (array && parseInt(last) == last) {
|
| +var coll = Polymer.Collection.get(array);
|
| +var old = prop[last];
|
| +var key = coll.getKey(old);
|
| +parts[i] = key;
|
| +coll.setItem(key, value);
|
| +}
|
| +prop[last] = value;
|
| +if (!root) {
|
| +this.notifyPath(parts.join('.'), value);
|
| +}
|
| +} else {
|
| +prop[path] = value;
|
| +}
|
| +},
|
| +get: function (path, root) {
|
| +var prop = root || this;
|
| +var parts = this._getPathParts(path);
|
| +var last = parts.pop();
|
| +while (parts.length) {
|
| +prop = prop[parts.shift()];
|
| +if (!prop) {
|
| +return;
|
| +}
|
| +}
|
| +return prop[last];
|
| +},
|
| +_pathEffector: function (path, value) {
|
| +var model = this._modelForPath(path);
|
| +var fx$ = this._propertyEffects[model];
|
| +if (fx$) {
|
| +fx$.forEach(function (fx) {
|
| +var fxFn = this['_' + fx.kind + 'PathEffect'];
|
| +if (fxFn) {
|
| +fxFn.call(this, path, value, fx.effect);
|
| +}
|
| +}, this);
|
| +}
|
| +if (this._boundPaths) {
|
| +this._notifyBoundPaths(path, value);
|
| +}
|
| +},
|
| +_annotationPathEffect: function (path, value, effect) {
|
| +if (effect.value === path || effect.value.indexOf(path + '.') === 0) {
|
| +Polymer.Bind._annotationEffect.call(this, path, value, effect);
|
| +} else if (path.indexOf(effect.value + '.') === 0 && !effect.negate) {
|
| +var node = this._nodes[effect.index];
|
| +if (node && node.notifyPath) {
|
| +var p = this._fixPath(effect.name, effect.value, path);
|
| +node.notifyPath(p, value, true);
|
| +}
|
| +}
|
| +},
|
| +_complexObserverPathEffect: function (path, value, effect) {
|
| +if (this._pathMatchesEffect(path, effect)) {
|
| +Polymer.Bind._complexObserverEffect.call(this, path, value, effect);
|
| +}
|
| +},
|
| +_computePathEffect: function (path, value, effect) {
|
| +if (this._pathMatchesEffect(path, effect)) {
|
| +Polymer.Bind._computeEffect.call(this, path, value, effect);
|
| +}
|
| +},
|
| +_annotatedComputationPathEffect: function (path, value, effect) {
|
| +if (this._pathMatchesEffect(path, effect)) {
|
| +Polymer.Bind._annotatedComputationEffect.call(this, path, value, effect);
|
| +}
|
| +},
|
| +_pathMatchesEffect: function (path, effect) {
|
| +var effectArg = effect.trigger.name;
|
| +return effectArg == path || effectArg.indexOf(path + '.') === 0 || effect.trigger.wildcard && path.indexOf(effectArg) === 0;
|
| +},
|
| +linkPaths: function (to, from) {
|
| +this._boundPaths = this._boundPaths || {};
|
| +if (from) {
|
| +this._boundPaths[to] = from;
|
| +} else {
|
| +this.unlinkPaths(to);
|
| +}
|
| +},
|
| +unlinkPaths: function (path) {
|
| +if (this._boundPaths) {
|
| +delete this._boundPaths[path];
|
| +}
|
| +},
|
| +_notifyBoundPaths: function (path, value) {
|
| +for (var a in this._boundPaths) {
|
| +var b = this._boundPaths[a];
|
| +if (path.indexOf(a + '.') == 0) {
|
| +this.notifyPath(this._fixPath(b, a, path), value);
|
| +} else if (path.indexOf(b + '.') == 0) {
|
| +this.notifyPath(this._fixPath(a, b, path), value);
|
| +}
|
| +}
|
| +},
|
| +_fixPath: function (property, root, path) {
|
| +return property + path.slice(root.length);
|
| +},
|
| +_notifyPath: function (path, value) {
|
| +var rootName = this._modelForPath(path);
|
| +var dashCaseName = Polymer.CaseMap.camelToDashCase(rootName);
|
| +var eventName = dashCaseName + this._EVENT_CHANGED;
|
| +this.fire(eventName, {
|
| +path: path,
|
| +value: value
|
| +}, { bubbles: false });
|
| +},
|
| +_modelForPath: function (path) {
|
| +var dot = path.indexOf('.');
|
| +return dot < 0 ? path : path.slice(0, dot);
|
| +},
|
| +_EVENT_CHANGED: '-changed',
|
| +_notifySplice: function (array, path, index, added, removed) {
|
| +var splices = [{
|
| +index: index,
|
| +addedCount: added,
|
| +removed: removed,
|
| +object: array,
|
| +type: 'splice'
|
| +}];
|
| +var change = {
|
| +keySplices: Polymer.Collection.applySplices(array, splices),
|
| +indexSplices: splices
|
| +};
|
| +this.set(path + '.splices', change);
|
| +if (added != removed.length) {
|
| +this.notifyPath(path + '.length', array.length);
|
| +}
|
| +change.keySplices = null;
|
| +change.indexSplices = null;
|
| +},
|
| +push: function (path) {
|
| +var array = this.get(path);
|
| +var args = Array.prototype.slice.call(arguments, 1);
|
| +var len = array.length;
|
| +var ret = array.push.apply(array, args);
|
| +if (args.length) {
|
| +this._notifySplice(array, path, len, args.length, []);
|
| +}
|
| +return ret;
|
| +},
|
| +pop: function (path) {
|
| +var array = this.get(path);
|
| +var hadLength = Boolean(array.length);
|
| +var args = Array.prototype.slice.call(arguments, 1);
|
| +var ret = array.pop.apply(array, args);
|
| +if (hadLength) {
|
| +this._notifySplice(array, path, array.length, 0, [ret]);
|
| +}
|
| +return ret;
|
| +},
|
| +splice: function (path, start, deleteCount) {
|
| +var array = this.get(path);
|
| +if (start < 0) {
|
| +start = array.length - Math.floor(-start);
|
| +} else {
|
| +start = Math.floor(start);
|
| +}
|
| +if (!start) {
|
| +start = 0;
|
| +}
|
| +var args = Array.prototype.slice.call(arguments, 1);
|
| +var ret = array.splice.apply(array, args);
|
| +var addedCount = Math.max(args.length - 2, 0);
|
| +if (addedCount || ret.length) {
|
| +this._notifySplice(array, path, start, addedCount, ret);
|
| +}
|
| +return ret;
|
| +},
|
| +shift: function (path) {
|
| +var array = this.get(path);
|
| +var hadLength = Boolean(array.length);
|
| +var args = Array.prototype.slice.call(arguments, 1);
|
| +var ret = array.shift.apply(array, args);
|
| +if (hadLength) {
|
| +this._notifySplice(array, path, 0, 0, [ret]);
|
| +}
|
| +return ret;
|
| +},
|
| +unshift: function (path) {
|
| +var array = this.get(path);
|
| +var args = Array.prototype.slice.call(arguments, 1);
|
| +var ret = array.unshift.apply(array, args);
|
| +if (args.length) {
|
| +this._notifySplice(array, path, 0, args.length, []);
|
| +}
|
| +return ret;
|
| +}
|
| +});
|
| +}());
|
| +Polymer.Base._addFeature({
|
| +resolveUrl: function (url) {
|
| +var module = Polymer.DomModule.import(this.is);
|
| +var root = '';
|
| +if (module) {
|
| +var assetPath = module.getAttribute('assetpath') || '';
|
| +root = Polymer.ResolveUrl.resolveUrl(assetPath, module.ownerDocument.baseURI);
|
| +}
|
| +return Polymer.ResolveUrl.resolveUrl(url, root);
|
| +}
|
| +});
|
| +Polymer.CssParse = function () {
|
| +var api = {
|
| +parse: function (text) {
|
| +text = this._clean(text);
|
| +return this._parseCss(this._lex(text), text);
|
| +},
|
| +_clean: function (cssText) {
|
| +return cssText.replace(this._rx.comments, '').replace(this._rx.port, '');
|
| +},
|
| +_lex: function (text) {
|
| +var root = {
|
| +start: 0,
|
| +end: text.length
|
| +};
|
| +var n = root;
|
| +for (var i = 0, s = 0, l = text.length; i < l; i++) {
|
| +switch (text[i]) {
|
| +case this.OPEN_BRACE:
|
| +if (!n.rules) {
|
| +n.rules = [];
|
| +}
|
| +var p = n;
|
| +var previous = p.rules[p.rules.length - 1];
|
| +n = {
|
| +start: i + 1,
|
| +parent: p,
|
| +previous: previous
|
| +};
|
| +p.rules.push(n);
|
| +break;
|
| +case this.CLOSE_BRACE:
|
| +n.end = i + 1;
|
| +n = n.parent || root;
|
| +break;
|
| +}
|
| +}
|
| +return root;
|
| +},
|
| +_parseCss: function (node, text) {
|
| +var t = text.substring(node.start, node.end - 1);
|
| +node.parsedCssText = node.cssText = t.trim();
|
| +if (node.parent) {
|
| +var ss = node.previous ? node.previous.end : node.parent.start;
|
| +t = text.substring(ss, node.start - 1);
|
| +t = t.substring(t.lastIndexOf(';') + 1);
|
| +var s = node.parsedSelector = node.selector = t.trim();
|
| +node.atRule = s.indexOf(this.AT_START) === 0;
|
| +if (node.atRule) {
|
| +if (s.indexOf(this.MEDIA_START) === 0) {
|
| +node.type = this.types.MEDIA_RULE;
|
| +} else if (s.match(this._rx.keyframesRule)) {
|
| +node.type = this.types.KEYFRAMES_RULE;
|
| +}
|
| +} else {
|
| +if (s.indexOf(this.VAR_START) === 0) {
|
| +node.type = this.types.MIXIN_RULE;
|
| +} else {
|
| +node.type = this.types.STYLE_RULE;
|
| +}
|
| +}
|
| +}
|
| +var r$ = node.rules;
|
| +if (r$) {
|
| +for (var i = 0, l = r$.length, r; i < l && (r = r$[i]); i++) {
|
| +this._parseCss(r, text);
|
| +}
|
| +}
|
| +return node;
|
| +},
|
| +stringify: function (node, preserveProperties, text) {
|
| +text = text || '';
|
| +var cssText = '';
|
| +if (node.cssText || node.rules) {
|
| +var r$ = node.rules;
|
| +if (r$ && (preserveProperties || !this._hasMixinRules(r$))) {
|
| +for (var i = 0, l = r$.length, r; i < l && (r = r$[i]); i++) {
|
| +cssText = this.stringify(r, preserveProperties, cssText);
|
| +}
|
| +} else {
|
| +cssText = preserveProperties ? node.cssText : this.removeCustomProps(node.cssText);
|
| +cssText = cssText.trim();
|
| +if (cssText) {
|
| +cssText = ' ' + cssText + '\n';
|
| +}
|
| +}
|
| +}
|
| +if (cssText) {
|
| +if (node.selector) {
|
| +text += node.selector + ' ' + this.OPEN_BRACE + '\n';
|
| +}
|
| +text += cssText;
|
| +if (node.selector) {
|
| +text += this.CLOSE_BRACE + '\n\n';
|
| +}
|
| +}
|
| +return text;
|
| +},
|
| +_hasMixinRules: function (rules) {
|
| +return rules[0].selector.indexOf(this.VAR_START) >= 0;
|
| +},
|
| +removeCustomProps: function (cssText) {
|
| +return cssText;
|
| +},
|
| +removeCustomPropAssignment: function (cssText) {
|
| +return cssText.replace(this._rx.customProp, '').replace(this._rx.mixinProp, '');
|
| +},
|
| +removeCustomPropApply: function (cssText) {
|
| +return cssText.replace(this._rx.mixinApply, '').replace(this._rx.varApply, '');
|
| +},
|
| +types: {
|
| +STYLE_RULE: 1,
|
| +KEYFRAMES_RULE: 7,
|
| +MEDIA_RULE: 4,
|
| +MIXIN_RULE: 1000
|
| +},
|
| +OPEN_BRACE: '{',
|
| +CLOSE_BRACE: '}',
|
| +_rx: {
|
| +comments: /\/\*[^*]*\*+([^\/*][^*]*\*+)*\//gim,
|
| +port: /@import[^;]*;/gim,
|
| +customProp: /(?:^|[\s;])--[^;{]*?:[^{};]*?(?:[;\n]|$)/gim,
|
| +mixinProp: /(?:^|[\s;])--[^;{]*?:[^{;]*?{[^}]*?}(?:[;\n]|$)?/gim,
|
| +mixinApply: /@apply[\s]*\([^)]*?\)[\s]*(?:[;\n]|$)?/gim,
|
| +varApply: /[^;:]*?:[^;]*var[^;]*(?:[;\n]|$)?/gim,
|
| +keyframesRule: /^@[^\s]*keyframes/
|
| +},
|
| +VAR_START: '--',
|
| +MEDIA_START: '@media',
|
| +AT_START: '@'
|
| +};
|
| +return api;
|
| +}();
|
| +Polymer.StyleUtil = function () {
|
| +return {
|
| +MODULE_STYLES_SELECTOR: 'style, link[rel=import][type~=css], template',
|
| +INCLUDE_ATTR: 'include',
|
| +toCssText: function (rules, callback, preserveProperties) {
|
| +if (typeof rules === 'string') {
|
| +rules = this.parser.parse(rules);
|
| +}
|
| +if (callback) {
|
| +this.forEachStyleRule(rules, callback);
|
| +}
|
| +return this.parser.stringify(rules, preserveProperties);
|
| +},
|
| +forRulesInStyles: function (styles, callback) {
|
| +if (styles) {
|
| +for (var i = 0, l = styles.length, s; i < l && (s = styles[i]); i++) {
|
| +this.forEachStyleRule(this.rulesForStyle(s), callback);
|
| +}
|
| +}
|
| +},
|
| +rulesForStyle: function (style) {
|
| +if (!style.__cssRules && style.textContent) {
|
| +style.__cssRules = this.parser.parse(style.textContent);
|
| +}
|
| +return style.__cssRules;
|
| +},
|
| +clearStyleRules: function (style) {
|
| +style.__cssRules = null;
|
| +},
|
| +forEachStyleRule: function (node, callback) {
|
| +if (!node) {
|
| +return;
|
| +}
|
| +var s = node.parsedSelector;
|
| +var skipRules = false;
|
| +if (node.type === this.ruleTypes.STYLE_RULE) {
|
| +callback(node);
|
| +} else if (node.type === this.ruleTypes.KEYFRAMES_RULE || node.type === this.ruleTypes.MIXIN_RULE) {
|
| +skipRules = true;
|
| +}
|
| +var r$ = node.rules;
|
| +if (r$ && !skipRules) {
|
| +for (var i = 0, l = r$.length, r; i < l && (r = r$[i]); i++) {
|
| +this.forEachStyleRule(r, callback);
|
| +}
|
| +}
|
| +},
|
| +applyCss: function (cssText, moniker, target, afterNode) {
|
| +var style = document.createElement('style');
|
| +if (moniker) {
|
| +style.setAttribute('scope', moniker);
|
| +}
|
| +style.textContent = cssText;
|
| +target = target || document.head;
|
| +if (!afterNode) {
|
| +var n$ = target.querySelectorAll('style[scope]');
|
| +afterNode = n$[n$.length - 1];
|
| +}
|
| +target.insertBefore(style, afterNode && afterNode.nextSibling || target.firstChild);
|
| +return style;
|
| +},
|
| +cssFromModules: function (moduleIds, warnIfNotFound) {
|
| +var modules = moduleIds.trim().split(' ');
|
| +var cssText = '';
|
| +for (var i = 0; i < modules.length; i++) {
|
| +cssText += this.cssFromModule(modules[i], warnIfNotFound);
|
| +}
|
| +return cssText;
|
| +},
|
| +cssFromModule: function (moduleId, warnIfNotFound) {
|
| +var m = Polymer.DomModule.import(moduleId);
|
| +if (m && !m._cssText) {
|
| +m._cssText = this._cssFromElement(m);
|
| +}
|
| +if (!m && warnIfNotFound) {
|
| +console.warn('Could not find style data in module named', moduleId);
|
| +}
|
| +return m && m._cssText || '';
|
| +},
|
| +_cssFromElement: function (element) {
|
| +var cssText = '';
|
| +var content = element.content || element;
|
| +var e$ = Array.prototype.slice.call(content.querySelectorAll(this.MODULE_STYLES_SELECTOR));
|
| +for (var i = 0, e; i < e$.length; i++) {
|
| +e = e$[i];
|
| +if (e.localName === 'template') {
|
| +cssText += this._cssFromElement(e);
|
| +} else {
|
| +if (e.localName === 'style') {
|
| +var include = e.getAttribute(this.INCLUDE_ATTR);
|
| +if (include) {
|
| +cssText += this.cssFromModules(include, true);
|
| +}
|
| +e = e.__appliedElement || e;
|
| +e.parentNode.removeChild(e);
|
| +cssText += this.resolveCss(e.textContent, element.ownerDocument);
|
| +} else if (e.import && e.import.body) {
|
| +cssText += this.resolveCss(e.import.body.textContent, e.import);
|
| +}
|
| +}
|
| +}
|
| +return cssText;
|
| +},
|
| +resolveCss: Polymer.ResolveUrl.resolveCss,
|
| +parser: Polymer.CssParse,
|
| +ruleTypes: Polymer.CssParse.types
|
| +};
|
| +}();
|
| +Polymer.StyleTransformer = function () {
|
| +var nativeShadow = Polymer.Settings.useNativeShadow;
|
| +var styleUtil = Polymer.StyleUtil;
|
| +var api = {
|
| +dom: function (node, scope, useAttr, shouldRemoveScope) {
|
| +this._transformDom(node, scope || '', useAttr, shouldRemoveScope);
|
| +},
|
| +_transformDom: function (node, selector, useAttr, shouldRemoveScope) {
|
| +if (node.setAttribute) {
|
| +this.element(node, selector, useAttr, shouldRemoveScope);
|
| +}
|
| +var c$ = Polymer.dom(node).childNodes;
|
| +for (var i = 0; i < c$.length; i++) {
|
| +this._transformDom(c$[i], selector, useAttr, shouldRemoveScope);
|
| +}
|
| +},
|
| +element: function (element, scope, useAttr, shouldRemoveScope) {
|
| +if (useAttr) {
|
| +if (shouldRemoveScope) {
|
| +element.removeAttribute(SCOPE_NAME);
|
| +} else {
|
| +element.setAttribute(SCOPE_NAME, scope);
|
| +}
|
| +} else {
|
| +if (scope) {
|
| +if (element.classList) {
|
| +if (shouldRemoveScope) {
|
| +element.classList.remove(SCOPE_NAME);
|
| +element.classList.remove(scope);
|
| +} else {
|
| +element.classList.add(SCOPE_NAME);
|
| +element.classList.add(scope);
|
| +}
|
| +} else if (element.getAttribute) {
|
| +var c = element.getAttribute(CLASS);
|
| +if (shouldRemoveScope) {
|
| +if (c) {
|
| +element.setAttribute(CLASS, c.replace(SCOPE_NAME, '').replace(scope, ''));
|
| +}
|
| +} else {
|
| +element.setAttribute(CLASS, c + (c ? ' ' : '') + SCOPE_NAME + ' ' + scope);
|
| +}
|
| +}
|
| +}
|
| +}
|
| +},
|
| +elementStyles: function (element, callback) {
|
| +var styles = element._styles;
|
| +var cssText = '';
|
| +for (var i = 0, l = styles.length, s, text; i < l && (s = styles[i]); i++) {
|
| +var rules = styleUtil.rulesForStyle(s);
|
| +cssText += nativeShadow ? styleUtil.toCssText(rules, callback) : this.css(rules, element.is, element.extends, callback, element._scopeCssViaAttr) + '\n\n';
|
| +}
|
| +return cssText.trim();
|
| +},
|
| +css: function (rules, scope, ext, callback, useAttr) {
|
| +var hostScope = this._calcHostScope(scope, ext);
|
| +scope = this._calcElementScope(scope, useAttr);
|
| +var self = this;
|
| +return styleUtil.toCssText(rules, function (rule) {
|
| +if (!rule.isScoped) {
|
| +self.rule(rule, scope, hostScope);
|
| +rule.isScoped = true;
|
| +}
|
| +if (callback) {
|
| +callback(rule, scope, hostScope);
|
| +}
|
| +});
|
| +},
|
| +_calcElementScope: function (scope, useAttr) {
|
| +if (scope) {
|
| +return useAttr ? CSS_ATTR_PREFIX + scope + CSS_ATTR_SUFFIX : CSS_CLASS_PREFIX + scope;
|
| +} else {
|
| +return '';
|
| +}
|
| +},
|
| +_calcHostScope: function (scope, ext) {
|
| +return ext ? '[is=' + scope + ']' : scope;
|
| +},
|
| +rule: function (rule, scope, hostScope) {
|
| +this._transformRule(rule, this._transformComplexSelector, scope, hostScope);
|
| +},
|
| +_transformRule: function (rule, transformer, scope, hostScope) {
|
| +var p$ = rule.selector.split(COMPLEX_SELECTOR_SEP);
|
| +for (var i = 0, l = p$.length, p; i < l && (p = p$[i]); i++) {
|
| +p$[i] = transformer.call(this, p, scope, hostScope);
|
| +}
|
| +rule.selector = rule.transformedSelector = p$.join(COMPLEX_SELECTOR_SEP);
|
| +},
|
| +_transformComplexSelector: function (selector, scope, hostScope) {
|
| +var stop = false;
|
| +var hostContext = false;
|
| +var self = this;
|
| +selector = selector.replace(SIMPLE_SELECTOR_SEP, function (m, c, s) {
|
| +if (!stop) {
|
| +var info = self._transformCompoundSelector(s, c, scope, hostScope);
|
| +stop = stop || info.stop;
|
| +hostContext = hostContext || info.hostContext;
|
| +c = info.combinator;
|
| +s = info.value;
|
| +} else {
|
| +s = s.replace(SCOPE_JUMP, ' ');
|
| +}
|
| +return c + s;
|
| +});
|
| +if (hostContext) {
|
| +selector = selector.replace(HOST_CONTEXT_PAREN, function (m, pre, paren, post) {
|
| +return pre + paren + ' ' + hostScope + post + COMPLEX_SELECTOR_SEP + ' ' + pre + hostScope + paren + post;
|
| +});
|
| +}
|
| +return selector;
|
| +},
|
| +_transformCompoundSelector: function (selector, combinator, scope, hostScope) {
|
| +var jumpIndex = selector.search(SCOPE_JUMP);
|
| +var hostContext = false;
|
| +if (selector.indexOf(HOST_CONTEXT) >= 0) {
|
| +hostContext = true;
|
| +} else if (selector.indexOf(HOST) >= 0) {
|
| +selector = selector.replace(HOST_PAREN, function (m, host, paren) {
|
| +return hostScope + paren;
|
| +});
|
| +selector = selector.replace(HOST, hostScope);
|
| +} else if (jumpIndex !== 0) {
|
| +selector = scope ? this._transformSimpleSelector(selector, scope) : selector;
|
| +}
|
| +if (selector.indexOf(CONTENT) >= 0) {
|
| +combinator = '';
|
| +}
|
| +var stop;
|
| +if (jumpIndex >= 0) {
|
| +selector = selector.replace(SCOPE_JUMP, ' ');
|
| +stop = true;
|
| +}
|
| +return {
|
| +value: selector,
|
| +combinator: combinator,
|
| +stop: stop,
|
| +hostContext: hostContext
|
| +};
|
| +},
|
| +_transformSimpleSelector: function (selector, scope) {
|
| +var p$ = selector.split(PSEUDO_PREFIX);
|
| +p$[0] += scope;
|
| +return p$.join(PSEUDO_PREFIX);
|
| +},
|
| +documentRule: function (rule) {
|
| +rule.selector = rule.parsedSelector;
|
| +this.normalizeRootSelector(rule);
|
| +if (!nativeShadow) {
|
| +this._transformRule(rule, this._transformDocumentSelector);
|
| +}
|
| +},
|
| +normalizeRootSelector: function (rule) {
|
| +if (rule.selector === ROOT) {
|
| +rule.selector = 'body';
|
| +}
|
| +},
|
| +_transformDocumentSelector: function (selector) {
|
| +return selector.match(SCOPE_JUMP) ? this._transformComplexSelector(selector, SCOPE_DOC_SELECTOR) : this._transformSimpleSelector(selector.trim(), SCOPE_DOC_SELECTOR);
|
| +},
|
| +SCOPE_NAME: 'style-scope'
|
| +};
|
| +var SCOPE_NAME = api.SCOPE_NAME;
|
| +var SCOPE_DOC_SELECTOR = ':not([' + SCOPE_NAME + '])' + ':not(.' + SCOPE_NAME + ')';
|
| +var COMPLEX_SELECTOR_SEP = ',';
|
| +var SIMPLE_SELECTOR_SEP = /(^|[\s>+~]+)([^\s>+~]+)/g;
|
| +var HOST = ':host';
|
| +var ROOT = ':root';
|
| +var HOST_PAREN = /(\:host)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/g;
|
| +var HOST_CONTEXT = ':host-context';
|
| +var HOST_CONTEXT_PAREN = /(.*)(?:\:host-context)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))(.*)/;
|
| +var CONTENT = '::content';
|
| +var SCOPE_JUMP = /\:\:content|\:\:shadow|\/deep\//;
|
| +var CSS_CLASS_PREFIX = '.';
|
| +var CSS_ATTR_PREFIX = '[' + SCOPE_NAME + '~=';
|
| +var CSS_ATTR_SUFFIX = ']';
|
| +var PSEUDO_PREFIX = ':';
|
| +var CLASS = 'class';
|
| +return api;
|
| +}();
|
| +Polymer.StyleExtends = function () {
|
| +var styleUtil = Polymer.StyleUtil;
|
| +return {
|
| +hasExtends: function (cssText) {
|
| +return Boolean(cssText.match(this.rx.EXTEND));
|
| +},
|
| +transform: function (style) {
|
| +var rules = styleUtil.rulesForStyle(style);
|
| +var self = this;
|
| +styleUtil.forEachStyleRule(rules, function (rule) {
|
| +var map = self._mapRule(rule);
|
| +if (rule.parent) {
|
| +var m;
|
| +while (m = self.rx.EXTEND.exec(rule.cssText)) {
|
| +var extend = m[1];
|
| +var extendor = self._findExtendor(extend, rule);
|
| +if (extendor) {
|
| +self._extendRule(rule, extendor);
|
| +}
|
| +}
|
| +}
|
| +rule.cssText = rule.cssText.replace(self.rx.EXTEND, '');
|
| +});
|
| +return styleUtil.toCssText(rules, function (rule) {
|
| +if (rule.selector.match(self.rx.STRIP)) {
|
| +rule.cssText = '';
|
| +}
|
| +}, true);
|
| +},
|
| +_mapRule: function (rule) {
|
| +if (rule.parent) {
|
| +var map = rule.parent.map || (rule.parent.map = {});
|
| +var parts = rule.selector.split(',');
|
| +for (var i = 0, p; i < parts.length; i++) {
|
| +p = parts[i];
|
| +map[p.trim()] = rule;
|
| +}
|
| +return map;
|
| +}
|
| +},
|
| +_findExtendor: function (extend, rule) {
|
| +return rule.parent && rule.parent.map && rule.parent.map[extend] || this._findExtendor(extend, rule.parent);
|
| +},
|
| +_extendRule: function (target, source) {
|
| +if (target.parent !== source.parent) {
|
| +this._cloneAndAddRuleToParent(source, target.parent);
|
| +}
|
| +target.extends = target.extends || (target.extends = []);
|
| +target.extends.push(source);
|
| +source.selector = source.selector.replace(this.rx.STRIP, '');
|
| +source.selector = (source.selector && source.selector + ',\n') + target.selector;
|
| +if (source.extends) {
|
| +source.extends.forEach(function (e) {
|
| +this._extendRule(target, e);
|
| +}, this);
|
| +}
|
| +},
|
| +_cloneAndAddRuleToParent: function (rule, parent) {
|
| +rule = Object.create(rule);
|
| +rule.parent = parent;
|
| +if (rule.extends) {
|
| +rule.extends = rule.extends.slice();
|
| +}
|
| +parent.rules.push(rule);
|
| +},
|
| +rx: {
|
| +EXTEND: /@extends\(([^)]*)\)\s*?;/gim,
|
| +STRIP: /%[^,]*$/
|
| +}
|
| +};
|
| +}();
|
| +(function () {
|
| +var prepElement = Polymer.Base._prepElement;
|
| +var nativeShadow = Polymer.Settings.useNativeShadow;
|
| +var styleUtil = Polymer.StyleUtil;
|
| +var styleTransformer = Polymer.StyleTransformer;
|
| +var styleExtends = Polymer.StyleExtends;
|
| +Polymer.Base._addFeature({
|
| +_prepElement: function (element) {
|
| +if (this._encapsulateStyle) {
|
| +styleTransformer.element(element, this.is, this._scopeCssViaAttr);
|
| +}
|
| +prepElement.call(this, element);
|
| +},
|
| +_prepStyles: function () {
|
| +if (this._encapsulateStyle === undefined) {
|
| +this._encapsulateStyle = !nativeShadow && Boolean(this._template);
|
| +}
|
| +this._styles = this._collectStyles();
|
| +var cssText = styleTransformer.elementStyles(this);
|
| +if (cssText && this._template) {
|
| +var style = styleUtil.applyCss(cssText, this.is, nativeShadow ? this._template.content : null);
|
| +if (!nativeShadow) {
|
| +this._scopeStyle = style;
|
| +}
|
| +}
|
| +},
|
| +_collectStyles: function () {
|
| +var styles = [];
|
| +var cssText = '', m$ = this.styleModules;
|
| +if (m$) {
|
| +for (var i = 0, l = m$.length, m; i < l && (m = m$[i]); i++) {
|
| +cssText += styleUtil.cssFromModule(m);
|
| +}
|
| +}
|
| +cssText += styleUtil.cssFromModule(this.is);
|
| +if (cssText) {
|
| +var style = document.createElement('style');
|
| +style.textContent = cssText;
|
| +if (styleExtends.hasExtends(style.textContent)) {
|
| +cssText = styleExtends.transform(style);
|
| +}
|
| +styles.push(style);
|
| +}
|
| +return styles;
|
| +},
|
| +_elementAdd: function (node) {
|
| +if (this._encapsulateStyle) {
|
| +if (node.__styleScoped) {
|
| +node.__styleScoped = false;
|
| +} else {
|
| +styleTransformer.dom(node, this.is, this._scopeCssViaAttr);
|
| +}
|
| +}
|
| +},
|
| +_elementRemove: function (node) {
|
| +if (this._encapsulateStyle) {
|
| +styleTransformer.dom(node, this.is, this._scopeCssViaAttr, true);
|
| +}
|
| +},
|
| +scopeSubtree: function (container, shouldObserve) {
|
| +if (nativeShadow) {
|
| +return;
|
| +}
|
| +var self = this;
|
| +var scopify = function (node) {
|
| +if (node.nodeType === Node.ELEMENT_NODE) {
|
| +node.className = self._scopeElementClass(node, node.className);
|
| +var n$ = node.querySelectorAll('*');
|
| +Array.prototype.forEach.call(n$, function (n) {
|
| +n.className = self._scopeElementClass(n, n.className);
|
| +});
|
| +}
|
| +};
|
| +scopify(container);
|
| +if (shouldObserve) {
|
| +var mo = new MutationObserver(function (mxns) {
|
| +mxns.forEach(function (m) {
|
| +if (m.addedNodes) {
|
| +for (var i = 0; i < m.addedNodes.length; i++) {
|
| +scopify(m.addedNodes[i]);
|
| +}
|
| +}
|
| +});
|
| +});
|
| +mo.observe(container, {
|
| +childList: true,
|
| +subtree: true
|
| +});
|
| +return mo;
|
| +}
|
| +}
|
| +});
|
| +}());
|
| +Polymer.StyleProperties = function () {
|
| +'use strict';
|
| +var nativeShadow = Polymer.Settings.useNativeShadow;
|
| +var matchesSelector = Polymer.DomApi.matchesSelector;
|
| +var styleUtil = Polymer.StyleUtil;
|
| +var styleTransformer = Polymer.StyleTransformer;
|
| +return {
|
| +decorateStyles: function (styles) {
|
| +var self = this, props = {};
|
| +styleUtil.forRulesInStyles(styles, function (rule) {
|
| +self.decorateRule(rule);
|
| +self.collectPropertiesInCssText(rule.propertyInfo.cssText, props);
|
| +});
|
| +var names = [];
|
| +for (var i in props) {
|
| +names.push(i);
|
| +}
|
| +return names;
|
| +},
|
| +decorateRule: function (rule) {
|
| +if (rule.propertyInfo) {
|
| +return rule.propertyInfo;
|
| +}
|
| +var info = {}, properties = {};
|
| +var hasProperties = this.collectProperties(rule, properties);
|
| +if (hasProperties) {
|
| +info.properties = properties;
|
| +rule.rules = null;
|
| +}
|
| +info.cssText = this.collectCssText(rule);
|
| +rule.propertyInfo = info;
|
| +return info;
|
| +},
|
| +collectProperties: function (rule, properties) {
|
| +var info = rule.propertyInfo;
|
| +if (info) {
|
| +if (info.properties) {
|
| +Polymer.Base.mixin(properties, info.properties);
|
| +return true;
|
| +}
|
| +} else {
|
| +var m, rx = this.rx.VAR_ASSIGN;
|
| +var cssText = rule.parsedCssText;
|
| +var any;
|
| +while (m = rx.exec(cssText)) {
|
| +properties[m[1]] = (m[2] || m[3]).trim();
|
| +any = true;
|
| +}
|
| +return any;
|
| +}
|
| +},
|
| +collectCssText: function (rule) {
|
| +var customCssText = '';
|
| +var cssText = rule.parsedCssText;
|
| +cssText = cssText.replace(this.rx.BRACKETED, '').replace(this.rx.VAR_ASSIGN, '');
|
| +var parts = cssText.split(';');
|
| +for (var i = 0, p; i < parts.length; i++) {
|
| +p = parts[i];
|
| +if (p.match(this.rx.MIXIN_MATCH) || p.match(this.rx.VAR_MATCH)) {
|
| +customCssText += p + ';\n';
|
| +}
|
| +}
|
| +return customCssText;
|
| +},
|
| +collectPropertiesInCssText: function (cssText, props) {
|
| +var m;
|
| +while (m = this.rx.VAR_CAPTURE.exec(cssText)) {
|
| +props[m[1]] = true;
|
| +var def = m[2];
|
| +if (def && def.match(this.rx.IS_VAR)) {
|
| +props[def] = true;
|
| +}
|
| +}
|
| +},
|
| +reify: function (props) {
|
| +var names = Object.getOwnPropertyNames(props);
|
| +for (var i = 0, n; i < names.length; i++) {
|
| +n = names[i];
|
| +props[n] = this.valueForProperty(props[n], props);
|
| +}
|
| +},
|
| +valueForProperty: function (property, props) {
|
| +if (property) {
|
| +if (property.indexOf(';') >= 0) {
|
| +property = this.valueForProperties(property, props);
|
| +} else {
|
| +var self = this;
|
| +var fn = function (all, prefix, value, fallback) {
|
| +var propertyValue = self.valueForProperty(props[value], props) || (props[fallback] ? self.valueForProperty(props[fallback], props) : fallback);
|
| +return prefix + (propertyValue || '');
|
| +};
|
| +property = property.replace(this.rx.VAR_MATCH, fn);
|
| +}
|
| +}
|
| +return property && property.trim() || '';
|
| +},
|
| +valueForProperties: function (property, props) {
|
| +var parts = property.split(';');
|
| +for (var i = 0, p, m; i < parts.length; i++) {
|
| +if (p = parts[i]) {
|
| +m = p.match(this.rx.MIXIN_MATCH);
|
| +if (m) {
|
| +p = this.valueForProperty(props[m[1]], props);
|
| +} else {
|
| +var pp = p.split(':');
|
| +if (pp[1]) {
|
| +pp[1] = pp[1].trim();
|
| +pp[1] = this.valueForProperty(pp[1], props) || pp[1];
|
| +}
|
| +p = pp.join(':');
|
| +}
|
| +parts[i] = p && p.lastIndexOf(';') === p.length - 1 ? p.slice(0, -1) : p || '';
|
| +}
|
| +}
|
| +return parts.join(';');
|
| +},
|
| +applyProperties: function (rule, props) {
|
| +var output = '';
|
| +if (!rule.propertyInfo) {
|
| +this.decorateRule(rule);
|
| +}
|
| +if (rule.propertyInfo.cssText) {
|
| +output = this.valueForProperties(rule.propertyInfo.cssText, props);
|
| +}
|
| +rule.cssText = output;
|
| +},
|
| +propertyDataFromStyles: function (styles, element) {
|
| +var props = {}, self = this;
|
| +var o = [], i = 0;
|
| +styleUtil.forRulesInStyles(styles, function (rule) {
|
| +if (!rule.propertyInfo) {
|
| +self.decorateRule(rule);
|
| +}
|
| +if (element && rule.propertyInfo.properties && matchesSelector.call(element, rule.transformedSelector || rule.parsedSelector)) {
|
| +self.collectProperties(rule, props);
|
| +addToBitMask(i, o);
|
| +}
|
| +i++;
|
| +});
|
| +return {
|
| +properties: props,
|
| +key: o
|
| +};
|
| +},
|
| +scopePropertiesFromStyles: function (styles) {
|
| +if (!styles._scopeStyleProperties) {
|
| +styles._scopeStyleProperties = this.selectedPropertiesFromStyles(styles, this.SCOPE_SELECTORS);
|
| +}
|
| +return styles._scopeStyleProperties;
|
| +},
|
| +hostPropertiesFromStyles: function (styles) {
|
| +if (!styles._hostStyleProperties) {
|
| +styles._hostStyleProperties = this.selectedPropertiesFromStyles(styles, this.HOST_SELECTORS);
|
| +}
|
| +return styles._hostStyleProperties;
|
| +},
|
| +selectedPropertiesFromStyles: function (styles, selectors) {
|
| +var props = {}, self = this;
|
| +styleUtil.forRulesInStyles(styles, function (rule) {
|
| +if (!rule.propertyInfo) {
|
| +self.decorateRule(rule);
|
| +}
|
| +for (var i = 0; i < selectors.length; i++) {
|
| +if (rule.parsedSelector === selectors[i]) {
|
| +self.collectProperties(rule, props);
|
| +return;
|
| +}
|
| +}
|
| +});
|
| +return props;
|
| +},
|
| +transformStyles: function (element, properties, scopeSelector) {
|
| +var self = this;
|
| +var hostSelector = styleTransformer._calcHostScope(element.is, element.extends);
|
| +var rxHostSelector = element.extends ? '\\' + hostSelector.slice(0, -1) + '\\]' : hostSelector;
|
| +var hostRx = new RegExp(this.rx.HOST_PREFIX + rxHostSelector + this.rx.HOST_SUFFIX);
|
| +return styleTransformer.elementStyles(element, function (rule) {
|
| +self.applyProperties(rule, properties);
|
| +if (rule.cssText && !nativeShadow) {
|
| +self._scopeSelector(rule, hostRx, hostSelector, element._scopeCssViaAttr, scopeSelector);
|
| +}
|
| +});
|
| +},
|
| +_scopeSelector: function (rule, hostRx, hostSelector, viaAttr, scopeId) {
|
| +rule.transformedSelector = rule.transformedSelector || rule.selector;
|
| +var selector = rule.transformedSelector;
|
| +var scope = viaAttr ? '[' + styleTransformer.SCOPE_NAME + '~=' + scopeId + ']' : '.' + scopeId;
|
| +var parts = selector.split(',');
|
| +for (var i = 0, l = parts.length, p; i < l && (p = parts[i]); i++) {
|
| +parts[i] = p.match(hostRx) ? p.replace(hostSelector, hostSelector + scope) : scope + ' ' + p;
|
| +}
|
| +rule.selector = parts.join(',');
|
| +},
|
| +applyElementScopeSelector: function (element, selector, old, viaAttr) {
|
| +var c = viaAttr ? element.getAttribute(styleTransformer.SCOPE_NAME) : element.className;
|
| +var v = old ? c.replace(old, selector) : (c ? c + ' ' : '') + this.XSCOPE_NAME + ' ' + selector;
|
| +if (c !== v) {
|
| +if (viaAttr) {
|
| +element.setAttribute(styleTransformer.SCOPE_NAME, v);
|
| +} else {
|
| +element.className = v;
|
| +}
|
| +}
|
| +},
|
| +applyElementStyle: function (element, properties, selector, style) {
|
| +var cssText = style ? style.textContent || '' : this.transformStyles(element, properties, selector);
|
| +var s = element._customStyle;
|
| +if (s && !nativeShadow && s !== style) {
|
| +s._useCount--;
|
| +if (s._useCount <= 0 && s.parentNode) {
|
| +s.parentNode.removeChild(s);
|
| +}
|
| +}
|
| +if (nativeShadow || (!style || !style.parentNode)) {
|
| +if (nativeShadow && element._customStyle) {
|
| +element._customStyle.textContent = cssText;
|
| +style = element._customStyle;
|
| +} else if (cssText) {
|
| +style = styleUtil.applyCss(cssText, selector, nativeShadow ? element.root : null, element._scopeStyle);
|
| +}
|
| +}
|
| +if (style) {
|
| +style._useCount = style._useCount || 0;
|
| +if (element._customStyle != style) {
|
| +style._useCount++;
|
| +}
|
| +element._customStyle = style;
|
| +}
|
| +return style;
|
| +},
|
| +mixinCustomStyle: function (props, customStyle) {
|
| +var v;
|
| +for (var i in customStyle) {
|
| +v = customStyle[i];
|
| +if (v || v === 0) {
|
| +props[i] = v;
|
| +}
|
| +}
|
| +},
|
| +rx: {
|
| +VAR_ASSIGN: /(?:^|[;\n]\s*)(--[\w-]*?):\s*(?:([^;{]*)|{([^}]*)})(?:(?=[;\n])|$)/gi,
|
| +MIXIN_MATCH: /(?:^|\W+)@apply[\s]*\(([^)]*)\)/i,
|
| +VAR_MATCH: /(^|\W+)var\([\s]*([^,)]*)[\s]*,?[\s]*((?:[^,)]*)|(?:[^;]*\([^;)]*\)))[\s]*?\)/gi,
|
| +VAR_CAPTURE: /\([\s]*(--[^,\s)]*)(?:,[\s]*(--[^,\s)]*))?(?:\)|,)/gi,
|
| +IS_VAR: /^--/,
|
| +BRACKETED: /\{[^}]*\}/g,
|
| +HOST_PREFIX: '(?:^|[^.#[:])',
|
| +HOST_SUFFIX: '($|[.:[\\s>+~])'
|
| +},
|
| +HOST_SELECTORS: [':host'],
|
| +SCOPE_SELECTORS: [':root'],
|
| +XSCOPE_NAME: 'x-scope'
|
| +};
|
| +function addToBitMask(n, bits) {
|
| +var o = parseInt(n / 32);
|
| +var v = 1 << n % 32;
|
| +bits[o] = (bits[o] || 0) | v;
|
| +}
|
| +}();
|
| +(function () {
|
| +Polymer.StyleCache = function () {
|
| +this.cache = {};
|
| +};
|
| +Polymer.StyleCache.prototype = {
|
| +MAX: 100,
|
| +store: function (is, data, keyValues, keyStyles) {
|
| +data.keyValues = keyValues;
|
| +data.styles = keyStyles;
|
| +var s$ = this.cache[is] = this.cache[is] || [];
|
| +s$.push(data);
|
| +if (s$.length > this.MAX) {
|
| +s$.shift();
|
| +}
|
| +},
|
| +retrieve: function (is, keyValues, keyStyles) {
|
| +var cache = this.cache[is];
|
| +if (cache) {
|
| +for (var i = cache.length - 1, data; i >= 0; i--) {
|
| +data = cache[i];
|
| +if (keyStyles === data.styles && this._objectsEqual(keyValues, data.keyValues)) {
|
| +return data;
|
| +}
|
| +}
|
| +}
|
| +},
|
| +clear: function () {
|
| +this.cache = {};
|
| +},
|
| +_objectsEqual: function (target, source) {
|
| +var t, s;
|
| +for (var i in target) {
|
| +t = target[i], s = source[i];
|
| +if (!(typeof t === 'object' && t ? this._objectsStrictlyEqual(t, s) : t === s)) {
|
| +return false;
|
| +}
|
| +}
|
| +if (Array.isArray(target)) {
|
| +return target.length === source.length;
|
| +}
|
| +return true;
|
| +},
|
| +_objectsStrictlyEqual: function (target, source) {
|
| +return this._objectsEqual(target, source) && this._objectsEqual(source, target);
|
| +}
|
| +};
|
| +}());
|
| +Polymer.StyleDefaults = function () {
|
| +var styleProperties = Polymer.StyleProperties;
|
| +var styleUtil = Polymer.StyleUtil;
|
| +var StyleCache = Polymer.StyleCache;
|
| +var api = {
|
| +_styles: [],
|
| +_properties: null,
|
| +customStyle: {},
|
| +_styleCache: new StyleCache(),
|
| +addStyle: function (style) {
|
| +this._styles.push(style);
|
| +this._properties = null;
|
| +},
|
| +get _styleProperties() {
|
| +if (!this._properties) {
|
| +styleProperties.decorateStyles(this._styles);
|
| +this._styles._scopeStyleProperties = null;
|
| +this._properties = styleProperties.scopePropertiesFromStyles(this._styles);
|
| +styleProperties.mixinCustomStyle(this._properties, this.customStyle);
|
| +styleProperties.reify(this._properties);
|
| +}
|
| +return this._properties;
|
| +},
|
| +_needsStyleProperties: function () {
|
| +},
|
| +_computeStyleProperties: function () {
|
| +return this._styleProperties;
|
| +},
|
| +updateStyles: function (properties) {
|
| +this._properties = null;
|
| +if (properties) {
|
| +Polymer.Base.mixin(this.customStyle, properties);
|
| +}
|
| +this._styleCache.clear();
|
| +for (var i = 0, s; i < this._styles.length; i++) {
|
| +s = this._styles[i];
|
| +s = s.__importElement || s;
|
| +s._apply();
|
| +}
|
| +}
|
| +};
|
| +return api;
|
| +}();
|
| +(function () {
|
| +'use strict';
|
| +var serializeValueToAttribute = Polymer.Base.serializeValueToAttribute;
|
| +var propertyUtils = Polymer.StyleProperties;
|
| +var styleTransformer = Polymer.StyleTransformer;
|
| +var styleUtil = Polymer.StyleUtil;
|
| +var styleDefaults = Polymer.StyleDefaults;
|
| +var nativeShadow = Polymer.Settings.useNativeShadow;
|
| +Polymer.Base._addFeature({
|
| +_prepStyleProperties: function () {
|
| +this._ownStylePropertyNames = this._styles ? propertyUtils.decorateStyles(this._styles) : [];
|
| +},
|
| +customStyle: {},
|
| +_setupStyleProperties: function () {
|
| +this.customStyle = {};
|
| +},
|
| +_needsStyleProperties: function () {
|
| +return Boolean(this._ownStylePropertyNames && this._ownStylePropertyNames.length);
|
| +},
|
| +_beforeAttached: function () {
|
| +if (!this._scopeSelector && this._needsStyleProperties()) {
|
| +this._updateStyleProperties();
|
| +}
|
| +},
|
| +_findStyleHost: function () {
|
| +var e = this, root;
|
| +while (root = Polymer.dom(e).getOwnerRoot()) {
|
| +if (Polymer.isInstance(root.host)) {
|
| +return root.host;
|
| +}
|
| +e = root.host;
|
| +}
|
| +return styleDefaults;
|
| +},
|
| +_updateStyleProperties: function () {
|
| +var info, scope = this._findStyleHost();
|
| +if (!scope._styleCache) {
|
| +scope._styleCache = new Polymer.StyleCache();
|
| +}
|
| +var scopeData = propertyUtils.propertyDataFromStyles(scope._styles, this);
|
| +scopeData.key.customStyle = this.customStyle;
|
| +info = scope._styleCache.retrieve(this.is, scopeData.key, this._styles);
|
| +var scopeCached = Boolean(info);
|
| +if (scopeCached) {
|
| +this._styleProperties = info._styleProperties;
|
| +} else {
|
| +this._computeStyleProperties(scopeData.properties);
|
| +}
|
| +this._computeOwnStyleProperties();
|
| +if (!scopeCached) {
|
| +info = styleCache.retrieve(this.is, this._ownStyleProperties, this._styles);
|
| +}
|
| +var globalCached = Boolean(info) && !scopeCached;
|
| +var style = this._applyStyleProperties(info);
|
| +if (!scopeCached) {
|
| +style = style && nativeShadow ? style.cloneNode(true) : style;
|
| +info = {
|
| +style: style,
|
| +_scopeSelector: this._scopeSelector,
|
| +_styleProperties: this._styleProperties
|
| +};
|
| +scopeData.key.customStyle = {};
|
| +this.mixin(scopeData.key.customStyle, this.customStyle);
|
| +scope._styleCache.store(this.is, info, scopeData.key, this._styles);
|
| +if (!globalCached) {
|
| +styleCache.store(this.is, Object.create(info), this._ownStyleProperties, this._styles);
|
| +}
|
| +}
|
| +},
|
| +_computeStyleProperties: function (scopeProps) {
|
| +var scope = this._findStyleHost();
|
| +if (!scope._styleProperties) {
|
| +scope._computeStyleProperties();
|
| +}
|
| +var props = Object.create(scope._styleProperties);
|
| +this.mixin(props, propertyUtils.hostPropertiesFromStyles(this._styles));
|
| +scopeProps = scopeProps || propertyUtils.propertyDataFromStyles(scope._styles, this).properties;
|
| +this.mixin(props, scopeProps);
|
| +this.mixin(props, propertyUtils.scopePropertiesFromStyles(this._styles));
|
| +propertyUtils.mixinCustomStyle(props, this.customStyle);
|
| +propertyUtils.reify(props);
|
| +this._styleProperties = props;
|
| +},
|
| +_computeOwnStyleProperties: function () {
|
| +var props = {};
|
| +for (var i = 0, n; i < this._ownStylePropertyNames.length; i++) {
|
| +n = this._ownStylePropertyNames[i];
|
| +props[n] = this._styleProperties[n];
|
| +}
|
| +this._ownStyleProperties = props;
|
| +},
|
| +_scopeCount: 0,
|
| +_applyStyleProperties: function (info) {
|
| +var oldScopeSelector = this._scopeSelector;
|
| +this._scopeSelector = info ? info._scopeSelector : this.is + '-' + this.__proto__._scopeCount++;
|
| +var style = propertyUtils.applyElementStyle(this, this._styleProperties, this._scopeSelector, info && info.style);
|
| +if (!nativeShadow) {
|
| +propertyUtils.applyElementScopeSelector(this, this._scopeSelector, oldScopeSelector, this._scopeCssViaAttr);
|
| +}
|
| +return style;
|
| +},
|
| +serializeValueToAttribute: function (value, attribute, node) {
|
| +node = node || this;
|
| +if (attribute === 'class' && !nativeShadow) {
|
| +var host = node === this ? this.domHost || this.dataHost : this;
|
| +if (host) {
|
| +value = host._scopeElementClass(node, value);
|
| +}
|
| +}
|
| +node = Polymer.dom(node);
|
| +serializeValueToAttribute.call(this, value, attribute, node);
|
| +},
|
| +_scopeElementClass: function (element, selector) {
|
| +if (!nativeShadow && !this._scopeCssViaAttr) {
|
| +selector += (selector ? ' ' : '') + SCOPE_NAME + ' ' + this.is + (element._scopeSelector ? ' ' + XSCOPE_NAME + ' ' + element._scopeSelector : '');
|
| +}
|
| +return selector;
|
| +},
|
| +updateStyles: function (properties) {
|
| +if (this.isAttached) {
|
| +if (properties) {
|
| +this.mixin(this.customStyle, properties);
|
| +}
|
| +if (this._needsStyleProperties()) {
|
| +this._updateStyleProperties();
|
| +} else {
|
| +this._styleProperties = null;
|
| +}
|
| +if (this._styleCache) {
|
| +this._styleCache.clear();
|
| +}
|
| +this._updateRootStyles();
|
| +}
|
| +},
|
| +_updateRootStyles: function (root) {
|
| +root = root || this.root;
|
| +var c$ = Polymer.dom(root)._query(function (e) {
|
| +return e.shadyRoot || e.shadowRoot;
|
| +});
|
| +for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) {
|
| +if (c.updateStyles) {
|
| +c.updateStyles();
|
| +}
|
| +}
|
| +}
|
| +});
|
| +Polymer.updateStyles = function (properties) {
|
| +styleDefaults.updateStyles(properties);
|
| +Polymer.Base._updateRootStyles(document);
|
| +};
|
| +var styleCache = new Polymer.StyleCache();
|
| +Polymer.customStyleCache = styleCache;
|
| +var SCOPE_NAME = styleTransformer.SCOPE_NAME;
|
| +var XSCOPE_NAME = propertyUtils.XSCOPE_NAME;
|
| +}());
|
| +Polymer.Base._addFeature({
|
| +_registerFeatures: function () {
|
| +this._prepIs();
|
| +this._prepAttributes();
|
| +this._prepConstructor();
|
| +this._prepTemplate();
|
| +this._prepStyles();
|
| +this._prepStyleProperties();
|
| +this._prepAnnotations();
|
| +this._prepEffects();
|
| +this._prepBehaviors();
|
| +this._prepBindings();
|
| +this._prepShady();
|
| +},
|
| +_prepBehavior: function (b) {
|
| +this._addPropertyEffects(b.properties);
|
| +this._addComplexObserverEffects(b.observers);
|
| +this._addHostAttributes(b.hostAttributes);
|
| +},
|
| +_initFeatures: function () {
|
| +this._poolContent();
|
| +this._setupConfigure();
|
| +this._setupStyleProperties();
|
| +this._pushHost();
|
| +this._stampTemplate();
|
| +this._popHost();
|
| +this._marshalAnnotationReferences();
|
| +this._setupDebouncers();
|
| +this._marshalInstanceEffects();
|
| +this._marshalHostAttributes();
|
| +this._marshalBehaviors();
|
| +this._marshalAttributes();
|
| +this._tryReady();
|
| +},
|
| +_marshalBehavior: function (b) {
|
| +this._listenListeners(b.listeners);
|
| +}
|
| +});
|
| +(function () {
|
| +var nativeShadow = Polymer.Settings.useNativeShadow;
|
| +var propertyUtils = Polymer.StyleProperties;
|
| +var styleUtil = Polymer.StyleUtil;
|
| +var cssParse = Polymer.CssParse;
|
| +var styleDefaults = Polymer.StyleDefaults;
|
| +var styleTransformer = Polymer.StyleTransformer;
|
| +Polymer({
|
| +is: 'custom-style',
|
| +extends: 'style',
|
| +properties: { include: String },
|
| +ready: function () {
|
| +this._tryApply();
|
| +},
|
| +attached: function () {
|
| +this._tryApply();
|
| +},
|
| +_tryApply: function () {
|
| +if (!this._appliesToDocument) {
|
| +if (this.parentNode && this.parentNode.localName !== 'dom-module') {
|
| +this._appliesToDocument = true;
|
| +var e = this.__appliedElement || this;
|
| +styleDefaults.addStyle(e);
|
| +if (e.textContent || this.include) {
|
| +this._apply();
|
| +} else {
|
| +var observer = new MutationObserver(function () {
|
| +observer.disconnect();
|
| +this._apply();
|
| +}.bind(this));
|
| +observer.observe(e, { childList: true });
|
| +}
|
| +}
|
| +}
|
| +},
|
| +_apply: function () {
|
| +var e = this.__appliedElement || this;
|
| +if (this.include) {
|
| +e.textContent = styleUtil.cssFromModules(this.include, true) + e.textContent;
|
| +}
|
| +if (e.textContent) {
|
| +styleUtil.forEachStyleRule(styleUtil.rulesForStyle(e), function (rule) {
|
| +styleTransformer.documentRule(rule);
|
| +});
|
| +this._applyCustomProperties(e);
|
| +}
|
| +},
|
| +_applyCustomProperties: function (element) {
|
| +this._computeStyleProperties();
|
| +var props = this._styleProperties;
|
| +var rules = styleUtil.rulesForStyle(element);
|
| +element.textContent = styleUtil.toCssText(rules, function (rule) {
|
| +var css = rule.cssText = rule.parsedCssText;
|
| +if (rule.propertyInfo && rule.propertyInfo.cssText) {
|
| +css = cssParse.removeCustomPropAssignment(css);
|
| +rule.cssText = propertyUtils.valueForProperties(css, props);
|
| +}
|
| +});
|
| +}
|
| +});
|
| +}());
|
| +Polymer.Templatizer = {
|
| +properties: { __hideTemplateChildren__: { observer: '_showHideChildren' } },
|
| +_instanceProps: Polymer.nob,
|
| +_parentPropPrefix: '_parent_',
|
| +templatize: function (template) {
|
| +if (!template._content) {
|
| +template._content = template.content;
|
| +}
|
| +if (template._content._ctor) {
|
| +this.ctor = template._content._ctor;
|
| +this._prepParentProperties(this.ctor.prototype, template);
|
| +return;
|
| +}
|
| +var archetype = Object.create(Polymer.Base);
|
| +this._customPrepAnnotations(archetype, template);
|
| +archetype._prepEffects();
|
| +this._customPrepEffects(archetype);
|
| +archetype._prepBehaviors();
|
| +archetype._prepBindings();
|
| +this._prepParentProperties(archetype, template);
|
| +archetype._notifyPath = this._notifyPathImpl;
|
| +archetype._scopeElementClass = this._scopeElementClassImpl;
|
| +archetype.listen = this._listenImpl;
|
| +archetype._showHideChildren = this._showHideChildrenImpl;
|
| +var _constructor = this._constructorImpl;
|
| +var ctor = function TemplateInstance(model, host) {
|
| +_constructor.call(this, model, host);
|
| +};
|
| +ctor.prototype = archetype;
|
| +archetype.constructor = ctor;
|
| +template._content._ctor = ctor;
|
| +this.ctor = ctor;
|
| +},
|
| +_getRootDataHost: function () {
|
| +return this.dataHost && this.dataHost._rootDataHost || this.dataHost;
|
| +},
|
| +_showHideChildrenImpl: function (hide) {
|
| +var c = this._children;
|
| +for (var i = 0; i < c.length; i++) {
|
| +var n = c[i];
|
| +if (Boolean(hide) != Boolean(n.__hideTemplateChildren__)) {
|
| +if (n.nodeType === Node.TEXT_NODE) {
|
| +if (hide) {
|
| +n.__polymerTextContent__ = n.textContent;
|
| +n.textContent = '';
|
| +} else {
|
| +n.textContent = n.__polymerTextContent__;
|
| +}
|
| +} else if (n.style) {
|
| +if (hide) {
|
| +n.__polymerDisplay__ = n.style.display;
|
| +n.style.display = 'none';
|
| +} else {
|
| +n.style.display = n.__polymerDisplay__;
|
| +}
|
| +}
|
| +}
|
| +n.__hideTemplateChildren__ = hide;
|
| +}
|
| +},
|
| +_debounceTemplate: function (fn) {
|
| +Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', fn));
|
| +},
|
| +_flushTemplates: function (debouncerExpired) {
|
| +Polymer.dom.flush();
|
| +},
|
| +_customPrepEffects: function (archetype) {
|
| +var parentProps = archetype._parentProps;
|
| +for (var prop in parentProps) {
|
| +archetype._addPropertyEffect(prop, 'function', this._createHostPropEffector(prop));
|
| +}
|
| +for (var prop in this._instanceProps) {
|
| +archetype._addPropertyEffect(prop, 'function', this._createInstancePropEffector(prop));
|
| +}
|
| +},
|
| +_customPrepAnnotations: function (archetype, template) {
|
| +archetype._template = template;
|
| +var c = template._content;
|
| +if (!c._notes) {
|
| +var rootDataHost = archetype._rootDataHost;
|
| +if (rootDataHost) {
|
| +Polymer.Annotations.prepElement = rootDataHost._prepElement.bind(rootDataHost);
|
| +}
|
| +c._notes = Polymer.Annotations.parseAnnotations(template);
|
| +Polymer.Annotations.prepElement = null;
|
| +this._processAnnotations(c._notes);
|
| +}
|
| +archetype._notes = c._notes;
|
| +archetype._parentProps = c._parentProps;
|
| +},
|
| +_prepParentProperties: function (archetype, template) {
|
| +var parentProps = this._parentProps = archetype._parentProps;
|
| +if (this._forwardParentProp && parentProps) {
|
| +var proto = archetype._parentPropProto;
|
| +var prop;
|
| +if (!proto) {
|
| +for (prop in this._instanceProps) {
|
| +delete parentProps[prop];
|
| +}
|
| +proto = archetype._parentPropProto = Object.create(null);
|
| +if (template != this) {
|
| +Polymer.Bind.prepareModel(proto);
|
| +}
|
| +for (prop in parentProps) {
|
| +var parentProp = this._parentPropPrefix + prop;
|
| +var effects = [
|
| +{
|
| +kind: 'function',
|
| +effect: this._createForwardPropEffector(prop)
|
| +},
|
| +{ kind: 'notify' }
|
| +];
|
| +Polymer.Bind._createAccessors(proto, parentProp, effects);
|
| +}
|
| +}
|
| +if (template != this) {
|
| +Polymer.Bind.prepareInstance(template);
|
| +template._forwardParentProp = this._forwardParentProp.bind(this);
|
| +}
|
| +this._extendTemplate(template, proto);
|
| +}
|
| +},
|
| +_createForwardPropEffector: function (prop) {
|
| +return function (source, value) {
|
| +this._forwardParentProp(prop, value);
|
| +};
|
| +},
|
| +_createHostPropEffector: function (prop) {
|
| +var prefix = this._parentPropPrefix;
|
| +return function (source, value) {
|
| +this.dataHost[prefix + prop] = value;
|
| +};
|
| +},
|
| +_createInstancePropEffector: function (prop) {
|
| +return function (source, value, old, fromAbove) {
|
| +if (!fromAbove) {
|
| +this.dataHost._forwardInstanceProp(this, prop, value);
|
| +}
|
| +};
|
| +},
|
| +_extendTemplate: function (template, proto) {
|
| +Object.getOwnPropertyNames(proto).forEach(function (n) {
|
| +var val = template[n];
|
| +var pd = Object.getOwnPropertyDescriptor(proto, n);
|
| +Object.defineProperty(template, n, pd);
|
| +if (val !== undefined) {
|
| +template._propertySetter(n, val);
|
| +}
|
| +});
|
| +},
|
| +_showHideChildren: function (hidden) {
|
| +},
|
| +_forwardInstancePath: function (inst, path, value) {
|
| +},
|
| +_forwardInstanceProp: function (inst, prop, value) {
|
| +},
|
| +_notifyPathImpl: function (path, value) {
|
| +var dataHost = this.dataHost;
|
| +var dot = path.indexOf('.');
|
| +var root = dot < 0 ? path : path.slice(0, dot);
|
| +dataHost._forwardInstancePath.call(dataHost, this, path, value);
|
| +if (root in dataHost._parentProps) {
|
| +dataHost.notifyPath(dataHost._parentPropPrefix + path, value);
|
| +}
|
| +},
|
| +_pathEffector: function (path, value, fromAbove) {
|
| +if (this._forwardParentPath) {
|
| +if (path.indexOf(this._parentPropPrefix) === 0) {
|
| +this._forwardParentPath(path.substring(8), value);
|
| +}
|
| +}
|
| +Polymer.Base._pathEffector.apply(this, arguments);
|
| +},
|
| +_constructorImpl: function (model, host) {
|
| +this._rootDataHost = host._getRootDataHost();
|
| +this._setupConfigure(model);
|
| +this._pushHost(host);
|
| +this.root = this.instanceTemplate(this._template);
|
| +this.root.__noContent = !this._notes._hasContent;
|
| +this.root.__styleScoped = true;
|
| +this._popHost();
|
| +this._marshalAnnotatedNodes();
|
| +this._marshalInstanceEffects();
|
| +this._marshalAnnotatedListeners();
|
| +var children = [];
|
| +for (var n = this.root.firstChild; n; n = n.nextSibling) {
|
| +children.push(n);
|
| +n._templateInstance = this;
|
| +}
|
| +this._children = children;
|
| +if (host.__hideTemplateChildren__) {
|
| +this._showHideChildren(true);
|
| +}
|
| +this._tryReady();
|
| +},
|
| +_listenImpl: function (node, eventName, methodName) {
|
| +var model = this;
|
| +var host = this._rootDataHost;
|
| +var handler = host._createEventHandler(node, eventName, methodName);
|
| +var decorated = function (e) {
|
| +e.model = model;
|
| +handler(e);
|
| +};
|
| +host._listen(node, eventName, decorated);
|
| +},
|
| +_scopeElementClassImpl: function (node, value) {
|
| +var host = this._rootDataHost;
|
| +if (host) {
|
| +return host._scopeElementClass(node, value);
|
| +}
|
| +},
|
| +stamp: function (model) {
|
| +model = model || {};
|
| +if (this._parentProps) {
|
| +for (var prop in this._parentProps) {
|
| +model[prop] = this[this._parentPropPrefix + prop];
|
| +}
|
| +}
|
| +return new this.ctor(model, this);
|
| +},
|
| +modelForElement: function (el) {
|
| +var model;
|
| +while (el) {
|
| +if (model = el._templateInstance) {
|
| +if (model.dataHost != this) {
|
| +el = model.dataHost;
|
| +} else {
|
| +return model;
|
| +}
|
| +} else {
|
| +el = el.parentNode;
|
| +}
|
| +}
|
| +}
|
| +};
|
| +Polymer({
|
| +is: 'dom-template',
|
| +extends: 'template',
|
| +behaviors: [Polymer.Templatizer],
|
| +ready: function () {
|
| +this.templatize(this);
|
| +}
|
| +});
|
| +Polymer._collections = new WeakMap();
|
| +Polymer.Collection = function (userArray) {
|
| +Polymer._collections.set(userArray, this);
|
| +this.userArray = userArray;
|
| +this.store = userArray.slice();
|
| +this.initMap();
|
| +};
|
| +Polymer.Collection.prototype = {
|
| +constructor: Polymer.Collection,
|
| +initMap: function () {
|
| +var omap = this.omap = new WeakMap();
|
| +var pmap = this.pmap = {};
|
| +var s = this.store;
|
| +for (var i = 0; i < s.length; i++) {
|
| +var item = s[i];
|
| +if (item && typeof item == 'object') {
|
| +omap.set(item, i);
|
| +} else {
|
| +pmap[item] = i;
|
| +}
|
| +}
|
| +},
|
| +add: function (item) {
|
| +var key = this.store.push(item) - 1;
|
| +if (item && typeof item == 'object') {
|
| +this.omap.set(item, key);
|
| +} else {
|
| +this.pmap[item] = key;
|
| +}
|
| +return key;
|
| +},
|
| +removeKey: function (key) {
|
| +this._removeFromMap(this.store[key]);
|
| +delete this.store[key];
|
| +},
|
| +_removeFromMap: function (item) {
|
| +if (item && typeof item == 'object') {
|
| +this.omap.delete(item);
|
| +} else {
|
| +delete this.pmap[item];
|
| +}
|
| +},
|
| +remove: function (item) {
|
| +var key = this.getKey(item);
|
| +this.removeKey(key);
|
| +return key;
|
| +},
|
| +getKey: function (item) {
|
| +if (item && typeof item == 'object') {
|
| +return this.omap.get(item);
|
| +} else {
|
| +return this.pmap[item];
|
| +}
|
| +},
|
| +getKeys: function () {
|
| +return Object.keys(this.store);
|
| +},
|
| +setItem: function (key, item) {
|
| +var old = this.store[key];
|
| +if (old) {
|
| +this._removeFromMap(old);
|
| +}
|
| +if (item && typeof item == 'object') {
|
| +this.omap.set(item, key);
|
| +} else {
|
| +this.pmap[item] = key;
|
| +}
|
| +this.store[key] = item;
|
| +},
|
| +getItem: function (key) {
|
| +return this.store[key];
|
| +},
|
| +getItems: function () {
|
| +var items = [], store = this.store;
|
| +for (var key in store) {
|
| +items.push(store[key]);
|
| +}
|
| +return items;
|
| +},
|
| +_applySplices: function (splices) {
|
| +var keyMap = {}, key, i;
|
| +splices.forEach(function (s) {
|
| +s.addedKeys = [];
|
| +for (i = 0; i < s.removed.length; i++) {
|
| +key = this.getKey(s.removed[i]);
|
| +keyMap[key] = keyMap[key] ? null : -1;
|
| +}
|
| +for (i = 0; i < s.addedCount; i++) {
|
| +var item = this.userArray[s.index + i];
|
| +key = this.getKey(item);
|
| +key = key === undefined ? this.add(item) : key;
|
| +keyMap[key] = keyMap[key] ? null : 1;
|
| +s.addedKeys.push(key);
|
| +}
|
| +}, this);
|
| +var removed = [];
|
| +var added = [];
|
| +for (var key in keyMap) {
|
| +if (keyMap[key] < 0) {
|
| +this.removeKey(key);
|
| +removed.push(key);
|
| +}
|
| +if (keyMap[key] > 0) {
|
| +added.push(key);
|
| +}
|
| +}
|
| +return [{
|
| +removed: removed,
|
| +added: added
|
| +}];
|
| +}
|
| +};
|
| +Polymer.Collection.get = function (userArray) {
|
| +return Polymer._collections.get(userArray) || new Polymer.Collection(userArray);
|
| +};
|
| +Polymer.Collection.applySplices = function (userArray, splices) {
|
| +var coll = Polymer._collections.get(userArray);
|
| +return coll ? coll._applySplices(splices) : null;
|
| +};
|
| +Polymer({
|
| +is: 'dom-repeat',
|
| +extends: 'template',
|
| +properties: {
|
| +items: { type: Array },
|
| +as: {
|
| +type: String,
|
| +value: 'item'
|
| +},
|
| +indexAs: {
|
| +type: String,
|
| +value: 'index'
|
| +},
|
| +sort: {
|
| +type: Function,
|
| +observer: '_sortChanged'
|
| +},
|
| +filter: {
|
| +type: Function,
|
| +observer: '_filterChanged'
|
| +},
|
| +observe: {
|
| +type: String,
|
| +observer: '_observeChanged'
|
| +},
|
| +delay: Number
|
| +},
|
| +behaviors: [Polymer.Templatizer],
|
| +observers: ['_itemsChanged(items.*)'],
|
| +created: function () {
|
| +this._instances = [];
|
| +},
|
| +detached: function () {
|
| +for (var i = 0; i < this._instances.length; i++) {
|
| +this._detachRow(i);
|
| +}
|
| +},
|
| +attached: function () {
|
| +var parentNode = Polymer.dom(this).parentNode;
|
| +for (var i = 0; i < this._instances.length; i++) {
|
| +Polymer.dom(parentNode).insertBefore(this._instances[i].root, this);
|
| +}
|
| +},
|
| +ready: function () {
|
| +this._instanceProps = { __key__: true };
|
| +this._instanceProps[this.as] = true;
|
| +this._instanceProps[this.indexAs] = true;
|
| +if (!this.ctor) {
|
| +this.templatize(this);
|
| +}
|
| +},
|
| +_sortChanged: function () {
|
| +var dataHost = this._getRootDataHost();
|
| +var sort = this.sort;
|
| +this._sortFn = sort && (typeof sort == 'function' ? sort : function () {
|
| +return dataHost[sort].apply(dataHost, arguments);
|
| +});
|
| +this._needFullRefresh = true;
|
| +if (this.items) {
|
| +this._debounceTemplate(this._render);
|
| +}
|
| +},
|
| +_filterChanged: function () {
|
| +var dataHost = this._getRootDataHost();
|
| +var filter = this.filter;
|
| +this._filterFn = filter && (typeof filter == 'function' ? filter : function () {
|
| +return dataHost[filter].apply(dataHost, arguments);
|
| +});
|
| +this._needFullRefresh = true;
|
| +if (this.items) {
|
| +this._debounceTemplate(this._render);
|
| +}
|
| +},
|
| +_observeChanged: function () {
|
| +this._observePaths = this.observe && this.observe.replace('.*', '.').split(' ');
|
| +},
|
| +_itemsChanged: function (change) {
|
| +if (change.path == 'items') {
|
| +if (Array.isArray(this.items)) {
|
| +this.collection = Polymer.Collection.get(this.items);
|
| +} else if (!this.items) {
|
| +this.collection = null;
|
| +} else {
|
| +this._error(this._logf('dom-repeat', 'expected array for `items`,' + ' found', this.items));
|
| +}
|
| +this._keySplices = [];
|
| +this._indexSplices = [];
|
| +this._needFullRefresh = true;
|
| +this._debounceTemplate(this._render);
|
| +} else if (change.path == 'items.splices') {
|
| +this._keySplices = this._keySplices.concat(change.value.keySplices);
|
| +this._indexSplices = this._indexSplices.concat(change.value.indexSplices);
|
| +this._debounceTemplate(this._render);
|
| +} else {
|
| +var subpath = change.path.slice(6);
|
| +this._forwardItemPath(subpath, change.value);
|
| +this._checkObservedPaths(subpath);
|
| +}
|
| +},
|
| +_checkObservedPaths: function (path) {
|
| +if (this._observePaths) {
|
| +path = path.substring(path.indexOf('.') + 1);
|
| +var paths = this._observePaths;
|
| +for (var i = 0; i < paths.length; i++) {
|
| +if (path.indexOf(paths[i]) === 0) {
|
| +this._needFullRefresh = true;
|
| +if (this.delay) {
|
| +this.debounce('render', this._render, this.delay);
|
| +} else {
|
| +this._debounceTemplate(this._render);
|
| +}
|
| +return;
|
| +}
|
| +}
|
| +}
|
| +},
|
| +render: function () {
|
| +this._needFullRefresh = true;
|
| +this._debounceTemplate(this._render);
|
| +this._flushTemplates();
|
| +},
|
| +_render: function () {
|
| +var c = this.collection;
|
| +if (this._needFullRefresh) {
|
| +this._applyFullRefresh();
|
| +this._needFullRefresh = false;
|
| +} else {
|
| +if (this._sortFn) {
|
| +this._applySplicesUserSort(this._keySplices);
|
| +} else {
|
| +if (this._filterFn) {
|
| +this._applyFullRefresh();
|
| +} else {
|
| +this._applySplicesArrayOrder(this._indexSplices);
|
| +}
|
| +}
|
| +}
|
| +this._keySplices = [];
|
| +this._indexSplices = [];
|
| +var keyToIdx = this._keyToInstIdx = {};
|
| +for (var i = 0; i < this._instances.length; i++) {
|
| +var inst = this._instances[i];
|
| +keyToIdx[inst.__key__] = i;
|
| +inst.__setProperty(this.indexAs, i, true);
|
| +}
|
| +this.fire('dom-change');
|
| +},
|
| +_applyFullRefresh: function () {
|
| +var c = this.collection;
|
| +var keys;
|
| +if (this._sortFn) {
|
| +keys = c ? c.getKeys() : [];
|
| +} else {
|
| +keys = [];
|
| +var items = this.items;
|
| +if (items) {
|
| +for (var i = 0; i < items.length; i++) {
|
| +keys.push(c.getKey(items[i]));
|
| +}
|
| +}
|
| +}
|
| +if (this._filterFn) {
|
| +keys = keys.filter(function (a) {
|
| +return this._filterFn(c.getItem(a));
|
| +}, this);
|
| +}
|
| +if (this._sortFn) {
|
| +keys.sort(function (a, b) {
|
| +return this._sortFn(c.getItem(a), c.getItem(b));
|
| +}.bind(this));
|
| +}
|
| +for (var i = 0; i < keys.length; i++) {
|
| +var key = keys[i];
|
| +var inst = this._instances[i];
|
| +if (inst) {
|
| +inst.__setProperty('__key__', key, true);
|
| +inst.__setProperty(this.as, c.getItem(key), true);
|
| +} else {
|
| +this._instances.push(this._insertRow(i, key));
|
| +}
|
| +}
|
| +for (; i < this._instances.length; i++) {
|
| +this._detachRow(i);
|
| +}
|
| +this._instances.splice(keys.length, this._instances.length - keys.length);
|
| +},
|
| +_keySort: function (a, b) {
|
| +return this.collection.getKey(a) - this.collection.getKey(b);
|
| +},
|
| +_numericSort: function (a, b) {
|
| +return a - b;
|
| +},
|
| +_applySplicesUserSort: function (splices) {
|
| +var c = this.collection;
|
| +var instances = this._instances;
|
| +var keyMap = {};
|
| +var pool = [];
|
| +var sortFn = this._sortFn || this._keySort.bind(this);
|
| +splices.forEach(function (s) {
|
| +for (var i = 0; i < s.removed.length; i++) {
|
| +var key = s.removed[i];
|
| +keyMap[key] = keyMap[key] ? null : -1;
|
| +}
|
| +for (var i = 0; i < s.added.length; i++) {
|
| +var key = s.added[i];
|
| +keyMap[key] = keyMap[key] ? null : 1;
|
| +}
|
| +}, this);
|
| +var removedIdxs = [];
|
| +var addedKeys = [];
|
| +for (var key in keyMap) {
|
| +if (keyMap[key] === -1) {
|
| +removedIdxs.push(this._keyToInstIdx[key]);
|
| +}
|
| +if (keyMap[key] === 1) {
|
| +addedKeys.push(key);
|
| +}
|
| +}
|
| +if (removedIdxs.length) {
|
| +removedIdxs.sort(this._numericSort);
|
| +for (var i = removedIdxs.length - 1; i >= 0; i--) {
|
| +var idx = removedIdxs[i];
|
| +if (idx !== undefined) {
|
| +pool.push(this._detachRow(idx));
|
| +instances.splice(idx, 1);
|
| +}
|
| +}
|
| +}
|
| +if (addedKeys.length) {
|
| +if (this._filterFn) {
|
| +addedKeys = addedKeys.filter(function (a) {
|
| +return this._filterFn(c.getItem(a));
|
| +}, this);
|
| +}
|
| +addedKeys.sort(function (a, b) {
|
| +return this._sortFn(c.getItem(a), c.getItem(b));
|
| +}.bind(this));
|
| +var start = 0;
|
| +for (var i = 0; i < addedKeys.length; i++) {
|
| +start = this._insertRowUserSort(start, addedKeys[i], pool);
|
| +}
|
| +}
|
| +},
|
| +_insertRowUserSort: function (start, key, pool) {
|
| +var c = this.collection;
|
| +var item = c.getItem(key);
|
| +var end = this._instances.length - 1;
|
| +var idx = -1;
|
| +var sortFn = this._sortFn || this._keySort.bind(this);
|
| +while (start <= end) {
|
| +var mid = start + end >> 1;
|
| +var midKey = this._instances[mid].__key__;
|
| +var cmp = sortFn(c.getItem(midKey), item);
|
| +if (cmp < 0) {
|
| +start = mid + 1;
|
| +} else if (cmp > 0) {
|
| +end = mid - 1;
|
| +} else {
|
| +idx = mid;
|
| +break;
|
| +}
|
| +}
|
| +if (idx < 0) {
|
| +idx = end + 1;
|
| +}
|
| +this._instances.splice(idx, 0, this._insertRow(idx, key, pool));
|
| +return idx;
|
| +},
|
| +_applySplicesArrayOrder: function (splices) {
|
| +var pool = [];
|
| +var c = this.collection;
|
| +splices.forEach(function (s) {
|
| +for (var i = 0; i < s.removed.length; i++) {
|
| +var inst = this._detachRow(s.index + i);
|
| +if (!inst.isPlaceholder) {
|
| +pool.push(inst);
|
| +}
|
| +}
|
| +this._instances.splice(s.index, s.removed.length);
|
| +for (var i = 0; i < s.addedKeys.length; i++) {
|
| +var inst = {
|
| +isPlaceholder: true,
|
| +key: s.addedKeys[i]
|
| +};
|
| +this._instances.splice(s.index + i, 0, inst);
|
| +}
|
| +}, this);
|
| +for (var i = this._instances.length - 1; i >= 0; i--) {
|
| +var inst = this._instances[i];
|
| +if (inst.isPlaceholder) {
|
| +this._instances[i] = this._insertRow(i, inst.key, pool, true);
|
| +}
|
| +}
|
| +},
|
| +_detachRow: function (idx) {
|
| +var inst = this._instances[idx];
|
| +if (!inst.isPlaceholder) {
|
| +var parentNode = Polymer.dom(this).parentNode;
|
| +for (var i = 0; i < inst._children.length; i++) {
|
| +var el = inst._children[i];
|
| +Polymer.dom(inst.root).appendChild(el);
|
| +}
|
| +}
|
| +return inst;
|
| +},
|
| +_insertRow: function (idx, key, pool, replace) {
|
| +var inst;
|
| +if (inst = pool && pool.pop()) {
|
| +inst.__setProperty(this.as, this.collection.getItem(key), true);
|
| +inst.__setProperty('__key__', key, true);
|
| +} else {
|
| +inst = this._generateRow(idx, key);
|
| +}
|
| +var beforeRow = this._instances[replace ? idx + 1 : idx];
|
| +var beforeNode = beforeRow ? beforeRow._children[0] : this;
|
| +var parentNode = Polymer.dom(this).parentNode;
|
| +Polymer.dom(parentNode).insertBefore(inst.root, beforeNode);
|
| +return inst;
|
| +},
|
| +_generateRow: function (idx, key) {
|
| +var model = { __key__: key };
|
| +model[this.as] = this.collection.getItem(key);
|
| +model[this.indexAs] = idx;
|
| +var inst = this.stamp(model);
|
| +return inst;
|
| +},
|
| +_showHideChildren: function (hidden) {
|
| +for (var i = 0; i < this._instances.length; i++) {
|
| +this._instances[i]._showHideChildren(hidden);
|
| +}
|
| +},
|
| +_forwardInstanceProp: function (inst, prop, value) {
|
| +if (prop == this.as) {
|
| +var idx;
|
| +if (this._sortFn || this._filterFn) {
|
| +idx = this.items.indexOf(this.collection.getItem(inst.__key__));
|
| +} else {
|
| +idx = inst[this.indexAs];
|
| +}
|
| +this.set('items.' + idx, value);
|
| +}
|
| +},
|
| +_forwardInstancePath: function (inst, path, value) {
|
| +if (path.indexOf(this.as + '.') === 0) {
|
| +this.notifyPath('items.' + inst.__key__ + '.' + path.slice(this.as.length + 1), value);
|
| +}
|
| +},
|
| +_forwardParentProp: function (prop, value) {
|
| +this._instances.forEach(function (inst) {
|
| +inst.__setProperty(prop, value, true);
|
| +}, this);
|
| +},
|
| +_forwardParentPath: function (path, value) {
|
| +this._instances.forEach(function (inst) {
|
| +inst.notifyPath(path, value, true);
|
| +}, this);
|
| +},
|
| +_forwardItemPath: function (path, value) {
|
| +if (this._keyToInstIdx) {
|
| +var dot = path.indexOf('.');
|
| +var key = path.substring(0, dot < 0 ? path.length : dot);
|
| +var idx = this._keyToInstIdx[key];
|
| +var inst = this._instances[idx];
|
| +if (inst) {
|
| +if (dot >= 0) {
|
| +path = this.as + '.' + path.substring(dot + 1);
|
| +inst.notifyPath(path, value, true);
|
| +} else {
|
| +inst.__setProperty(this.as, value, true);
|
| +}
|
| +}
|
| +}
|
| +},
|
| +itemForElement: function (el) {
|
| +var instance = this.modelForElement(el);
|
| +return instance && instance[this.as];
|
| +},
|
| +keyForElement: function (el) {
|
| +var instance = this.modelForElement(el);
|
| +return instance && instance.__key__;
|
| +},
|
| +indexForElement: function (el) {
|
| +var instance = this.modelForElement(el);
|
| +return instance && instance[this.indexAs];
|
| +}
|
| +});
|
| +Polymer({
|
| +is: 'array-selector',
|
| +properties: {
|
| +items: {
|
| +type: Array,
|
| +observer: 'clearSelection'
|
| +},
|
| +multi: {
|
| +type: Boolean,
|
| +value: false,
|
| +observer: 'clearSelection'
|
| +},
|
| +selected: {
|
| +type: Object,
|
| +notify: true
|
| +},
|
| +selectedItem: {
|
| +type: Object,
|
| +notify: true
|
| +},
|
| +toggle: {
|
| +type: Boolean,
|
| +value: false
|
| +}
|
| +},
|
| +clearSelection: function () {
|
| +if (Array.isArray(this.selected)) {
|
| +for (var i = 0; i < this.selected.length; i++) {
|
| +this.unlinkPaths('selected.' + i);
|
| +}
|
| +} else {
|
| +this.unlinkPaths('selected');
|
| +}
|
| +if (this.multi) {
|
| +if (!this.selected || this.selected.length) {
|
| +this.selected = [];
|
| +this._selectedColl = Polymer.Collection.get(this.selected);
|
| +}
|
| +} else {
|
| +this.selected = null;
|
| +this._selectedColl = null;
|
| +}
|
| +this.selectedItem = null;
|
| +},
|
| +isSelected: function (item) {
|
| +if (this.multi) {
|
| +return this._selectedColl.getKey(item) !== undefined;
|
| +} else {
|
| +return this.selected == item;
|
| +}
|
| +},
|
| +deselect: function (item) {
|
| +if (this.multi) {
|
| +if (this.isSelected(item)) {
|
| +var skey = this._selectedColl.getKey(item);
|
| +this.arrayDelete('selected', item);
|
| +this.unlinkPaths('selected.' + skey);
|
| +}
|
| +} else {
|
| +this.selected = null;
|
| +this.selectedItem = null;
|
| +this.unlinkPaths('selected');
|
| +this.unlinkPaths('selectedItem');
|
| +}
|
| +},
|
| +select: function (item) {
|
| +var icol = Polymer.Collection.get(this.items);
|
| +var key = icol.getKey(item);
|
| +if (this.multi) {
|
| +if (this.isSelected(item)) {
|
| +if (this.toggle) {
|
| +this.deselect(item);
|
| +}
|
| +} else {
|
| +this.push('selected', item);
|
| +skey = this._selectedColl.getKey(item);
|
| +this.linkPaths('selected.' + skey, 'items.' + key);
|
| +}
|
| +} else {
|
| +if (this.toggle && item == this.selected) {
|
| +this.deselect();
|
| +} else {
|
| +this.selected = item;
|
| +this.selectedItem = item;
|
| +this.linkPaths('selected', 'items.' + key);
|
| +this.linkPaths('selectedItem', 'items.' + key);
|
| +}
|
| +}
|
| +}
|
| +});
|
| +Polymer({
|
| +is: 'dom-if',
|
| +extends: 'template',
|
| +properties: {
|
| +'if': {
|
| +type: Boolean,
|
| +value: false,
|
| +observer: '_queueRender'
|
| +},
|
| +restamp: {
|
| +type: Boolean,
|
| +value: false,
|
| +observer: '_queueRender'
|
| +}
|
| +},
|
| +behaviors: [Polymer.Templatizer],
|
| +_queueRender: function () {
|
| +this._debounceTemplate(this._render);
|
| +},
|
| +detached: function () {
|
| +this._teardownInstance();
|
| +},
|
| +attached: function () {
|
| +if (this.if && this.ctor) {
|
| +this.async(this._ensureInstance);
|
| +}
|
| +},
|
| +render: function () {
|
| +this._flushTemplates();
|
| +},
|
| +_render: function () {
|
| +if (this.if) {
|
| +if (!this.ctor) {
|
| +this.templatize(this);
|
| +}
|
| +this._ensureInstance();
|
| +this._showHideChildren();
|
| +} else if (this.restamp) {
|
| +this._teardownInstance();
|
| +}
|
| +if (!this.restamp && this._instance) {
|
| +this._showHideChildren();
|
| +}
|
| +if (this.if != this._lastIf) {
|
| +this.fire('dom-change');
|
| +this._lastIf = this.if;
|
| +}
|
| +},
|
| +_ensureInstance: function () {
|
| +if (!this._instance) {
|
| +this._instance = this.stamp();
|
| +var root = this._instance.root;
|
| +var parent = Polymer.dom(Polymer.dom(this).parentNode);
|
| +parent.insertBefore(root, this);
|
| +}
|
| +},
|
| +_teardownInstance: function () {
|
| +if (this._instance) {
|
| +var c = this._instance._children;
|
| +if (c) {
|
| +var parent = Polymer.dom(Polymer.dom(c[0]).parentNode);
|
| +c.forEach(function (n) {
|
| +parent.removeChild(n);
|
| +});
|
| +}
|
| +this._instance = null;
|
| +}
|
| +},
|
| +_showHideChildren: function () {
|
| +var hidden = this.__hideTemplateChildren__ || !this.if;
|
| +if (this._instance) {
|
| +this._instance._showHideChildren(hidden);
|
| +}
|
| +},
|
| +_forwardParentProp: function (prop, value) {
|
| +if (this._instance) {
|
| +this._instance[prop] = value;
|
| +}
|
| +},
|
| +_forwardParentPath: function (path, value) {
|
| +if (this._instance) {
|
| +this._instance.notifyPath(path, value, true);
|
| +}
|
| +}
|
| +});
|
| +Polymer({
|
| +is: 'dom-bind',
|
| +extends: 'template',
|
| +created: function () {
|
| +Polymer.RenderStatus.whenReady(this._markImportsReady.bind(this));
|
| +},
|
| +_ensureReady: function () {
|
| +if (!this._readied) {
|
| +this._readySelf();
|
| +}
|
| +},
|
| +_markImportsReady: function () {
|
| +this._importsReady = true;
|
| +this._ensureReady();
|
| +},
|
| +_registerFeatures: function () {
|
| +this._prepConstructor();
|
| +},
|
| +_insertChildren: function () {
|
| +var parentDom = Polymer.dom(Polymer.dom(this).parentNode);
|
| +parentDom.insertBefore(this.root, this);
|
| +},
|
| +_removeChildren: function () {
|
| +if (this._children) {
|
| +for (var i = 0; i < this._children.length; i++) {
|
| +this.root.appendChild(this._children[i]);
|
| +}
|
| +}
|
| +},
|
| +_initFeatures: function () {
|
| +},
|
| +_scopeElementClass: function (element, selector) {
|
| +if (this.dataHost) {
|
| +return this.dataHost._scopeElementClass(element, selector);
|
| +} else {
|
| +return selector;
|
| +}
|
| +},
|
| +_prepConfigure: function () {
|
| +var config = {};
|
| +for (var prop in this._propertyEffects) {
|
| +config[prop] = this[prop];
|
| +}
|
| +this._setupConfigure = this._setupConfigure.bind(this, config);
|
| +},
|
| +attached: function () {
|
| +if (this._importsReady) {
|
| +this.render();
|
| +}
|
| +},
|
| +detached: function () {
|
| +this._removeChildren();
|
| +},
|
| +render: function () {
|
| +this._ensureReady();
|
| +if (!this._children) {
|
| +this._template = this;
|
| +this._prepAnnotations();
|
| +this._prepEffects();
|
| +this._prepBehaviors();
|
| +this._prepConfigure();
|
| +this._prepBindings();
|
| +Polymer.Base._initFeatures.call(this);
|
| +this._children = Array.prototype.slice.call(this.root.childNodes);
|
| +}
|
| +this._insertChildren();
|
| +this.fire('dom-change');
|
| +}
|
| +});
|
| +(function() {
|
| +
|
| + 'use strict';
|
| +
|
| + var SHADOW_WHEN_SCROLLING = 1;
|
| + var SHADOW_ALWAYS = 2;
|
| +
|
| +
|
| + var MODE_CONFIGS = {
|
| +
|
| + outerScroll: {
|
| + 'scroll': true
|
| + },
|
| +
|
| + shadowMode: {
|
| + 'standard': SHADOW_ALWAYS,
|
| + 'waterfall': SHADOW_WHEN_SCROLLING,
|
| + 'waterfall-tall': SHADOW_WHEN_SCROLLING
|
| + },
|
| +
|
| + tallMode: {
|
| + 'waterfall-tall': true
|
| + }
|
| + };
|
| +
|
| + Polymer({
|
| +
|
| + is: 'paper-header-panel',
|
| +
|
| + /**
|
| + * Fired when the content has been scrolled. `event.detail.target` returns
|
| + * the scrollable element which you can use to access scroll info such as
|
| + * `scrollTop`.
|
| + *
|
| + * <paper-header-panel on-content-scroll="scrollHandler">
|
| + * ...
|
| + * </paper-header-panel>
|
| + *
|
| + *
|
| + * scrollHandler: function(event) {
|
| + * var scroller = event.detail.target;
|
| + * console.log(scroller.scrollTop);
|
| + * }
|
| + *
|
| + * @event content-scroll
|
| + */
|
| +
|
| + properties: {
|
| +
|
| + /**
|
| + * Controls header and scrolling behavior. Options are
|
| + * `standard`, `seamed`, `waterfall`, `waterfall-tall`, `scroll` and
|
| + * `cover`. Default is `standard`.
|
| + *
|
| + * `standard`: The header is a step above the panel. The header will consume the
|
| + * panel at the point of entry, preventing it from passing through to the
|
| + * opposite side.
|
| + *
|
| + * `seamed`: The header is presented as seamed with the panel.
|
| + *
|
| + * `waterfall`: Similar to standard mode, but header is initially presented as
|
| + * seamed with panel, but then separates to form the step.
|
| + *
|
| + * `waterfall-tall`: The header is initially taller (`tall` class is added to
|
| + * the header). As the user scrolls, the header separates (forming an edge)
|
| + * while condensing (`tall` class is removed from the header).
|
| + *
|
| + * `scroll`: The header keeps its seam with the panel, and is pushed off screen.
|
| + *
|
| + * `cover`: The panel covers the whole `paper-header-panel` including the
|
| + * header. This allows user to style the panel in such a way that the panel is
|
| + * partially covering the header.
|
| + *
|
| + * <paper-header-panel mode="cover">
|
| + * <paper-toolbar class="tall">
|
| + * <core-icon-button icon="menu"></core-icon-button>
|
| + * </paper-toolbar>
|
| + * <div class="content"></div>
|
| + * </paper-header-panel>
|
| + */
|
| + mode: {
|
| + type: String,
|
| + value: 'standard',
|
| + observer: '_modeChanged',
|
| + reflectToAttribute: true
|
| + },
|
| +
|
| + /**
|
| + * If true, the drop-shadow is always shown no matter what mode is set to.
|
| + */
|
| + shadow: {
|
| + type: Boolean,
|
| + value: false
|
| + },
|
| +
|
| + /**
|
| + * The class used in waterfall-tall mode. Change this if the header
|
| + * accepts a different class for toggling height, e.g. "medium-tall"
|
| + */
|
| + tallClass: {
|
| + type: String,
|
| + value: 'tall'
|
| + },
|
| +
|
| + /**
|
| + * If true, the scroller is at the top
|
| + */
|
| + atTop: {
|
| + type: Boolean,
|
| + value: true,
|
| + readOnly: true
|
| + }
|
| + },
|
| +
|
| + observers: [
|
| + '_computeDropShadowHidden(atTop, mode, shadow)'
|
| + ],
|
| +
|
| + ready: function() {
|
| + this.scrollHandler = this._scroll.bind(this);
|
| + this._addListener();
|
| +
|
| + // Run `scroll` logic once to initialze class names, etc.
|
| + this._keepScrollingState();
|
| + },
|
| +
|
| + detached: function() {
|
| + this._removeListener();
|
| + },
|
| +
|
| + /**
|
| + * Returns the header element
|
| + *
|
| + * @property header
|
| + * @type Object
|
| + */
|
| + get header() {
|
| + return Polymer.dom(this.$.headerContent).getDistributedNodes()[0];
|
| + },
|
| +
|
| + /**
|
| + * Returns the scrollable element.
|
| + *
|
| + * @property scroller
|
| + * @type Object
|
| + */
|
| + get scroller() {
|
| + return this._getScrollerForMode(this.mode);
|
| + },
|
| +
|
| + /**
|
| + * Returns true if the scroller has a visible shadow.
|
| + *
|
| + * @property visibleShadow
|
| + * @type Boolean
|
| + */
|
| + get visibleShadow() {
|
| + return this.$.dropShadow.classList.contains('has-shadow');
|
| + },
|
| +
|
| + _computeDropShadowHidden: function(atTop, mode, shadow) {
|
| +
|
| + var shadowMode = MODE_CONFIGS.shadowMode[mode];
|
| +
|
| + if (this.shadow) {
|
| + this.toggleClass('has-shadow', true, this.$.dropShadow);
|
| +
|
| + } else if (shadowMode === SHADOW_ALWAYS) {
|
| + this.toggleClass('has-shadow', true, this.$.dropShadow);
|
| +
|
| + } else if (shadowMode === SHADOW_WHEN_SCROLLING && !atTop) {
|
| + this.toggleClass('has-shadow', true, this.$.dropShadow);
|
| +
|
| + } else {
|
| + this.toggleClass('has-shadow', false, this.$.dropShadow);
|
| +
|
| + }
|
| + },
|
| +
|
| + _computeMainContainerClass: function(mode) {
|
| + // TODO: It will be useful to have a utility for classes
|
| + // e.g. Polymer.Utils.classes({ foo: true });
|
| +
|
| + var classes = {};
|
| +
|
| + classes['flex'] = mode !== 'cover';
|
| +
|
| + return Object.keys(classes).filter(
|
| + function(className) {
|
| + return classes[className];
|
| + }).join(' ');
|
| + },
|
| +
|
| + _addListener: function() {
|
| + this.scroller.addEventListener('scroll', this.scrollHandler, false);
|
| + },
|
| +
|
| + _removeListener: function() {
|
| + this.scroller.removeEventListener('scroll', this.scrollHandler);
|
| + },
|
| +
|
| + _modeChanged: function(newMode, oldMode) {
|
| + var configs = MODE_CONFIGS;
|
| + var header = this.header;
|
| + var animateDuration = 200;
|
| +
|
| + if (header) {
|
| + // in tallMode it may add tallClass to the header; so do the cleanup
|
| + // when mode is changed from tallMode to not tallMode
|
| + if (configs.tallMode[oldMode] && !configs.tallMode[newMode]) {
|
| + header.classList.remove(this.tallClass);
|
| + this.async(function() {
|
| + header.classList.remove('animate');
|
| + }, animateDuration);
|
| + } else {
|
| + header.classList.toggle('animate', configs.tallMode[newMode]);
|
| + }
|
| + }
|
| + this._keepScrollingState();
|
| + },
|
| +
|
| + _keepScrollingState: function() {
|
| + var main = this.scroller;
|
| + var header = this.header;
|
| +
|
| + this._setAtTop(main.scrollTop === 0);
|
| +
|
| + if (header && this.tallClass && MODE_CONFIGS.tallMode[this.mode]) {
|
| + this.toggleClass(this.tallClass, this.atTop ||
|
| + header.classList.contains(this.tallClass) &&
|
| + main.scrollHeight < this.offsetHeight, header);
|
| + }
|
| + },
|
| +
|
| + _scroll: function() {
|
| + this._keepScrollingState();
|
| + this.fire('content-scroll', {target: this.scroller}, {bubbles: false});
|
| + },
|
| +
|
| + _getScrollerForMode: function(mode) {
|
| + return MODE_CONFIGS.outerScroll[mode] ?
|
| + this : this.$.mainContainer;
|
| + }
|
| +
|
| + });
|
| +
|
| + })();
|
| +Polymer({
|
| + is: 'paper-material',
|
| +
|
| + properties: {
|
| +
|
| + /**
|
| + * The z-depth of this element, from 0-5. Setting to 0 will remove the
|
| + * shadow, and each increasing number greater than 0 will be "deeper"
|
| + * than the last.
|
| + *
|
| + * @attribute elevation
|
| + * @type number
|
| + * @default 1
|
| + */
|
| + elevation: {
|
| + type: Number,
|
| + reflectToAttribute: true,
|
| + value: 1
|
| + },
|
| +
|
| + /**
|
| + * Set this to true to animate the shadow when setting a new
|
| + * `elevation` value.
|
| + *
|
| + * @attribute animated
|
| + * @type boolean
|
| + * @default false
|
| + */
|
| + animated: {
|
| + type: Boolean,
|
| + reflectToAttribute: true,
|
| + value: false
|
| + }
|
| + }
|
| + });
|
| +(function() {
|
| + 'use strict';
|
| +
|
| + /**
|
| + * Chrome uses an older version of DOM Level 3 Keyboard Events
|
| + *
|
| + * Most keys are labeled as text, but some are Unicode codepoints.
|
| + * Values taken from: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/keyset.html#KeySet-Set
|
| + */
|
| + var KEY_IDENTIFIER = {
|
| + 'U+0009': 'tab',
|
| + 'U+001B': 'esc',
|
| + 'U+0020': 'space',
|
| + 'U+002A': '*',
|
| + 'U+0030': '0',
|
| + 'U+0031': '1',
|
| + 'U+0032': '2',
|
| + 'U+0033': '3',
|
| + 'U+0034': '4',
|
| + 'U+0035': '5',
|
| + 'U+0036': '6',
|
| + 'U+0037': '7',
|
| + 'U+0038': '8',
|
| + 'U+0039': '9',
|
| + 'U+0041': 'a',
|
| + 'U+0042': 'b',
|
| + 'U+0043': 'c',
|
| + 'U+0044': 'd',
|
| + 'U+0045': 'e',
|
| + 'U+0046': 'f',
|
| + 'U+0047': 'g',
|
| + 'U+0048': 'h',
|
| + 'U+0049': 'i',
|
| + 'U+004A': 'j',
|
| + 'U+004B': 'k',
|
| + 'U+004C': 'l',
|
| + 'U+004D': 'm',
|
| + 'U+004E': 'n',
|
| + 'U+004F': 'o',
|
| + 'U+0050': 'p',
|
| + 'U+0051': 'q',
|
| + 'U+0052': 'r',
|
| + 'U+0053': 's',
|
| + 'U+0054': 't',
|
| + 'U+0055': 'u',
|
| + 'U+0056': 'v',
|
| + 'U+0057': 'w',
|
| + 'U+0058': 'x',
|
| + 'U+0059': 'y',
|
| + 'U+005A': 'z',
|
| + 'U+007F': 'del'
|
| + };
|
| +
|
| + /**
|
| + * Special table for KeyboardEvent.keyCode.
|
| + * KeyboardEvent.keyIdentifier is better, and KeyBoardEvent.key is even better
|
| + * than that.
|
| + *
|
| + * Values from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.keyCode#Value_of_keyCode
|
| + */
|
| + var KEY_CODE = {
|
| + 9: 'tab',
|
| + 13: 'enter',
|
| + 27: 'esc',
|
| + 33: 'pageup',
|
| + 34: 'pagedown',
|
| + 35: 'end',
|
| + 36: 'home',
|
| + 32: 'space',
|
| + 37: 'left',
|
| + 38: 'up',
|
| + 39: 'right',
|
| + 40: 'down',
|
| + 46: 'del',
|
| + 106: '*'
|
| + };
|
| +
|
| + /**
|
| + * MODIFIER_KEYS maps the short name for modifier keys used in a key
|
| + * combo string to the property name that references those same keys
|
| + * in a KeyboardEvent instance.
|
| + */
|
| + var MODIFIER_KEYS = {
|
| + 'shift': 'shiftKey',
|
| + 'ctrl': 'ctrlKey',
|
| + 'alt': 'altKey',
|
| + 'meta': 'metaKey'
|
| + };
|
| +
|
| + /**
|
| + * KeyboardEvent.key is mostly represented by printable character made by
|
| + * the keyboard, with unprintable keys labeled nicely.
|
| + *
|
| + * However, on OS X, Alt+char can make a Unicode character that follows an
|
| + * Apple-specific mapping. In this case, we
|
| + * fall back to .keyCode.
|
| + */
|
| + var KEY_CHAR = /[a-z0-9*]/;
|
| +
|
| + /**
|
| + * Matches a keyIdentifier string.
|
| + */
|
| + var IDENT_CHAR = /U\+/;
|
| +
|
| + /**
|
| + * Matches arrow keys in Gecko 27.0+
|
| + */
|
| + var ARROW_KEY = /^arrow/;
|
| +
|
| + /**
|
| + * Matches space keys everywhere (notably including IE10's exceptional name
|
| + * `spacebar`).
|
| + */
|
| + var SPACE_KEY = /^space(bar)?/;
|
| +
|
| + function transformKey(key) {
|
| + var validKey = '';
|
| + if (key) {
|
| + var lKey = key.toLowerCase();
|
| + if (lKey.length == 1) {
|
| + if (KEY_CHAR.test(lKey)) {
|
| + validKey = lKey;
|
| + }
|
| + } else if (ARROW_KEY.test(lKey)) {
|
| + validKey = lKey.replace('arrow', '');
|
| + } else if (SPACE_KEY.test(lKey)) {
|
| + validKey = 'space';
|
| + } else if (lKey == 'multiply') {
|
| + // numpad '*' can map to Multiply on IE/Windows
|
| + validKey = '*';
|
| + } else {
|
| + validKey = lKey;
|
| + }
|
| + }
|
| + return validKey;
|
| + }
|
| +
|
| + function transformKeyIdentifier(keyIdent) {
|
| + var validKey = '';
|
| + if (keyIdent) {
|
| + if (IDENT_CHAR.test(keyIdent)) {
|
| + validKey = KEY_IDENTIFIER[keyIdent];
|
| + } else {
|
| + validKey = keyIdent.toLowerCase();
|
| + }
|
| + }
|
| + return validKey;
|
| + }
|
| +
|
| + function transformKeyCode(keyCode) {
|
| + var validKey = '';
|
| + if (Number(keyCode)) {
|
| + if (keyCode >= 65 && keyCode <= 90) {
|
| + // ascii a-z
|
| + // lowercase is 32 offset from uppercase
|
| + validKey = String.fromCharCode(32 + keyCode);
|
| + } else if (keyCode >= 112 && keyCode <= 123) {
|
| + // function keys f1-f12
|
| + validKey = 'f' + (keyCode - 112);
|
| + } else if (keyCode >= 48 && keyCode <= 57) {
|
| + // top 0-9 keys
|
| + validKey = String(48 - keyCode);
|
| + } else if (keyCode >= 96 && keyCode <= 105) {
|
| + // num pad 0-9
|
| + validKey = String(96 - keyCode);
|
| + } else {
|
| + validKey = KEY_CODE[keyCode];
|
| + }
|
| + }
|
| + return validKey;
|
| + }
|
| +
|
| + function normalizedKeyForEvent(keyEvent) {
|
| + // fall back from .key, to .keyIdentifier, to .keyCode, and then to
|
| + // .detail.key to support artificial keyboard events
|
| + return transformKey(keyEvent.key) ||
|
| + transformKeyIdentifier(keyEvent.keyIdentifier) ||
|
| + transformKeyCode(keyEvent.keyCode) ||
|
| + transformKey(keyEvent.detail.key) || '';
|
| + }
|
| +
|
| + function keyComboMatchesEvent(keyCombo, keyEvent) {
|
| + return normalizedKeyForEvent(keyEvent) === keyCombo.key &&
|
| + !!keyEvent.shiftKey === !!keyCombo.shiftKey &&
|
| + !!keyEvent.ctrlKey === !!keyCombo.ctrlKey &&
|
| + !!keyEvent.altKey === !!keyCombo.altKey &&
|
| + !!keyEvent.metaKey === !!keyCombo.metaKey;
|
| + }
|
| +
|
| + function parseKeyComboString(keyComboString) {
|
| + return keyComboString.split('+').reduce(function(parsedKeyCombo, keyComboPart) {
|
| + var eventParts = keyComboPart.split(':');
|
| + var keyName = eventParts[0];
|
| + var event = eventParts[1];
|
| +
|
| + if (keyName in MODIFIER_KEYS) {
|
| + parsedKeyCombo[MODIFIER_KEYS[keyName]] = true;
|
| + } else {
|
| + parsedKeyCombo.key = keyName;
|
| + parsedKeyCombo.event = event || 'keydown';
|
| + }
|
| +
|
| + return parsedKeyCombo;
|
| + }, {
|
| + combo: keyComboString.split(':').shift()
|
| + });
|
| + }
|
| +
|
| + function parseEventString(eventString) {
|
| + return eventString.split(' ').map(function(keyComboString) {
|
| + return parseKeyComboString(keyComboString);
|
| + });
|
| + }
|
| +
|
| +
|
| + /**
|
| + * `Polymer.IronA11yKeysBehavior` provides a normalized interface for processing
|
| + * keyboard commands that pertain to [WAI-ARIA best practices](http://www.w3.org/TR/wai-aria-practices/#kbd_general_binding).
|
| + * The element takes care of browser differences with respect to Keyboard events
|
| + * and uses an expressive syntax to filter key presses.
|
| + *
|
| + * Use the `keyBindings` prototype property to express what combination of keys
|
| + * will trigger the event to fire.
|
| + *
|
| + * Use the `key-event-target` attribute to set up event handlers on a specific
|
| + * node.
|
| + * The `keys-pressed` event will fire when one of the key combinations set with the
|
| + * `keys` property is pressed.
|
| + *
|
| + * @demo demo/index.html
|
| + * @polymerBehavior
|
| + */
|
| + Polymer.IronA11yKeysBehavior = {
|
| + properties: {
|
| + /**
|
| + * The HTMLElement that will be firing relevant KeyboardEvents.
|
| + */
|
| + keyEventTarget: {
|
| + type: Object,
|
| + value: function() {
|
| + return this;
|
| + }
|
| + },
|
| +
|
| + _boundKeyHandlers: {
|
| + type: Array,
|
| + value: function() {
|
| + return [];
|
| + }
|
| + },
|
| +
|
| + // We use this due to a limitation in IE10 where instances will have
|
| + // own properties of everything on the "prototype".
|
| + _imperativeKeyBindings: {
|
| + type: Object,
|
| + value: function() {
|
| + return {};
|
| + }
|
| + }
|
| + },
|
| +
|
| + observers: [
|
| + '_resetKeyEventListeners(keyEventTarget, _boundKeyHandlers)'
|
| + ],
|
| +
|
| + keyBindings: {},
|
| +
|
| + registered: function() {
|
| + this._prepKeyBindings();
|
| + },
|
| +
|
| + attached: function() {
|
| + this._listenKeyEventListeners();
|
| + },
|
| +
|
| + detached: function() {
|
| + this._unlistenKeyEventListeners();
|
| + },
|
| +
|
| + /**
|
| + * Can be used to imperatively add a key binding to the implementing
|
| + * element. This is the imperative equivalent of declaring a keybinding
|
| + * in the `keyBindings` prototype property.
|
| + */
|
| + addOwnKeyBinding: function(eventString, handlerName) {
|
| + this._imperativeKeyBindings[eventString] = handlerName;
|
| + this._prepKeyBindings();
|
| + this._resetKeyEventListeners();
|
| + },
|
| +
|
| + /**
|
| + * When called, will remove all imperatively-added key bindings.
|
| + */
|
| + removeOwnKeyBindings: function() {
|
| + this._imperativeKeyBindings = {};
|
| + this._prepKeyBindings();
|
| + this._resetKeyEventListeners();
|
| + },
|
| +
|
| + keyboardEventMatchesKeys: function(event, eventString) {
|
| + var keyCombos = parseEventString(eventString);
|
| + var index;
|
| +
|
| + for (index = 0; index < keyCombos.length; ++index) {
|
| + if (keyComboMatchesEvent(keyCombos[index], event)) {
|
| + return true;
|
| + }
|
| + }
|
| +
|
| + return false;
|
| + },
|
| +
|
| + _collectKeyBindings: function() {
|
| + var keyBindings = this.behaviors.map(function(behavior) {
|
| + return behavior.keyBindings;
|
| + });
|
| +
|
| + if (keyBindings.indexOf(this.keyBindings) === -1) {
|
| + keyBindings.push(this.keyBindings);
|
| + }
|
| +
|
| + return keyBindings;
|
| + },
|
| +
|
| + _prepKeyBindings: function() {
|
| + this._keyBindings = {};
|
| +
|
| + this._collectKeyBindings().forEach(function(keyBindings) {
|
| + for (var eventString in keyBindings) {
|
| + this._addKeyBinding(eventString, keyBindings[eventString]);
|
| + }
|
| + }, this);
|
| +
|
| + for (var eventString in this._imperativeKeyBindings) {
|
| + this._addKeyBinding(eventString, this._imperativeKeyBindings[eventString]);
|
| + }
|
| + },
|
| +
|
| + _addKeyBinding: function(eventString, handlerName) {
|
| + parseEventString(eventString).forEach(function(keyCombo) {
|
| + this._keyBindings[keyCombo.event] =
|
| + this._keyBindings[keyCombo.event] || [];
|
| +
|
| + this._keyBindings[keyCombo.event].push([
|
| + keyCombo,
|
| + handlerName
|
| + ]);
|
| + }, this);
|
| + },
|
| +
|
| + _resetKeyEventListeners: function() {
|
| + this._unlistenKeyEventListeners();
|
| +
|
| + if (this.isAttached) {
|
| + this._listenKeyEventListeners();
|
| + }
|
| + },
|
| +
|
| + _listenKeyEventListeners: function() {
|
| + Object.keys(this._keyBindings).forEach(function(eventName) {
|
| + var keyBindings = this._keyBindings[eventName];
|
| + var boundKeyHandler = this._onKeyBindingEvent.bind(this, keyBindings);
|
| +
|
| + this._boundKeyHandlers.push([this.keyEventTarget, eventName, boundKeyHandler]);
|
| +
|
| + this.keyEventTarget.addEventListener(eventName, boundKeyHandler);
|
| + }, this);
|
| + },
|
| +
|
| + _unlistenKeyEventListeners: function() {
|
| + var keyHandlerTuple;
|
| + var keyEventTarget;
|
| + var eventName;
|
| + var boundKeyHandler;
|
| +
|
| + while (this._boundKeyHandlers.length) {
|
| + // My kingdom for block-scope binding and destructuring assignment..
|
| + keyHandlerTuple = this._boundKeyHandlers.pop();
|
| + keyEventTarget = keyHandlerTuple[0];
|
| + eventName = keyHandlerTuple[1];
|
| + boundKeyHandler = keyHandlerTuple[2];
|
| +
|
| + keyEventTarget.removeEventListener(eventName, boundKeyHandler);
|
| + }
|
| + },
|
| +
|
| + _onKeyBindingEvent: function(keyBindings, event) {
|
| + keyBindings.forEach(function(keyBinding) {
|
| + var keyCombo = keyBinding[0];
|
| + var handlerName = keyBinding[1];
|
| +
|
| + if (!event.defaultPrevented && keyComboMatchesEvent(keyCombo, event)) {
|
| + this._triggerKeyHandler(keyCombo, handlerName, event);
|
| + }
|
| + }, this);
|
| + },
|
| +
|
| + _triggerKeyHandler: function(keyCombo, handlerName, keyboardEvent) {
|
| + var detail = Object.create(keyCombo);
|
| + detail.keyboardEvent = keyboardEvent;
|
| +
|
| + this[handlerName].call(this, new CustomEvent(keyCombo.event, {
|
| + detail: detail
|
| + }));
|
| + }
|
| + };
|
| + })();
|
| +(function() {
|
| + var Utility = {
|
| + distance: function(x1, y1, x2, y2) {
|
| + var xDelta = (x1 - x2);
|
| + var yDelta = (y1 - y2);
|
| +
|
| + return Math.sqrt(xDelta * xDelta + yDelta * yDelta);
|
| + },
|
| +
|
| + now: window.performance && window.performance.now ?
|
| + window.performance.now.bind(window.performance) : Date.now
|
| + };
|
| +
|
| + /**
|
| + * @param {HTMLElement} element
|
| + * @constructor
|
| + */
|
| + function ElementMetrics(element) {
|
| + this.element = element;
|
| + this.width = this.boundingRect.width;
|
| + this.height = this.boundingRect.height;
|
| +
|
| + this.size = Math.max(this.width, this.height);
|
| + }
|
| +
|
| + ElementMetrics.prototype = {
|
| + get boundingRect () {
|
| + return this.element.getBoundingClientRect();
|
| + },
|
| +
|
| + furthestCornerDistanceFrom: function(x, y) {
|
| + var topLeft = Utility.distance(x, y, 0, 0);
|
| + var topRight = Utility.distance(x, y, this.width, 0);
|
| + var bottomLeft = Utility.distance(x, y, 0, this.height);
|
| + var bottomRight = Utility.distance(x, y, this.width, this.height);
|
| +
|
| + return Math.max(topLeft, topRight, bottomLeft, bottomRight);
|
| + }
|
| + };
|
| +
|
| + /**
|
| + * @param {HTMLElement} element
|
| + * @constructor
|
| + */
|
| + function Ripple(element) {
|
| + this.element = element;
|
| + this.color = window.getComputedStyle(element).color;
|
| +
|
| + this.wave = document.createElement('div');
|
| + this.waveContainer = document.createElement('div');
|
| + this.wave.style.backgroundColor = this.color;
|
| + this.wave.classList.add('wave');
|
| + this.waveContainer.classList.add('wave-container');
|
| + Polymer.dom(this.waveContainer).appendChild(this.wave);
|
| +
|
| + this.resetInteractionState();
|
| + }
|
| +
|
| + Ripple.MAX_RADIUS = 300;
|
| +
|
| + Ripple.prototype = {
|
| + get recenters() {
|
| + return this.element.recenters;
|
| + },
|
| +
|
| + get center() {
|
| + return this.element.center;
|
| + },
|
| +
|
| + get mouseDownElapsed() {
|
| + var elapsed;
|
| +
|
| + if (!this.mouseDownStart) {
|
| + return 0;
|
| + }
|
| +
|
| + elapsed = Utility.now() - this.mouseDownStart;
|
| +
|
| + if (this.mouseUpStart) {
|
| + elapsed -= this.mouseUpElapsed;
|
| + }
|
| +
|
| + return elapsed;
|
| + },
|
| +
|
| + get mouseUpElapsed() {
|
| + return this.mouseUpStart ?
|
| + Utility.now () - this.mouseUpStart : 0;
|
| + },
|
| +
|
| + get mouseDownElapsedSeconds() {
|
| + return this.mouseDownElapsed / 1000;
|
| + },
|
| +
|
| + get mouseUpElapsedSeconds() {
|
| + return this.mouseUpElapsed / 1000;
|
| + },
|
| +
|
| + get mouseInteractionSeconds() {
|
| + return this.mouseDownElapsedSeconds + this.mouseUpElapsedSeconds;
|
| + },
|
| +
|
| + get initialOpacity() {
|
| + return this.element.initialOpacity;
|
| + },
|
| +
|
| + get opacityDecayVelocity() {
|
| + return this.element.opacityDecayVelocity;
|
| + },
|
| +
|
| + get radius() {
|
| + var width2 = this.containerMetrics.width * this.containerMetrics.width;
|
| + var height2 = this.containerMetrics.height * this.containerMetrics.height;
|
| + var waveRadius = Math.min(
|
| + Math.sqrt(width2 + height2),
|
| + Ripple.MAX_RADIUS
|
| + ) * 1.1 + 5;
|
| +
|
| + var duration = 1.1 - 0.2 * (waveRadius / Ripple.MAX_RADIUS);
|
| + var timeNow = this.mouseInteractionSeconds / duration;
|
| + var size = waveRadius * (1 - Math.pow(80, -timeNow));
|
| +
|
| + return Math.abs(size);
|
| + },
|
| +
|
| + get opacity() {
|
| + if (!this.mouseUpStart) {
|
| + return this.initialOpacity;
|
| + }
|
| +
|
| + return Math.max(
|
| + 0,
|
| + this.initialOpacity - this.mouseUpElapsedSeconds * this.opacityDecayVelocity
|
| + );
|
| + },
|
| +
|
| + get outerOpacity() {
|
| + // Linear increase in background opacity, capped at the opacity
|
| + // of the wavefront (waveOpacity).
|
| + var outerOpacity = this.mouseUpElapsedSeconds * 0.3;
|
| + var waveOpacity = this.opacity;
|
| +
|
| + return Math.max(
|
| + 0,
|
| + Math.min(outerOpacity, waveOpacity)
|
| + );
|
| + },
|
| +
|
| + get isOpacityFullyDecayed() {
|
| + return this.opacity < 0.01 &&
|
| + this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS);
|
| + },
|
| +
|
| + get isRestingAtMaxRadius() {
|
| + return this.opacity >= this.initialOpacity &&
|
| + this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS);
|
| + },
|
| +
|
| + get isAnimationComplete() {
|
| + return this.mouseUpStart ?
|
| + this.isOpacityFullyDecayed : this.isRestingAtMaxRadius;
|
| + },
|
| +
|
| + get translationFraction() {
|
| + return Math.min(
|
| + 1,
|
| + this.radius / this.containerMetrics.size * 2 / Math.sqrt(2)
|
| + );
|
| + },
|
| +
|
| + get xNow() {
|
| + if (this.xEnd) {
|
| + return this.xStart + this.translationFraction * (this.xEnd - this.xStart);
|
| + }
|
| +
|
| + return this.xStart;
|
| + },
|
| +
|
| + get yNow() {
|
| + if (this.yEnd) {
|
| + return this.yStart + this.translationFraction * (this.yEnd - this.yStart);
|
| + }
|
| +
|
| + return this.yStart;
|
| + },
|
| +
|
| + get isMouseDown() {
|
| + return this.mouseDownStart && !this.mouseUpStart;
|
| + },
|
| +
|
| + resetInteractionState: function() {
|
| + this.maxRadius = 0;
|
| + this.mouseDownStart = 0;
|
| + this.mouseUpStart = 0;
|
| +
|
| + this.xStart = 0;
|
| + this.yStart = 0;
|
| + this.xEnd = 0;
|
| + this.yEnd = 0;
|
| + this.slideDistance = 0;
|
| +
|
| + this.containerMetrics = new ElementMetrics(this.element);
|
| + },
|
| +
|
| + draw: function() {
|
| + var scale;
|
| + var translateString;
|
| + var dx;
|
| + var dy;
|
| +
|
| + this.wave.style.opacity = this.opacity;
|
| +
|
| + scale = this.radius / (this.containerMetrics.size / 2);
|
| + dx = this.xNow - (this.containerMetrics.width / 2);
|
| + dy = this.yNow - (this.containerMetrics.height / 2);
|
| +
|
| +
|
| + // 2d transform for safari because of border-radius and overflow:hidden clipping bug.
|
| + // https://bugs.webkit.org/show_bug.cgi?id=98538
|
| + this.waveContainer.style.webkitTransform = 'translate(' + dx + 'px, ' + dy + 'px)';
|
| + this.waveContainer.style.transform = 'translate3d(' + dx + 'px, ' + dy + 'px, 0)';
|
| + this.wave.style.webkitTransform = 'scale(' + scale + ',' + scale + ')';
|
| + this.wave.style.transform = 'scale3d(' + scale + ',' + scale + ',1)';
|
| + },
|
| +
|
| + /** @param {Event=} event */
|
| + downAction: function(event) {
|
| + var xCenter = this.containerMetrics.width / 2;
|
| + var yCenter = this.containerMetrics.height / 2;
|
| +
|
| + this.resetInteractionState();
|
| + this.mouseDownStart = Utility.now();
|
| +
|
| + if (this.center) {
|
| + this.xStart = xCenter;
|
| + this.yStart = yCenter;
|
| + this.slideDistance = Utility.distance(
|
| + this.xStart, this.yStart, this.xEnd, this.yEnd
|
| + );
|
| + } else {
|
| + this.xStart = event ?
|
| + event.detail.x - this.containerMetrics.boundingRect.left :
|
| + this.containerMetrics.width / 2;
|
| + this.yStart = event ?
|
| + event.detail.y - this.containerMetrics.boundingRect.top :
|
| + this.containerMetrics.height / 2;
|
| + }
|
| +
|
| + if (this.recenters) {
|
| + this.xEnd = xCenter;
|
| + this.yEnd = yCenter;
|
| + this.slideDistance = Utility.distance(
|
| + this.xStart, this.yStart, this.xEnd, this.yEnd
|
| + );
|
| + }
|
| +
|
| + this.maxRadius = this.containerMetrics.furthestCornerDistanceFrom(
|
| + this.xStart,
|
| + this.yStart
|
| + );
|
| +
|
| + this.waveContainer.style.top =
|
| + (this.containerMetrics.height - this.containerMetrics.size) / 2 + 'px';
|
| + this.waveContainer.style.left =
|
| + (this.containerMetrics.width - this.containerMetrics.size) / 2 + 'px';
|
| +
|
| + this.waveContainer.style.width = this.containerMetrics.size + 'px';
|
| + this.waveContainer.style.height = this.containerMetrics.size + 'px';
|
| + },
|
| +
|
| + /** @param {Event=} event */
|
| + upAction: function(event) {
|
| + if (!this.isMouseDown) {
|
| + return;
|
| + }
|
| +
|
| + this.mouseUpStart = Utility.now();
|
| + },
|
| +
|
| + remove: function() {
|
| + Polymer.dom(this.waveContainer.parentNode).removeChild(
|
| + this.waveContainer
|
| + );
|
| + }
|
| + };
|
| +
|
| + Polymer({
|
| + is: 'paper-ripple',
|
| +
|
| + behaviors: [
|
| + Polymer.IronA11yKeysBehavior
|
| + ],
|
| +
|
| + properties: {
|
| + /**
|
| + * The initial opacity set on the wave.
|
| + *
|
| + * @attribute initialOpacity
|
| + * @type number
|
| + * @default 0.25
|
| + */
|
| + initialOpacity: {
|
| + type: Number,
|
| + value: 0.25
|
| + },
|
| +
|
| + /**
|
| + * How fast (opacity per second) the wave fades out.
|
| + *
|
| + * @attribute opacityDecayVelocity
|
| + * @type number
|
| + * @default 0.8
|
| + */
|
| + opacityDecayVelocity: {
|
| + type: Number,
|
| + value: 0.8
|
| + },
|
| +
|
| + /**
|
| + * If true, ripples will exhibit a gravitational pull towards
|
| + * the center of their container as they fade away.
|
| + *
|
| + * @attribute recenters
|
| + * @type boolean
|
| + * @default false
|
| + */
|
| + recenters: {
|
| + type: Boolean,
|
| + value: false
|
| + },
|
| +
|
| + /**
|
| + * If true, ripples will center inside its container
|
| + *
|
| + * @attribute recenters
|
| + * @type boolean
|
| + * @default false
|
| + */
|
| + center: {
|
| + type: Boolean,
|
| + value: false
|
| + },
|
| +
|
| + /**
|
| + * A list of the visual ripples.
|
| + *
|
| + * @attribute ripples
|
| + * @type Array
|
| + * @default []
|
| + */
|
| + ripples: {
|
| + type: Array,
|
| + value: function() {
|
| + return [];
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * True when there are visible ripples animating within the
|
| + * element.
|
| + */
|
| + animating: {
|
| + type: Boolean,
|
| + readOnly: true,
|
| + reflectToAttribute: true,
|
| + value: false
|
| + },
|
| +
|
| + /**
|
| + * If true, the ripple will remain in the "down" state until `holdDown`
|
| + * is set to false again.
|
| + */
|
| + holdDown: {
|
| + type: Boolean,
|
| + value: false,
|
| + observer: '_holdDownChanged'
|
| + },
|
| +
|
| + _animating: {
|
| + type: Boolean
|
| + },
|
| +
|
| + _boundAnimate: {
|
| + type: Function,
|
| + value: function() {
|
| + return this.animate.bind(this);
|
| + }
|
| + }
|
| + },
|
| +
|
| + get target () {
|
| + var ownerRoot = Polymer.dom(this).getOwnerRoot();
|
| + var target;
|
| +
|
| + if (this.parentNode.nodeType == 11) { // DOCUMENT_FRAGMENT_NODE
|
| + target = ownerRoot.host;
|
| + } else {
|
| + target = this.parentNode;
|
| + }
|
| +
|
| + return target;
|
| + },
|
| +
|
| + keyBindings: {
|
| + 'enter:keydown': '_onEnterKeydown',
|
| + 'space:keydown': '_onSpaceKeydown',
|
| + 'space:keyup': '_onSpaceKeyup'
|
| + },
|
| +
|
| + attached: function() {
|
| + this.listen(this.target, 'up', 'upAction');
|
| + this.listen(this.target, 'down', 'downAction');
|
| +
|
| + if (!this.target.hasAttribute('noink')) {
|
| + this.keyEventTarget = this.target;
|
| + }
|
| + },
|
| +
|
| + get shouldKeepAnimating () {
|
| + for (var index = 0; index < this.ripples.length; ++index) {
|
| + if (!this.ripples[index].isAnimationComplete) {
|
| + return true;
|
| + }
|
| + }
|
| +
|
| + return false;
|
| + },
|
| +
|
| + simulatedRipple: function() {
|
| + this.downAction(null);
|
| +
|
| + // Please see polymer/polymer#1305
|
| + this.async(function() {
|
| + this.upAction();
|
| + }, 1);
|
| + },
|
| +
|
| + /** @param {Event=} event */
|
| + downAction: function(event) {
|
| + if (this.holdDown && this.ripples.length > 0) {
|
| + return;
|
| + }
|
| +
|
| + var ripple = this.addRipple();
|
| +
|
| + ripple.downAction(event);
|
| +
|
| + if (!this._animating) {
|
| + this.animate();
|
| + }
|
| + },
|
| +
|
| + /** @param {Event=} event */
|
| + upAction: function(event) {
|
| + if (this.holdDown) {
|
| + return;
|
| + }
|
| +
|
| + this.ripples.forEach(function(ripple) {
|
| + ripple.upAction(event);
|
| + });
|
| +
|
| + this.animate();
|
| + },
|
| +
|
| + onAnimationComplete: function() {
|
| + this._animating = false;
|
| + this.$.background.style.backgroundColor = null;
|
| + this.fire('transitionend');
|
| + },
|
| +
|
| + addRipple: function() {
|
| + var ripple = new Ripple(this);
|
| +
|
| + Polymer.dom(this.$.waves).appendChild(ripple.waveContainer);
|
| + this.$.background.style.backgroundColor = ripple.color;
|
| + this.ripples.push(ripple);
|
| +
|
| + this._setAnimating(true);
|
| +
|
| + return ripple;
|
| + },
|
| +
|
| + removeRipple: function(ripple) {
|
| + var rippleIndex = this.ripples.indexOf(ripple);
|
| +
|
| + if (rippleIndex < 0) {
|
| + return;
|
| + }
|
| +
|
| + this.ripples.splice(rippleIndex, 1);
|
| +
|
| + ripple.remove();
|
| +
|
| + if (!this.ripples.length) {
|
| + this._setAnimating(false);
|
| + }
|
| + },
|
| +
|
| + animate: function() {
|
| + var index;
|
| + var ripple;
|
| +
|
| + this._animating = true;
|
| +
|
| + for (index = 0; index < this.ripples.length; ++index) {
|
| + ripple = this.ripples[index];
|
| +
|
| + ripple.draw();
|
| +
|
| + this.$.background.style.opacity = ripple.outerOpacity;
|
| +
|
| + if (ripple.isOpacityFullyDecayed && !ripple.isRestingAtMaxRadius) {
|
| + this.removeRipple(ripple);
|
| + }
|
| + }
|
| +
|
| + if (!this.shouldKeepAnimating && this.ripples.length === 0) {
|
| + this.onAnimationComplete();
|
| + } else {
|
| + window.requestAnimationFrame(this._boundAnimate);
|
| + }
|
| + },
|
| +
|
| + _onEnterKeydown: function() {
|
| + this.downAction();
|
| + this.async(this.upAction, 1);
|
| + },
|
| +
|
| + _onSpaceKeydown: function() {
|
| + this.downAction();
|
| + },
|
| +
|
| + _onSpaceKeyup: function() {
|
| + this.upAction();
|
| + },
|
| +
|
| + _holdDownChanged: function(holdDown) {
|
| + if (holdDown) {
|
| + this.downAction();
|
| + } else {
|
| + this.upAction();
|
| + }
|
| + }
|
| + });
|
| + })();
|
| +/**
|
| + * @demo demo/index.html
|
| + * @polymerBehavior
|
| + */
|
| + Polymer.IronControlState = {
|
| +
|
| + properties: {
|
| +
|
| + /**
|
| + * If true, the element currently has focus.
|
| + */
|
| + focused: {
|
| + type: Boolean,
|
| + value: false,
|
| + notify: true,
|
| + readOnly: true,
|
| + reflectToAttribute: true
|
| + },
|
| +
|
| + /**
|
| + * If true, the user cannot interact with this element.
|
| + */
|
| + disabled: {
|
| + type: Boolean,
|
| + value: false,
|
| + notify: true,
|
| + observer: '_disabledChanged',
|
| + reflectToAttribute: true
|
| + },
|
| +
|
| + _oldTabIndex: {
|
| + type: Number
|
| + },
|
| +
|
| + _boundFocusBlurHandler: {
|
| + type: Function,
|
| + value: function() {
|
| + return this._focusBlurHandler.bind(this);
|
| + }
|
| + }
|
| +
|
| + },
|
| +
|
| + observers: [
|
| + '_changedControlState(focused, disabled)'
|
| + ],
|
| +
|
| + ready: function() {
|
| + this.addEventListener('focus', this._boundFocusBlurHandler, true);
|
| + this.addEventListener('blur', this._boundFocusBlurHandler, true);
|
| + },
|
| +
|
| + _focusBlurHandler: function(event) {
|
| + // NOTE(cdata): if we are in ShadowDOM land, `event.target` will
|
| + // eventually become `this` due to retargeting; if we are not in
|
| + // ShadowDOM land, `event.target` will eventually become `this` due
|
| + // to the second conditional which fires a synthetic event (that is also
|
| + // handled). In either case, we can disregard `event.path`.
|
| +
|
| + if (event.target === this) {
|
| + var focused = event.type === 'focus';
|
| + this._setFocused(focused);
|
| + } else if (!this.shadowRoot) {
|
| + this.fire(event.type, {sourceEvent: event}, {
|
| + node: this,
|
| + bubbles: event.bubbles,
|
| + cancelable: event.cancelable
|
| + });
|
| + }
|
| + },
|
| +
|
| + _disabledChanged: function(disabled, old) {
|
| + this.setAttribute('aria-disabled', disabled ? 'true' : 'false');
|
| + this.style.pointerEvents = disabled ? 'none' : '';
|
| + if (disabled) {
|
| + this._oldTabIndex = this.tabIndex;
|
| + this.focused = false;
|
| + this.tabIndex = -1;
|
| + } else if (this._oldTabIndex !== undefined) {
|
| + this.tabIndex = this._oldTabIndex;
|
| + }
|
| + },
|
| +
|
| + _changedControlState: function() {
|
| + // _controlStateChanged is abstract, follow-on behaviors may implement it
|
| + if (this._controlStateChanged) {
|
| + this._controlStateChanged();
|
| + }
|
| + }
|
| +
|
| + };
|
| +/**
|
| + * @demo demo/index.html
|
| + * @polymerBehavior Polymer.IronButtonState
|
| + */
|
| + Polymer.IronButtonStateImpl = {
|
| +
|
| + properties: {
|
| +
|
| + /**
|
| + * If true, the user is currently holding down the button.
|
| + */
|
| + pressed: {
|
| + type: Boolean,
|
| + readOnly: true,
|
| + value: false,
|
| + reflectToAttribute: true,
|
| + observer: '_pressedChanged'
|
| + },
|
| +
|
| + /**
|
| + * If true, the button toggles the active state with each tap or press
|
| + * of the spacebar.
|
| + */
|
| + toggles: {
|
| + type: Boolean,
|
| + value: false,
|
| + reflectToAttribute: true
|
| + },
|
| +
|
| + /**
|
| + * If true, the button is a toggle and is currently in the active state.
|
| + */
|
| + active: {
|
| + type: Boolean,
|
| + value: false,
|
| + notify: true,
|
| + reflectToAttribute: true
|
| + },
|
| +
|
| + /**
|
| + * True if the element is currently being pressed by a "pointer," which
|
| + * is loosely defined as mouse or touch input (but specifically excluding
|
| + * keyboard input).
|
| + */
|
| + pointerDown: {
|
| + type: Boolean,
|
| + readOnly: true,
|
| + value: false
|
| + },
|
| +
|
| + /**
|
| + * True if the input device that caused the element to receive focus
|
| + * was a keyboard.
|
| + */
|
| + receivedFocusFromKeyboard: {
|
| + type: Boolean,
|
| + readOnly: true
|
| + },
|
| +
|
| + /**
|
| + * The aria attribute to be set if the button is a toggle and in the
|
| + * active state.
|
| + */
|
| + ariaActiveAttribute: {
|
| + type: String,
|
| + value: 'aria-pressed',
|
| + observer: '_ariaActiveAttributeChanged'
|
| + }
|
| + },
|
| +
|
| + listeners: {
|
| + down: '_downHandler',
|
| + up: '_upHandler',
|
| + tap: '_tapHandler'
|
| + },
|
| +
|
| + observers: [
|
| + '_detectKeyboardFocus(focused)',
|
| + '_activeChanged(active, ariaActiveAttribute)'
|
| + ],
|
| +
|
| + keyBindings: {
|
| + 'enter:keydown': '_asyncClick',
|
| + 'space:keydown': '_spaceKeyDownHandler',
|
| + 'space:keyup': '_spaceKeyUpHandler',
|
| + },
|
| +
|
| + _mouseEventRe: /^mouse/,
|
| +
|
| + _tapHandler: function() {
|
| + if (this.toggles) {
|
| + // a tap is needed to toggle the active state
|
| + this._userActivate(!this.active);
|
| + } else {
|
| + this.active = false;
|
| + }
|
| + },
|
| +
|
| + _detectKeyboardFocus: function(focused) {
|
| + this._setReceivedFocusFromKeyboard(!this.pointerDown && focused);
|
| + },
|
| +
|
| + // to emulate native checkbox, (de-)activations from a user interaction fire
|
| + // 'change' events
|
| + _userActivate: function(active) {
|
| + if (this.active !== active) {
|
| + this.active = active;
|
| + this.fire('change');
|
| + }
|
| + },
|
| +
|
| + _eventSourceIsPrimaryInput: function(event) {
|
| + event = event.detail.sourceEvent || event;
|
| +
|
| + // Always true for non-mouse events....
|
| + if (!this._mouseEventRe.test(event.type)) {
|
| + return true;
|
| + }
|
| +
|
| + // http://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
|
| + if ('buttons' in event) {
|
| + return event.buttons === 1;
|
| + }
|
| +
|
| + // http://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/which
|
| + if (typeof event.which === 'number') {
|
| + return event.which < 2;
|
| + }
|
| +
|
| + // http://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
|
| + return event.button < 1;
|
| + },
|
| +
|
| + _downHandler: function(event) {
|
| + if (!this._eventSourceIsPrimaryInput(event)) {
|
| + return;
|
| + }
|
| +
|
| + this._setPointerDown(true);
|
| + this._setPressed(true);
|
| + this._setReceivedFocusFromKeyboard(false);
|
| + },
|
| +
|
| + _upHandler: function() {
|
| + this._setPointerDown(false);
|
| + this._setPressed(false);
|
| + },
|
| +
|
| + _spaceKeyDownHandler: function(event) {
|
| + var keyboardEvent = event.detail.keyboardEvent;
|
| + keyboardEvent.preventDefault();
|
| + keyboardEvent.stopImmediatePropagation();
|
| + this._setPressed(true);
|
| + },
|
| +
|
| + _spaceKeyUpHandler: function() {
|
| + if (this.pressed) {
|
| + this._asyncClick();
|
| + }
|
| + this._setPressed(false);
|
| + },
|
| +
|
| + // trigger click asynchronously, the asynchrony is useful to allow one
|
| + // event handler to unwind before triggering another event
|
| + _asyncClick: function() {
|
| + this.async(function() {
|
| + this.click();
|
| + }, 1);
|
| + },
|
| +
|
| + // any of these changes are considered a change to button state
|
| +
|
| + _pressedChanged: function(pressed) {
|
| + this._changedButtonState();
|
| + },
|
| +
|
| + _ariaActiveAttributeChanged: function(value, oldValue) {
|
| + if (oldValue && oldValue != value && this.hasAttribute(oldValue)) {
|
| + this.removeAttribute(oldValue);
|
| + }
|
| + },
|
| +
|
| + _activeChanged: function(active, ariaActiveAttribute) {
|
| + if (this.toggles) {
|
| + this.setAttribute(this.ariaActiveAttribute,
|
| + active ? 'true' : 'false');
|
| + } else {
|
| + this.removeAttribute(this.ariaActiveAttribute);
|
| + }
|
| + this._changedButtonState();
|
| + },
|
| +
|
| + _controlStateChanged: function() {
|
| + if (this.disabled) {
|
| + this._setPressed(false);
|
| + } else {
|
| + this._changedButtonState();
|
| + }
|
| + },
|
| +
|
| + // provide hook for follow-on behaviors to react to button-state
|
| +
|
| + _changedButtonState: function() {
|
| + if (this._buttonStateChanged) {
|
| + this._buttonStateChanged(); // abstract
|
| + }
|
| + }
|
| +
|
| + };
|
| +
|
| + /** @polymerBehavior */
|
| + Polymer.IronButtonState = [
|
| + Polymer.IronA11yKeysBehavior,
|
| + Polymer.IronButtonStateImpl
|
| + ];
|
| +/** @polymerBehavior */
|
| + Polymer.PaperButtonBehaviorImpl = {
|
| +
|
| + properties: {
|
| +
|
| + _elevation: {
|
| + type: Number
|
| + }
|
| +
|
| + },
|
| +
|
| + observers: [
|
| + '_calculateElevation(focused, disabled, active, pressed, receivedFocusFromKeyboard)'
|
| + ],
|
| +
|
| + hostAttributes: {
|
| + role: 'button',
|
| + tabindex: '0'
|
| + },
|
| +
|
| + _calculateElevation: function() {
|
| + var e = 1;
|
| + if (this.disabled) {
|
| + e = 0;
|
| + } else if (this.active || this.pressed) {
|
| + e = 4;
|
| + } else if (this.receivedFocusFromKeyboard) {
|
| + e = 3;
|
| + }
|
| + this._elevation = e;
|
| + }
|
| + };
|
| +
|
| + /** @polymerBehavior */
|
| + Polymer.PaperButtonBehavior = [
|
| + Polymer.IronButtonState,
|
| + Polymer.IronControlState,
|
| + Polymer.PaperButtonBehaviorImpl
|
| + ];
|
| +Polymer({
|
| + is: 'paper-button',
|
| +
|
| + behaviors: [
|
| + Polymer.PaperButtonBehavior
|
| + ],
|
| +
|
| + properties: {
|
| + /**
|
| + * If true, the button should be styled with a shadow.
|
| + */
|
| + raised: {
|
| + type: Boolean,
|
| + reflectToAttribute: true,
|
| + value: false,
|
| + observer: '_calculateElevation'
|
| + }
|
| + },
|
| +
|
| + _calculateElevation: function() {
|
| + if (!this.raised) {
|
| + this._elevation = 0;
|
| + } else {
|
| + Polymer.PaperButtonBehaviorImpl._calculateElevation.apply(this);
|
| + }
|
| + },
|
| +
|
| + _computeContentClass: function(receivedFocusFromKeyboard) {
|
| + var className = 'content ';
|
| + if (receivedFocusFromKeyboard) {
|
| + className += ' keyboard-focus';
|
| + }
|
| + return className;
|
| + }
|
| + });
|
| +/**
|
| + * `iron-range-behavior` provides the behavior for something with a minimum to maximum range.
|
| + *
|
| + * @demo demo/index.html
|
| + * @polymerBehavior
|
| + */
|
| + Polymer.IronRangeBehavior = {
|
| +
|
| + properties: {
|
| +
|
| + /**
|
| + * The number that represents the current value.
|
| + */
|
| + value: {
|
| + type: Number,
|
| + value: 0,
|
| + notify: true,
|
| + reflectToAttribute: true
|
| + },
|
| +
|
| + /**
|
| + * The number that indicates the minimum value of the range.
|
| + */
|
| + min: {
|
| + type: Number,
|
| + value: 0,
|
| + notify: true
|
| + },
|
| +
|
| + /**
|
| + * The number that indicates the maximum value of the range.
|
| + */
|
| + max: {
|
| + type: Number,
|
| + value: 100,
|
| + notify: true
|
| + },
|
| +
|
| + /**
|
| + * Specifies the value granularity of the range's value.
|
| + */
|
| + step: {
|
| + type: Number,
|
| + value: 1,
|
| + notify: true
|
| + },
|
| +
|
| + /**
|
| + * Returns the ratio of the value.
|
| + */
|
| + ratio: {
|
| + type: Number,
|
| + value: 0,
|
| + readOnly: true,
|
| + notify: true
|
| + },
|
| + },
|
| +
|
| + observers: [
|
| + '_update(value, min, max, step)'
|
| + ],
|
| +
|
| + _calcRatio: function(value) {
|
| + return (this._clampValue(value) - this.min) / (this.max - this.min);
|
| + },
|
| +
|
| + _clampValue: function(value) {
|
| + return Math.min(this.max, Math.max(this.min, this._calcStep(value)));
|
| + },
|
| +
|
| + _calcStep: function(value) {
|
| + /**
|
| + * if we calculate the step using
|
| + * `Math.round(value / step) * step` we may hit a precision point issue
|
| + * eg. 0.1 * 0.2 = 0.020000000000000004
|
| + * http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
|
| + *
|
| + * as a work around we can divide by the reciprocal of `step`
|
| + */
|
| + // polymer/issues/2493
|
| + value = parseFloat(value);
|
| + return this.step ? (Math.round((value + this.min) / this.step) / (1 / this.step)) - this.min : value;
|
| + },
|
| +
|
| + _validateValue: function() {
|
| + var v = this._clampValue(this.value);
|
| + this.value = this.oldValue = isNaN(v) ? this.oldValue : v;
|
| + return this.value !== v;
|
| + },
|
| +
|
| + _update: function() {
|
| + this._validateValue();
|
| + this._setRatio(this._calcRatio(this.value) * 100);
|
| + }
|
| +
|
| +};
|
| +Polymer({
|
| +
|
| + is: 'paper-progress',
|
| +
|
| + behaviors: [
|
| + Polymer.IronRangeBehavior
|
| + ],
|
| +
|
| + properties: {
|
| +
|
| + /**
|
| + * The number that represents the current secondary progress.
|
| + */
|
| + secondaryProgress: {
|
| + type: Number,
|
| + value: 0
|
| + },
|
| +
|
| + /**
|
| + * The secondary ratio
|
| + */
|
| + secondaryRatio: {
|
| + type: Number,
|
| + value: 0,
|
| + readOnly: true
|
| + },
|
| +
|
| + /**
|
| + * Use an indeterminate progress indicator.
|
| + */
|
| + indeterminate: {
|
| + type: Boolean,
|
| + value: false,
|
| + observer: '_toggleIndeterminate'
|
| + },
|
| +
|
| + /**
|
| + * True if the progress is disabled.
|
| + */
|
| + disabled: {
|
| + type: Boolean,
|
| + value: false,
|
| + reflectToAttribute: true,
|
| + observer: '_disabledChanged'
|
| + }
|
| + },
|
| +
|
| + observers: [
|
| + '_progressChanged(secondaryProgress, value, min, max)'
|
| + ],
|
| +
|
| + hostAttributes: {
|
| + role: 'progressbar'
|
| + },
|
| +
|
| + _toggleIndeterminate: function(indeterminate) {
|
| + // If we use attribute/class binding, the animation sometimes doesn't translate properly
|
| + // on Safari 7.1. So instead, we toggle the class here in the update method.
|
| + this.toggleClass('indeterminate', indeterminate, this.$.primaryProgress);
|
| + },
|
| +
|
| + _transformProgress: function(progress, ratio) {
|
| + var transform = 'scaleX(' + (ratio / 100) + ')';
|
| + progress.style.transform = progress.style.webkitTransform = transform;
|
| + },
|
| +
|
| + _mainRatioChanged: function(ratio) {
|
| + this._transformProgress(this.$.primaryProgress, ratio);
|
| + },
|
| +
|
| + _progressChanged: function(secondaryProgress, value, min, max) {
|
| + secondaryProgress = this._clampValue(secondaryProgress);
|
| + value = this._clampValue(value);
|
| +
|
| + var secondaryRatio = this._calcRatio(secondaryProgress) * 100;
|
| + var mainRatio = this._calcRatio(value) * 100;
|
| +
|
| + this._setSecondaryRatio(secondaryRatio);
|
| + this._transformProgress(this.$.secondaryProgress, secondaryRatio);
|
| + this._transformProgress(this.$.primaryProgress, mainRatio);
|
| +
|
| + this.secondaryProgress = secondaryProgress;
|
| +
|
| + this.setAttribute('aria-valuenow', value);
|
| + this.setAttribute('aria-valuemin', min);
|
| + this.setAttribute('aria-valuemax', max);
|
| + },
|
| +
|
| + _disabledChanged: function(disabled) {
|
| + this.setAttribute('aria-disabled', disabled ? 'true' : 'false');
|
| + },
|
| +
|
| + _hideSecondaryProgress: function(secondaryRatio) {
|
| + return secondaryRatio === 0;
|
| + }
|
| +
|
| + });
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +cr.define('downloads', function() {
|
| + var Item = Polymer({
|
| + is: 'downloads-item',
|
| +
|
| + /**
|
| + * @param {!downloads.ThrottledIconLoader} iconLoader
|
| + * @param {!downloads.ActionService} actionService
|
| + */
|
| + factoryImpl: function(iconLoader, actionService) {
|
| + /** @private {!downloads.ThrottledIconLoader} */
|
| + this.iconLoader_ = iconLoader;
|
| +
|
| + /** @private {!downloads.ActionService} */
|
| + this.actionService_ = actionService;
|
| + },
|
| +
|
| + properties: {
|
| + hideDate: {
|
| + type: Boolean,
|
| + value: true,
|
| + },
|
| +
|
| + readyPromise: {
|
| + type: Object,
|
| + value: function() {
|
| + return new Promise(function(resolve, reject) {
|
| + this.resolveReadyPromise_ = resolve;
|
| + }.bind(this));
|
| + },
|
| + },
|
| +
|
| + completelyOnDisk_: {
|
| + computed: 'computeCompletelyOnDisk_(' +
|
| + 'data_.state, data_.file_externally_removed)',
|
| + type: Boolean,
|
| + value: true,
|
| + },
|
| +
|
| + controlledBy_: {
|
| + computed: 'computeControlledBy_(data_.by_ext_id, data_.by_ext_name)',
|
| + type: String,
|
| + value: '',
|
| + },
|
| +
|
| + i18n_: {
|
| + readOnly: true,
|
| + type: Object,
|
| + value: function() {
|
| + return {
|
| + cancel: loadTimeData.getString('controlCancel'),
|
| + discard: loadTimeData.getString('dangerDiscard'),
|
| + pause: loadTimeData.getString('controlPause'),
|
| + remove: loadTimeData.getString('controlRemoveFromList'),
|
| + resume: loadTimeData.getString('controlResume'),
|
| + restore: loadTimeData.getString('dangerRestore'),
|
| + retry: loadTimeData.getString('controlRetry'),
|
| + save: loadTimeData.getString('dangerSave'),
|
| + };
|
| + },
|
| + },
|
| +
|
| + isActive_: {
|
| + computed: 'computeIsActive_(' +
|
| + 'data_.state, data_.file_externally_removed)',
|
| + type: Boolean,
|
| + value: true,
|
| + },
|
| +
|
| + isDangerous_: {
|
| + computed: 'computeIsDangerous_(data_.state)',
|
| + type: Boolean,
|
| + value: false,
|
| + },
|
| +
|
| + isInProgress_: {
|
| + computed: 'computeIsInProgress_(data_.state)',
|
| + type: Boolean,
|
| + value: false,
|
| + },
|
| +
|
| + showCancel_: {
|
| + computed: 'computeShowCancel_(data_.state)',
|
| + type: Boolean,
|
| + value: false,
|
| + },
|
| +
|
| + showProgress_: {
|
| + computed: 'computeShowProgress_(showCancel_, data_.percent)',
|
| + type: Boolean,
|
| + value: false,
|
| + },
|
| +
|
| + isMalware_: {
|
| + computed: 'computeIsMalware_(isDangerous_, data_.danger_type)',
|
| + type: Boolean,
|
| + value: false,
|
| + },
|
| +
|
| + data_: {
|
| + type: Object,
|
| + },
|
| + },
|
| +
|
| + observers: [
|
| + // TODO(dbeam): this gets called way more when I observe data_.by_ext_id
|
| + // and data_.by_ext_name directly. Why?
|
| + 'observeControlledBy_(controlledBy_)',
|
| + ],
|
| +
|
| + ready: function() {
|
| + this.content = this.$.content;
|
| + this.resolveReadyPromise_();
|
| + },
|
| +
|
| + /** @param {!downloads.Data} data */
|
| + update: function(data) {
|
| + this.data_ = data;
|
| +
|
| + if (!this.isDangerous_) {
|
| + var icon = 'chrome://fileicon/' + encodeURIComponent(data.file_path);
|
| + this.iconLoader_.loadScaledIcon(this.$['file-icon'], icon);
|
| + }
|
| + },
|
| +
|
| + /** @private */
|
| + computeClass_: function() {
|
| + var classes = [];
|
| +
|
| + if (this.isActive_)
|
| + classes.push('is-active');
|
| +
|
| + if (this.isDangerous_)
|
| + classes.push('dangerous');
|
| +
|
| + if (this.showProgress_)
|
| + classes.push('show-progress');
|
| +
|
| + return classes.join(' ');
|
| + },
|
| +
|
| + /** @private */
|
| + computeCompletelyOnDisk_: function() {
|
| + return this.data_.state == downloads.States.COMPLETE &&
|
| + !this.data_.file_externally_removed;
|
| + },
|
| +
|
| + /** @private */
|
| + computeControlledBy_: function() {
|
| + if (!this.data_.by_ext_id || !this.data_.by_ext_name)
|
| + return '';
|
| +
|
| + var url = 'chrome://extensions#' + this.data_.by_ext_id;
|
| + var name = this.data_.by_ext_name;
|
| + return loadTimeData.getStringF('controlledByUrl', url, name);
|
| + },
|
| +
|
| + /** @private */
|
| + computeDate_: function() {
|
| + if (this.hideDate)
|
| + return '';
|
| + return assert(this.data_.since_string || this.data_.date_string);
|
| + },
|
| +
|
| + /** @private */
|
| + computeDescription_: function() {
|
| + var data = this.data_;
|
| +
|
| + switch (data.state) {
|
| + case downloads.States.DANGEROUS:
|
| + var fileName = data.file_name;
|
| + switch (data.danger_type) {
|
| + case downloads.DangerType.DANGEROUS_FILE:
|
| + return loadTimeData.getStringF('dangerFileDesc', fileName);
|
| + case downloads.DangerType.DANGEROUS_URL:
|
| + return loadTimeData.getString('dangerUrlDesc');
|
| + case downloads.DangerType.DANGEROUS_CONTENT: // Fall through.
|
| + case downloads.DangerType.DANGEROUS_HOST:
|
| + return loadTimeData.getStringF('dangerContentDesc', fileName);
|
| + case downloads.DangerType.UNCOMMON_CONTENT:
|
| + return loadTimeData.getStringF('dangerUncommonDesc', fileName);
|
| + case downloads.DangerType.POTENTIALLY_UNWANTED:
|
| + return loadTimeData.getStringF('dangerSettingsDesc', fileName);
|
| + }
|
| + break;
|
| +
|
| + case downloads.States.IN_PROGRESS:
|
| + case downloads.States.PAUSED: // Fallthrough.
|
| + return data.progress_status_text;
|
| + }
|
| +
|
| + return '';
|
| + },
|
| +
|
| + /** @private */
|
| + computeIsActive_: function() {
|
| + return this.data_.state != downloads.States.CANCELLED &&
|
| + this.data_.state != downloads.States.INTERRUPTED &&
|
| + !this.data_.file_externally_removed;
|
| + },
|
| +
|
| + /** @private */
|
| + computeIsDangerous_: function() {
|
| + return this.data_.state == downloads.States.DANGEROUS;
|
| + },
|
| +
|
| + /** @private */
|
| + computeIsInProgress_: function() {
|
| + return this.data_.state == downloads.States.IN_PROGRESS;
|
| + },
|
| +
|
| + /** @private */
|
| + computeIsMalware_: function() {
|
| + return this.isDangerous_ &&
|
| + (this.data_.danger_type == downloads.DangerType.DANGEROUS_CONTENT ||
|
| + this.data_.danger_type == downloads.DangerType.DANGEROUS_HOST ||
|
| + this.data_.danger_type == downloads.DangerType.DANGEROUS_URL ||
|
| + this.data_.danger_type == downloads.DangerType.POTENTIALLY_UNWANTED);
|
| + },
|
| +
|
| + /** @private */
|
| + computeRemoveStyle_: function() {
|
| + var canDelete = loadTimeData.getBoolean('allowDeletingHistory');
|
| + var hideRemove = this.isDangerous_ || this.showCancel_ || !canDelete;
|
| + return hideRemove ? 'visibility: hidden' : '';
|
| + },
|
| +
|
| + /** @private */
|
| + computeShowCancel_: function() {
|
| + return this.data_.state == downloads.States.IN_PROGRESS ||
|
| + this.data_.state == downloads.States.PAUSED;
|
| + },
|
| +
|
| + /** @private */
|
| + computeShowProgress_: function() {
|
| + return this.showCancel_ && this.data_.percent >= -1;
|
| + },
|
| +
|
| + /** @private */
|
| + computeTag_: function() {
|
| + switch (this.data_.state) {
|
| + case downloads.States.CANCELLED:
|
| + return loadTimeData.getString('statusCancelled');
|
| +
|
| + case downloads.States.INTERRUPTED:
|
| + return this.data_.last_reason_text;
|
| +
|
| + case downloads.States.COMPLETE:
|
| + return this.data_.file_externally_removed ?
|
| + loadTimeData.getString('statusRemoved') : '';
|
| + }
|
| +
|
| + return '';
|
| + },
|
| +
|
| + /** @private */
|
| + isIndeterminate_: function() {
|
| + return this.data_.percent == -1;
|
| + },
|
| +
|
| + /** @private */
|
| + observeControlledBy_: function() {
|
| + this.$['controlled-by'].innerHTML = this.controlledBy_;
|
| + },
|
| +
|
| + /** @private */
|
| + onCancelClick_: function() {
|
| + this.actionService_.cancel(this.data_.id);
|
| + },
|
| +
|
| + /** @private */
|
| + onDiscardDangerous_: function() {
|
| + this.actionService_.discardDangerous(this.data_.id);
|
| + },
|
| +
|
| + /**
|
| + * @private
|
| + * @param {Event} e
|
| + */
|
| + onDragStart_: function(e) {
|
| + e.preventDefault();
|
| + this.actionService_.drag(this.data_.id);
|
| + },
|
| +
|
| + /**
|
| + * @param {Event} e
|
| + * @private
|
| + */
|
| + onFileLinkClick_: function(e) {
|
| + e.preventDefault();
|
| + this.actionService_.openFile(this.data_.id);
|
| + },
|
| +
|
| + /** @private */
|
| + onPauseClick_: function() {
|
| + this.actionService_.pause(this.data_.id);
|
| + },
|
| +
|
| + /** @private */
|
| + onRemoveClick_: function() {
|
| + this.actionService_.remove(this.data_.id);
|
| + },
|
| +
|
| + /** @private */
|
| + onResumeClick_: function() {
|
| + this.actionService_.resume(this.data_.id);
|
| + },
|
| +
|
| + /** @private */
|
| + onRetryClick_: function() {
|
| + this.actionService_.download(this.$['file-link'].href);
|
| + },
|
| +
|
| + /** @private */
|
| + onSaveDangerous_: function() {
|
| + this.actionService_.saveDangerous(this.data_.id);
|
| + },
|
| +
|
| + /** @private */
|
| + onShowClick_: function() {
|
| + this.actionService_.show(this.data_.id);
|
| + },
|
| + });
|
| +
|
| + return {Item: Item};
|
| +});
|
| +(function() {
|
| +
|
| + // monostate data
|
| + var metaDatas = {};
|
| + var metaArrays = {};
|
| +
|
| + Polymer.IronMeta = Polymer({
|
| +
|
| + is: 'iron-meta',
|
| +
|
| + properties: {
|
| +
|
| + /**
|
| + * The type of meta-data. All meta-data of the same type is stored
|
| + * together.
|
| + */
|
| + type: {
|
| + type: String,
|
| + value: 'default',
|
| + observer: '_typeChanged'
|
| + },
|
| +
|
| + /**
|
| + * The key used to store `value` under the `type` namespace.
|
| + */
|
| + key: {
|
| + type: String,
|
| + observer: '_keyChanged'
|
| + },
|
| +
|
| + /**
|
| + * The meta-data to store or retrieve.
|
| + */
|
| + value: {
|
| + type: Object,
|
| + notify: true,
|
| + observer: '_valueChanged'
|
| + },
|
| +
|
| + /**
|
| + * If true, `value` is set to the iron-meta instance itself.
|
| + */
|
| + self: {
|
| + type: Boolean,
|
| + observer: '_selfChanged'
|
| + },
|
| +
|
| + /**
|
| + * Array of all meta-data values for the given type.
|
| + */
|
| + list: {
|
| + type: Array,
|
| + notify: true
|
| + }
|
| +
|
| + },
|
| +
|
| + /**
|
| + * Only runs if someone invokes the factory/constructor directly
|
| + * e.g. `new Polymer.IronMeta()`
|
| + */
|
| + factoryImpl: function(config) {
|
| + if (config) {
|
| + for (var n in config) {
|
| + switch(n) {
|
| + case 'type':
|
| + case 'key':
|
| + case 'value':
|
| + this[n] = config[n];
|
| + break;
|
| + }
|
| + }
|
| + }
|
| + },
|
| +
|
| + created: function() {
|
| + // TODO(sjmiles): good for debugging?
|
| + this._metaDatas = metaDatas;
|
| + this._metaArrays = metaArrays;
|
| + },
|
| +
|
| + _keyChanged: function(key, old) {
|
| + this._resetRegistration(old);
|
| + },
|
| +
|
| + _valueChanged: function(value) {
|
| + this._resetRegistration(this.key);
|
| + },
|
| +
|
| + _selfChanged: function(self) {
|
| + if (self) {
|
| + this.value = this;
|
| + }
|
| + },
|
| +
|
| + _typeChanged: function(type) {
|
| + this._unregisterKey(this.key);
|
| + if (!metaDatas[type]) {
|
| + metaDatas[type] = {};
|
| + }
|
| + this._metaData = metaDatas[type];
|
| + if (!metaArrays[type]) {
|
| + metaArrays[type] = [];
|
| + }
|
| + this.list = metaArrays[type];
|
| + this._registerKeyValue(this.key, this.value);
|
| + },
|
| +
|
| + /**
|
| + * Retrieves meta data value by key.
|
| + *
|
| + * @method byKey
|
| + * @param {string} key The key of the meta-data to be returned.
|
| + * @return {*}
|
| + */
|
| + byKey: function(key) {
|
| + return this._metaData && this._metaData[key];
|
| + },
|
| +
|
| + _resetRegistration: function(oldKey) {
|
| + this._unregisterKey(oldKey);
|
| + this._registerKeyValue(this.key, this.value);
|
| + },
|
| +
|
| + _unregisterKey: function(key) {
|
| + this._unregister(key, this._metaData, this.list);
|
| + },
|
| +
|
| + _registerKeyValue: function(key, value) {
|
| + this._register(key, value, this._metaData, this.list);
|
| + },
|
| +
|
| + _register: function(key, value, data, list) {
|
| + if (key && data && value !== undefined) {
|
| + data[key] = value;
|
| + list.push(value);
|
| + }
|
| + },
|
| +
|
| + _unregister: function(key, data, list) {
|
| + if (key && data) {
|
| + if (key in data) {
|
| + var value = data[key];
|
| + delete data[key];
|
| + this.arrayDelete(list, value);
|
| + }
|
| + }
|
| + }
|
| +
|
| + });
|
| +
|
| + /**
|
| + `iron-meta-query` can be used to access infomation stored in `iron-meta`.
|
| +
|
| + Examples:
|
| +
|
| + If I create an instance like this:
|
| +
|
| + <iron-meta key="info" value="foo/bar"></iron-meta>
|
| +
|
| + Note that value="foo/bar" is the metadata I've defined. I could define more
|
| + attributes or use child nodes to define additional metadata.
|
| +
|
| + Now I can access that element (and it's metadata) from any `iron-meta-query` instance:
|
| +
|
| + var value = new Polymer.IronMetaQuery({key: 'info'}).value;
|
| +
|
| + @group Polymer Iron Elements
|
| + @element iron-meta-query
|
| + */
|
| + Polymer.IronMetaQuery = Polymer({
|
| +
|
| + is: 'iron-meta-query',
|
| +
|
| + properties: {
|
| +
|
| + /**
|
| + * The type of meta-data. All meta-data of the same type is stored
|
| + * together.
|
| + */
|
| + type: {
|
| + type: String,
|
| + value: 'default',
|
| + observer: '_typeChanged'
|
| + },
|
| +
|
| + /**
|
| + * Specifies a key to use for retrieving `value` from the `type`
|
| + * namespace.
|
| + */
|
| + key: {
|
| + type: String,
|
| + observer: '_keyChanged'
|
| + },
|
| +
|
| + /**
|
| + * The meta-data to store or retrieve.
|
| + */
|
| + value: {
|
| + type: Object,
|
| + notify: true,
|
| + readOnly: true
|
| + },
|
| +
|
| + /**
|
| + * Array of all meta-data values for the given type.
|
| + */
|
| + list: {
|
| + type: Array,
|
| + notify: true
|
| + }
|
| +
|
| + },
|
| +
|
| + /**
|
| + * Actually a factory method, not a true constructor. Only runs if
|
| + * someone invokes it directly (via `new Polymer.IronMeta()`);
|
| + */
|
| + factoryImpl: function(config) {
|
| + if (config) {
|
| + for (var n in config) {
|
| + switch(n) {
|
| + case 'type':
|
| + case 'key':
|
| + this[n] = config[n];
|
| + break;
|
| + }
|
| + }
|
| + }
|
| + },
|
| +
|
| + created: function() {
|
| + // TODO(sjmiles): good for debugging?
|
| + this._metaDatas = metaDatas;
|
| + this._metaArrays = metaArrays;
|
| + },
|
| +
|
| + _keyChanged: function(key) {
|
| + this._setValue(this._metaData && this._metaData[key]);
|
| + },
|
| +
|
| + _typeChanged: function(type) {
|
| + this._metaData = metaDatas[type];
|
| + this.list = metaArrays[type];
|
| + if (this.key) {
|
| + this._keyChanged(this.key);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Retrieves meta data value by key.
|
| + * @param {string} key The key of the meta-data to be returned.
|
| + * @return {*}
|
| + */
|
| + byKey: function(key) {
|
| + return this._metaData && this._metaData[key];
|
| + }
|
| +
|
| + });
|
| +
|
| + })();
|
| +Polymer({
|
| +
|
| + is: 'iron-icon',
|
| +
|
| + properties: {
|
| +
|
| + /**
|
| + * The name of the icon to use. The name should be of the form:
|
| + * `iconset_name:icon_name`.
|
| + */
|
| + icon: {
|
| + type: String,
|
| + observer: '_iconChanged'
|
| + },
|
| +
|
| + /**
|
| + * The name of the theme to used, if one is specified by the
|
| + * iconset.
|
| + */
|
| + theme: {
|
| + type: String,
|
| + observer: '_updateIcon'
|
| + },
|
| +
|
| + /**
|
| + * If using iron-icon without an iconset, you can set the src to be
|
| + * the URL of an individual icon image file. Note that this will take
|
| + * precedence over a given icon attribute.
|
| + */
|
| + src: {
|
| + type: String,
|
| + observer: '_srcChanged'
|
| + },
|
| +
|
| + /**
|
| + * @type {!Polymer.IronMeta}
|
| + */
|
| + _meta: {
|
| + value: Polymer.Base.create('iron-meta', {type: 'iconset'})
|
| + }
|
| +
|
| + },
|
| +
|
| + _DEFAULT_ICONSET: 'icons',
|
| +
|
| + _iconChanged: function(icon) {
|
| + var parts = (icon || '').split(':');
|
| + this._iconName = parts.pop();
|
| + this._iconsetName = parts.pop() || this._DEFAULT_ICONSET;
|
| + this._updateIcon();
|
| + },
|
| +
|
| + _srcChanged: function(src) {
|
| + this._updateIcon();
|
| + },
|
| +
|
| + _usesIconset: function() {
|
| + return this.icon || !this.src;
|
| + },
|
| +
|
| + /** @suppress {visibility} */
|
| + _updateIcon: function() {
|
| + if (this._usesIconset()) {
|
| + if (this._iconsetName) {
|
| + this._iconset = /** @type {?Polymer.Iconset} */ (
|
| + this._meta.byKey(this._iconsetName));
|
| + if (this._iconset) {
|
| + this._iconset.applyIcon(this, this._iconName, this.theme);
|
| + this.unlisten(window, 'iron-iconset-added', '_updateIcon');
|
| + } else {
|
| + this.listen(window, 'iron-iconset-added', '_updateIcon');
|
| + }
|
| + }
|
| + } else {
|
| + if (!this._img) {
|
| + this._img = document.createElement('img');
|
| + this._img.style.width = '100%';
|
| + this._img.style.height = '100%';
|
| + this._img.draggable = false;
|
| + }
|
| + this._img.src = this.src;
|
| + Polymer.dom(this.root).appendChild(this._img);
|
| + }
|
| + }
|
| +
|
| + });
|
| +/**
|
| + * The `iron-iconset-svg` element allows users to define their own icon sets
|
| + * that contain svg icons. The svg icon elements should be children of the
|
| + * `iron-iconset-svg` element. Multiple icons should be given distinct id's.
|
| + *
|
| + * Using svg elements to create icons has a few advantages over traditional
|
| + * bitmap graphics like jpg or png. Icons that use svg are vector based so they
|
| + * are resolution independent and should look good on any device. They are
|
| + * stylable via css. Icons can be themed, colorized, and even animated.
|
| + *
|
| + * Example:
|
| + *
|
| + * <iron-iconset-svg name="my-svg-icons" size="24">
|
| + * <svg>
|
| + * <defs>
|
| + * <g id="shape">
|
| + * <rect x="50" y="50" width="50" height="50" />
|
| + * <circle cx="50" cy="50" r="50" />
|
| + * </g>
|
| + * </defs>
|
| + * </svg>
|
| + * </iron-iconset-svg>
|
| + *
|
| + * This will automatically register the icon set "my-svg-icons" to the iconset
|
| + * database. To use these icons from within another element, make a
|
| + * `iron-iconset` element and call the `byId` method
|
| + * to retrieve a given iconset. To apply a particular icon inside an
|
| + * element use the `applyIcon` method. For example:
|
| + *
|
| + * iconset.applyIcon(iconNode, 'car');
|
| + *
|
| + * @element iron-iconset-svg
|
| + * @demo demo/index.html
|
| + */
|
| + Polymer({
|
| +
|
| + is: 'iron-iconset-svg',
|
| +
|
| + properties: {
|
| +
|
| + /**
|
| + * The name of the iconset.
|
| + *
|
| + * @attribute name
|
| + * @type string
|
| + */
|
| + name: {
|
| + type: String,
|
| + observer: '_nameChanged'
|
| + },
|
| +
|
| + /**
|
| + * The size of an individual icon. Note that icons must be square.
|
| + *
|
| + * @attribute iconSize
|
| + * @type number
|
| + * @default 24
|
| + */
|
| + size: {
|
| + type: Number,
|
| + value: 24
|
| + }
|
| +
|
| + },
|
| +
|
| + /**
|
| + * Construct an array of all icon names in this iconset.
|
| + *
|
| + * @return {!Array} Array of icon names.
|
| + */
|
| + getIconNames: function() {
|
| + this._icons = this._createIconMap();
|
| + return Object.keys(this._icons).map(function(n) {
|
| + return this.name + ':' + n;
|
| + }, this);
|
| + },
|
| +
|
| + /**
|
| + * Applies an icon to the given element.
|
| + *
|
| + * An svg icon is prepended to the element's shadowRoot if it exists,
|
| + * otherwise to the element itself.
|
| + *
|
| + * @method applyIcon
|
| + * @param {Element} element Element to which the icon is applied.
|
| + * @param {string} iconName Name of the icon to apply.
|
| + * @return {Element} The svg element which renders the icon.
|
| + */
|
| + applyIcon: function(element, iconName) {
|
| + // insert svg element into shadow root, if it exists
|
| + element = element.root || element;
|
| + // Remove old svg element
|
| + this.removeIcon(element);
|
| + // install new svg element
|
| + var svg = this._cloneIcon(iconName);
|
| + if (svg) {
|
| + var pde = Polymer.dom(element);
|
| + pde.insertBefore(svg, pde.childNodes[0]);
|
| + return element._svgIcon = svg;
|
| + }
|
| + return null;
|
| + },
|
| +
|
| + /**
|
| + * Remove an icon from the given element by undoing the changes effected
|
| + * by `applyIcon`.
|
| + *
|
| + * @param {Element} element The element from which the icon is removed.
|
| + */
|
| + removeIcon: function(element) {
|
| + // Remove old svg element
|
| + if (element._svgIcon) {
|
| + Polymer.dom(element).removeChild(element._svgIcon);
|
| + element._svgIcon = null;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + *
|
| + * When name is changed, register iconset metadata
|
| + *
|
| + */
|
| + _nameChanged: function() {
|
| + new Polymer.IronMeta({type: 'iconset', key: this.name, value: this});
|
| + this.async(function() {
|
| + this.fire('iron-iconset-added', this, {node: window});
|
| + });
|
| + },
|
| +
|
| + /**
|
| + * Create a map of child SVG elements by id.
|
| + *
|
| + * @return {!Object} Map of id's to SVG elements.
|
| + */
|
| + _createIconMap: function() {
|
| + // Objects chained to Object.prototype (`{}`) have members. Specifically,
|
| + // on FF there is a `watch` method that confuses the icon map, so we
|
| + // need to use a null-based object here.
|
| + var icons = Object.create(null);
|
| + Polymer.dom(this).querySelectorAll('[id]')
|
| + .forEach(function(icon) {
|
| + icons[icon.id] = icon;
|
| + });
|
| + return icons;
|
| + },
|
| +
|
| + /**
|
| + * Produce installable clone of the SVG element matching `id` in this
|
| + * iconset, or `undefined` if there is no matching element.
|
| + *
|
| + * @return {Element} Returns an installable clone of the SVG element
|
| + * matching `id`.
|
| + */
|
| + _cloneIcon: function(id) {
|
| + // create the icon map on-demand, since the iconset itself has no discrete
|
| + // signal to know when it's children are fully parsed
|
| + this._icons = this._icons || this._createIconMap();
|
| + return this._prepareSvgClone(this._icons[id], this.size);
|
| + },
|
| +
|
| + /**
|
| + * @param {Element} sourceSvg
|
| + * @param {number} size
|
| + * @return {Element}
|
| + */
|
| + _prepareSvgClone: function(sourceSvg, size) {
|
| + if (sourceSvg) {
|
| + var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
| + svg.setAttribute('viewBox', ['0', '0', size, size].join(' '));
|
| + svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
|
| + // TODO(dfreedm): `pointer-events: none` works around https://crbug.com/370136
|
| + // TODO(sjmiles): inline style may not be ideal, but avoids requiring a shadow-root
|
| + svg.style.cssText = 'pointer-events: none; display: block; width: 100%; height: 100%;';
|
| + svg.appendChild(sourceSvg.cloneNode(true)).removeAttribute('id');
|
| + return svg;
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + });
|
| +Polymer({
|
| + is: 'paper-item',
|
| +
|
| + hostAttributes: {
|
| + role: 'listitem',
|
| + tabindex: '0'
|
| + },
|
| +
|
| + behaviors: [
|
| + Polymer.IronControlState,
|
| + Polymer.IronButtonState
|
| + ]
|
| + });
|
| +/**
|
| + * @param {!Function} selectCallback
|
| + * @constructor
|
| + */
|
| + Polymer.IronSelection = function(selectCallback) {
|
| + this.selection = [];
|
| + this.selectCallback = selectCallback;
|
| + };
|
| +
|
| + Polymer.IronSelection.prototype = {
|
| +
|
| + /**
|
| + * Retrieves the selected item(s).
|
| + *
|
| + * @method get
|
| + * @returns Returns the selected item(s). If the multi property is true,
|
| + * `get` will return an array, otherwise it will return
|
| + * the selected item or undefined if there is no selection.
|
| + */
|
| + get: function() {
|
| + return this.multi ? this.selection.slice() : this.selection[0];
|
| + },
|
| +
|
| + /**
|
| + * Clears all the selection except the ones indicated.
|
| + *
|
| + * @method clear
|
| + * @param {Array} excludes items to be excluded.
|
| + */
|
| + clear: function(excludes) {
|
| + this.selection.slice().forEach(function(item) {
|
| + if (!excludes || excludes.indexOf(item) < 0) {
|
| + this.setItemSelected(item, false);
|
| + }
|
| + }, this);
|
| + },
|
| +
|
| + /**
|
| + * Indicates if a given item is selected.
|
| + *
|
| + * @method isSelected
|
| + * @param {*} item The item whose selection state should be checked.
|
| + * @returns Returns true if `item` is selected.
|
| + */
|
| + isSelected: function(item) {
|
| + return this.selection.indexOf(item) >= 0;
|
| + },
|
| +
|
| + /**
|
| + * Sets the selection state for a given item to either selected or deselected.
|
| + *
|
| + * @method setItemSelected
|
| + * @param {*} item The item to select.
|
| + * @param {boolean} isSelected True for selected, false for deselected.
|
| + */
|
| + setItemSelected: function(item, isSelected) {
|
| + if (item != null) {
|
| + if (isSelected) {
|
| + this.selection.push(item);
|
| + } else {
|
| + var i = this.selection.indexOf(item);
|
| + if (i >= 0) {
|
| + this.selection.splice(i, 1);
|
| + }
|
| + }
|
| + if (this.selectCallback) {
|
| + this.selectCallback(item, isSelected);
|
| + }
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Sets the selection state for a given item. If the `multi` property
|
| + * is true, then the selected state of `item` will be toggled; otherwise
|
| + * the `item` will be selected.
|
| + *
|
| + * @method select
|
| + * @param {*} item The item to select.
|
| + */
|
| + select: function(item) {
|
| + if (this.multi) {
|
| + this.toggle(item);
|
| + } else if (this.get() !== item) {
|
| + this.setItemSelected(this.get(), false);
|
| + this.setItemSelected(item, true);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Toggles the selection state for `item`.
|
| + *
|
| + * @method toggle
|
| + * @param {*} item The item to toggle.
|
| + */
|
| + toggle: function(item) {
|
| + this.setItemSelected(item, !this.isSelected(item));
|
| + }
|
| +
|
| + };
|
| +/** @polymerBehavior */
|
| + Polymer.IronSelectableBehavior = {
|
| +
|
| + /**
|
| + * Fired when iron-selector is activated (selected or deselected).
|
| + * It is fired before the selected items are changed.
|
| + * Cancel the event to abort selection.
|
| + *
|
| + * @event iron-activate
|
| + */
|
| +
|
| + /**
|
| + * Fired when an item is selected
|
| + *
|
| + * @event iron-select
|
| + */
|
| +
|
| + /**
|
| + * Fired when an item is deselected
|
| + *
|
| + * @event iron-deselect
|
| + */
|
| +
|
| + /**
|
| + * Fired when the list of selectable items changes (e.g., items are
|
| + * added or removed). The detail of the event is a list of mutation
|
| + * records that describe what changed.
|
| + *
|
| + * @event iron-items-changed
|
| + */
|
| +
|
| + properties: {
|
| +
|
| + /**
|
| + * If you want to use the attribute value of an element for `selected` instead of the index,
|
| + * set this to the name of the attribute.
|
| + */
|
| + attrForSelected: {
|
| + type: String,
|
| + value: null
|
| + },
|
| +
|
| + /**
|
| + * Gets or sets the selected element. The default is to use the index of the item.
|
| + */
|
| + selected: {
|
| + type: String,
|
| + notify: true
|
| + },
|
| +
|
| + /**
|
| + * Returns the currently selected item.
|
| + */
|
| + selectedItem: {
|
| + type: Object,
|
| + readOnly: true,
|
| + notify: true
|
| + },
|
| +
|
| + /**
|
| + * The event that fires from items when they are selected. Selectable
|
| + * will listen for this event from items and update the selection state.
|
| + * Set to empty string to listen to no events.
|
| + */
|
| + activateEvent: {
|
| + type: String,
|
| + value: 'tap',
|
| + observer: '_activateEventChanged'
|
| + },
|
| +
|
| + /**
|
| + * This is a CSS selector string. If this is set, only items that match the CSS selector
|
| + * are selectable.
|
| + */
|
| + selectable: String,
|
| +
|
| + /**
|
| + * The class to set on elements when selected.
|
| + */
|
| + selectedClass: {
|
| + type: String,
|
| + value: 'iron-selected'
|
| + },
|
| +
|
| + /**
|
| + * The attribute to set on elements when selected.
|
| + */
|
| + selectedAttribute: {
|
| + type: String,
|
| + value: null
|
| + },
|
| +
|
| + /**
|
| + * The set of excluded elements where the key is the `localName`
|
| + * of the element that will be ignored from the item list.
|
| + *
|
| + * @type {object}
|
| + * @default {template: 1}
|
| + */
|
| + excludedLocalNames: {
|
| + type: Object,
|
| + value: function() {
|
| + return {
|
| + 'template': 1
|
| + };
|
| + }
|
| + }
|
| + },
|
| +
|
| + observers: [
|
| + '_updateSelected(attrForSelected, selected)'
|
| + ],
|
| +
|
| + created: function() {
|
| + this._bindFilterItem = this._filterItem.bind(this);
|
| + this._selection = new Polymer.IronSelection(this._applySelection.bind(this));
|
| + },
|
| +
|
| + attached: function() {
|
| + this._observer = this._observeItems(this);
|
| + this._contentObserver = this._observeContent(this);
|
| + if (!this.selectedItem && this.selected) {
|
| + this._updateSelected(this.attrForSelected,this.selected)
|
| + }
|
| + },
|
| +
|
| + detached: function() {
|
| + if (this._observer) {
|
| + this._observer.disconnect();
|
| + }
|
| + if (this._contentObserver) {
|
| + this._contentObserver.disconnect();
|
| + }
|
| + this._removeListener(this.activateEvent);
|
| + },
|
| +
|
| + /**
|
| + * Returns an array of selectable items.
|
| + *
|
| + * @property items
|
| + * @type Array
|
| + */
|
| + get items() {
|
| + var nodes = Polymer.dom(this).queryDistributedElements(this.selectable || '*');
|
| + return Array.prototype.filter.call(nodes, this._bindFilterItem);
|
| + },
|
| +
|
| + /**
|
| + * Returns the index of the given item.
|
| + *
|
| + * @method indexOf
|
| + * @param {Object} item
|
| + * @returns Returns the index of the item
|
| + */
|
| + indexOf: function(item) {
|
| + return this.items.indexOf(item);
|
| + },
|
| +
|
| + /**
|
| + * Selects the given value.
|
| + *
|
| + * @method select
|
| + * @param {string} value the value to select.
|
| + */
|
| + select: function(value) {
|
| + this.selected = value;
|
| + },
|
| +
|
| + /**
|
| + * Selects the previous item.
|
| + *
|
| + * @method selectPrevious
|
| + */
|
| + selectPrevious: function() {
|
| + var length = this.items.length;
|
| + var index = (Number(this._valueToIndex(this.selected)) - 1 + length) % length;
|
| + this.selected = this._indexToValue(index);
|
| + },
|
| +
|
| + /**
|
| + * Selects the next item.
|
| + *
|
| + * @method selectNext
|
| + */
|
| + selectNext: function() {
|
| + var index = (Number(this._valueToIndex(this.selected)) + 1) % this.items.length;
|
| + this.selected = this._indexToValue(index);
|
| + },
|
| +
|
| + _addListener: function(eventName) {
|
| + this.listen(this, eventName, '_activateHandler');
|
| + },
|
| +
|
| + _removeListener: function(eventName) {
|
| + this.unlisten(this, eventName, '_activateHandler');
|
| + },
|
| +
|
| + _activateEventChanged: function(eventName, old) {
|
| + this._removeListener(old);
|
| + this._addListener(eventName);
|
| + },
|
| +
|
| + _updateSelected: function() {
|
| + this._selectSelected(this.selected);
|
| + },
|
| +
|
| + _selectSelected: function(selected) {
|
| + this._selection.select(this._valueToItem(this.selected));
|
| + },
|
| +
|
| + _filterItem: function(node) {
|
| + return !this.excludedLocalNames[node.localName];
|
| + },
|
| +
|
| + _valueToItem: function(value) {
|
| + return (value == null) ? null : this.items[this._valueToIndex(value)];
|
| + },
|
| +
|
| + _valueToIndex: function(value) {
|
| + if (this.attrForSelected) {
|
| + for (var i = 0, item; item = this.items[i]; i++) {
|
| + if (this._valueForItem(item) == value) {
|
| + return i;
|
| + }
|
| + }
|
| + } else {
|
| + return Number(value);
|
| + }
|
| + },
|
| +
|
| + _indexToValue: function(index) {
|
| + if (this.attrForSelected) {
|
| + var item = this.items[index];
|
| + if (item) {
|
| + return this._valueForItem(item);
|
| + }
|
| + } else {
|
| + return index;
|
| + }
|
| + },
|
| +
|
| + _valueForItem: function(item) {
|
| + return item[this.attrForSelected] || item.getAttribute(this.attrForSelected);
|
| + },
|
| +
|
| + _applySelection: function(item, isSelected) {
|
| + if (this.selectedClass) {
|
| + this.toggleClass(this.selectedClass, isSelected, item);
|
| + }
|
| + if (this.selectedAttribute) {
|
| + this.toggleAttribute(this.selectedAttribute, isSelected, item);
|
| + }
|
| + this._selectionChange();
|
| + this.fire('iron-' + (isSelected ? 'select' : 'deselect'), {item: item});
|
| + },
|
| +
|
| + _selectionChange: function() {
|
| + this._setSelectedItem(this._selection.get());
|
| + },
|
| +
|
| + // observe content changes under the given node.
|
| + _observeContent: function(node) {
|
| + var content = node.querySelector('content');
|
| + if (content && content.parentElement === node) {
|
| + return this._observeItems(node.domHost);
|
| + }
|
| + },
|
| +
|
| + // observe items change under the given node.
|
| + _observeItems: function(node) {
|
| + // TODO(cdata): Update this when we get distributed children changed.
|
| + var observer = new MutationObserver(function(mutations) {
|
| + // Let other interested parties know about the change so that
|
| + // we don't have to recreate mutation observers everywher.
|
| + this.fire('iron-items-changed', mutations, {
|
| + bubbles: false,
|
| + cancelable: false
|
| + });
|
| +
|
| + if (this.selected != null) {
|
| + this._updateSelected();
|
| + }
|
| + }.bind(this));
|
| + observer.observe(node, {
|
| + childList: true,
|
| + subtree: true
|
| + });
|
| + return observer;
|
| + },
|
| +
|
| + _activateHandler: function(e) {
|
| + var t = e.target;
|
| + var items = this.items;
|
| + while (t && t != this) {
|
| + var i = items.indexOf(t);
|
| + if (i >= 0) {
|
| + var value = this._indexToValue(i);
|
| + this._itemActivate(value, t);
|
| + return;
|
| + }
|
| + t = t.parentNode;
|
| + }
|
| + },
|
| +
|
| + _itemActivate: function(value, item) {
|
| + if (!this.fire('iron-activate',
|
| + {selected: value, item: item}, {cancelable: true}).defaultPrevented) {
|
| + this.select(value);
|
| + }
|
| + }
|
| +
|
| + };
|
| +/** @polymerBehavior Polymer.IronMultiSelectableBehavior */
|
| + Polymer.IronMultiSelectableBehaviorImpl = {
|
| + properties: {
|
| +
|
| + /**
|
| + * If true, multiple selections are allowed.
|
| + */
|
| + multi: {
|
| + type: Boolean,
|
| + value: false,
|
| + observer: 'multiChanged'
|
| + },
|
| +
|
| + /**
|
| + * Gets or sets the selected elements. This is used instead of `selected` when `multi`
|
| + * is true.
|
| + */
|
| + selectedValues: {
|
| + type: Array,
|
| + notify: true
|
| + },
|
| +
|
| + /**
|
| + * Returns an array of currently selected items.
|
| + */
|
| + selectedItems: {
|
| + type: Array,
|
| + readOnly: true,
|
| + notify: true
|
| + },
|
| +
|
| + },
|
| +
|
| + observers: [
|
| + '_updateSelected(attrForSelected, selectedValues)'
|
| + ],
|
| +
|
| + /**
|
| + * Selects the given value. If the `multi` property is true, then the selected state of the
|
| + * `value` will be toggled; otherwise the `value` will be selected.
|
| + *
|
| + * @method select
|
| + * @param {string} value the value to select.
|
| + */
|
| + select: function(value) {
|
| + if (this.multi) {
|
| + if (this.selectedValues) {
|
| + this._toggleSelected(value);
|
| + } else {
|
| + this.selectedValues = [value];
|
| + }
|
| + } else {
|
| + this.selected = value;
|
| + }
|
| + },
|
| +
|
| + multiChanged: function(multi) {
|
| + this._selection.multi = multi;
|
| + },
|
| +
|
| + _updateSelected: function() {
|
| + if (this.multi) {
|
| + this._selectMulti(this.selectedValues);
|
| + } else {
|
| + this._selectSelected(this.selected);
|
| + }
|
| + },
|
| +
|
| + _selectMulti: function(values) {
|
| + this._selection.clear();
|
| + if (values) {
|
| + for (var i = 0; i < values.length; i++) {
|
| + this._selection.setItemSelected(this._valueToItem(values[i]), true);
|
| + }
|
| + }
|
| + },
|
| +
|
| + _selectionChange: function() {
|
| + var s = this._selection.get();
|
| + if (this.multi) {
|
| + this._setSelectedItems(s);
|
| + } else {
|
| + this._setSelectedItems([s]);
|
| + this._setSelectedItem(s);
|
| + }
|
| + },
|
| +
|
| + _toggleSelected: function(value) {
|
| + var i = this.selectedValues.indexOf(value);
|
| + var unselected = i < 0;
|
| + if (unselected) {
|
| + this.push('selectedValues',value);
|
| + } else {
|
| + this.splice('selectedValues',i,1);
|
| + }
|
| + this._selection.setItemSelected(this._valueToItem(value), unselected);
|
| + }
|
| + };
|
| +
|
| + /** @polymerBehavior */
|
| + Polymer.IronMultiSelectableBehavior = [
|
| + Polymer.IronSelectableBehavior,
|
| + Polymer.IronMultiSelectableBehaviorImpl
|
| + ];
|
| +/**
|
| + * `Polymer.IronMenuBehavior` implements accessible menu behavior.
|
| + *
|
| + * @demo demo/index.html
|
| + * @polymerBehavior Polymer.IronMenuBehavior
|
| + */
|
| + Polymer.IronMenuBehaviorImpl = {
|
| +
|
| + properties: {
|
| +
|
| + /**
|
| + * Returns the currently focused item.
|
| + * @type {?Object}
|
| + */
|
| + focusedItem: {
|
| + observer: '_focusedItemChanged',
|
| + readOnly: true,
|
| + type: Object
|
| + },
|
| +
|
| + /**
|
| + * The attribute to use on menu items to look up the item title. Typing the first
|
| + * letter of an item when the menu is open focuses that item. If unset, `textContent`
|
| + * will be used.
|
| + */
|
| + attrForItemTitle: {
|
| + type: String
|
| + }
|
| + },
|
| +
|
| + hostAttributes: {
|
| + 'role': 'menu',
|
| + 'tabindex': '0'
|
| + },
|
| +
|
| + observers: [
|
| + '_updateMultiselectable(multi)'
|
| + ],
|
| +
|
| + listeners: {
|
| + 'focus': '_onFocus',
|
| + 'keydown': '_onKeydown',
|
| + 'iron-items-changed': '_onIronItemsChanged'
|
| + },
|
| +
|
| + keyBindings: {
|
| + 'up': '_onUpKey',
|
| + 'down': '_onDownKey',
|
| + 'esc': '_onEscKey',
|
| + 'shift+tab:keydown': '_onShiftTabDown'
|
| + },
|
| +
|
| + attached: function() {
|
| + this._resetTabindices();
|
| + },
|
| +
|
| + /**
|
| + * Selects the given value. If the `multi` property is true, then the selected state of the
|
| + * `value` will be toggled; otherwise the `value` will be selected.
|
| + *
|
| + * @param {string} value the value to select.
|
| + */
|
| + select: function(value) {
|
| + if (this._defaultFocusAsync) {
|
| + this.cancelAsync(this._defaultFocusAsync);
|
| + this._defaultFocusAsync = null;
|
| + }
|
| + var item = this._valueToItem(value);
|
| + if (item && item.hasAttribute('disabled')) return;
|
| + this._setFocusedItem(item);
|
| + Polymer.IronMultiSelectableBehaviorImpl.select.apply(this, arguments);
|
| + },
|
| +
|
| + /**
|
| + * Resets all tabindex attributes to the appropriate value based on the
|
| + * current selection state. The appropriate value is `0` (focusable) for
|
| + * the default selected item, and `-1` (not keyboard focusable) for all
|
| + * other items.
|
| + */
|
| + _resetTabindices: function() {
|
| + var selectedItem = this.multi ? (this.selectedItems && this.selectedItems[0]) : this.selectedItem;
|
| +
|
| + this.items.forEach(function(item) {
|
| + item.setAttribute('tabindex', item === selectedItem ? '0' : '-1');
|
| + }, this);
|
| + },
|
| +
|
| + /**
|
| + * Sets appropriate ARIA based on whether or not the menu is meant to be
|
| + * multi-selectable.
|
| + *
|
| + * @param {boolean} multi True if the menu should be multi-selectable.
|
| + */
|
| + _updateMultiselectable: function(multi) {
|
| + if (multi) {
|
| + this.setAttribute('aria-multiselectable', 'true');
|
| + } else {
|
| + this.removeAttribute('aria-multiselectable');
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Given a KeyboardEvent, this method will focus the appropriate item in the
|
| + * menu (if there is a relevant item, and it is possible to focus it).
|
| + *
|
| + * @param {KeyboardEvent} event A KeyboardEvent.
|
| + */
|
| + _focusWithKeyboardEvent: function(event) {
|
| + for (var i = 0, item; item = this.items[i]; i++) {
|
| + var attr = this.attrForItemTitle || 'textContent';
|
| + var title = item[attr] || item.getAttribute(attr);
|
| + if (title && title.trim().charAt(0).toLowerCase() === String.fromCharCode(event.keyCode).toLowerCase()) {
|
| + this._setFocusedItem(item);
|
| + break;
|
| + }
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Focuses the previous item (relative to the currently focused item) in the
|
| + * menu.
|
| + */
|
| + _focusPrevious: function() {
|
| + var length = this.items.length;
|
| + var index = (Number(this.indexOf(this.focusedItem)) - 1 + length) % length;
|
| + this._setFocusedItem(this.items[index]);
|
| + },
|
| +
|
| + /**
|
| + * Focuses the next item (relative to the currently focused item) in the
|
| + * menu.
|
| + */
|
| + _focusNext: function() {
|
| + var index = (Number(this.indexOf(this.focusedItem)) + 1) % this.items.length;
|
| + this._setFocusedItem(this.items[index]);
|
| + },
|
| +
|
| + /**
|
| + * Mutates items in the menu based on provided selection details, so that
|
| + * all items correctly reflect selection state.
|
| + *
|
| + * @param {Element} item An item in the menu.
|
| + * @param {boolean} isSelected True if the item should be shown in a
|
| + * selected state, otherwise false.
|
| + */
|
| + _applySelection: function(item, isSelected) {
|
| + if (isSelected) {
|
| + item.setAttribute('aria-selected', 'true');
|
| + } else {
|
| + item.removeAttribute('aria-selected');
|
| + }
|
| +
|
| + Polymer.IronSelectableBehavior._applySelection.apply(this, arguments);
|
| + },
|
| +
|
| + /**
|
| + * Discretely updates tabindex values among menu items as the focused item
|
| + * changes.
|
| + *
|
| + * @param {Element} focusedItem The element that is currently focused.
|
| + * @param {?Element} old The last element that was considered focused, if
|
| + * applicable.
|
| + */
|
| + _focusedItemChanged: function(focusedItem, old) {
|
| + old && old.setAttribute('tabindex', '-1');
|
| + if (focusedItem) {
|
| + focusedItem.setAttribute('tabindex', '0');
|
| + focusedItem.focus();
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * A handler that responds to mutation changes related to the list of items
|
| + * in the menu.
|
| + *
|
| + * @param {CustomEvent} event An event containing mutation records as its
|
| + * detail.
|
| + */
|
| + _onIronItemsChanged: function(event) {
|
| + var mutations = event.detail;
|
| + var mutation;
|
| + var index;
|
| +
|
| + for (index = 0; index < mutations.length; ++index) {
|
| + mutation = mutations[index];
|
| +
|
| + if (mutation.addedNodes.length) {
|
| + this._resetTabindices();
|
| + break;
|
| + }
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Handler that is called when a shift+tab keypress is detected by the menu.
|
| + *
|
| + * @param {CustomEvent} event A key combination event.
|
| + */
|
| + _onShiftTabDown: function(event) {
|
| + var oldTabIndex;
|
| +
|
| + Polymer.IronMenuBehaviorImpl._shiftTabPressed = true;
|
| +
|
| + oldTabIndex = this.getAttribute('tabindex');
|
| +
|
| + this.setAttribute('tabindex', '-1');
|
| +
|
| + this.async(function() {
|
| + this.setAttribute('tabindex', oldTabIndex);
|
| + Polymer.IronMenuBehaviorImpl._shiftTabPressed = false;
|
| + // NOTE(cdata): polymer/polymer#1305
|
| + }, 1);
|
| + },
|
| +
|
| + /**
|
| + * Handler that is called when the menu receives focus.
|
| + *
|
| + * @param {FocusEvent} event A focus event.
|
| + */
|
| + _onFocus: function(event) {
|
| + if (Polymer.IronMenuBehaviorImpl._shiftTabPressed) {
|
| + return;
|
| + }
|
| + // do not focus the menu itself
|
| + this.blur();
|
| + // clear the cached focus item
|
| + this._setFocusedItem(null);
|
| + this._defaultFocusAsync = this.async(function() {
|
| + // focus the selected item when the menu receives focus, or the first item
|
| + // if no item is selected
|
| + var selectedItem = this.multi ? (this.selectedItems && this.selectedItems[0]) : this.selectedItem;
|
| + if (selectedItem) {
|
| + this._setFocusedItem(selectedItem);
|
| + } else {
|
| + this._setFocusedItem(this.items[0]);
|
| + }
|
| + // async 100ms to wait for `select` to get called from `_itemActivate`
|
| + }, 100);
|
| + },
|
| +
|
| + /**
|
| + * Handler that is called when the up key is pressed.
|
| + *
|
| + * @param {CustomEvent} event A key combination event.
|
| + */
|
| + _onUpKey: function(event) {
|
| + // up and down arrows moves the focus
|
| + this._focusPrevious();
|
| + },
|
| +
|
| + /**
|
| + * Handler that is called when the down key is pressed.
|
| + *
|
| + * @param {CustomEvent} event A key combination event.
|
| + */
|
| + _onDownKey: function(event) {
|
| + this._focusNext();
|
| + },
|
| +
|
| + /**
|
| + * Handler that is called when the esc key is pressed.
|
| + *
|
| + * @param {CustomEvent} event A key combination event.
|
| + */
|
| + _onEscKey: function(event) {
|
| + // esc blurs the control
|
| + this.focusedItem.blur();
|
| + },
|
| +
|
| + /**
|
| + * Handler that is called when a keydown event is detected.
|
| + *
|
| + * @param {KeyboardEvent} event A keyboard event.
|
| + */
|
| + _onKeydown: function(event) {
|
| + if (this.keyboardEventMatchesKeys(event, 'up down esc')) {
|
| + return;
|
| + }
|
| +
|
| + // all other keys focus the menu item starting with that character
|
| + this._focusWithKeyboardEvent(event);
|
| + }
|
| + };
|
| +
|
| + Polymer.IronMenuBehaviorImpl._shiftTabPressed = false;
|
| +
|
| + /** @polymerBehavior Polymer.IronMenuBehavior */
|
| + Polymer.IronMenuBehavior = [
|
| + Polymer.IronMultiSelectableBehavior,
|
| + Polymer.IronA11yKeysBehavior,
|
| + Polymer.IronMenuBehaviorImpl
|
| + ];
|
| +(function() {
|
| +
|
| + Polymer({
|
| +
|
| + is: 'paper-menu',
|
| +
|
| + behaviors: [
|
| + Polymer.IronMenuBehavior
|
| + ]
|
| +
|
| + });
|
| +
|
| +})();
|
| +/**
|
| + * `IronResizableBehavior` is a behavior that can be used in Polymer elements to
|
| + * coordinate the flow of resize events between "resizers" (elements that control the
|
| + * size or hidden state of their children) and "resizables" (elements that need to be
|
| + * notified when they are resized or un-hidden by their parents in order to take
|
| + * action on their new measurements).
|
| + * Elements that perform measurement should add the `IronResizableBehavior` behavior to
|
| + * their element definition and listen for the `iron-resize` event on themselves.
|
| + * This event will be fired when they become showing after having been hidden,
|
| + * when they are resized explicitly by another resizable, or when the window has been
|
| + * resized.
|
| + * Note, the `iron-resize` event is non-bubbling.
|
| + *
|
| + * @polymerBehavior Polymer.IronResizableBehavior
|
| + * @demo demo/index.html
|
| + **/
|
| + Polymer.IronResizableBehavior = {
|
| + properties: {
|
| + /**
|
| + * The closest ancestor element that implements `IronResizableBehavior`.
|
| + */
|
| + _parentResizable: {
|
| + type: Object,
|
| + observer: '_parentResizableChanged'
|
| + },
|
| +
|
| + /**
|
| + * True if this element is currently notifying its descedant elements of
|
| + * resize.
|
| + */
|
| + _notifyingDescendant: {
|
| + type: Boolean,
|
| + value: false
|
| + }
|
| + },
|
| +
|
| + listeners: {
|
| + 'iron-request-resize-notifications': '_onIronRequestResizeNotifications'
|
| + },
|
| +
|
| + created: function() {
|
| + // We don't really need property effects on these, and also we want them
|
| + // to be created before the `_parentResizable` observer fires:
|
| + this._interestedResizables = [];
|
| + this._boundNotifyResize = this.notifyResize.bind(this);
|
| + },
|
| +
|
| + attached: function() {
|
| + this.fire('iron-request-resize-notifications', null, {
|
| + node: this,
|
| + bubbles: true,
|
| + cancelable: true
|
| + });
|
| +
|
| + if (!this._parentResizable) {
|
| + window.addEventListener('resize', this._boundNotifyResize);
|
| + this.notifyResize();
|
| + }
|
| + },
|
| +
|
| + detached: function() {
|
| + if (this._parentResizable) {
|
| + this._parentResizable.stopResizeNotificationsFor(this);
|
| + } else {
|
| + window.removeEventListener('resize', this._boundNotifyResize);
|
| + }
|
| +
|
| + this._parentResizable = null;
|
| + },
|
| +
|
| + /**
|
| + * Can be called to manually notify a resizable and its descendant
|
| + * resizables of a resize change.
|
| + */
|
| + notifyResize: function() {
|
| + if (!this.isAttached) {
|
| + return;
|
| + }
|
| +
|
| + this._interestedResizables.forEach(function(resizable) {
|
| + if (this.resizerShouldNotify(resizable)) {
|
| + this._notifyDescendant(resizable);
|
| + }
|
| + }, this);
|
| +
|
| + this._fireResize();
|
| + },
|
| +
|
| + /**
|
| + * Used to assign the closest resizable ancestor to this resizable
|
| + * if the ancestor detects a request for notifications.
|
| + */
|
| + assignParentResizable: function(parentResizable) {
|
| + this._parentResizable = parentResizable;
|
| + },
|
| +
|
| + /**
|
| + * Used to remove a resizable descendant from the list of descendants
|
| + * that should be notified of a resize change.
|
| + */
|
| + stopResizeNotificationsFor: function(target) {
|
| + var index = this._interestedResizables.indexOf(target);
|
| +
|
| + if (index > -1) {
|
| + this._interestedResizables.splice(index, 1);
|
| + this.unlisten(target, 'iron-resize', '_onDescendantIronResize');
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * This method can be overridden to filter nested elements that should or
|
| + * should not be notified by the current element. Return true if an element
|
| + * should be notified, or false if it should not be notified.
|
| + *
|
| + * @param {HTMLElement} element A candidate descendant element that
|
| + * implements `IronResizableBehavior`.
|
| + * @return {boolean} True if the `element` should be notified of resize.
|
| + */
|
| + resizerShouldNotify: function(element) { return true; },
|
| +
|
| + _onDescendantIronResize: function(event) {
|
| + if (this._notifyingDescendant) {
|
| + event.stopPropagation();
|
| + return;
|
| + }
|
| +
|
| + // NOTE(cdata): In ShadowDOM, event retargetting makes echoing of the
|
| + // otherwise non-bubbling event "just work." We do it manually here for
|
| + // the case where Polymer is not using shadow roots for whatever reason:
|
| + if (!Polymer.Settings.useShadow) {
|
| + this._fireResize();
|
| + }
|
| + },
|
| +
|
| + _fireResize: function() {
|
| + this.fire('iron-resize', null, {
|
| + node: this,
|
| + bubbles: false
|
| + });
|
| + },
|
| +
|
| + _onIronRequestResizeNotifications: function(event) {
|
| + var target = event.path ? event.path[0] : event.target;
|
| +
|
| + if (target === this) {
|
| + return;
|
| + }
|
| +
|
| + if (this._interestedResizables.indexOf(target) === -1) {
|
| + this._interestedResizables.push(target);
|
| + this.listen(target, 'iron-resize', '_onDescendantIronResize');
|
| + }
|
| +
|
| + target.assignParentResizable(this);
|
| + this._notifyDescendant(target);
|
| +
|
| + event.stopPropagation();
|
| + },
|
| +
|
| + _parentResizableChanged: function(parentResizable) {
|
| + if (parentResizable) {
|
| + window.removeEventListener('resize', this._boundNotifyResize);
|
| + }
|
| + },
|
| +
|
| + _notifyDescendant: function(descendant) {
|
| + // NOTE(cdata): In IE10, attached is fired on children first, so it's
|
| + // important not to notify them if the parent is not attached yet (or
|
| + // else they will get redundantly notified when the parent attaches).
|
| + if (!this.isAttached) {
|
| + return;
|
| + }
|
| +
|
| + this._notifyingDescendant = true;
|
| + descendant.notifyResize();
|
| + this._notifyingDescendant = false;
|
| + }
|
| + };
|
| +/**
|
| +Polymer.IronFitBehavior fits an element in another element using `max-height` and `max-width`, and
|
| +optionally centers it in the window or another element.
|
| +
|
| +The element will only be sized and/or positioned if it has not already been sized and/or positioned
|
| +by CSS.
|
| +
|
| +CSS properties | Action
|
| +-----------------------------|-------------------------------------------
|
| +`position` set | Element is not centered horizontally or vertically
|
| +`top` or `bottom` set | Element is not vertically centered
|
| +`left` or `right` set | Element is not horizontally centered
|
| +`max-height` or `height` set | Element respects `max-height` or `height`
|
| +`max-width` or `width` set | Element respects `max-width` or `width`
|
| +
|
| +@demo demo/index.html
|
| +@polymerBehavior
|
| +*/
|
| +
|
| + Polymer.IronFitBehavior = {
|
| +
|
| + properties: {
|
| +
|
| + /**
|
| + * The element that will receive a `max-height`/`width`. By default it is the same as `this`,
|
| + * but it can be set to a child element. This is useful, for example, for implementing a
|
| + * scrolling region inside the element.
|
| + * @type {!Element}
|
| + */
|
| + sizingTarget: {
|
| + type: Object,
|
| + value: function() {
|
| + return this;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * The element to fit `this` into.
|
| + */
|
| + fitInto: {
|
| + type: Object,
|
| + value: window
|
| + },
|
| +
|
| + /**
|
| + * Set to true to auto-fit on attach.
|
| + */
|
| + autoFitOnAttach: {
|
| + type: Boolean,
|
| + value: false
|
| + },
|
| +
|
| + /** @type {?Object} */
|
| + _fitInfo: {
|
| + type: Object
|
| + }
|
| +
|
| + },
|
| +
|
| + get _fitWidth() {
|
| + var fitWidth;
|
| + if (this.fitInto === window) {
|
| + fitWidth = this.fitInto.innerWidth;
|
| + } else {
|
| + fitWidth = this.fitInto.getBoundingClientRect().width;
|
| + }
|
| + return fitWidth;
|
| + },
|
| +
|
| + get _fitHeight() {
|
| + var fitHeight;
|
| + if (this.fitInto === window) {
|
| + fitHeight = this.fitInto.innerHeight;
|
| + } else {
|
| + fitHeight = this.fitInto.getBoundingClientRect().height;
|
| + }
|
| + return fitHeight;
|
| + },
|
| +
|
| + get _fitLeft() {
|
| + var fitLeft;
|
| + if (this.fitInto === window) {
|
| + fitLeft = 0;
|
| + } else {
|
| + fitLeft = this.fitInto.getBoundingClientRect().left;
|
| + }
|
| + return fitLeft;
|
| + },
|
| +
|
| + get _fitTop() {
|
| + var fitTop;
|
| + if (this.fitInto === window) {
|
| + fitTop = 0;
|
| + } else {
|
| + fitTop = this.fitInto.getBoundingClientRect().top;
|
| + }
|
| + return fitTop;
|
| + },
|
| +
|
| + attached: function() {
|
| + if (this.autoFitOnAttach) {
|
| + if (window.getComputedStyle(this).display === 'none') {
|
| + setTimeout(function() {
|
| + this.fit();
|
| + }.bind(this));
|
| + } else {
|
| + this.fit();
|
| + }
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Fits and optionally centers the element into the window, or `fitInfo` if specified.
|
| + */
|
| + fit: function() {
|
| + this._discoverInfo();
|
| + this.constrain();
|
| + this.center();
|
| + },
|
| +
|
| + /**
|
| + * Memoize information needed to position and size the target element.
|
| + */
|
| + _discoverInfo: function() {
|
| + if (this._fitInfo) {
|
| + return;
|
| + }
|
| + var target = window.getComputedStyle(this);
|
| + var sizer = window.getComputedStyle(this.sizingTarget);
|
| + this._fitInfo = {
|
| + inlineStyle: {
|
| + top: this.style.top || '',
|
| + left: this.style.left || ''
|
| + },
|
| + positionedBy: {
|
| + vertically: target.top !== 'auto' ? 'top' : (target.bottom !== 'auto' ?
|
| + 'bottom' : null),
|
| + horizontally: target.left !== 'auto' ? 'left' : (target.right !== 'auto' ?
|
| + 'right' : null),
|
| + css: target.position
|
| + },
|
| + sizedBy: {
|
| + height: sizer.maxHeight !== 'none',
|
| + width: sizer.maxWidth !== 'none'
|
| + },
|
| + margin: {
|
| + top: parseInt(target.marginTop, 10) || 0,
|
| + right: parseInt(target.marginRight, 10) || 0,
|
| + bottom: parseInt(target.marginBottom, 10) || 0,
|
| + left: parseInt(target.marginLeft, 10) || 0
|
| + }
|
| + };
|
| + },
|
| +
|
| + /**
|
| + * Resets the target element's position and size constraints, and clear
|
| + * the memoized data.
|
| + */
|
| + resetFit: function() {
|
| + if (!this._fitInfo || !this._fitInfo.sizedBy.height) {
|
| + this.sizingTarget.style.maxHeight = '';
|
| + this.style.top = this._fitInfo ? this._fitInfo.inlineStyle.top : '';
|
| + }
|
| + if (!this._fitInfo || !this._fitInfo.sizedBy.width) {
|
| + this.sizingTarget.style.maxWidth = '';
|
| + this.style.left = this._fitInfo ? this._fitInfo.inlineStyle.left : '';
|
| + }
|
| + if (this._fitInfo) {
|
| + this.style.position = this._fitInfo.positionedBy.css;
|
| + }
|
| + this._fitInfo = null;
|
| + },
|
| +
|
| + /**
|
| + * Equivalent to calling `resetFit()` and `fit()`. Useful to call this after the element,
|
| + * the window, or the `fitInfo` element has been resized.
|
| + */
|
| + refit: function() {
|
| + this.resetFit();
|
| + this.fit();
|
| + },
|
| +
|
| + /**
|
| + * Constrains the size of the element to the window or `fitInfo` by setting `max-height`
|
| + * and/or `max-width`.
|
| + */
|
| + constrain: function() {
|
| + var info = this._fitInfo;
|
| + // position at (0px, 0px) if not already positioned, so we can measure the natural size.
|
| + if (!this._fitInfo.positionedBy.vertically) {
|
| + this.style.top = '0px';
|
| + }
|
| + if (!this._fitInfo.positionedBy.horizontally) {
|
| + this.style.left = '0px';
|
| + }
|
| + // need border-box for margin/padding
|
| + this.sizingTarget.style.boxSizing = 'border-box';
|
| + // constrain the width and height if not already set
|
| + var rect = this.getBoundingClientRect();
|
| + if (!info.sizedBy.height) {
|
| + this._sizeDimension(rect, info.positionedBy.vertically, 'top', 'bottom', 'Height');
|
| + }
|
| + if (!info.sizedBy.width) {
|
| + this._sizeDimension(rect, info.positionedBy.horizontally, 'left', 'right', 'Width');
|
| + }
|
| + },
|
| +
|
| + _sizeDimension: function(rect, positionedBy, start, end, extent) {
|
| + var info = this._fitInfo;
|
| + var max = extent === 'Width' ? this._fitWidth : this._fitHeight;
|
| + var flip = (positionedBy === end);
|
| + var offset = flip ? max - rect[end] : rect[start];
|
| + var margin = info.margin[flip ? start : end];
|
| + var offsetExtent = 'offset' + extent;
|
| + var sizingOffset = this[offsetExtent] - this.sizingTarget[offsetExtent];
|
| + this.sizingTarget.style['max' + extent] = (max - margin - offset - sizingOffset) + 'px';
|
| + },
|
| +
|
| + /**
|
| + * Centers horizontally and vertically if not already positioned. This also sets
|
| + * `position:fixed`.
|
| + */
|
| + center: function() {
|
| + if (!this._fitInfo.positionedBy.vertically || !this._fitInfo.positionedBy.horizontally) {
|
| + // need position:fixed to center
|
| + this.style.position = 'fixed';
|
| + }
|
| + if (!this._fitInfo.positionedBy.vertically) {
|
| + var top = (this._fitHeight - this.offsetHeight) / 2 + this._fitTop;
|
| + top -= this._fitInfo.margin.top;
|
| + this.style.top = top + 'px';
|
| + }
|
| + if (!this._fitInfo.positionedBy.horizontally) {
|
| + var left = (this._fitWidth - this.offsetWidth) / 2 + this._fitLeft;
|
| + left -= this._fitInfo.margin.left;
|
| + this.style.left = left + 'px';
|
| + }
|
| + }
|
| +
|
| + };
|
| +Polymer.IronOverlayManager = (function() {
|
| +
|
| + var overlays = [];
|
| + var DEFAULT_Z = 10;
|
| + var backdrops = [];
|
| +
|
| + // track overlays for z-index and focus managemant
|
| + function addOverlay(overlay) {
|
| + var z0 = currentOverlayZ();
|
| + overlays.push(overlay);
|
| + var z1 = currentOverlayZ();
|
| + if (z1 <= z0) {
|
| + applyOverlayZ(overlay, z0);
|
| + }
|
| + }
|
| +
|
| + function removeOverlay(overlay) {
|
| + var i = overlays.indexOf(overlay);
|
| + if (i >= 0) {
|
| + overlays.splice(i, 1);
|
| + setZ(overlay, '');
|
| + }
|
| + }
|
| +
|
| + function applyOverlayZ(overlay, aboveZ) {
|
| + setZ(overlay, aboveZ + 2);
|
| + }
|
| +
|
| + function setZ(element, z) {
|
| + element.style.zIndex = z;
|
| + }
|
| +
|
| + function currentOverlay() {
|
| + var i = overlays.length - 1;
|
| + while (overlays[i] && !overlays[i].opened) {
|
| + --i;
|
| + }
|
| + return overlays[i];
|
| + }
|
| +
|
| + function currentOverlayZ() {
|
| + var z;
|
| + var current = currentOverlay();
|
| + if (current) {
|
| + var z1 = window.getComputedStyle(current).zIndex;
|
| + if (!isNaN(z1)) {
|
| + z = Number(z1);
|
| + }
|
| + }
|
| + return z || DEFAULT_Z;
|
| + }
|
| +
|
| + function focusOverlay() {
|
| + var current = currentOverlay();
|
| + // We have to be careful to focus the next overlay _after_ any current
|
| + // transitions are complete (due to the state being toggled prior to the
|
| + // transition). Otherwise, we risk infinite recursion when a transitioning
|
| + // (closed) overlay becomes the current overlay.
|
| + //
|
| + // NOTE: We make the assumption that any overlay that completes a transition
|
| + // will call into focusOverlay to kick the process back off. Currently:
|
| + // transitionend -> _applyFocus -> focusOverlay.
|
| + if (current && !current.transitioning) {
|
| + current._applyFocus();
|
| + }
|
| + }
|
| +
|
| + function trackBackdrop(element) {
|
| + // backdrops contains the overlays with a backdrop that are currently
|
| + // visible
|
| + if (element.opened) {
|
| + backdrops.push(element);
|
| + } else {
|
| + var index = backdrops.indexOf(element);
|
| + if (index >= 0) {
|
| + backdrops.splice(index, 1);
|
| + }
|
| + }
|
| + }
|
| +
|
| + function getBackdrops() {
|
| + return backdrops;
|
| + }
|
| +
|
| + return {
|
| + addOverlay: addOverlay,
|
| + removeOverlay: removeOverlay,
|
| + currentOverlay: currentOverlay,
|
| + currentOverlayZ: currentOverlayZ,
|
| + focusOverlay: focusOverlay,
|
| + trackBackdrop: trackBackdrop,
|
| + getBackdrops: getBackdrops
|
| + };
|
| +
|
| + })();
|
| +(function() {
|
| +
|
| + Polymer({
|
| +
|
| + is: 'iron-overlay-backdrop',
|
| +
|
| + properties: {
|
| +
|
| + /**
|
| + * Returns true if the backdrop is opened.
|
| + */
|
| + opened: {
|
| + readOnly: true,
|
| + reflectToAttribute: true,
|
| + type: Boolean,
|
| + value: false
|
| + },
|
| +
|
| + _manager: {
|
| + type: Object,
|
| + value: Polymer.IronOverlayManager
|
| + }
|
| +
|
| + },
|
| +
|
| + /**
|
| + * Appends the backdrop to document body and sets its `z-index` to be below the latest overlay.
|
| + */
|
| + prepare: function() {
|
| + if (!this.parentNode) {
|
| + Polymer.dom(document.body).appendChild(this);
|
| + this.style.zIndex = this._manager.currentOverlayZ() - 1;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Shows the backdrop if needed.
|
| + */
|
| + open: function() {
|
| + // only need to make the backdrop visible if this is called by the first overlay with a backdrop
|
| + if (this._manager.getBackdrops().length < 2) {
|
| + this._setOpened(true);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Hides the backdrop if needed.
|
| + */
|
| + close: function() {
|
| + // only need to make the backdrop invisible if this is called by the last overlay with a backdrop
|
| + if (this._manager.getBackdrops().length < 2) {
|
| + this._setOpened(false);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Removes the backdrop from document body if needed.
|
| + */
|
| + complete: function() {
|
| + // only remove the backdrop if there are no more overlays with backdrops
|
| + if (this._manager.getBackdrops().length === 0 && this.parentNode) {
|
| + Polymer.dom(this.parentNode).removeChild(this);
|
| + }
|
| + }
|
| +
|
| + });
|
| +
|
| +})();
|
| +/**
|
| +Use `Polymer.IronOverlayBehavior` to implement an element that can be hidden or shown, and displays
|
| +on top of other content. It includes an optional backdrop, and can be used to implement a variety
|
| +of UI controls including dialogs and drop downs. Multiple overlays may be displayed at once.
|
| +
|
| +### Closing and canceling
|
| +
|
| +A dialog may be hidden by closing or canceling. The difference between close and cancel is user
|
| +intent. Closing generally implies that the user acknowledged the content on the overlay. By default,
|
| +it will cancel whenever the user taps outside it or presses the escape key. This behavior is
|
| +configurable with the `no-cancel-on-esc-key` and the `no-cancel-on-outside-click` properties.
|
| +`close()` should be called explicitly by the implementer when the user interacts with a control
|
| +in the overlay element.
|
| +
|
| +### Positioning
|
| +
|
| +By default the element is sized and positioned to fit and centered inside the window. You can
|
| +position and size it manually using CSS. See `Polymer.IronFitBehavior`.
|
| +
|
| +### Backdrop
|
| +
|
| +Set the `with-backdrop` attribute to display a backdrop behind the overlay. The backdrop is
|
| +appended to `<body>` and is of type `<iron-overlay-backdrop>`. See its doc page for styling
|
| +options.
|
| +
|
| +### Limitations
|
| +
|
| +The element is styled to appear on top of other content by setting its `z-index` property. You
|
| +must ensure no element has a stacking context with a higher `z-index` than its parent stacking
|
| +context. You should place this element as a child of `<body>` whenever possible.
|
| +
|
| +@demo demo/index.html
|
| +@polymerBehavior Polymer.IronOverlayBehavior
|
| +*/
|
| +
|
| + Polymer.IronOverlayBehaviorImpl = {
|
| +
|
| + properties: {
|
| +
|
| + /**
|
| + * True if the overlay is currently displayed.
|
| + */
|
| + opened: {
|
| + observer: '_openedChanged',
|
| + type: Boolean,
|
| + value: false,
|
| + notify: true
|
| + },
|
| +
|
| + /**
|
| + * True if the overlay was canceled when it was last closed.
|
| + */
|
| + canceled: {
|
| + observer: '_canceledChanged',
|
| + readOnly: true,
|
| + type: Boolean,
|
| + value: false
|
| + },
|
| +
|
| + /**
|
| + * Set to true to display a backdrop behind the overlay.
|
| + */
|
| + withBackdrop: {
|
| + type: Boolean,
|
| + value: false
|
| + },
|
| +
|
| + /**
|
| + * Set to true to disable auto-focusing the overlay or child nodes with
|
| + * the `autofocus` attribute` when the overlay is opened.
|
| + */
|
| + noAutoFocus: {
|
| + type: Boolean,
|
| + value: false
|
| + },
|
| +
|
| + /**
|
| + * Set to true to disable canceling the overlay with the ESC key.
|
| + */
|
| + noCancelOnEscKey: {
|
| + type: Boolean,
|
| + value: false
|
| + },
|
| +
|
| + /**
|
| + * Set to true to disable canceling the overlay by clicking outside it.
|
| + */
|
| + noCancelOnOutsideClick: {
|
| + type: Boolean,
|
| + value: false
|
| + },
|
| +
|
| + /**
|
| + * Returns the reason this dialog was last closed.
|
| + */
|
| + closingReason: {
|
| + // was a getter before, but needs to be a property so other
|
| + // behaviors can override this.
|
| + type: Object
|
| + },
|
| +
|
| + _manager: {
|
| + type: Object,
|
| + value: Polymer.IronOverlayManager
|
| + },
|
| +
|
| + _boundOnCaptureClick: {
|
| + type: Function,
|
| + value: function() {
|
| + return this._onCaptureClick.bind(this);
|
| + }
|
| + },
|
| +
|
| + _boundOnCaptureKeydown: {
|
| + type: Function,
|
| + value: function() {
|
| + return this._onCaptureKeydown.bind(this);
|
| + }
|
| + }
|
| +
|
| + },
|
| +
|
| + listeners: {
|
| + 'tap': '_onClick',
|
| + 'iron-resize': '_onIronResize'
|
| + },
|
| +
|
| + /**
|
| + * The backdrop element.
|
| + * @type Node
|
| + */
|
| + get backdropElement() {
|
| + return this._backdrop;
|
| + },
|
| +
|
| + get _focusNode() {
|
| + return Polymer.dom(this).querySelector('[autofocus]') || this;
|
| + },
|
| +
|
| + registered: function() {
|
| + this._backdrop = document.createElement('iron-overlay-backdrop');
|
| + },
|
| +
|
| + ready: function() {
|
| + this._ensureSetup();
|
| + if (this._callOpenedWhenReady) {
|
| + this._openedChanged();
|
| + }
|
| + },
|
| +
|
| + detached: function() {
|
| + this.opened = false;
|
| + this._completeBackdrop();
|
| + this._manager.removeOverlay(this);
|
| + },
|
| +
|
| + /**
|
| + * Toggle the opened state of the overlay.
|
| + */
|
| + toggle: function() {
|
| + this.opened = !this.opened;
|
| + },
|
| +
|
| + /**
|
| + * Open the overlay.
|
| + */
|
| + open: function() {
|
| + this.opened = true;
|
| + this.closingReason = {canceled: false};
|
| + },
|
| +
|
| + /**
|
| + * Close the overlay.
|
| + */
|
| + close: function() {
|
| + this.opened = false;
|
| + this._setCanceled(false);
|
| + },
|
| +
|
| + /**
|
| + * Cancels the overlay.
|
| + */
|
| + cancel: function() {
|
| + this.opened = false;
|
| + this._setCanceled(true);
|
| + },
|
| +
|
| + _ensureSetup: function() {
|
| + if (this._overlaySetup) {
|
| + return;
|
| + }
|
| + this._overlaySetup = true;
|
| + this.style.outline = 'none';
|
| + this.style.display = 'none';
|
| + },
|
| +
|
| + _openedChanged: function() {
|
| + if (this.opened) {
|
| + this.removeAttribute('aria-hidden');
|
| + } else {
|
| + this.setAttribute('aria-hidden', 'true');
|
| + }
|
| +
|
| + // wait to call after ready only if we're initially open
|
| + if (!this._overlaySetup) {
|
| + this._callOpenedWhenReady = this.opened;
|
| + return;
|
| + }
|
| + if (this._openChangedAsync) {
|
| + this.cancelAsync(this._openChangedAsync);
|
| + }
|
| +
|
| + this._toggleListeners();
|
| +
|
| + if (this.opened) {
|
| + this._prepareRenderOpened();
|
| + }
|
| +
|
| + // async here to allow overlay layer to become visible.
|
| + this._openChangedAsync = this.async(function() {
|
| + // overlay becomes visible here
|
| + this.style.display = '';
|
| + // force layout to ensure transitions will go
|
| + /** @suppress {suspiciousCode} */ this.offsetWidth;
|
| + if (this.opened) {
|
| + this._renderOpened();
|
| + } else {
|
| + this._renderClosed();
|
| + }
|
| + this._openChangedAsync = null;
|
| + });
|
| +
|
| + },
|
| +
|
| + _canceledChanged: function() {
|
| + this.closingReason = this.closingReason || {};
|
| + this.closingReason.canceled = this.canceled;
|
| + },
|
| +
|
| + _toggleListener: function(enable, node, event, boundListener, capture) {
|
| + if (enable) {
|
| + // enable document-wide tap recognizer
|
| + if (event === 'tap') {
|
| + Polymer.Gestures.add(document, 'tap', null);
|
| + }
|
| + node.addEventListener(event, boundListener, capture);
|
| + } else {
|
| + // disable document-wide tap recognizer
|
| + if (event === 'tap') {
|
| + Polymer.Gestures.remove(document, 'tap', null);
|
| + }
|
| + node.removeEventListener(event, boundListener, capture);
|
| + }
|
| + },
|
| +
|
| + _toggleListeners: function() {
|
| + if (this._toggleListenersAsync) {
|
| + this.cancelAsync(this._toggleListenersAsync);
|
| + }
|
| + // async so we don't auto-close immediately via a click.
|
| + this._toggleListenersAsync = this.async(function() {
|
| + this._toggleListener(this.opened, document, 'tap', this._boundOnCaptureClick, true);
|
| + this._toggleListener(this.opened, document, 'keydown', this._boundOnCaptureKeydown, true);
|
| + this._toggleListenersAsync = null;
|
| + }, 1);
|
| + },
|
| +
|
| + // tasks which must occur before opening; e.g. making the element visible
|
| + _prepareRenderOpened: function() {
|
| + this._manager.addOverlay(this);
|
| +
|
| + if (this.withBackdrop) {
|
| + this.backdropElement.prepare();
|
| + this._manager.trackBackdrop(this);
|
| + }
|
| +
|
| + this._preparePositioning();
|
| + this.fit();
|
| + this._finishPositioning();
|
| + },
|
| +
|
| + // tasks which cause the overlay to actually open; typically play an
|
| + // animation
|
| + _renderOpened: function() {
|
| + if (this.withBackdrop) {
|
| + this.backdropElement.open();
|
| + }
|
| + this._finishRenderOpened();
|
| + },
|
| +
|
| + _renderClosed: function() {
|
| + if (this.withBackdrop) {
|
| + this.backdropElement.close();
|
| + }
|
| + this._finishRenderClosed();
|
| + },
|
| +
|
| + _onTransitionend: function(event) {
|
| + // make sure this is our transition event.
|
| + if (event && event.target !== this) {
|
| + return;
|
| + }
|
| + if (this.opened) {
|
| + this._finishRenderOpened();
|
| + } else {
|
| + this._finishRenderClosed();
|
| + }
|
| + },
|
| +
|
| + _finishRenderOpened: function() {
|
| + // focus the child node with [autofocus]
|
| + if (!this.noAutoFocus) {
|
| + this._focusNode.focus();
|
| + }
|
| +
|
| + this.fire('iron-overlay-opened');
|
| +
|
| + this._squelchNextResize = true;
|
| + this.async(this.notifyResize);
|
| + },
|
| +
|
| + _finishRenderClosed: function() {
|
| + // hide the overlay and remove the backdrop
|
| + this.resetFit();
|
| + this.style.display = 'none';
|
| + this._completeBackdrop();
|
| + this._manager.removeOverlay(this);
|
| +
|
| + this._focusNode.blur();
|
| + // focus the next overlay, if there is one
|
| + this._manager.focusOverlay();
|
| +
|
| + this.fire('iron-overlay-closed', this.closingReason);
|
| +
|
| + this._squelchNextResize = true;
|
| + this.async(this.notifyResize);
|
| + },
|
| +
|
| + _completeBackdrop: function() {
|
| + if (this.withBackdrop) {
|
| + this._manager.trackBackdrop(this);
|
| + this.backdropElement.complete();
|
| + }
|
| + },
|
| +
|
| + _preparePositioning: function() {
|
| + this.style.transition = this.style.webkitTransition = 'none';
|
| + this.style.transform = this.style.webkitTransform = 'none';
|
| + this.style.display = '';
|
| + },
|
| +
|
| + _finishPositioning: function() {
|
| + this.style.display = 'none';
|
| + this.style.transform = this.style.webkitTransform = '';
|
| + // force layout to avoid application of transform
|
| + /** @suppress {suspiciousCode} */ this.offsetWidth;
|
| + this.style.transition = this.style.webkitTransition = '';
|
| + },
|
| +
|
| + _applyFocus: function() {
|
| + if (this.opened) {
|
| + if (!this.noAutoFocus) {
|
| + this._focusNode.focus();
|
| + }
|
| + } else {
|
| + this._focusNode.blur();
|
| + this._manager.focusOverlay();
|
| + }
|
| + },
|
| +
|
| + _onCaptureClick: function(event) {
|
| + // attempt to close asynchronously and prevent the close of a tap event is immediately heard
|
| + // on target. This is because in shadow dom due to event retargetting event.target is not
|
| + // useful.
|
| + if (!this.noCancelOnOutsideClick && (this._manager.currentOverlay() == this)) {
|
| + this._cancelJob = this.async(function() {
|
| + this.cancel();
|
| + }, 10);
|
| + }
|
| + },
|
| +
|
| + _onClick: function(event) {
|
| + if (this._cancelJob) {
|
| + this.cancelAsync(this._cancelJob);
|
| + this._cancelJob = null;
|
| + }
|
| + },
|
| +
|
| + _onCaptureKeydown: function(event) {
|
| + var ESC = 27;
|
| + if (!this.noCancelOnEscKey && (event.keyCode === ESC)) {
|
| + this.cancel();
|
| + event.stopPropagation();
|
| + }
|
| + },
|
| +
|
| + _onIronResize: function() {
|
| + if (this._squelchNextResize) {
|
| + this._squelchNextResize = false;
|
| + return;
|
| + }
|
| + if (this.opened) {
|
| + this.refit();
|
| + }
|
| + }
|
| +
|
| +/**
|
| + * Fired after the `iron-overlay` opens.
|
| + * @event iron-overlay-opened
|
| + */
|
| +
|
| +/**
|
| + * Fired after the `iron-overlay` closes.
|
| + * @event iron-overlay-closed
|
| + * @param {{canceled: (boolean|undefined)}} set to the `closingReason` attribute
|
| + */
|
| + };
|
| +
|
| + /** @polymerBehavior */
|
| + Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableBehavior, Polymer.IronOverlayBehaviorImpl];
|
| +/**
|
| + * Use `Polymer.NeonAnimationBehavior` to implement an animation.
|
| + * @polymerBehavior
|
| + */
|
| + Polymer.NeonAnimationBehavior = {
|
| +
|
| + properties: {
|
| +
|
| + /**
|
| + * Defines the animation timing.
|
| + */
|
| + animationTiming: {
|
| + type: Object,
|
| + value: function() {
|
| + return {
|
| + duration: 500,
|
| + easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
| + fill: 'both'
|
| + }
|
| + }
|
| + }
|
| +
|
| + },
|
| +
|
| + registered: function() {
|
| + new Polymer.IronMeta({type: 'animation', key: this.is, value: this.constructor});
|
| + },
|
| +
|
| + /**
|
| + * Do any animation configuration here.
|
| + */
|
| + // configure: function(config) {
|
| + // },
|
| +
|
| + /**
|
| + * Returns the animation timing by mixing in properties from `config` to the defaults defined
|
| + * by the animation.
|
| + */
|
| + timingFromConfig: function(config) {
|
| + if (config.timing) {
|
| + for (var property in config.timing) {
|
| + this.animationTiming[property] = config.timing[property];
|
| + }
|
| + }
|
| + return this.animationTiming;
|
| + },
|
| +
|
| + /**
|
| + * Sets `transform` and `transformOrigin` properties along with the prefixed versions.
|
| + */
|
| + setPrefixedProperty: function(node, property, value) {
|
| + var map = {
|
| + 'transform': ['webkitTransform'],
|
| + 'transformOrigin': ['mozTransformOrigin', 'webkitTransformOrigin']
|
| + };
|
| + var prefixes = map[property];
|
| + for (var prefix, index = 0; prefix = prefixes[index]; index++) {
|
| + node.style[prefix] = value;
|
| + }
|
| + node.style[property] = value;
|
| + },
|
| +
|
| + /**
|
| + * Called when the animation finishes.
|
| + */
|
| + complete: function() {}
|
| +
|
| + };
|
| +Polymer({
|
| +
|
| + is: 'opaque-animation',
|
| +
|
| + behaviors: [
|
| + Polymer.NeonAnimationBehavior
|
| + ],
|
| +
|
| + configure: function(config) {
|
| + var node = config.node;
|
| + node.style.opacity = '0';
|
| + this._effect = new KeyframeEffect(node, [
|
| + {'opacity': '1'},
|
| + {'opacity': '1'}
|
| + ], this.timingFromConfig(config));
|
| + return this._effect;
|
| + },
|
| +
|
| + complete: function(config) {
|
| + config.node.style.opacity = '';
|
| + }
|
| +
|
| + });
|
| +/**
|
| + * `Polymer.NeonAnimatableBehavior` is implemented by elements containing animations for use with
|
| + * elements implementing `Polymer.NeonAnimationRunnerBehavior`.
|
| + * @polymerBehavior
|
| + */
|
| + Polymer.NeonAnimatableBehavior = {
|
| +
|
| + properties: {
|
| +
|
| + /**
|
| + * Animation configuration. See README for more info.
|
| + */
|
| + animationConfig: {
|
| + type: Object
|
| + },
|
| +
|
| + /**
|
| + * Convenience property for setting an 'entry' animation. Do not set `animationConfig.entry`
|
| + * manually if using this. The animated node is set to `this` if using this property.
|
| + */
|
| + entryAnimation: {
|
| + observer: '_entryAnimationChanged',
|
| + type: String
|
| + },
|
| +
|
| + /**
|
| + * Convenience property for setting an 'exit' animation. Do not set `animationConfig.exit`
|
| + * manually if using this. The animated node is set to `this` if using this property.
|
| + */
|
| + exitAnimation: {
|
| + observer: '_exitAnimationChanged',
|
| + type: String
|
| + }
|
| +
|
| + },
|
| +
|
| + _entryAnimationChanged: function() {
|
| + this.animationConfig = this.animationConfig || {};
|
| + if (this.entryAnimation !== 'fade-in-animation') {
|
| + // insert polyfill hack
|
| + this.animationConfig['entry'] = [{
|
| + name: 'opaque-animation',
|
| + node: this
|
| + }, {
|
| + name: this.entryAnimation,
|
| + node: this
|
| + }];
|
| + } else {
|
| + this.animationConfig['entry'] = [{
|
| + name: this.entryAnimation,
|
| + node: this
|
| + }];
|
| + }
|
| + },
|
| +
|
| + _exitAnimationChanged: function() {
|
| + this.animationConfig = this.animationConfig || {};
|
| + this.animationConfig['exit'] = [{
|
| + name: this.exitAnimation,
|
| + node: this
|
| + }];
|
| + },
|
| +
|
| + _copyProperties: function(config1, config2) {
|
| + // shallowly copy properties from config2 to config1
|
| + for (var property in config2) {
|
| + config1[property] = config2[property];
|
| + }
|
| + },
|
| +
|
| + _cloneConfig: function(config) {
|
| + var clone = {
|
| + isClone: true
|
| + };
|
| + this._copyProperties(clone, config);
|
| + return clone;
|
| + },
|
| +
|
| + _getAnimationConfigRecursive: function(type, map, allConfigs) {
|
| + if (!this.animationConfig) {
|
| + return;
|
| + }
|
| +
|
| + // type is optional
|
| + var thisConfig;
|
| + if (type) {
|
| + thisConfig = this.animationConfig[type];
|
| + } else {
|
| + thisConfig = this.animationConfig;
|
| + }
|
| +
|
| + if (!Array.isArray(thisConfig)) {
|
| + thisConfig = [thisConfig];
|
| + }
|
| +
|
| + // iterate animations and recurse to process configurations from child nodes
|
| + if (thisConfig) {
|
| + for (var config, index = 0; config = thisConfig[index]; index++) {
|
| + if (config.animatable) {
|
| + config.animatable._getAnimationConfigRecursive(config.type || type, map, allConfigs);
|
| + } else {
|
| + if (config.id) {
|
| + var cachedConfig = map[config.id];
|
| + if (cachedConfig) {
|
| + // merge configurations with the same id, making a clone lazily
|
| + if (!cachedConfig.isClone) {
|
| + map[config.id] = this._cloneConfig(cachedConfig)
|
| + cachedConfig = map[config.id];
|
| + }
|
| + this._copyProperties(cachedConfig, config);
|
| + } else {
|
| + // put any configs with an id into a map
|
| + map[config.id] = config;
|
| + }
|
| + } else {
|
| + allConfigs.push(config);
|
| + }
|
| + }
|
| + }
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * An element implementing `Polymer.NeonAnimationRunnerBehavior` calls this method to configure
|
| + * an animation with an optional type. Elements implementing `Polymer.NeonAnimatableBehavior`
|
| + * should define the property `animationConfig`, which is either a configuration object
|
| + * or a map of animation type to array of configuration objects.
|
| + */
|
| + getAnimationConfig: function(type) {
|
| + var map = [];
|
| + var allConfigs = [];
|
| + this._getAnimationConfigRecursive(type, map, allConfigs);
|
| + // append the configurations saved in the map to the array
|
| + for (var key in map) {
|
| + allConfigs.push(map[key]);
|
| + }
|
| + return allConfigs;
|
| + }
|
| +
|
| + };
|
| +/**
|
| + * `Polymer.NeonAnimationRunnerBehavior` adds a method to run animations.
|
| + *
|
| + * @polymerBehavior Polymer.NeonAnimationRunnerBehavior
|
| + */
|
| + Polymer.NeonAnimationRunnerBehaviorImpl = {
|
| +
|
| + properties: {
|
| +
|
| + _animationMeta: {
|
| + type: Object,
|
| + value: function() {
|
| + return new Polymer.IronMeta({type: 'animation'});
|
| + }
|
| + },
|
| +
|
| + /** @type {?Object} */
|
| + _player: {
|
| + type: Object
|
| + }
|
| +
|
| + },
|
| +
|
| + _configureAnimationEffects: function(allConfigs) {
|
| + var allAnimations = [];
|
| + if (allConfigs.length > 0) {
|
| + for (var config, index = 0; config = allConfigs[index]; index++) {
|
| + var animationConstructor = this._animationMeta.byKey(config.name);
|
| + if (animationConstructor) {
|
| + var animation = animationConstructor && new animationConstructor();
|
| + var effect = animation.configure(config);
|
| + if (effect) {
|
| + allAnimations.push({
|
| + animation: animation,
|
| + config: config,
|
| + effect: effect
|
| + });
|
| + }
|
| + } else {
|
| + console.warn(this.is + ':', config.name, 'not found!');
|
| + }
|
| + }
|
| + }
|
| + return allAnimations;
|
| + },
|
| +
|
| + _runAnimationEffects: function(allEffects) {
|
| + return document.timeline.play(new GroupEffect(allEffects));
|
| + },
|
| +
|
| + _completeAnimations: function(allAnimations) {
|
| + for (var animation, index = 0; animation = allAnimations[index]; index++) {
|
| + animation.animation.complete(animation.config);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Plays an animation with an optional `type`.
|
| + * @param {string=} type
|
| + * @param {!Object=} cookie
|
| + */
|
| + playAnimation: function(type, cookie) {
|
| + var allConfigs = this.getAnimationConfig(type);
|
| + if (!allConfigs) {
|
| + return;
|
| + }
|
| + var allAnimations = this._configureAnimationEffects(allConfigs);
|
| + var allEffects = allAnimations.map(function(animation) {
|
| + return animation.effect;
|
| + });
|
| +
|
| + if (allEffects.length > 0) {
|
| + this._player = this._runAnimationEffects(allEffects);
|
| + this._player.onfinish = function() {
|
| + this._completeAnimations(allAnimations);
|
| +
|
| + if (this._player) {
|
| + this._player.cancel();
|
| + this._player = null;
|
| + }
|
| +
|
| + this.fire('neon-animation-finish', cookie, {bubbles: false});
|
| + }.bind(this);
|
| +
|
| + } else {
|
| + this.fire('neon-animation-finish', cookie, {bubbles: false});
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Cancels the currently running animation.
|
| + */
|
| + cancelAnimation: function() {
|
| + if (this._player) {
|
| + this._player.cancel();
|
| + }
|
| + }
|
| + };
|
| +
|
| + /** @polymerBehavior Polymer.NeonAnimationRunnerBehavior */
|
| + Polymer.NeonAnimationRunnerBehavior = [
|
| + Polymer.NeonAnimatableBehavior,
|
| + Polymer.NeonAnimationRunnerBehaviorImpl
|
| + ];
|
| +(function() {
|
| + 'use strict';
|
| +
|
| + /**
|
| + * The IronDropdownScrollManager is intended to provide a central source
|
| + * of authority and control over which elements in a document are currently
|
| + * allowed to scroll.
|
| + */
|
| +
|
| + Polymer.IronDropdownScrollManager = {
|
| +
|
| + /**
|
| + * The current element that defines the DOM boundaries of the
|
| + * scroll lock. This is always the most recently locking element.
|
| + */
|
| + get currentLockingElement() {
|
| + return this._lockingElements[this._lockingElements.length - 1];
|
| + },
|
| +
|
| +
|
| + /**
|
| + * Returns true if the provided element is "scroll locked," which is to
|
| + * say that it cannot be scrolled via pointer or keyboard interactions.
|
| + *
|
| + * @param {HTMLElement} element An HTML element instance which may or may
|
| + * not be scroll locked.
|
| + */
|
| + elementIsScrollLocked: function(element) {
|
| + var currentLockingElement = this.currentLockingElement;
|
| + var scrollLocked;
|
| +
|
| + if (this._hasCachedLockedElement(element)) {
|
| + return true;
|
| + }
|
| +
|
| + if (this._hasCachedUnlockedElement(element)) {
|
| + return false;
|
| + }
|
| +
|
| + scrollLocked = !!currentLockingElement &&
|
| + currentLockingElement !== element &&
|
| + !this._composedTreeContains(currentLockingElement, element);
|
| +
|
| + if (scrollLocked) {
|
| + this._lockedElementCache.push(element);
|
| + } else {
|
| + this._unlockedElementCache.push(element);
|
| + }
|
| +
|
| + return scrollLocked;
|
| + },
|
| +
|
| + /**
|
| + * Push an element onto the current scroll lock stack. The most recently
|
| + * pushed element and its children will be considered scrollable. All
|
| + * other elements will not be scrollable.
|
| + *
|
| + * Scroll locking is implemented as a stack so that cases such as
|
| + * dropdowns within dropdowns are handled well.
|
| + *
|
| + * @param {HTMLElement} element The element that should lock scroll.
|
| + */
|
| + pushScrollLock: function(element) {
|
| + if (this._lockingElements.length === 0) {
|
| + this._lockScrollInteractions();
|
| + }
|
| +
|
| + this._lockingElements.push(element);
|
| +
|
| + this._lockedElementCache = [];
|
| + this._unlockedElementCache = [];
|
| + },
|
| +
|
| + /**
|
| + * Remove an element from the scroll lock stack. The element being
|
| + * removed does not need to be the most recently pushed element. However,
|
| + * the scroll lock constraints only change when the most recently pushed
|
| + * element is removed.
|
| + *
|
| + * @param {HTMLElement} element The element to remove from the scroll
|
| + * lock stack.
|
| + */
|
| + removeScrollLock: function(element) {
|
| + var index = this._lockingElements.indexOf(element);
|
| +
|
| + if (index === -1) {
|
| + return;
|
| + }
|
| +
|
| + this._lockingElements.splice(index, 1);
|
| +
|
| + this._lockedElementCache = [];
|
| + this._unlockedElementCache = [];
|
| +
|
| + if (this._lockingElements.length === 0) {
|
| + this._unlockScrollInteractions();
|
| + }
|
| + },
|
| +
|
| + _lockingElements: [],
|
| +
|
| + _lockedElementCache: null,
|
| +
|
| + _unlockedElementCache: null,
|
| +
|
| + _originalBodyStyles: {},
|
| +
|
| + _isScrollingKeypress: function(event) {
|
| + return Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(
|
| + event, 'pageup pagedown home end up left down right');
|
| + },
|
| +
|
| + _hasCachedLockedElement: function(element) {
|
| + return this._lockedElementCache.indexOf(element) > -1;
|
| + },
|
| +
|
| + _hasCachedUnlockedElement: function(element) {
|
| + return this._unlockedElementCache.indexOf(element) > -1;
|
| + },
|
| +
|
| + _composedTreeContains: function(element, child) {
|
| + // NOTE(cdata): This method iterates over content elements and their
|
| + // corresponding distributed nodes to implement a contains-like method
|
| + // that pierces through the composed tree of the ShadowDOM. Results of
|
| + // this operation are cached (elsewhere) on a per-scroll-lock basis, to
|
| + // guard against potentially expensive lookups happening repeatedly as
|
| + // a user scrolls / touchmoves.
|
| + var contentElements;
|
| + var distributedNodes;
|
| + var contentIndex;
|
| + var nodeIndex;
|
| +
|
| + if (element.contains(child)) {
|
| + return true;
|
| + }
|
| +
|
| + contentElements = Polymer.dom(element).querySelectorAll('content');
|
| +
|
| + for (contentIndex = 0; contentIndex < contentElements.length; ++contentIndex) {
|
| +
|
| + distributedNodes = Polymer.dom(contentElements[contentIndex]).getDistributedNodes();
|
| +
|
| + for (nodeIndex = 0; nodeIndex < distributedNodes.length; ++nodeIndex) {
|
| +
|
| + if (this._composedTreeContains(distributedNodes[nodeIndex], child)) {
|
| + return true;
|
| + }
|
| + }
|
| + }
|
| +
|
| + return false;
|
| + },
|
| +
|
| + _scrollInteractionHandler: function(event) {
|
| + if (Polymer
|
| + .IronDropdownScrollManager
|
| + .elementIsScrollLocked(event.target)) {
|
| + if (event.type === 'keydown' &&
|
| + !Polymer.IronDropdownScrollManager._isScrollingKeypress(event)) {
|
| + return;
|
| + }
|
| +
|
| + event.preventDefault();
|
| + }
|
| + },
|
| +
|
| + _lockScrollInteractions: function() {
|
| + // Memoize body inline styles:
|
| + this._originalBodyStyles.overflow = document.body.style.overflow;
|
| + this._originalBodyStyles.overflowX = document.body.style.overflowX;
|
| + this._originalBodyStyles.overflowY = document.body.style.overflowY;
|
| +
|
| + // Disable overflow scrolling on body:
|
| + // TODO(cdata): It is technically not sufficient to hide overflow on
|
| + // body alone. A better solution might be to traverse all ancestors of
|
| + // the current scroll locking element and hide overflow on them. This
|
| + // becomes expensive, though, as it would have to be redone every time
|
| + // a new scroll locking element is added.
|
| + document.body.style.overflow = 'hidden';
|
| + document.body.style.overflowX = 'hidden';
|
| + document.body.style.overflowY = 'hidden';
|
| +
|
| + // Modern `wheel` event for mouse wheel scrolling:
|
| + window.addEventListener('wheel', this._scrollInteractionHandler, true);
|
| + // Older, non-standard `mousewheel` event for some FF:
|
| + window.addEventListener('mousewheel', this._scrollInteractionHandler, true);
|
| + // IE:
|
| + window.addEventListener('DOMMouseScroll', this._scrollInteractionHandler, true);
|
| + // Mobile devices can scroll on touch move:
|
| + window.addEventListener('touchmove', this._scrollInteractionHandler, true);
|
| + // Capture keydown to prevent scrolling keys (pageup, pagedown etc.)
|
| + document.addEventListener('keydown', this._scrollInteractionHandler, true);
|
| + },
|
| +
|
| + _unlockScrollInteractions: function() {
|
| + document.body.style.overflow = this._originalBodyStyles.overflow;
|
| + document.body.style.overflowX = this._originalBodyStyles.overflowX;
|
| + document.body.style.overflowY = this._originalBodyStyles.overflowY;
|
| +
|
| + window.removeEventListener('wheel', this._scrollInteractionHandler, true);
|
| + window.removeEventListener('mousewheel', this._scrollInteractionHandler, true);
|
| + window.removeEventListener('DOMMouseScroll', this._scrollInteractionHandler, true);
|
| + window.removeEventListener('touchmove', this._scrollInteractionHandler, true);
|
| + document.removeEventListener('keydown', this._scrollInteractionHandler, true);
|
| + }
|
| + };
|
| + })();
|
| +(function() {
|
| + 'use strict';
|
| +
|
| + Polymer({
|
| + is: 'iron-dropdown',
|
| +
|
| + behaviors: [
|
| + Polymer.IronControlState,
|
| + Polymer.IronA11yKeysBehavior,
|
| + Polymer.IronOverlayBehavior,
|
| + Polymer.NeonAnimationRunnerBehavior
|
| + ],
|
| +
|
| + properties: {
|
| + /**
|
| + * The orientation against which to align the dropdown content
|
| + * horizontally relative to the dropdown trigger.
|
| + */
|
| + horizontalAlign: {
|
| + type: String,
|
| + value: 'left',
|
| + reflectToAttribute: true
|
| + },
|
| +
|
| + /**
|
| + * The orientation against which to align the dropdown content
|
| + * vertically relative to the dropdown trigger.
|
| + */
|
| + verticalAlign: {
|
| + type: String,
|
| + value: 'top',
|
| + reflectToAttribute: true
|
| + },
|
| +
|
| + /**
|
| + * A pixel value that will be added to the position calculated for the
|
| + * given `horizontalAlign`. Use a negative value to offset to the
|
| + * left, or a positive value to offset to the right.
|
| + */
|
| + horizontalOffset: {
|
| + type: Number,
|
| + value: 0,
|
| + notify: true
|
| + },
|
| +
|
| + /**
|
| + * A pixel value that will be added to the position calculated for the
|
| + * given `verticalAlign`. Use a negative value to offset towards the
|
| + * top, or a positive value to offset towards the bottom.
|
| + */
|
| + verticalOffset: {
|
| + type: Number,
|
| + value: 0,
|
| + notify: true
|
| + },
|
| +
|
| + /**
|
| + * The element that should be used to position the dropdown when
|
| + * it is opened.
|
| + */
|
| + positionTarget: {
|
| + type: Object,
|
| + observer: '_positionTargetChanged'
|
| + },
|
| +
|
| + /**
|
| + * An animation config. If provided, this will be used to animate the
|
| + * opening of the dropdown.
|
| + */
|
| + openAnimationConfig: {
|
| + type: Object
|
| + },
|
| +
|
| + /**
|
| + * An animation config. If provided, this will be used to animate the
|
| + * closing of the dropdown.
|
| + */
|
| + closeAnimationConfig: {
|
| + type: Object
|
| + },
|
| +
|
| + /**
|
| + * If provided, this will be the element that will be focused when
|
| + * the dropdown opens.
|
| + */
|
| + focusTarget: {
|
| + type: Object
|
| + },
|
| +
|
| + /**
|
| + * Set to true to disable animations when opening and closing the
|
| + * dropdown.
|
| + */
|
| + noAnimations: {
|
| + type: Boolean,
|
| + value: false
|
| + },
|
| +
|
| + /**
|
| + * By default, the dropdown will constrain scrolling on the page
|
| + * to itself when opened.
|
| + * Set to true in order to prevent scroll from being constrained
|
| + * to the dropdown when it opens.
|
| + */
|
| + allowOutsideScroll: {
|
| + type: Boolean,
|
| + value: false
|
| + },
|
| +
|
| + /**
|
| + * We memoize the positionTarget bounding rectangle so that we can
|
| + * limit the number of times it is queried per resize / relayout.
|
| + * @type {?Object}
|
| + */
|
| + _positionRectMemo: {
|
| + type: Object
|
| + }
|
| + },
|
| +
|
| + listeners: {
|
| + 'neon-animation-finish': '_onNeonAnimationFinish'
|
| + },
|
| +
|
| + observers: [
|
| + '_updateOverlayPosition(verticalAlign, horizontalAlign, verticalOffset, horizontalOffset)'
|
| + ],
|
| +
|
| + attached: function() {
|
| + if (this.positionTarget === undefined) {
|
| + this.positionTarget = this._defaultPositionTarget;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * The element that is contained by the dropdown, if any.
|
| + */
|
| + get containedElement() {
|
| + return Polymer.dom(this.$.content).getDistributedNodes()[0];
|
| + },
|
| +
|
| + /**
|
| + * The element that should be focused when the dropdown opens.
|
| + */
|
| + get _focusTarget() {
|
| + return this.focusTarget || this.containedElement;
|
| + },
|
| +
|
| + /**
|
| + * The element that should be used to position the dropdown when
|
| + * it opens, if no position target is configured.
|
| + */
|
| + get _defaultPositionTarget() {
|
| + var parent = Polymer.dom(this).parentNode;
|
| +
|
| + if (parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
| + parent = parent.host;
|
| + }
|
| +
|
| + return parent;
|
| + },
|
| +
|
| + /**
|
| + * The bounding rect of the position target.
|
| + */
|
| + get _positionRect() {
|
| + if (!this._positionRectMemo && this.positionTarget) {
|
| + this._positionRectMemo = this.positionTarget.getBoundingClientRect();
|
| + }
|
| +
|
| + return this._positionRectMemo;
|
| + },
|
| +
|
| + /**
|
| + * The horizontal offset value used to position the dropdown.
|
| + */
|
| + get _horizontalAlignTargetValue() {
|
| + var target;
|
| +
|
| + if (this.horizontalAlign === 'right') {
|
| + target = document.documentElement.clientWidth - this._positionRect.right;
|
| + } else {
|
| + target = this._positionRect.left;
|
| + }
|
| +
|
| + target += this.horizontalOffset;
|
| +
|
| + return Math.max(target, 0);
|
| + },
|
| +
|
| + /**
|
| + * The vertical offset value used to position the dropdown.
|
| + */
|
| + get _verticalAlignTargetValue() {
|
| + var target;
|
| +
|
| + if (this.verticalAlign === 'bottom') {
|
| + target = document.documentElement.clientHeight - this._positionRect.bottom;
|
| + } else {
|
| + target = this._positionRect.top;
|
| + }
|
| +
|
| + target += this.verticalOffset;
|
| +
|
| + return Math.max(target, 0);
|
| + },
|
| +
|
| + /**
|
| + * Called when the value of `opened` changes.
|
| + *
|
| + * @param {boolean} opened True if the dropdown is opened.
|
| + */
|
| + _openedChanged: function(opened) {
|
| + if (opened && this.disabled) {
|
| + this.cancel();
|
| + } else {
|
| + this.cancelAnimation();
|
| + this._prepareDropdown();
|
| + Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this, arguments);
|
| + }
|
| +
|
| + if (this.opened) {
|
| + this._focusContent();
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Overridden from `IronOverlayBehavior`.
|
| + */
|
| + _renderOpened: function() {
|
| + if (!this.allowOutsideScroll) {
|
| + Polymer.IronDropdownScrollManager.pushScrollLock(this);
|
| + }
|
| +
|
| + if (!this.noAnimations && this.animationConfig && this.animationConfig.open) {
|
| + this.$.contentWrapper.classList.add('animating');
|
| + this.playAnimation('open');
|
| + } else {
|
| + Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this, arguments);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Overridden from `IronOverlayBehavior`.
|
| + */
|
| + _renderClosed: function() {
|
| + Polymer.IronDropdownScrollManager.removeScrollLock(this);
|
| + if (!this.noAnimations && this.animationConfig && this.animationConfig.close) {
|
| + this.$.contentWrapper.classList.add('animating');
|
| + this.playAnimation('close');
|
| + } else {
|
| + Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this, arguments);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Called when animation finishes on the dropdown (when opening or
|
| + * closing). Responsible for "completing" the process of opening or
|
| + * closing the dropdown by positioning it or setting its display to
|
| + * none.
|
| + */
|
| + _onNeonAnimationFinish: function() {
|
| + this.$.contentWrapper.classList.remove('animating');
|
| + if (this.opened) {
|
| + Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this);
|
| + } else {
|
| + Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Called when an `iron-resize` event fires.
|
| + */
|
| + _onIronResize: function() {
|
| + var containedElement = this.containedElement;
|
| + var scrollTop;
|
| + var scrollLeft;
|
| +
|
| + if (containedElement) {
|
| + scrollTop = containedElement.scrollTop;
|
| + scrollLeft = containedElement.scrollLeft;
|
| + }
|
| +
|
| + if (this.opened) {
|
| + this._updateOverlayPosition();
|
| + }
|
| +
|
| + Polymer.IronOverlayBehaviorImpl._onIronResize.apply(this, arguments);
|
| +
|
| + if (containedElement) {
|
| + containedElement.scrollTop = scrollTop;
|
| + containedElement.scrollLeft = scrollLeft;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Called when the `positionTarget` property changes.
|
| + */
|
| + _positionTargetChanged: function() {
|
| + this._updateOverlayPosition();
|
| + },
|
| +
|
| + /**
|
| + * Constructs the final animation config from different properties used
|
| + * to configure specific parts of the opening and closing animations.
|
| + */
|
| + _updateAnimationConfig: function() {
|
| + var animationConfig = {};
|
| + var animations = [];
|
| +
|
| + if (this.openAnimationConfig) {
|
| + // NOTE(cdata): When making `display:none` elements visible in Safari,
|
| + // the element will paint once in a fully visible state, causing the
|
| + // dropdown to flash before it fades in. We prepend an
|
| + // `opaque-animation` to fix this problem:
|
| + animationConfig.open = [{
|
| + name: 'opaque-animation',
|
| + }].concat(this.openAnimationConfig);
|
| + animations = animations.concat(animationConfig.open);
|
| + }
|
| +
|
| + if (this.closeAnimationConfig) {
|
| + animationConfig.close = this.closeAnimationConfig;
|
| + animations = animations.concat(animationConfig.close);
|
| + }
|
| +
|
| + animations.forEach(function(animation) {
|
| + animation.node = this.containedElement;
|
| + }, this);
|
| +
|
| + this.animationConfig = animationConfig;
|
| + },
|
| +
|
| + /**
|
| + * Prepares the dropdown for opening by updating measured layout
|
| + * values.
|
| + */
|
| + _prepareDropdown: function() {
|
| + this.sizingTarget = this.containedElement || this.sizingTarget;
|
| + this._updateAnimationConfig();
|
| + this._updateOverlayPosition();
|
| + },
|
| +
|
| + /**
|
| + * Updates the overlay position based on configured horizontal
|
| + * and vertical alignment, and re-memoizes these values for the sake
|
| + * of behavior in `IronFitBehavior`.
|
| + */
|
| + _updateOverlayPosition: function() {
|
| + this._positionRectMemo = null;
|
| +
|
| + if (!this.positionTarget) {
|
| + return;
|
| + }
|
| +
|
| + this.style[this.horizontalAlign] =
|
| + this._horizontalAlignTargetValue + 'px';
|
| +
|
| + this.style[this.verticalAlign] =
|
| + this._verticalAlignTargetValue + 'px';
|
| +
|
| + // NOTE(cdata): We re-memoize inline styles here, otherwise
|
| + // calling `refit` from `IronFitBehavior` will reset inline styles
|
| + // to whatever they were when the dropdown first opened.
|
| + if (this._fitInfo) {
|
| + this._fitInfo.inlineStyle[this.horizontalAlign] =
|
| + this.style[this.horizontalAlign];
|
| +
|
| + this._fitInfo.inlineStyle[this.verticalAlign] =
|
| + this.style[this.verticalAlign];
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Focuses the configured focus target.
|
| + */
|
| + _focusContent: function() {
|
| + // NOTE(cdata): This is async so that it can attempt the focus after
|
| + // `display: none` is removed from the element.
|
| + this.async(function() {
|
| + if (this._focusTarget) {
|
| + this._focusTarget.focus();
|
| + }
|
| + });
|
| + }
|
| + });
|
| + })();
|
| +Polymer({
|
| +
|
| + is: 'fade-in-animation',
|
| +
|
| + behaviors: [
|
| + Polymer.NeonAnimationBehavior
|
| + ],
|
| +
|
| + configure: function(config) {
|
| + var node = config.node;
|
| + this._effect = new KeyframeEffect(node, [
|
| + {'opacity': '0'},
|
| + {'opacity': '1'}
|
| + ], this.timingFromConfig(config));
|
| + return this._effect;
|
| + }
|
| +
|
| + });
|
| +Polymer({
|
| +
|
| + is: 'fade-out-animation',
|
| +
|
| + behaviors: [
|
| + Polymer.NeonAnimationBehavior
|
| + ],
|
| +
|
| + configure: function(config) {
|
| + var node = config.node;
|
| + this._effect = new KeyframeEffect(node, [
|
| + {'opacity': '1'},
|
| + {'opacity': '0'}
|
| + ], this.timingFromConfig(config));
|
| + return this._effect;
|
| + }
|
| +
|
| + });
|
| +Polymer({
|
| + is: 'paper-menu-grow-height-animation',
|
| +
|
| + behaviors: [
|
| + Polymer.NeonAnimationBehavior
|
| + ],
|
| +
|
| + configure: function(config) {
|
| + var node = config.node;
|
| + var rect = node.getBoundingClientRect();
|
| + var height = rect.height;
|
| +
|
| + this._effect = new KeyframeEffect(node, [{
|
| + height: (height / 2) + 'px'
|
| + }, {
|
| + height: height + 'px'
|
| + }], this.timingFromConfig(config));
|
| +
|
| + return this._effect;
|
| + }
|
| + });
|
| +
|
| + Polymer({
|
| + is: 'paper-menu-grow-width-animation',
|
| +
|
| + behaviors: [
|
| + Polymer.NeonAnimationBehavior
|
| + ],
|
| +
|
| + configure: function(config) {
|
| + var node = config.node;
|
| + var rect = node.getBoundingClientRect();
|
| + var width = rect.width;
|
| +
|
| + this._effect = new KeyframeEffect(node, [{
|
| + width: (width / 2) + 'px'
|
| + }, {
|
| + width: width + 'px'
|
| + }], this.timingFromConfig(config));
|
| +
|
| + return this._effect;
|
| + }
|
| + });
|
| +
|
| + Polymer({
|
| + is: 'paper-menu-shrink-width-animation',
|
| +
|
| + behaviors: [
|
| + Polymer.NeonAnimationBehavior
|
| + ],
|
| +
|
| + configure: function(config) {
|
| + var node = config.node;
|
| + var rect = node.getBoundingClientRect();
|
| + var width = rect.width;
|
| +
|
| + this._effect = new KeyframeEffect(node, [{
|
| + width: width + 'px'
|
| + }, {
|
| + width: width - (width / 20) + 'px'
|
| + }], this.timingFromConfig(config));
|
| +
|
| + return this._effect;
|
| + }
|
| + });
|
| +
|
| + Polymer({
|
| + is: 'paper-menu-shrink-height-animation',
|
| +
|
| + behaviors: [
|
| + Polymer.NeonAnimationBehavior
|
| + ],
|
| +
|
| + configure: function(config) {
|
| + var node = config.node;
|
| + var rect = node.getBoundingClientRect();
|
| + var height = rect.height;
|
| + var top = rect.top;
|
| +
|
| + this.setPrefixedProperty(node, 'transformOrigin', '0 0');
|
| +
|
| + this._effect = new KeyframeEffect(node, [{
|
| + height: height + 'px',
|
| + transform: 'translateY(0)'
|
| + }, {
|
| + height: height / 2 + 'px',
|
| + transform: 'translateY(-20px)'
|
| + }], this.timingFromConfig(config));
|
| +
|
| + return this._effect;
|
| + }
|
| + });
|
| +(function() {
|
| + 'use strict';
|
| +
|
| + var PaperMenuButton = Polymer({
|
| + is: 'paper-menu-button',
|
| +
|
| + /**
|
| + * Fired when the dropdown opens.
|
| + *
|
| + * @event paper-dropdown-open
|
| + */
|
| +
|
| + /**
|
| + * Fired when the dropdown closes.
|
| + *
|
| + * @event paper-dropdown-close
|
| + */
|
| +
|
| + behaviors: [
|
| + Polymer.IronA11yKeysBehavior,
|
| + Polymer.IronControlState
|
| + ],
|
| +
|
| + properties: {
|
| +
|
| + /**
|
| + * True if the content is currently displayed.
|
| + */
|
| + opened: {
|
| + type: Boolean,
|
| + value: false,
|
| + notify: true,
|
| + observer: '_openedChanged'
|
| + },
|
| +
|
| + /**
|
| + * The orientation against which to align the menu dropdown
|
| + * horizontally relative to the dropdown trigger.
|
| + */
|
| + horizontalAlign: {
|
| + type: String,
|
| + value: 'left',
|
| + reflectToAttribute: true
|
| + },
|
| +
|
| + /**
|
| + * The orientation against which to align the menu dropdown
|
| + * vertically relative to the dropdown trigger.
|
| + */
|
| + verticalAlign: {
|
| + type: String,
|
| + value: 'top',
|
| + reflectToAttribute: true
|
| + },
|
| +
|
| + /**
|
| + * A pixel value that will be added to the position calculated for the
|
| + * given `horizontalAlign`. Use a negative value to offset to the
|
| + * left, or a positive value to offset to the right.
|
| + */
|
| + horizontalOffset: {
|
| + type: Number,
|
| + value: 0,
|
| + notify: true
|
| + },
|
| +
|
| + /**
|
| + * A pixel value that will be added to the position calculated for the
|
| + * given `verticalAlign`. Use a negative value to offset towards the
|
| + * top, or a positive value to offset towards the bottom.
|
| + */
|
| + verticalOffset: {
|
| + type: Number,
|
| + value: 0,
|
| + notify: true
|
| + },
|
| +
|
| + /**
|
| + * Set to true to disable animations when opening and closing the
|
| + * dropdown.
|
| + */
|
| + noAnimations: {
|
| + type: Boolean,
|
| + value: false
|
| + },
|
| +
|
| + /**
|
| + * Set to true to disable automatically closing the dropdown after
|
| + * a selection has been made.
|
| + */
|
| + ignoreSelect: {
|
| + type: Boolean,
|
| + value: false
|
| + },
|
| +
|
| + /**
|
| + * An animation config. If provided, this will be used to animate the
|
| + * opening of the dropdown.
|
| + */
|
| + openAnimationConfig: {
|
| + type: Object,
|
| + value: function() {
|
| + return [{
|
| + name: 'fade-in-animation',
|
| + timing: {
|
| + delay: 100,
|
| + duration: 200
|
| + }
|
| + }, {
|
| + name: 'paper-menu-grow-width-animation',
|
| + timing: {
|
| + delay: 100,
|
| + duration: 150,
|
| + easing: PaperMenuButton.ANIMATION_CUBIC_BEZIER
|
| + }
|
| + }, {
|
| + name: 'paper-menu-grow-height-animation',
|
| + timing: {
|
| + delay: 100,
|
| + duration: 275,
|
| + easing: PaperMenuButton.ANIMATION_CUBIC_BEZIER
|
| + }
|
| + }];
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * An animation config. If provided, this will be used to animate the
|
| + * closing of the dropdown.
|
| + */
|
| + closeAnimationConfig: {
|
| + type: Object,
|
| + value: function() {
|
| + return [{
|
| + name: 'fade-out-animation',
|
| + timing: {
|
| + duration: 150
|
| + }
|
| + }, {
|
| + name: 'paper-menu-shrink-width-animation',
|
| + timing: {
|
| + delay: 100,
|
| + duration: 50,
|
| + easing: PaperMenuButton.ANIMATION_CUBIC_BEZIER
|
| + }
|
| + }, {
|
| + name: 'paper-menu-shrink-height-animation',
|
| + timing: {
|
| + duration: 200,
|
| + easing: 'ease-in'
|
| + }
|
| + }];
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * This is the element intended to be bound as the focus target
|
| + * for the `iron-dropdown` contained by `paper-menu-button`.
|
| + */
|
| + _dropdownContent: {
|
| + type: Object
|
| + }
|
| + },
|
| +
|
| + hostAttributes: {
|
| + role: 'group',
|
| + 'aria-haspopup': 'true'
|
| + },
|
| +
|
| + listeners: {
|
| + 'iron-select': '_onIronSelect'
|
| + },
|
| +
|
| + /**
|
| + * The content element that is contained by the menu button, if any.
|
| + */
|
| + get contentElement() {
|
| + return Polymer.dom(this.$.content).getDistributedNodes()[0];
|
| + },
|
| +
|
| + /**
|
| + * Make the dropdown content appear as an overlay positioned relative
|
| + * to the dropdown trigger.
|
| + */
|
| + open: function() {
|
| + if (this.disabled) {
|
| + return;
|
| + }
|
| +
|
| + this.$.dropdown.open();
|
| + },
|
| +
|
| + /**
|
| + * Hide the dropdown content.
|
| + */
|
| + close: function() {
|
| + this.$.dropdown.close();
|
| + },
|
| +
|
| + /**
|
| + * When an `iron-select` event is received, the dropdown should
|
| + * automatically close on the assumption that a value has been chosen.
|
| + *
|
| + * @param {CustomEvent} event A CustomEvent instance with type
|
| + * set to `"iron-select"`.
|
| + */
|
| + _onIronSelect: function(event) {
|
| + if (!this.ignoreSelect) {
|
| + this.close();
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * When the dropdown opens, the `paper-menu-button` fires `paper-open`.
|
| + * When the dropdown closes, the `paper-menu-button` fires `paper-close`.
|
| + *
|
| + * @param {boolean} opened True if the dropdown is opened, otherwise false.
|
| + * @param {boolean} oldOpened The previous value of `opened`.
|
| + */
|
| + _openedChanged: function(opened, oldOpened) {
|
| + if (opened) {
|
| + // TODO(cdata): Update this when we can measure changes in distributed
|
| + // children in an idiomatic way.
|
| + // We poke this property in case the element has changed. This will
|
| + // cause the focus target for the `iron-dropdown` to be updated as
|
| + // necessary:
|
| + this._dropdownContent = this.contentElement;
|
| + this.fire('paper-dropdown-open');
|
| + } else if (oldOpened != null) {
|
| + this.fire('paper-dropdown-close');
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * If the dropdown is open when disabled becomes true, close the
|
| + * dropdown.
|
| + *
|
| + * @param {boolean} disabled True if disabled, otherwise false.
|
| + */
|
| + _disabledChanged: function(disabled) {
|
| + Polymer.IronControlState._disabledChanged.apply(this, arguments);
|
| + if (disabled && this.opened) {
|
| + this.close();
|
| + }
|
| + }
|
| + });
|
| +
|
| + PaperMenuButton.ANIMATION_CUBIC_BEZIER = 'cubic-bezier(.3,.95,.5,1)';
|
| + PaperMenuButton.MAX_ANIMATION_TIME_MS = 400;
|
| +
|
| + Polymer.PaperMenuButton = PaperMenuButton;
|
| + })();
|
| +/**
|
| + * `Polymer.PaperInkyFocusBehavior` implements a ripple when the element has keyboard focus.
|
| + *
|
| + * @polymerBehavior Polymer.PaperInkyFocusBehavior
|
| + */
|
| + Polymer.PaperInkyFocusBehaviorImpl = {
|
| +
|
| + observers: [
|
| + '_focusedChanged(receivedFocusFromKeyboard)'
|
| + ],
|
| +
|
| + _focusedChanged: function(receivedFocusFromKeyboard) {
|
| + if (!this.$.ink) {
|
| + return;
|
| + }
|
| +
|
| + this.$.ink.holdDown = receivedFocusFromKeyboard;
|
| + }
|
| +
|
| + };
|
| +
|
| + /** @polymerBehavior Polymer.PaperInkyFocusBehavior */
|
| + Polymer.PaperInkyFocusBehavior = [
|
| + Polymer.IronButtonState,
|
| + Polymer.IronControlState,
|
| + Polymer.PaperInkyFocusBehaviorImpl
|
| + ];
|
| +Polymer({
|
| + is: 'paper-icon-button',
|
| +
|
| + hostAttributes: {
|
| + role: 'button',
|
| + tabindex: '0'
|
| + },
|
| +
|
| + behaviors: [
|
| + Polymer.PaperInkyFocusBehavior
|
| + ],
|
| +
|
| + properties: {
|
| + /**
|
| + * The URL of an image for the icon. If the src property is specified,
|
| + * the icon property should not be.
|
| + */
|
| + src: {
|
| + type: String
|
| + },
|
| +
|
| + /**
|
| + * Specifies the icon name or index in the set of icons available in
|
| + * the icon's icon set. If the icon property is specified,
|
| + * the src property should not be.
|
| + */
|
| + icon: {
|
| + type: String
|
| + },
|
| +
|
| + /**
|
| + * Specifies the alternate text for the button, for accessibility.
|
| + */
|
| + alt: {
|
| + type: String,
|
| + observer: "_altChanged"
|
| + }
|
| + },
|
| +
|
| + _altChanged: function(newValue, oldValue) {
|
| + var label = this.getAttribute('aria-label');
|
| +
|
| + // Don't stomp over a user-set aria-label.
|
| + if (!label || oldValue == label) {
|
| + this.setAttribute('aria-label', newValue);
|
| + }
|
| + }
|
| + });
|
| +/**
|
| + * Use `Polymer.IronValidatableBehavior` to implement an element that validates user input.
|
| + *
|
| + * ### Accessibility
|
| + *
|
| + * Changing the `invalid` property, either manually or by calling `validate()` will update the
|
| + * `aria-invalid` attribute.
|
| + *
|
| + * @demo demo/index.html
|
| + * @polymerBehavior
|
| + */
|
| + Polymer.IronValidatableBehavior = {
|
| +
|
| + properties: {
|
| +
|
| + /**
|
| + * Namespace for this validator.
|
| + */
|
| + validatorType: {
|
| + type: String,
|
| + value: 'validator'
|
| + },
|
| +
|
| + /**
|
| + * Name of the validator to use.
|
| + */
|
| + validator: {
|
| + type: String
|
| + },
|
| +
|
| + /**
|
| + * True if the last call to `validate` is invalid.
|
| + */
|
| + invalid: {
|
| + notify: true,
|
| + reflectToAttribute: true,
|
| + type: Boolean,
|
| + value: false
|
| + },
|
| +
|
| + _validatorMeta: {
|
| + type: Object
|
| + }
|
| +
|
| + },
|
| +
|
| + observers: [
|
| + '_invalidChanged(invalid)'
|
| + ],
|
| +
|
| + get _validator() {
|
| + return this._validatorMeta && this._validatorMeta.byKey(this.validator);
|
| + },
|
| +
|
| + ready: function() {
|
| + this._validatorMeta = new Polymer.IronMeta({type: this.validatorType});
|
| + },
|
| +
|
| + _invalidChanged: function() {
|
| + if (this.invalid) {
|
| + this.setAttribute('aria-invalid', 'true');
|
| + } else {
|
| + this.removeAttribute('aria-invalid');
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * @return {boolean} True if the validator `validator` exists.
|
| + */
|
| + hasValidator: function() {
|
| + return this._validator != null;
|
| + },
|
| +
|
| + /**
|
| + * Returns true if the `value` is valid, and updates `invalid`. If you want
|
| + * your element to have custom validation logic, do not override this method;
|
| + * override `_getValidity(value)` instead.
|
| +
|
| + * @param {Object} value The value to be validated. By default, it is passed
|
| + * to the validator's `validate()` function, if a validator is set.
|
| + * @return {boolean} True if `value` is valid.
|
| + */
|
| + validate: function(value) {
|
| + this.invalid = !this._getValidity(value);
|
| + return !this.invalid;
|
| + },
|
| +
|
| + /**
|
| + * Returns true if `value` is valid. By default, it is passed
|
| + * to the validator's `validate()` function, if a validator is set. You
|
| + * should override this method if you want to implement custom validity
|
| + * logic for your element.
|
| + *
|
| + * @param {Object} value The value to be validated.
|
| + * @return {boolean} True if `value` is valid.
|
| + */
|
| +
|
| + _getValidity: function(value) {
|
| + if (this.hasValidator()) {
|
| + return this._validator.validate(value);
|
| + }
|
| + return true;
|
| + }
|
| + };
|
| +/*
|
| +`<iron-input>` adds two-way binding and custom validators using `Polymer.IronValidatorBehavior`
|
| +to `<input>`.
|
| +
|
| +### Two-way binding
|
| +
|
| +By default you can only get notified of changes to an `input`'s `value` due to user input:
|
| +
|
| + <input value="{{myValue::input}}">
|
| +
|
| +`iron-input` adds the `bind-value` property that mirrors the `value` property, and can be used
|
| +for two-way data binding. `bind-value` will notify if it is changed either by user input or by script.
|
| +
|
| + <input is="iron-input" bind-value="{{myValue}}">
|
| +
|
| +### Custom validators
|
| +
|
| +You can use custom validators that implement `Polymer.IronValidatorBehavior` with `<iron-input>`.
|
| +
|
| + <input is="iron-input" validator="my-custom-validator">
|
| +
|
| +### Stopping invalid input
|
| +
|
| +It may be desirable to only allow users to enter certain characters. You can use the
|
| +`prevent-invalid-input` and `allowed-pattern` attributes together to accomplish this. This feature
|
| +is separate from validation, and `allowed-pattern` does not affect how the input is validated.
|
| +
|
| + <!-- only allow characters that match [0-9] -->
|
| + <input is="iron-input" prevent-invalid-input allowed-pattern="[0-9]">
|
| +
|
| +@hero hero.svg
|
| +@demo demo/index.html
|
| +*/
|
| +
|
| + Polymer({
|
| +
|
| + is: 'iron-input',
|
| +
|
| + extends: 'input',
|
| +
|
| + behaviors: [
|
| + Polymer.IronValidatableBehavior
|
| + ],
|
| +
|
| + properties: {
|
| +
|
| + /**
|
| + * Use this property instead of `value` for two-way data binding.
|
| + */
|
| + bindValue: {
|
| + observer: '_bindValueChanged',
|
| + type: String
|
| + },
|
| +
|
| + /**
|
| + * Set to true to prevent the user from entering invalid input. The new input characters are
|
| + * matched with `allowedPattern` if it is set, otherwise it will use the `pattern` attribute if
|
| + * set, or the `type` attribute (only supported for `type=number`).
|
| + */
|
| + preventInvalidInput: {
|
| + type: Boolean
|
| + },
|
| +
|
| + /**
|
| + * Regular expression to match valid input characters.
|
| + */
|
| + allowedPattern: {
|
| + type: String
|
| + },
|
| +
|
| + _previousValidInput: {
|
| + type: String,
|
| + value: ''
|
| + },
|
| +
|
| + _patternAlreadyChecked: {
|
| + type: Boolean,
|
| + value: false
|
| + }
|
| +
|
| + },
|
| +
|
| + listeners: {
|
| + 'input': '_onInput',
|
| + 'keypress': '_onKeypress'
|
| + },
|
| +
|
| + get _patternRegExp() {
|
| + var pattern;
|
| + if (this.allowedPattern) {
|
| + pattern = new RegExp(this.allowedPattern);
|
| + } else if (this.pattern) {
|
| + pattern = new RegExp(this.pattern);
|
| + } else {
|
| + switch (this.type) {
|
| + case 'number':
|
| + pattern = /[0-9.,e-]/;
|
| + break;
|
| + }
|
| + }
|
| + return pattern;
|
| + },
|
| +
|
| + ready: function() {
|
| + this.bindValue = this.value;
|
| + },
|
| +
|
| + /**
|
| + * @suppress {checkTypes}
|
| + */
|
| + _bindValueChanged: function() {
|
| + if (this.value !== this.bindValue) {
|
| + this.value = !(this.bindValue || this.bindValue === 0) ? '' : this.bindValue;
|
| + }
|
| + // manually notify because we don't want to notify until after setting value
|
| + this.fire('bind-value-changed', {value: this.bindValue});
|
| + },
|
| +
|
| + _onInput: function() {
|
| + // Need to validate each of the characters pasted if they haven't
|
| + // been validated inside `_onKeypress` already.
|
| + if (this.preventInvalidInput && !this._patternAlreadyChecked) {
|
| + var valid = this._checkPatternValidity();
|
| + if (!valid) {
|
| + this.value = this._previousValidInput;
|
| + }
|
| + }
|
| +
|
| + this.bindValue = this.value;
|
| + this._previousValidInput = this.value;
|
| + this._patternAlreadyChecked = false;
|
| + },
|
| +
|
| + _isPrintable: function(event) {
|
| + // What a control/printable character is varies wildly based on the browser.
|
| + // - most control characters (arrows, backspace) do not send a `keypress` event
|
| + // in Chrome, but the *do* on Firefox
|
| + // - in Firefox, when they do send a `keypress` event, control chars have
|
| + // a charCode = 0, keyCode = xx (for ex. 40 for down arrow)
|
| + // - printable characters always send a keypress event.
|
| + // - in Firefox, printable chars always have a keyCode = 0. In Chrome, the keyCode
|
| + // always matches the charCode.
|
| + // None of this makes any sense.
|
| +
|
| + // For these keys, ASCII code == browser keycode.
|
| + var anyNonPrintable =
|
| + (event.keyCode == 8) || // backspace
|
| + (event.keyCode == 9) || // tab
|
| + (event.keyCode == 13) || // enter
|
| + (event.keyCode == 27); // escape
|
| +
|
| + // For these keys, make sure it's a browser keycode and not an ASCII code.
|
| + var mozNonPrintable =
|
| + (event.keyCode == 19) || // pause
|
| + (event.keyCode == 20) || // caps lock
|
| + (event.keyCode == 45) || // insert
|
| + (event.keyCode == 46) || // delete
|
| + (event.keyCode == 144) || // num lock
|
| + (event.keyCode == 145) || // scroll lock
|
| + (event.keyCode > 32 && event.keyCode < 41) || // page up/down, end, home, arrows
|
| + (event.keyCode > 111 && event.keyCode < 124); // fn keys
|
| +
|
| + return !anyNonPrintable && !(event.charCode == 0 && mozNonPrintable);
|
| + },
|
| +
|
| + _onKeypress: function(event) {
|
| + if (!this.preventInvalidInput && this.type !== 'number') {
|
| + return;
|
| + }
|
| + var regexp = this._patternRegExp;
|
| + if (!regexp) {
|
| + return;
|
| + }
|
| +
|
| + // Handle special keys and backspace
|
| + if (event.metaKey || event.ctrlKey || event.altKey)
|
| + return;
|
| +
|
| + // Check the pattern either here or in `_onInput`, but not in both.
|
| + this._patternAlreadyChecked = true;
|
| +
|
| + var thisChar = String.fromCharCode(event.charCode);
|
| + if (this._isPrintable(event) && !regexp.test(thisChar)) {
|
| + event.preventDefault();
|
| + }
|
| + },
|
| +
|
| + _checkPatternValidity: function() {
|
| + var regexp = this._patternRegExp;
|
| + if (!regexp) {
|
| + return true;
|
| + }
|
| + for (var i = 0; i < this.value.length; i++) {
|
| + if (!regexp.test(this.value[i])) {
|
| + return false;
|
| + }
|
| + }
|
| + return true;
|
| + },
|
| +
|
| + /**
|
| + * Returns true if `value` is valid. The validator provided in `validator` will be used first,
|
| + * then any constraints.
|
| + * @return {boolean} True if the value is valid.
|
| + */
|
| + validate: function() {
|
| + // Empty, non-required input is valid.
|
| + if (!this.required && this.value == '') {
|
| + this.invalid = false;
|
| + return true;
|
| + }
|
| +
|
| + var valid;
|
| + if (this.hasValidator()) {
|
| + valid = Polymer.IronValidatableBehavior.validate.call(this, this.value);
|
| + } else {
|
| + this.invalid = !this.validity.valid;
|
| + valid = this.validity.valid;
|
| + }
|
| + this.fire('iron-input-validate');
|
| + return valid;
|
| + }
|
| +
|
| + });
|
| +
|
| + /*
|
| + The `iron-input-validate` event is fired whenever `validate()` is called.
|
| + @event iron-input-validate
|
| + */
|
| +Polymer({
|
| + is: 'paper-input-container',
|
| +
|
| + properties: {
|
| + /**
|
| + * Set to true to disable the floating label. The label disappears when the input value is
|
| + * not null.
|
| + */
|
| + noLabelFloat: {
|
| + type: Boolean,
|
| + value: false
|
| + },
|
| +
|
| + /**
|
| + * Set to true to always float the floating label.
|
| + */
|
| + alwaysFloatLabel: {
|
| + type: Boolean,
|
| + value: false
|
| + },
|
| +
|
| + /**
|
| + * The attribute to listen for value changes on.
|
| + */
|
| + attrForValue: {
|
| + type: String,
|
| + value: 'bind-value'
|
| + },
|
| +
|
| + /**
|
| + * Set to true to auto-validate the input value when it changes.
|
| + */
|
| + autoValidate: {
|
| + type: Boolean,
|
| + value: false
|
| + },
|
| +
|
| + /**
|
| + * True if the input is invalid. This property is set automatically when the input value
|
| + * changes if auto-validating, or when the `iron-input-validate` event is heard from a child.
|
| + */
|
| + invalid: {
|
| + observer: '_invalidChanged',
|
| + type: Boolean,
|
| + value: false
|
| + },
|
| +
|
| + /**
|
| + * True if the input has focus.
|
| + */
|
| + focused: {
|
| + readOnly: true,
|
| + type: Boolean,
|
| + value: false,
|
| + notify: true
|
| + },
|
| +
|
| + _addons: {
|
| + type: Array
|
| + // do not set a default value here intentionally - it will be initialized lazily when a
|
| + // distributed child is attached, which may occur before configuration for this element
|
| + // in polyfill.
|
| + },
|
| +
|
| + _inputHasContent: {
|
| + type: Boolean,
|
| + value: false
|
| + },
|
| +
|
| + _inputSelector: {
|
| + type: String,
|
| + value: 'input,textarea,.paper-input-input'
|
| + },
|
| +
|
| + _boundOnFocus: {
|
| + type: Function,
|
| + value: function() {
|
| + return this._onFocus.bind(this);
|
| + }
|
| + },
|
| +
|
| + _boundOnBlur: {
|
| + type: Function,
|
| + value: function() {
|
| + return this._onBlur.bind(this);
|
| + }
|
| + },
|
| +
|
| + _boundOnInput: {
|
| + type: Function,
|
| + value: function() {
|
| + return this._onInput.bind(this);
|
| + }
|
| + },
|
| +
|
| + _boundValueChanged: {
|
| + type: Function,
|
| + value: function() {
|
| + return this._onValueChanged.bind(this);
|
| + }
|
| + }
|
| + },
|
| +
|
| + listeners: {
|
| + 'addon-attached': '_onAddonAttached',
|
| + 'iron-input-validate': '_onIronInputValidate'
|
| + },
|
| +
|
| + get _valueChangedEvent() {
|
| + return this.attrForValue + '-changed';
|
| + },
|
| +
|
| + get _propertyForValue() {
|
| + return Polymer.CaseMap.dashToCamelCase(this.attrForValue);
|
| + },
|
| +
|
| + get _inputElement() {
|
| + return Polymer.dom(this).querySelector(this._inputSelector);
|
| + },
|
| +
|
| + get _inputElementValue() {
|
| + return this._inputElement[this._propertyForValue] || this._inputElement.value;
|
| + },
|
| +
|
| + ready: function() {
|
| + if (!this._addons) {
|
| + this._addons = [];
|
| + }
|
| + this.addEventListener('focus', this._boundOnFocus, true);
|
| + this.addEventListener('blur', this._boundOnBlur, true);
|
| + if (this.attrForValue) {
|
| + this._inputElement.addEventListener(this._valueChangedEvent, this._boundValueChanged);
|
| + } else {
|
| + this.addEventListener('input', this._onInput);
|
| + }
|
| + },
|
| +
|
| + attached: function() {
|
| + // Only validate when attached if the input already has a value.
|
| + if (this._inputElementValue != '') {
|
| + this._handleValueAndAutoValidate(this._inputElement);
|
| + } else {
|
| + this._handleValue(this._inputElement);
|
| + }
|
| + },
|
| +
|
| + _onAddonAttached: function(event) {
|
| + if (!this._addons) {
|
| + this._addons = [];
|
| + }
|
| + var target = event.target;
|
| + if (this._addons.indexOf(target) === -1) {
|
| + this._addons.push(target);
|
| + if (this.isAttached) {
|
| + this._handleValue(this._inputElement);
|
| + }
|
| + }
|
| + },
|
| +
|
| + _onFocus: function() {
|
| + this._setFocused(true);
|
| + },
|
| +
|
| + _onBlur: function() {
|
| + this._setFocused(false);
|
| + this._handleValueAndAutoValidate(this._inputElement);
|
| + },
|
| +
|
| + _onInput: function(event) {
|
| + this._handleValueAndAutoValidate(event.target);
|
| + },
|
| +
|
| + _onValueChanged: function(event) {
|
| + this._handleValueAndAutoValidate(event.target);
|
| + },
|
| +
|
| + _handleValue: function(inputElement) {
|
| + var value = this._inputElementValue;
|
| +
|
| + // type="number" hack needed because this.value is empty until it's valid
|
| + if (value || value === 0 || (inputElement.type === 'number' && !inputElement.checkValidity())) {
|
| + this._inputHasContent = true;
|
| + } else {
|
| + this._inputHasContent = false;
|
| + }
|
| +
|
| + this.updateAddons({
|
| + inputElement: inputElement,
|
| + value: value,
|
| + invalid: this.invalid
|
| + });
|
| + },
|
| +
|
| + _handleValueAndAutoValidate: function(inputElement) {
|
| + if (this.autoValidate) {
|
| + var valid;
|
| + if (inputElement.validate) {
|
| + valid = inputElement.validate(this._inputElementValue);
|
| + } else {
|
| + valid = inputElement.checkValidity();
|
| + }
|
| + this.invalid = !valid;
|
| + }
|
| +
|
| + // Call this last to notify the add-ons.
|
| + this._handleValue(inputElement);
|
| + },
|
| +
|
| + _onIronInputValidate: function(event) {
|
| + this.invalid = this._inputElement.invalid;
|
| + },
|
| +
|
| + _invalidChanged: function() {
|
| + if (this._addons) {
|
| + this.updateAddons({invalid: this.invalid});
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Call this to update the state of add-ons.
|
| + * @param {Object} state Add-on state.
|
| + */
|
| + updateAddons: function(state) {
|
| + for (var addon, index = 0; addon = this._addons[index]; index++) {
|
| + addon.update(state);
|
| + }
|
| + },
|
| +
|
| + _computeInputContentClass: function(noLabelFloat, alwaysFloatLabel, focused, invalid, _inputHasContent) {
|
| + var cls = 'input-content';
|
| + if (!noLabelFloat) {
|
| + var label = this.querySelector('label');
|
| +
|
| + if (alwaysFloatLabel || _inputHasContent) {
|
| + cls += ' label-is-floating';
|
| + if (invalid) {
|
| + cls += ' is-invalid';
|
| + } else if (focused) {
|
| + cls += " label-is-highlighted";
|
| + }
|
| + // The label might have a horizontal offset if a prefix element exists
|
| + // which needs to be undone when displayed as a floating label.
|
| + if (Polymer.dom(this.$.prefix).getDistributedNodes().length > 0 &&
|
| + label && label.offsetParent) {
|
| + label.style.left = -label.offsetParent.offsetLeft + 'px';
|
| + }
|
| + } else {
|
| + // When the label is not floating, it should overlap the input element.
|
| + if (label) {
|
| + label.style.left = 0;
|
| + }
|
| + }
|
| + } else {
|
| + if (_inputHasContent) {
|
| + cls += ' label-is-hidden';
|
| + }
|
| + }
|
| + return cls;
|
| + },
|
| +
|
| + _computeUnderlineClass: function(focused, invalid) {
|
| + var cls = 'underline';
|
| + if (invalid) {
|
| + cls += ' is-invalid';
|
| + } else if (focused) {
|
| + cls += ' is-highlighted'
|
| + }
|
| + return cls;
|
| + },
|
| +
|
| + _computeAddOnContentClass: function(focused, invalid) {
|
| + var cls = 'add-on-content';
|
| + if (invalid) {
|
| + cls += ' is-invalid';
|
| + } else if (focused) {
|
| + cls += ' is-highlighted'
|
| + }
|
| + return cls;
|
| + }
|
| + });
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +/** @interface */
|
| +var SearchFieldDelegate = function() {};
|
| +
|
| +SearchFieldDelegate.prototype = {
|
| + /**
|
| + * @param {string} value
|
| + */
|
| + onSearchTermSearch: assertNotReached,
|
| +};
|
| +
|
| +var SearchField = Polymer({
|
| + is: 'cr-search-field',
|
| +
|
| + properties: {
|
| + label: {
|
| + type: String,
|
| + value: '',
|
| + },
|
| +
|
| + clearLabel: {
|
| + type: String,
|
| + value: '',
|
| + },
|
| +
|
| + showingSearch_: {
|
| + type: Boolean,
|
| + value: false,
|
| + },
|
| + },
|
| +
|
| + /** @param {SearchFieldDelegate} delegate */
|
| + setDelegate: function(delegate) {
|
| + this.delegate_ = delegate;
|
| + },
|
| +
|
| + /**
|
| + * Returns the value of the search field.
|
| + * @return {string}
|
| + */
|
| + getValue: function() {
|
| + var searchInput = this.$$('#search-input');
|
| + return searchInput ? searchInput.value : '';
|
| + },
|
| +
|
| + /** @private */
|
| + onSearchTermSearch_: function() {
|
| + if (this.delegate_)
|
| + this.delegate_.onSearchTermSearch(this.getValue());
|
| + },
|
| +
|
| + /** @private */
|
| + onSearchTermKeydown_: function(e) {
|
| + assert(this.showingSearch_);
|
| + if (e.keyIdentifier == 'U+001B') // Escape.
|
| + this.toggleShowingSearch_();
|
| + },
|
| +
|
| + /** @private */
|
| + toggleShowingSearch_: function() {
|
| + this.showingSearch_ = !this.showingSearch_;
|
| + this.async(function() {
|
| + var searchInput = this.$$('#search-input');
|
| + if (this.showingSearch_) {
|
| + searchInput.focus();
|
| + } else {
|
| + searchInput.value = '';
|
| + this.onSearchTermSearch_();
|
| + }
|
| + });
|
| + },
|
| +});
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +cr.define('downloads', function() {
|
| + var Toolbar = Polymer({
|
| + is: 'downloads-toolbar',
|
| +
|
| + /** @param {!downloads.ActionService} actionService */
|
| + setActionService: function(actionService) {
|
| + /** @private {!downloads.ActionService} */
|
| + this.actionService_ = actionService;
|
| + },
|
| +
|
| + attached: function() {
|
| + /** @private {!SearchFieldDelegate} */
|
| + this.searchFieldDelegate_ = new ToolbarSearchFieldDelegate(this);
|
| + this.$['search-input'].setDelegate(this.searchFieldDelegate_);
|
| + },
|
| +
|
| + properties: {
|
| + downloadsShowing: {
|
| + reflectToAttribute: true,
|
| + type: Boolean,
|
| + value: false,
|
| + observer: 'onDownloadsShowingChange_',
|
| + },
|
| + },
|
| +
|
| + /** @return {boolean} Whether removal can be undone. */
|
| + canUndo: function() {
|
| + return this.$['search-input'] != this.shadowRoot.activeElement;
|
| + },
|
| +
|
| + /** @return {boolean} Whether "Clear all" should be allowed. */
|
| + canClearAll: function() {
|
| + return !this.$['search-input'].getValue() && this.downloadsShowing;
|
| + },
|
| +
|
| + /** @private */
|
| + onClearAllClick_: function() {
|
| + assert(this.canClearAll());
|
| + this.actionService_.clearAll();
|
| + },
|
| +
|
| + /** @private */
|
| + onDownloadsShowingChange_: function() {
|
| + this.updateClearAll_();
|
| + },
|
| +
|
| + /** @param {string} searchTerm */
|
| + onSearchTermSearch: function(searchTerm) {
|
| + this.actionService_.search(searchTerm);
|
| + this.updateClearAll_();
|
| + },
|
| +
|
| + /** @private */
|
| + onOpenDownloadsFolderClick_: function() {
|
| + this.actionService_.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',
|
| +
|
| + created: function() {
|
| + /** @private {!downloads.ActionService} */
|
| + this.actionService_ = new downloads.ActionService;
|
| + },
|
| +
|
| + properties: {
|
| + hasDownloads_: {
|
| + type: Boolean,
|
| + value: false,
|
| + },
|
| + },
|
| +
|
| + /**
|
| + * @return {number} A guess at how many items could be visible at once.
|
| + * @private
|
| + */
|
| + guesstimateNumberOfVisibleItems_: function() {
|
| + var toolbarHeight = this.$.toolbar.offsetHeight;
|
| + return Math.floor((window.innerHeight - toolbarHeight) / 46) + 1;
|
| + },
|
| +
|
| + /**
|
| + * @param {Event} e
|
| + * @private
|
| + */
|
| + onCanExecute_: function(e) {
|
| + e = /** @type {cr.ui.CanExecuteEvent} */(e);
|
| + switch (e.command.id) {
|
| + case 'undo-command':
|
| + e.canExecute = this.$.toolbar.canUndo();
|
| + break;
|
| + case 'clear-all-command':
|
| + e.canExecute = this.$.toolbar.canClearAll();
|
| + break;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * @param {Event} e
|
| + * @private
|
| + */
|
| + onCommand_: function(e) {
|
| + if (e.command.id == 'clear-all-command')
|
| + this.actionService_.clearAll();
|
| + else if (e.command.id == 'undo-command')
|
| + this.actionService_.undo();
|
| + },
|
| +
|
| + /** @private */
|
| + onLoad_: function() {
|
| + this.$.toolbar.setActionService(this.actionService_);
|
| +
|
| + cr.ui.decorate('command', cr.ui.Command);
|
| + document.addEventListener('canExecute', this.onCanExecute_.bind(this));
|
| + document.addEventListener('command', this.onCommand_.bind(this));
|
| +
|
| + // Shows all downloads.
|
| + this.actionService_.search('');
|
| + },
|
| +
|
| + /** @private */
|
| + rebuildFocusGrid_: function() {
|
| + var activeElement = this.shadowRoot.activeElement;
|
| +
|
| + var activeItem;
|
| + if (activeElement && activeElement.tagName == 'downloads-item')
|
| + activeItem = activeElement;
|
| +
|
| + var activeControl = activeItem && activeItem.shadowRoot.activeElement;
|
| +
|
| + /** @private {!cr.ui.FocusGrid} */
|
| + this.focusGrid_ = this.focusGrid_ || new cr.ui.FocusGrid;
|
| + this.focusGrid_.destroy();
|
| +
|
| + var boundary = this.$['downloads-list'];
|
| +
|
| + this.items_.forEach(function(item) {
|
| + var focusRow = new downloads.FocusRow(item.content, boundary);
|
| + this.focusGrid_.addRow(focusRow);
|
| +
|
| + if (item == activeItem && !cr.ui.FocusRow.isFocusable(activeControl))
|
| + focusRow.getEquivalentElement(activeControl).focus();
|
| + }, this);
|
| +
|
| + this.focusGrid_.ensureRowActive();
|
| + },
|
| +
|
| + /**
|
| + * @return {number} The number of downloads shown on the page.
|
| + * @private
|
| + */
|
| + size_: function() {
|
| + return this.items_.length;
|
| + },
|
| +
|
| + /**
|
| + * Called when all items need to be updated.
|
| + * @param {!Array<!downloads.Data>} list A list of new download data.
|
| + * @private
|
| + */
|
| + updateAll_: function(list) {
|
| + var oldIdMap = this.idMap_ || {};
|
| +
|
| + /** @private {!Object<!downloads.Item>} */
|
| + this.idMap_ = {};
|
| +
|
| + /** @private {!Array<!downloads.Item>} */
|
| + this.items_ = [];
|
| +
|
| + if (!this.iconLoader_) {
|
| + var guesstimate = Math.max(this.guesstimateNumberOfVisibleItems_(), 1);
|
| + /** @private {downloads.ThrottledIconLoader} */
|
| + this.iconLoader_ = new downloads.ThrottledIconLoader(guesstimate);
|
| + }
|
| +
|
| + for (var i = 0; i < list.length; ++i) {
|
| + var data = list[i];
|
| + var id = data.id;
|
| +
|
| + // Re-use old items when possible (saves work, preserves focus).
|
| + var item = oldIdMap[id] ||
|
| + new downloads.Item(this.iconLoader_, this.actionService_);
|
| +
|
| + this.idMap_[id] = item; // Associated by ID for fast lookup.
|
| + this.items_.push(item); // Add to sorted list for order.
|
| +
|
| + // Render |item| but don't actually add to the DOM yet. |this.items_|
|
| + // must be fully created to be able to find the right spot to insert.
|
| + item.update(data);
|
| +
|
| + // Collapse redundant dates.
|
| + var prev = list[i - 1];
|
| + item.hideDate = !!prev && prev.date_string == data.date_string;
|
| +
|
| + delete oldIdMap[id];
|
| + }
|
| +
|
| + // Remove stale, previously rendered items from the DOM.
|
| + for (var id in oldIdMap) {
|
| + if (oldIdMap[id].parentNode)
|
| + oldIdMap[id].parentNode.removeChild(oldIdMap[id]);
|
| + delete oldIdMap[id];
|
| + }
|
| +
|
| + for (var i = 0; i < this.items_.length; ++i) {
|
| + var item = this.items_[i];
|
| + if (item.parentNode) // Already in the DOM; skip.
|
| + continue;
|
| +
|
| + var before = null;
|
| + // Find the next rendered item after this one, and insert before it.
|
| + for (var j = i + 1; !before && j < this.items_.length; ++j) {
|
| + if (this.items_[j].parentNode)
|
| + before = this.items_[j];
|
| + }
|
| + // If |before| is null, |item| will just get added at the end.
|
| + this.$['downloads-list'].insertBefore(item, before);
|
| + }
|
| +
|
| + var hasDownloads = this.size_() > 0;
|
| + if (!hasDownloads) {
|
| + var isSearching = this.actionService_.isSearching();
|
| + var messageToShow = isSearching ? 'noSearchResults' : 'noDownloads';
|
| + this.$['no-downloads'].querySelector('span').textContent =
|
| + loadTimeData.getString(messageToShow);
|
| + }
|
| + this.hasDownloads_ = hasDownloads;
|
| +
|
| + if (loadTimeData.getBoolean('allowDeletingHistory'))
|
| + this.$.toolbar.downloadsShowing = this.hasDownloads_;
|
| +
|
| + this.$.panel.classList.remove('loading');
|
| +
|
| + var allReady = this.items_.map(function(i) { return i.readyPromise; });
|
| + Promise.all(allReady).then(this.rebuildFocusGrid_.bind(this));
|
| + },
|
| +
|
| + /**
|
| + * @param {!downloads.Data} data
|
| + * @private
|
| + */
|
| + updateItem_: function(data) {
|
| + var item = this.idMap_[data.id];
|
| +
|
| + var activeControl = this.shadowRoot.activeElement == item ?
|
| + item.shadowRoot.activeElement : null;
|
| +
|
| + item.update(data);
|
| +
|
| + this.async(function() {
|
| + if (activeControl && !cr.ui.FocusRow.isFocusable(activeControl)) {
|
| + var focusRow = this.focusGrid_.getRowForRoot(item.content);
|
| + focusRow.getEquivalentElement(activeControl).focus();
|
| + }
|
| + }.bind(this));
|
| + },
|
| + });
|
| +
|
| + Manager.size = function() {
|
| + return document.querySelector('downloads-manager').size_();
|
| + };
|
| +
|
| + Manager.updateAll = function(list) {
|
| + document.querySelector('downloads-manager').updateAll_(list);
|
| + };
|
| +
|
| + Manager.updateItem = function(item) {
|
| + document.querySelector('downloads-manager').updateItem_(item);
|
| + };
|
| +
|
| + Manager.onLoad = function() {
|
| + document.querySelector('downloads-manager').onLoad_();
|
| + };
|
| +
|
| + return {Manager: Manager};
|
| +});
|
| +
|
| +window.addEventListener('load', downloads.Manager.onLoad);
|
|
|