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

Side by Side Diff: chrome/browser/resources/md_downloads/crisper.js

Issue 1375333004: MD Downloads: use <iron-list> to render download items (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@iron-list2
Patch Set: merge Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 Polymer = {dom: 'shadow'}; 5 Polymer = {dom: 'shadow'};
6 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 6 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
7 // Use of this source code is governed by a BSD-style license that can be 7 // Use of this source code is governed by a BSD-style license that can be
8 // found in the LICENSE file. 8 // found in the LICENSE file.
9 9
10 /** 10 /**
(...skipping 2335 matching lines...) Expand 10 before | Expand all | Expand 10 after
2346 removeAttribute: function(attr) { 2346 removeAttribute: function(attr) {
2347 if (attr.toLowerCase() == 'disabled') 2347 if (attr.toLowerCase() == 'disabled')
2348 this.disabled = false; 2348 this.disabled = false;
2349 else 2349 else
2350 HTMLAnchorElement.prototype.removeAttribute.apply(this, arguments); 2350 HTMLAnchorElement.prototype.removeAttribute.apply(this, arguments);
2351 }, 2351 },
2352 }, 2352 },
2353 2353
2354 extends: 'a', 2354 extends: 'a',
2355 }); 2355 });
2356 // Copyright 2015 The Chromium Authors. All rights reserved.
2357 // Use of this source code is governed by a BSD-style license that can be
2358 // found in the LICENSE file.
2359
2360 /** @typedef {{img: HTMLImageElement, url: string}} */
2361 var LoadIconRequest;
2362
2363 cr.define('downloads', function() {
2364 /**
2365 * @param {number} maxAllowed The maximum number of simultaneous downloads
2366 * allowed.
2367 * @constructor
2368 */
2369 function ThrottledIconLoader(maxAllowed) {
2370 assert(maxAllowed > 0);
2371
2372 /** @private {number} */
2373 this.maxAllowed_ = maxAllowed;
2374
2375 /** @private {!Array<!LoadIconRequest>} */
2376 this.requests_ = [];
2377 }
2378
2379 ThrottledIconLoader.prototype = {
2380 /** @private {number} */
2381 loading_: 0,
2382
2383 /**
2384 * Load the provided |url| into |img.src| after appending ?scale=.
2385 * @param {!HTMLImageElement} img An <img> to show the loaded image in.
2386 * @param {string} url A remote image URL to load.
2387 */
2388 loadScaledIcon: function(img, url) {
2389 var scaledUrl = url + '?scale=' + window.devicePixelRatio + 'x';
2390 if (img.src == scaledUrl)
2391 return;
2392
2393 this.requests_.push({img: img, url: scaledUrl});
2394 this.loadNextIcon_();
2395 },
2396
2397 /** @private */
2398 loadNextIcon_: function() {
2399 if (this.loading_ > this.maxAllowed_ || !this.requests_.length)
2400 return;
2401
2402 var request = this.requests_.shift();
2403 var img = request.img;
2404
2405 img.onabort = img.onerror = img.onload = function() {
2406 this.loading_--;
2407 this.loadNextIcon_();
2408 }.bind(this);
2409
2410 this.loading_++;
2411 img.src = request.url;
2412 },
2413 };
2414
2415 return {ThrottledIconLoader: ThrottledIconLoader};
2416 });
2417 // Copyright 2014 Google Inc. All rights reserved. 2356 // Copyright 2014 Google Inc. All rights reserved.
2418 // 2357 //
2419 // Licensed under the Apache License, Version 2.0 (the "License"); 2358 // Licensed under the Apache License, Version 2.0 (the "License");
2420 // you may not use this file except in compliance with the License. 2359 // you may not use this file except in compliance with the License.
2421 // You may obtain a copy of the License at 2360 // You may obtain a copy of the License at
2422 // 2361 //
2423 // http://www.apache.org/licenses/LICENSE-2.0 2362 // http://www.apache.org/licenses/LICENSE-2.0
2424 // 2363 //
2425 // Unless required by applicable law or agreed to in writing, software 2364 // Unless required by applicable law or agreed to in writing, software
2426 // distributed under the License is distributed on an "AS IS" BASIS, 2365 // distributed under the License is distributed on an "AS IS" BASIS,
(...skipping 573 matching lines...) Expand 10 before | Expand all | Expand 10 after
3000 debouncer.complete(); 2939 debouncer.complete();
3001 } 2940 }
3002 }, 2941 },
3003 cancelDebouncer: function (jobName) { 2942 cancelDebouncer: function (jobName) {
3004 var debouncer = this._debouncers[jobName]; 2943 var debouncer = this._debouncers[jobName];
3005 if (debouncer) { 2944 if (debouncer) {
3006 debouncer.stop(); 2945 debouncer.stop();
3007 } 2946 }
3008 } 2947 }
3009 }); 2948 });
3010 Polymer.version = '1.1.4'; 2949 Polymer.version = '1.1.5';
3011 Polymer.Base._addFeature({ 2950 Polymer.Base._addFeature({
3012 _registerFeatures: function () { 2951 _registerFeatures: function () {
3013 this._prepIs(); 2952 this._prepIs();
3014 this._prepAttributes(); 2953 this._prepAttributes();
3015 this._prepBehaviors(); 2954 this._prepBehaviors();
3016 this._prepConstructor(); 2955 this._prepConstructor();
3017 }, 2956 },
3018 _prepBehavior: function (b) { 2957 _prepBehavior: function (b) {
3019 this._addHostAttributes(b.hostAttributes); 2958 this._addHostAttributes(b.hostAttributes);
3020 }, 2959 },
(...skipping 1856 matching lines...) Expand 10 before | Expand all | Expand 10 after
4877 name = key; 4816 name = key;
4878 } else { 4817 } else {
4879 name = key.split('.'); 4818 name = key.split('.');
4880 node = this.$[name[0]]; 4819 node = this.$[name[0]];
4881 name = name[1]; 4820 name = name[1];
4882 } 4821 }
4883 this.listen(node, name, listeners[key]); 4822 this.listen(node, name, listeners[key]);
4884 } 4823 }
4885 }, 4824 },
4886 listen: function (node, eventName, methodName) { 4825 listen: function (node, eventName, methodName) {
4887 this._listen(node, eventName, this._createEventHandler(node, eventName, methodNa me)); 4826 var handler = this._recallEventHandler(this, eventName, node, methodName);
4827 if (!handler) {
4828 handler = this._createEventHandler(node, eventName, methodName);
4829 }
4830 if (handler._listening) {
4831 return;
4832 }
4833 this._listen(node, eventName, handler);
4834 handler._listening = true;
4888 }, 4835 },
4889 _boundListenerKey: function (eventName, methodName) { 4836 _boundListenerKey: function (eventName, methodName) {
4890 return eventName + ':' + methodName; 4837 return eventName + ':' + methodName;
4891 }, 4838 },
4892 _recordEventHandler: function (host, eventName, target, methodName, handler) { 4839 _recordEventHandler: function (host, eventName, target, methodName, handler) {
4893 var hbl = host.__boundListeners; 4840 var hbl = host.__boundListeners;
4894 if (!hbl) { 4841 if (!hbl) {
4895 hbl = host.__boundListeners = new WeakMap(); 4842 hbl = host.__boundListeners = new WeakMap();
4896 } 4843 }
4897 var bl = hbl.get(target); 4844 var bl = hbl.get(target);
(...skipping 18 matching lines...) Expand all
4916 }, 4863 },
4917 _createEventHandler: function (node, eventName, methodName) { 4864 _createEventHandler: function (node, eventName, methodName) {
4918 var host = this; 4865 var host = this;
4919 var handler = function (e) { 4866 var handler = function (e) {
4920 if (host[methodName]) { 4867 if (host[methodName]) {
4921 host[methodName](e, e.detail); 4868 host[methodName](e, e.detail);
4922 } else { 4869 } else {
4923 host._warn(host._logf('_createEventHandler', 'listener method `' + methodName + '` not defined')); 4870 host._warn(host._logf('_createEventHandler', 'listener method `' + methodName + '` not defined'));
4924 } 4871 }
4925 }; 4872 };
4873 handler._listening = false;
4926 this._recordEventHandler(host, eventName, node, methodName, handler); 4874 this._recordEventHandler(host, eventName, node, methodName, handler);
4927 return handler; 4875 return handler;
4928 }, 4876 },
4929 unlisten: function (node, eventName, methodName) { 4877 unlisten: function (node, eventName, methodName) {
4930 var handler = this._recallEventHandler(this, eventName, node, methodName); 4878 var handler = this._recallEventHandler(this, eventName, node, methodName);
4931 if (handler) { 4879 if (handler) {
4932 this._unlisten(node, eventName, handler); 4880 this._unlisten(node, eventName, handler);
4881 handler._listening = false;
4933 } 4882 }
4934 }, 4883 },
4935 _listen: function (node, eventName, handler) { 4884 _listen: function (node, eventName, handler) {
4936 node.addEventListener(eventName, handler); 4885 node.addEventListener(eventName, handler);
4937 }, 4886 },
4938 _unlisten: function (node, eventName, handler) { 4887 _unlisten: function (node, eventName, handler) {
4939 node.removeEventListener(eventName, handler); 4888 node.removeEventListener(eventName, handler);
4940 } 4889 }
4941 }); 4890 });
4942 (function () { 4891 (function () {
(...skipping 827 matching lines...) Expand 10 before | Expand all | Expand 10 after
5770 return l; 5719 return l;
5771 }, 5720 },
5772 create: function (tag, props) { 5721 create: function (tag, props) {
5773 var elt = document.createElement(tag); 5722 var elt = document.createElement(tag);
5774 if (props) { 5723 if (props) {
5775 for (var n in props) { 5724 for (var n in props) {
5776 elt[n] = props[n]; 5725 elt[n] = props[n];
5777 } 5726 }
5778 } 5727 }
5779 return elt; 5728 return elt;
5729 },
5730 isLightDescendant: function (node) {
5731 return this.contains(node) && Polymer.dom(this).getOwnerRoot() === Polymer.dom(n ode).getOwnerRoot();
5732 },
5733 isLocalDescendant: function (node) {
5734 return this.root === Polymer.dom(node).getOwnerRoot();
5780 } 5735 }
5781 }); 5736 });
5782 Polymer.Bind = { 5737 Polymer.Bind = {
5783 prepareModel: function (model) { 5738 prepareModel: function (model) {
5784 model._propertyEffects = {}; 5739 model._propertyEffects = {};
5785 model._bindListeners = []; 5740 model._bindListeners = [];
5786 Polymer.Base.mixin(model, this._modelApi); 5741 Polymer.Base.mixin(model, this._modelApi);
5787 }, 5742 },
5788 _modelApi: { 5743 _modelApi: {
5789 _notifyChange: function (property) { 5744 _notifyChange: function (property) {
(...skipping 781 matching lines...) Expand 10 before | Expand all | Expand 10 after
6571 return ret; 6526 return ret;
6572 }, 6527 },
6573 unshift: function (path) { 6528 unshift: function (path) {
6574 var array = this.get(path); 6529 var array = this.get(path);
6575 var args = Array.prototype.slice.call(arguments, 1); 6530 var args = Array.prototype.slice.call(arguments, 1);
6576 var ret = array.unshift.apply(array, args); 6531 var ret = array.unshift.apply(array, args);
6577 if (args.length) { 6532 if (args.length) {
6578 this._notifySplice(array, path, 0, args.length, []); 6533 this._notifySplice(array, path, 0, args.length, []);
6579 } 6534 }
6580 return ret; 6535 return ret;
6536 },
6537 prepareModelNotifyPath: function (model) {
6538 this.mixin(model, {
6539 fire: Polymer.Base.fire,
6540 notifyPath: Polymer.Base.notifyPath,
6541 _EVENT_CHANGED: Polymer.Base._EVENT_CHANGED,
6542 _notifyPath: Polymer.Base._notifyPath,
6543 _pathEffector: Polymer.Base._pathEffector,
6544 _annotationPathEffect: Polymer.Base._annotationPathEffect,
6545 _complexObserverPathEffect: Polymer.Base._complexObserverPathEffect,
6546 _annotatedComputationPathEffect: Polymer.Base._annotatedComputationPathEffect,
6547 _computePathEffect: Polymer.Base._computePathEffect,
6548 _modelForPath: Polymer.Base._modelForPath,
6549 _pathMatchesEffect: Polymer.Base._pathMatchesEffect,
6550 _notifyBoundPaths: Polymer.Base._notifyBoundPaths
6551 });
6581 } 6552 }
6582 }); 6553 });
6583 }()); 6554 }());
6584 Polymer.Base._addFeature({ 6555 Polymer.Base._addFeature({
6585 resolveUrl: function (url) { 6556 resolveUrl: function (url) {
6586 var module = Polymer.DomModule.import(this.is); 6557 var module = Polymer.DomModule.import(this.is);
6587 var root = ''; 6558 var root = '';
6588 if (module) { 6559 if (module) {
6589 var assetPath = module.getAttribute('assetpath') || ''; 6560 var assetPath = module.getAttribute('assetpath') || '';
6590 root = Polymer.ResolveUrl.resolveUrl(assetPath, module.ownerDocument.baseURI); 6561 root = Polymer.ResolveUrl.resolveUrl(assetPath, module.ownerDocument.baseURI);
(...skipping 1186 matching lines...) Expand 10 before | Expand all | Expand 10 after
7777 } 7748 }
7778 }); 7749 });
7779 } 7750 }
7780 }); 7751 });
7781 }()); 7752 }());
7782 Polymer.Templatizer = { 7753 Polymer.Templatizer = {
7783 properties: { __hideTemplateChildren__: { observer: '_showHideChildren' } }, 7754 properties: { __hideTemplateChildren__: { observer: '_showHideChildren' } },
7784 _instanceProps: Polymer.nob, 7755 _instanceProps: Polymer.nob,
7785 _parentPropPrefix: '_parent_', 7756 _parentPropPrefix: '_parent_',
7786 templatize: function (template) { 7757 templatize: function (template) {
7758 this._templatized = template;
7787 if (!template._content) { 7759 if (!template._content) {
7788 template._content = template.content; 7760 template._content = template.content;
7789 } 7761 }
7790 if (template._content._ctor) { 7762 if (template._content._ctor) {
7791 this.ctor = template._content._ctor; 7763 this.ctor = template._content._ctor;
7792 this._prepParentProperties(this.ctor.prototype, template); 7764 this._prepParentProperties(this.ctor.prototype, template);
7793 return; 7765 return;
7794 } 7766 }
7795 var archetype = Object.create(Polymer.Base); 7767 var archetype = Object.create(Polymer.Base);
7796 this._customPrepAnnotations(archetype, template); 7768 this._customPrepAnnotations(archetype, template);
7769 this._prepParentProperties(archetype, template);
7797 archetype._prepEffects(); 7770 archetype._prepEffects();
7798 this._customPrepEffects(archetype); 7771 this._customPrepEffects(archetype);
7799 archetype._prepBehaviors(); 7772 archetype._prepBehaviors();
7800 archetype._prepBindings(); 7773 archetype._prepBindings();
7801 this._prepParentProperties(archetype, template);
7802 archetype._notifyPath = this._notifyPathImpl; 7774 archetype._notifyPath = this._notifyPathImpl;
7803 archetype._scopeElementClass = this._scopeElementClassImpl; 7775 archetype._scopeElementClass = this._scopeElementClassImpl;
7804 archetype.listen = this._listenImpl; 7776 archetype.listen = this._listenImpl;
7805 archetype._showHideChildren = this._showHideChildrenImpl; 7777 archetype._showHideChildren = this._showHideChildrenImpl;
7806 var _constructor = this._constructorImpl; 7778 var _constructor = this._constructorImpl;
7807 var ctor = function TemplateInstance(model, host) { 7779 var ctor = function TemplateInstance(model, host) {
7808 _constructor.call(this, model, host); 7780 _constructor.call(this, model, host);
7809 }; 7781 };
7810 ctor.prototype = archetype; 7782 ctor.prototype = archetype;
7811 archetype.constructor = ctor; 7783 archetype.constructor = ctor;
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after
7874 if (this._forwardParentProp && parentProps) { 7846 if (this._forwardParentProp && parentProps) {
7875 var proto = archetype._parentPropProto; 7847 var proto = archetype._parentPropProto;
7876 var prop; 7848 var prop;
7877 if (!proto) { 7849 if (!proto) {
7878 for (prop in this._instanceProps) { 7850 for (prop in this._instanceProps) {
7879 delete parentProps[prop]; 7851 delete parentProps[prop];
7880 } 7852 }
7881 proto = archetype._parentPropProto = Object.create(null); 7853 proto = archetype._parentPropProto = Object.create(null);
7882 if (template != this) { 7854 if (template != this) {
7883 Polymer.Bind.prepareModel(proto); 7855 Polymer.Bind.prepareModel(proto);
7856 Polymer.Base.prepareModelNotifyPath(proto);
7884 } 7857 }
7885 for (prop in parentProps) { 7858 for (prop in parentProps) {
7886 var parentProp = this._parentPropPrefix + prop; 7859 var parentProp = this._parentPropPrefix + prop;
7887 var effects = [ 7860 var effects = [
7888 { 7861 {
7889 kind: 'function', 7862 kind: 'function',
7890 effect: this._createForwardPropEffector(prop) 7863 effect: this._createForwardPropEffector(prop)
7891 }, 7864 },
7892 { kind: 'notify' } 7865 { kind: 'notify' }
7893 ]; 7866 ];
7894 Polymer.Bind._createAccessors(proto, parentProp, effects); 7867 Polymer.Bind._createAccessors(proto, parentProp, effects);
7895 } 7868 }
7896 } 7869 }
7897 if (template != this) { 7870 if (template != this) {
7898 Polymer.Bind.prepareInstance(template); 7871 Polymer.Bind.prepareInstance(template);
7899 template._forwardParentProp = this._forwardParentProp.bind(this); 7872 template._forwardParentProp = this._forwardParentProp.bind(this);
7900 } 7873 }
7901 this._extendTemplate(template, proto); 7874 this._extendTemplate(template, proto);
7875 template._pathEffector = this._pathEffectorImpl.bind(this);
7902 } 7876 }
7903 }, 7877 },
7904 _createForwardPropEffector: function (prop) { 7878 _createForwardPropEffector: function (prop) {
7905 return function (source, value) { 7879 return function (source, value) {
7906 this._forwardParentProp(prop, value); 7880 this._forwardParentProp(prop, value);
7907 }; 7881 };
7908 }, 7882 },
7909 _createHostPropEffector: function (prop) { 7883 _createHostPropEffector: function (prop) {
7910 var prefix = this._parentPropPrefix; 7884 var prefix = this._parentPropPrefix;
7911 return function (source, value) { 7885 return function (source, value) {
7912 this.dataHost[prefix + prop] = value; 7886 this.dataHost._templatized[prefix + prop] = value;
7913 }; 7887 };
7914 }, 7888 },
7915 _createInstancePropEffector: function (prop) { 7889 _createInstancePropEffector: function (prop) {
7916 return function (source, value, old, fromAbove) { 7890 return function (source, value, old, fromAbove) {
7917 if (!fromAbove) { 7891 if (!fromAbove) {
7918 this.dataHost._forwardInstanceProp(this, prop, value); 7892 this.dataHost._forwardInstanceProp(this, prop, value);
7919 } 7893 }
7920 }; 7894 };
7921 }, 7895 },
7922 _extendTemplate: function (template, proto) { 7896 _extendTemplate: function (template, proto) {
(...skipping 11 matching lines...) Expand all
7934 _forwardInstancePath: function (inst, path, value) { 7908 _forwardInstancePath: function (inst, path, value) {
7935 }, 7909 },
7936 _forwardInstanceProp: function (inst, prop, value) { 7910 _forwardInstanceProp: function (inst, prop, value) {
7937 }, 7911 },
7938 _notifyPathImpl: function (path, value) { 7912 _notifyPathImpl: function (path, value) {
7939 var dataHost = this.dataHost; 7913 var dataHost = this.dataHost;
7940 var dot = path.indexOf('.'); 7914 var dot = path.indexOf('.');
7941 var root = dot < 0 ? path : path.slice(0, dot); 7915 var root = dot < 0 ? path : path.slice(0, dot);
7942 dataHost._forwardInstancePath.call(dataHost, this, path, value); 7916 dataHost._forwardInstancePath.call(dataHost, this, path, value);
7943 if (root in dataHost._parentProps) { 7917 if (root in dataHost._parentProps) {
7944 dataHost.notifyPath(dataHost._parentPropPrefix + path, value); 7918 dataHost._templatized.notifyPath(dataHost._parentPropPrefix + path, value);
7945 } 7919 }
7946 }, 7920 },
7947 _pathEffector: function (path, value, fromAbove) { 7921 _pathEffectorImpl: function (path, value, fromAbove) {
7948 if (this._forwardParentPath) { 7922 if (this._forwardParentPath) {
7949 if (path.indexOf(this._parentPropPrefix) === 0) { 7923 if (path.indexOf(this._parentPropPrefix) === 0) {
7950 this._forwardParentPath(path.substring(8), value); 7924 var subPath = path.substring(this._parentPropPrefix.length);
7925 this._forwardParentPath(subPath, value);
7951 } 7926 }
7952 } 7927 }
7953 Polymer.Base._pathEffector.apply(this, arguments); 7928 Polymer.Base._pathEffector.call(this._templatized, path, value, fromAbove);
7954 }, 7929 },
7955 _constructorImpl: function (model, host) { 7930 _constructorImpl: function (model, host) {
7956 this._rootDataHost = host._getRootDataHost(); 7931 this._rootDataHost = host._getRootDataHost();
7957 this._setupConfigure(model); 7932 this._setupConfigure(model);
7958 this._pushHost(host); 7933 this._pushHost(host);
7959 this.root = this.instanceTemplate(this._template); 7934 this.root = this.instanceTemplate(this._template);
7960 this.root.__noContent = !this._notes._hasContent; 7935 this.root.__noContent = !this._notes._hasContent;
7961 this.root.__styleScoped = true; 7936 this.root.__styleScoped = true;
7962 this._popHost(); 7937 this._popHost();
7963 this._marshalAnnotatedNodes(); 7938 this._marshalAnnotatedNodes();
(...skipping 22 matching lines...) Expand all
7986 }, 7961 },
7987 _scopeElementClassImpl: function (node, value) { 7962 _scopeElementClassImpl: function (node, value) {
7988 var host = this._rootDataHost; 7963 var host = this._rootDataHost;
7989 if (host) { 7964 if (host) {
7990 return host._scopeElementClass(node, value); 7965 return host._scopeElementClass(node, value);
7991 } 7966 }
7992 }, 7967 },
7993 stamp: function (model) { 7968 stamp: function (model) {
7994 model = model || {}; 7969 model = model || {};
7995 if (this._parentProps) { 7970 if (this._parentProps) {
7971 var templatized = this._templatized;
7996 for (var prop in this._parentProps) { 7972 for (var prop in this._parentProps) {
7997 model[prop] = this[this._parentPropPrefix + prop]; 7973 model[prop] = templatized[this._parentPropPrefix + prop];
7998 } 7974 }
7999 } 7975 }
8000 return new this.ctor(model, this); 7976 return new this.ctor(model, this);
8001 }, 7977 },
8002 modelForElement: function (el) { 7978 modelForElement: function (el) {
8003 var model; 7979 var model;
8004 while (el) { 7980 while (el) {
8005 if (model = el._templateInstance) { 7981 if (model = el._templateInstance) {
8006 if (model.dataHost != this) { 7982 if (model.dataHost != this) {
8007 el = model.dataHost; 7983 el = model.dataHost;
(...skipping 587 matching lines...) Expand 10 before | Expand all | Expand 10 after
8595 select: function (item) { 8571 select: function (item) {
8596 var icol = Polymer.Collection.get(this.items); 8572 var icol = Polymer.Collection.get(this.items);
8597 var key = icol.getKey(item); 8573 var key = icol.getKey(item);
8598 if (this.multi) { 8574 if (this.multi) {
8599 if (this.isSelected(item)) { 8575 if (this.isSelected(item)) {
8600 if (this.toggle) { 8576 if (this.toggle) {
8601 this.deselect(item); 8577 this.deselect(item);
8602 } 8578 }
8603 } else { 8579 } else {
8604 this.push('selected', item); 8580 this.push('selected', item);
8605 skey = this._selectedColl.getKey(item); 8581 var skey = this._selectedColl.getKey(item);
8606 this.linkPaths('selected.' + skey, 'items.' + key); 8582 this.linkPaths('selected.' + skey, 'items.' + key);
8607 } 8583 }
8608 } else { 8584 } else {
8609 if (this.toggle && item == this.selected) { 8585 if (this.toggle && item == this.selected) {
8610 this.deselect(); 8586 this.deselect();
8611 } else { 8587 } else {
8612 this.selected = item; 8588 this.selected = item;
8613 this.selectedItem = item; 8589 this.selectedItem = item;
8614 this.linkPaths('selected', 'items.' + key); 8590 this.linkPaths('selected', 'items.' + key);
8615 this.linkPaths('selectedItem', 'items.' + key); 8591 this.linkPaths('selectedItem', 'items.' + key);
(...skipping 148 matching lines...) Expand 10 before | Expand all | Expand 10 after
8764 this._prepBehaviors(); 8740 this._prepBehaviors();
8765 this._prepConfigure(); 8741 this._prepConfigure();
8766 this._prepBindings(); 8742 this._prepBindings();
8767 Polymer.Base._initFeatures.call(this); 8743 Polymer.Base._initFeatures.call(this);
8768 this._children = Array.prototype.slice.call(this.root.childNodes); 8744 this._children = Array.prototype.slice.call(this.root.childNodes);
8769 } 8745 }
8770 this._insertChildren(); 8746 this._insertChildren();
8771 this.fire('dom-change'); 8747 this.fire('dom-change');
8772 } 8748 }
8773 }); 8749 });
8750 /**
8751 * `IronResizableBehavior` is a behavior that can be used in Polymer elements to
8752 * coordinate the flow of resize events between "resizers" (elements that cont rol the
8753 * size or hidden state of their children) and "resizables" (elements that nee d to be
8754 * notified when they are resized or un-hidden by their parents in order to ta ke
8755 * action on their new measurements).
8756 * Elements that perform measurement should add the `IronResizableBehavior` be havior to
8757 * their element definition and listen for the `iron-resize` event on themselv es.
8758 * This event will be fired when they become showing after having been hidden,
8759 * when they are resized explicitly by another resizable, or when the window h as been
8760 * resized.
8761 * Note, the `iron-resize` event is non-bubbling.
8762 *
8763 * @polymerBehavior Polymer.IronResizableBehavior
8764 * @demo demo/index.html
8765 **/
8766 Polymer.IronResizableBehavior = {
8767 properties: {
8768 /**
8769 * The closest ancestor element that implements `IronResizableBehavior`.
8770 */
8771 _parentResizable: {
8772 type: Object,
8773 observer: '_parentResizableChanged'
8774 },
8775
8776 /**
8777 * True if this element is currently notifying its descedant elements of
8778 * resize.
8779 */
8780 _notifyingDescendant: {
8781 type: Boolean,
8782 value: false
8783 }
8784 },
8785
8786 listeners: {
8787 'iron-request-resize-notifications': '_onIronRequestResizeNotifications'
8788 },
8789
8790 created: function() {
8791 // We don't really need property effects on these, and also we want them
8792 // to be created before the `_parentResizable` observer fires:
8793 this._interestedResizables = [];
8794 this._boundNotifyResize = this.notifyResize.bind(this);
8795 },
8796
8797 attached: function() {
8798 this.fire('iron-request-resize-notifications', null, {
8799 node: this,
8800 bubbles: true,
8801 cancelable: true
8802 });
8803
8804 if (!this._parentResizable) {
8805 window.addEventListener('resize', this._boundNotifyResize);
8806 this.notifyResize();
8807 }
8808 },
8809
8810 detached: function() {
8811 if (this._parentResizable) {
8812 this._parentResizable.stopResizeNotificationsFor(this);
8813 } else {
8814 window.removeEventListener('resize', this._boundNotifyResize);
8815 }
8816
8817 this._parentResizable = null;
8818 },
8819
8820 /**
8821 * Can be called to manually notify a resizable and its descendant
8822 * resizables of a resize change.
8823 */
8824 notifyResize: function() {
8825 if (!this.isAttached) {
8826 return;
8827 }
8828
8829 this._interestedResizables.forEach(function(resizable) {
8830 if (this.resizerShouldNotify(resizable)) {
8831 this._notifyDescendant(resizable);
8832 }
8833 }, this);
8834
8835 this._fireResize();
8836 },
8837
8838 /**
8839 * Used to assign the closest resizable ancestor to this resizable
8840 * if the ancestor detects a request for notifications.
8841 */
8842 assignParentResizable: function(parentResizable) {
8843 this._parentResizable = parentResizable;
8844 },
8845
8846 /**
8847 * Used to remove a resizable descendant from the list of descendants
8848 * that should be notified of a resize change.
8849 */
8850 stopResizeNotificationsFor: function(target) {
8851 var index = this._interestedResizables.indexOf(target);
8852
8853 if (index > -1) {
8854 this._interestedResizables.splice(index, 1);
8855 this.unlisten(target, 'iron-resize', '_onDescendantIronResize');
8856 }
8857 },
8858
8859 /**
8860 * This method can be overridden to filter nested elements that should or
8861 * should not be notified by the current element. Return true if an element
8862 * should be notified, or false if it should not be notified.
8863 *
8864 * @param {HTMLElement} element A candidate descendant element that
8865 * implements `IronResizableBehavior`.
8866 * @return {boolean} True if the `element` should be notified of resize.
8867 */
8868 resizerShouldNotify: function(element) { return true; },
8869
8870 _onDescendantIronResize: function(event) {
8871 if (this._notifyingDescendant) {
8872 event.stopPropagation();
8873 return;
8874 }
8875
8876 // NOTE(cdata): In ShadowDOM, event retargetting makes echoing of the
8877 // otherwise non-bubbling event "just work." We do it manually here for
8878 // the case where Polymer is not using shadow roots for whatever reason:
8879 if (!Polymer.Settings.useShadow) {
8880 this._fireResize();
8881 }
8882 },
8883
8884 _fireResize: function() {
8885 this.fire('iron-resize', null, {
8886 node: this,
8887 bubbles: false
8888 });
8889 },
8890
8891 _onIronRequestResizeNotifications: function(event) {
8892 var target = event.path ? event.path[0] : event.target;
8893
8894 if (target === this) {
8895 return;
8896 }
8897
8898 if (this._interestedResizables.indexOf(target) === -1) {
8899 this._interestedResizables.push(target);
8900 this.listen(target, 'iron-resize', '_onDescendantIronResize');
8901 }
8902
8903 target.assignParentResizable(this);
8904 this._notifyDescendant(target);
8905
8906 event.stopPropagation();
8907 },
8908
8909 _parentResizableChanged: function(parentResizable) {
8910 if (parentResizable) {
8911 window.removeEventListener('resize', this._boundNotifyResize);
8912 }
8913 },
8914
8915 _notifyDescendant: function(descendant) {
8916 // NOTE(cdata): In IE10, attached is fired on children first, so it's
8917 // important not to notify them if the parent is not attached yet (or
8918 // else they will get redundantly notified when the parent attaches).
8919 if (!this.isAttached) {
8920 return;
8921 }
8922
8923 this._notifyingDescendant = true;
8924 descendant.notifyResize();
8925 this._notifyingDescendant = false;
8926 }
8927 };
8928 (function() {
8929
8930 var IOS = navigator.userAgent.match(/iP(?:hone|ad;(?: U;)? CPU) OS (\d+)/);
8931 var IOS_TOUCH_SCROLLING = IOS && IOS[1] >= 8;
8932 var DEFAULT_PHYSICAL_COUNT = 20;
8933 var MAX_PHYSICAL_COUNT = 500;
8934
8935 Polymer({
8936
8937 is: 'iron-list',
8938
8939 properties: {
8940
8941 /**
8942 * An array containing items determining how many instances of the templat e
8943 * to stamp and that that each template instance should bind to.
8944 */
8945 items: {
8946 type: Array
8947 },
8948
8949 /**
8950 * The name of the variable to add to the binding scope for the array
8951 * element associated with a given template instance.
8952 */
8953 as: {
8954 type: String,
8955 value: 'item'
8956 },
8957
8958 /**
8959 * The name of the variable to add to the binding scope with the index
8960 * for the row. If `sort` is provided, the index will reflect the
8961 * sorted order (rather than the original array order).
8962 */
8963 indexAs: {
8964 type: String,
8965 value: 'index'
8966 },
8967
8968 /**
8969 * The name of the variable to add to the binding scope to indicate
8970 * if the row is selected.
8971 */
8972 selectedAs: {
8973 type: String,
8974 value: 'selected'
8975 },
8976
8977 /**
8978 * When true, tapping a row will select the item, placing its data model
8979 * in the set of selected items retrievable via the selection property.
8980 *
8981 * Note that tapping focusable elements within the list item will not
8982 * result in selection, since they are presumed to have their * own action .
8983 */
8984 selectionEnabled: {
8985 type: Boolean,
8986 value: false
8987 },
8988
8989 /**
8990 * When `multiSelection` is false, this is the currently selected item, or `null`
8991 * if no item is selected.
8992 */
8993 selectedItem: {
8994 type: Object,
8995 notify: true
8996 },
8997
8998 /**
8999 * When `multiSelection` is true, this is an array that contains the selec ted items.
9000 */
9001 selectedItems: {
9002 type: Object,
9003 notify: true
9004 },
9005
9006 /**
9007 * When `true`, multiple items may be selected at once (in this case,
9008 * `selected` is an array of currently selected items). When `false`,
9009 * only one item may be selected at a time.
9010 */
9011 multiSelection: {
9012 type: Boolean,
9013 value: false
9014 }
9015 },
9016
9017 observers: [
9018 '_itemsChanged(items.*)',
9019 '_selectionEnabledChanged(selectionEnabled)',
9020 '_multiSelectionChanged(multiSelection)'
9021 ],
9022
9023 behaviors: [
9024 Polymer.Templatizer,
9025 Polymer.IronResizableBehavior
9026 ],
9027
9028 listeners: {
9029 'iron-resize': '_resizeHandler'
9030 },
9031
9032 /**
9033 * The ratio of hidden tiles that should remain in the scroll direction.
9034 * Recommended value ~0.5, so it will distribute tiles evely in both directi ons.
9035 */
9036 _ratio: 0.5,
9037
9038 /**
9039 * The element that controls the scroll
9040 * @type {?Element}
9041 */
9042 _scroller: null,
9043
9044 /**
9045 * The padding-top value of the `scroller` element
9046 */
9047 _scrollerPaddingTop: 0,
9048
9049 /**
9050 * This value is the same as `scrollTop`.
9051 */
9052 _scrollPosition: 0,
9053
9054 /**
9055 * The number of tiles in the DOM.
9056 */
9057 _physicalCount: 0,
9058
9059 /**
9060 * The k-th tile that is at the top of the scrolling list.
9061 */
9062 _physicalStart: 0,
9063
9064 /**
9065 * The k-th tile that is at the bottom of the scrolling list.
9066 */
9067 _physicalEnd: 0,
9068
9069 /**
9070 * The sum of the heights of all the tiles in the DOM.
9071 */
9072 _physicalSize: 0,
9073
9074 /**
9075 * The average `offsetHeight` of the tiles observed till now.
9076 */
9077 _physicalAverage: 0,
9078
9079 /**
9080 * The number of tiles which `offsetHeight` > 0 observed until now.
9081 */
9082 _physicalAverageCount: 0,
9083
9084 /**
9085 * The Y position of the item rendered in the `_physicalStart`
9086 * tile relative to the scrolling list.
9087 */
9088 _physicalTop: 0,
9089
9090 /**
9091 * The number of items in the list.
9092 */
9093 _virtualCount: 0,
9094
9095 /**
9096 * The n-th item rendered in the `_physicalStart` tile.
9097 */
9098 _virtualStartVal: 0,
9099
9100 /**
9101 * A map between an item key and its physical item index
9102 */
9103 _physicalIndexForKey: null,
9104
9105 /**
9106 * The estimated scroll height based on `_physicalAverage`
9107 */
9108 _estScrollHeight: 0,
9109
9110 /**
9111 * The scroll height of the dom node
9112 */
9113 _scrollHeight: 0,
9114
9115 /**
9116 * The size of the viewport
9117 */
9118 _viewportSize: 0,
9119
9120 /**
9121 * An array of DOM nodes that are currently in the tree
9122 * @type {?Array<!TemplatizerNode>}
9123 */
9124 _physicalItems: null,
9125
9126 /**
9127 * An array of heights for each item in `_physicalItems`
9128 * @type {?Array<number>}
9129 */
9130 _physicalSizes: null,
9131
9132 /**
9133 * A cached value for the visible index.
9134 * See `firstVisibleIndex`
9135 * @type {?number}
9136 */
9137 _firstVisibleIndexVal: null,
9138
9139 /**
9140 * A Polymer collection for the items.
9141 * @type {?Polymer.Collection}
9142 */
9143 _collection: null,
9144
9145 /**
9146 * True if the current item list was rendered for the first time
9147 * after attached.
9148 */
9149 _itemsRendered: false,
9150
9151 /**
9152 * The bottom of the physical content.
9153 */
9154 get _physicalBottom() {
9155 return this._physicalTop + this._physicalSize;
9156 },
9157
9158 /**
9159 * The n-th item rendered in the last physical item.
9160 */
9161 get _virtualEnd() {
9162 return this._virtualStartVal + this._physicalCount - 1;
9163 },
9164
9165 /**
9166 * The lowest n-th value for an item such that it can be rendered in `_physi calStart`.
9167 */
9168 _minVirtualStart: 0,
9169
9170 /**
9171 * The largest n-th value for an item such that it can be rendered in `_phys icalStart`.
9172 */
9173 get _maxVirtualStart() {
9174 return this._virtualCount < this._physicalCount ?
9175 this._virtualCount : this._virtualCount - this._physicalCount;
9176 },
9177
9178 /**
9179 * The height of the physical content that isn't on the screen.
9180 */
9181 get _hiddenContentSize() {
9182 return this._physicalSize - this._viewportSize;
9183 },
9184
9185 /**
9186 * The maximum scroll top value.
9187 */
9188 get _maxScrollTop() {
9189 return this._estScrollHeight - this._viewportSize;
9190 },
9191
9192 /**
9193 * Sets the n-th item rendered in `_physicalStart`
9194 */
9195 set _virtualStart(val) {
9196 // clamp the value so that _minVirtualStart <= val <= _maxVirtualStart
9197 this._virtualStartVal = Math.min(this._maxVirtualStart, Math.max(this._min VirtualStart, val));
9198 this._physicalStart = this._virtualStartVal % this._physicalCount;
9199 this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this ._physicalCount;
9200 },
9201
9202 /**
9203 * Gets the n-th item rendered in `_physicalStart`
9204 */
9205 get _virtualStart() {
9206 return this._virtualStartVal;
9207 },
9208
9209 /**
9210 * An optimal physical size such that we will have enough physical items
9211 * to fill up the viewport and recycle when the user scrolls.
9212 *
9213 * This default value assumes that we will at least have the equivalent
9214 * to a viewport of physical items above and below the user's viewport.
9215 */
9216 get _optPhysicalSize() {
9217 return this._viewportSize * 3;
9218 },
9219
9220 /**
9221 * True if the current list is visible.
9222 */
9223 get _isVisible() {
9224 return this._scroller && Boolean(this._scroller.offsetWidth || this._scrol ler.offsetHeight);
9225 },
9226
9227 /**
9228 * Gets the first visible item in the viewport.
9229 *
9230 * @type {number}
9231 */
9232 get firstVisibleIndex() {
9233 var physicalOffset;
9234
9235 if (this._firstVisibleIndexVal === null) {
9236 physicalOffset = this._physicalTop;
9237
9238 this._firstVisibleIndexVal = this._iterateItems(
9239 function(pidx, vidx) {
9240 physicalOffset += this._physicalSizes[pidx];
9241
9242 if (physicalOffset > this._scrollPosition) {
9243 return vidx;
9244 }
9245 }) || 0;
9246 }
9247
9248 return this._firstVisibleIndexVal;
9249 },
9250
9251 ready: function() {
9252 if (IOS_TOUCH_SCROLLING) {
9253 this._scrollListener = function() {
9254 requestAnimationFrame(this._scrollHandler.bind(this));
9255 }.bind(this);
9256 } else {
9257 this._scrollListener = this._scrollHandler.bind(this);
9258 }
9259 },
9260
9261 /**
9262 * When the element has been attached to the DOM tree.
9263 */
9264 attached: function() {
9265 // delegate to the parent's scroller
9266 // e.g. paper-scroll-header-panel
9267 var el = Polymer.dom(this);
9268
9269 var parentNode = /** @type {?{scroller: ?Element}} */ (el.parentNode);
9270 if (parentNode && parentNode.scroller) {
9271 this._scroller = parentNode.scroller;
9272 } else {
9273 this._scroller = this;
9274 this.classList.add('has-scroller');
9275 }
9276
9277 if (IOS_TOUCH_SCROLLING) {
9278 this._scroller.style.webkitOverflowScrolling = 'touch';
9279 }
9280
9281 this._scroller.addEventListener('scroll', this._scrollListener);
9282
9283 this.updateViewportBoundaries();
9284 this._render();
9285 },
9286
9287 /**
9288 * When the element has been removed from the DOM tree.
9289 */
9290 detached: function() {
9291 this._itemsRendered = false;
9292 if (this._scroller) {
9293 this._scroller.removeEventListener('scroll', this._scrollListener);
9294 }
9295 },
9296
9297 /**
9298 * Invoke this method if you dynamically update the viewport's
9299 * size or CSS padding.
9300 *
9301 * @method updateViewportBoundaries
9302 */
9303 updateViewportBoundaries: function() {
9304 var scrollerStyle = window.getComputedStyle(this._scroller);
9305 this._scrollerPaddingTop = parseInt(scrollerStyle['padding-top'], 10);
9306 this._viewportSize = this._scroller.offsetHeight;
9307 },
9308
9309 /**
9310 * Update the models, the position of the
9311 * items in the viewport and recycle tiles as needed.
9312 */
9313 _refresh: function() {
9314 var SCROLL_DIRECTION_UP = -1;
9315 var SCROLL_DIRECTION_DOWN = 1;
9316 var SCROLL_DIRECTION_NONE = 0;
9317
9318 // clamp the `scrollTop` value
9319 // IE 10|11 scrollTop may go above `_maxScrollTop`
9320 // iOS `scrollTop` may go below 0 and above `_maxScrollTop`
9321 var scrollTop = Math.max(0, Math.min(this._maxScrollTop, this._scroller.sc rollTop));
9322
9323 var tileHeight, kth, recycledTileSet;
9324 var ratio = this._ratio;
9325 var delta = scrollTop - this._scrollPosition;
9326 var direction = SCROLL_DIRECTION_NONE;
9327 var recycledTiles = 0;
9328 var hiddenContentSize = this._hiddenContentSize;
9329 var currentRatio = ratio;
9330 var movingUp = [];
9331
9332 // track the last `scrollTop`
9333 this._scrollPosition = scrollTop;
9334
9335 // clear cached visible index
9336 this._firstVisibleIndexVal = null;
9337
9338 // random access
9339 if (Math.abs(delta) > this._physicalSize) {
9340 this._physicalTop += delta;
9341 direction = SCROLL_DIRECTION_NONE;
9342 recycledTiles = Math.round(delta / this._physicalAverage);
9343 }
9344 // scroll up
9345 else if (delta < 0) {
9346 var topSpace = scrollTop - this._physicalTop;
9347 var virtualStart = this._virtualStart;
9348
9349 direction = SCROLL_DIRECTION_UP;
9350 recycledTileSet = [];
9351
9352 kth = this._physicalEnd;
9353 currentRatio = topSpace / hiddenContentSize;
9354
9355 // move tiles from bottom to top
9356 while (
9357 // approximate `currentRatio` to `ratio`
9358 currentRatio < ratio &&
9359 // recycle less physical items than the total
9360 recycledTiles < this._physicalCount &&
9361 // ensure that these recycled tiles are needed
9362 virtualStart - recycledTiles > 0
9363 ) {
9364
9365 tileHeight = this._physicalSizes[kth] || this._physicalAverage;
9366 currentRatio += tileHeight / hiddenContentSize;
9367
9368 recycledTileSet.push(kth);
9369 recycledTiles++;
9370 kth = (kth === 0) ? this._physicalCount - 1 : kth - 1;
9371 }
9372
9373 movingUp = recycledTileSet;
9374 recycledTiles = -recycledTiles;
9375
9376 }
9377 // scroll down
9378 else if (delta > 0) {
9379 var bottomSpace = this._physicalBottom - (scrollTop + this._viewportSize );
9380 var virtualEnd = this._virtualEnd;
9381 var lastVirtualItemIndex = this._virtualCount-1;
9382
9383 direction = SCROLL_DIRECTION_DOWN;
9384 recycledTileSet = [];
9385
9386 kth = this._physicalStart;
9387 currentRatio = bottomSpace / hiddenContentSize;
9388
9389 // move tiles from top to bottom
9390 while (
9391 // approximate `currentRatio` to `ratio`
9392 currentRatio < ratio &&
9393 // recycle less physical items than the total
9394 recycledTiles < this._physicalCount &&
9395 // ensure that these recycled tiles are needed
9396 virtualEnd + recycledTiles < lastVirtualItemIndex
9397 ) {
9398
9399 tileHeight = this._physicalSizes[kth] || this._physicalAverage;
9400 currentRatio += tileHeight / hiddenContentSize;
9401
9402 this._physicalTop += tileHeight;
9403 recycledTileSet.push(kth);
9404 recycledTiles++;
9405 kth = (kth + 1) % this._physicalCount;
9406 }
9407 }
9408
9409 if (recycledTiles !== 0) {
9410 this._virtualStart = this._virtualStart + recycledTiles;
9411 this._update(recycledTileSet, movingUp);
9412 }
9413 },
9414
9415 /**
9416 * Update the list of items, starting from the `_virtualStartVal` item.
9417 * @param {!Array<number>=} itemSet
9418 * @param {!Array<number>=} movingUp
9419 */
9420 _update: function(itemSet, movingUp) {
9421 // update models
9422 this._assignModels(itemSet);
9423
9424 // measure heights
9425 this._updateMetrics(itemSet);
9426
9427 // adjust offset after measuring
9428 if (movingUp) {
9429 while (movingUp.length) {
9430 this._physicalTop -= this._physicalSizes[movingUp.pop()];
9431 }
9432 }
9433 // update the position of the items
9434 this._positionItems();
9435
9436 // set the scroller size
9437 this._updateScrollerSize();
9438
9439 // increase the pool of physical items if needed
9440 if (this._increasePoolIfNeeded()) {
9441 // set models to the new items
9442 this.async(this._update);
9443 }
9444 },
9445
9446 /**
9447 * Creates a pool of DOM elements and attaches them to the local dom.
9448 */
9449 _createPool: function(size) {
9450 var physicalItems = new Array(size);
9451
9452 this._ensureTemplatized();
9453
9454 for (var i = 0; i < size; i++) {
9455 var inst = this.stamp(null);
9456
9457 // First element child is item; Safari doesn't support children[0]
9458 // on a doc fragment
9459 physicalItems[i] = inst.root.querySelector('*');
9460 Polymer.dom(this).appendChild(inst.root);
9461 }
9462
9463 return physicalItems;
9464 },
9465
9466 /**
9467 * Increases the pool size. That is, the physical items in the DOM.
9468 * This function will allocate additional physical items
9469 * (limited by `MAX_PHYSICAL_COUNT`) if the content size is shorter than
9470 * `_optPhysicalSize`
9471 *
9472 * @return boolean
9473 */
9474 _increasePoolIfNeeded: function() {
9475 if (this._physicalSize >= this._optPhysicalSize || this._physicalAverage = == 0) {
9476 return false;
9477 }
9478
9479 // the estimated number of physical items that we will need to reach
9480 // the cap established by `_optPhysicalSize`.
9481 var missingItems = Math.round(
9482 (this._optPhysicalSize - this._physicalSize) * 1.2 / this._physicalAve rage
9483 );
9484
9485 // limit the size
9486 var nextPhysicalCount = Math.min(
9487 this._physicalCount + missingItems,
9488 this._virtualCount,
9489 MAX_PHYSICAL_COUNT
9490 );
9491
9492 var prevPhysicalCount = this._physicalCount;
9493 var delta = nextPhysicalCount - prevPhysicalCount;
9494
9495 if (delta <= 0) {
9496 return false;
9497 }
9498
9499 var newPhysicalItems = this._createPool(delta);
9500 var emptyArray = new Array(delta);
9501
9502 [].push.apply(this._physicalItems, newPhysicalItems);
9503 [].push.apply(this._physicalSizes, emptyArray);
9504
9505 this._physicalCount = prevPhysicalCount + delta;
9506
9507 return true;
9508 },
9509
9510 /**
9511 * Render a new list of items. This method does exactly the same as `update` ,
9512 * but it also ensures that only one `update` cycle is created.
9513 */
9514 _render: function() {
9515 var requiresUpdate = this._virtualCount > 0 || this._physicalCount > 0;
9516
9517 if (this.isAttached && !this._itemsRendered && this._isVisible && requires Update) {
9518 this._update();
9519 this._itemsRendered = true;
9520 }
9521 },
9522
9523 /**
9524 * Templetizes the user template.
9525 */
9526 _ensureTemplatized: function() {
9527 if (!this.ctor) {
9528 // Template instance props that should be excluded from forwarding
9529 var props = {};
9530
9531 props.__key__ = true;
9532 props[this.as] = true;
9533 props[this.indexAs] = true;
9534 props[this.selectedAs] = true;
9535
9536 this._instanceProps = props;
9537 this._userTemplate = Polymer.dom(this).querySelector('template');
9538
9539 if (this._userTemplate) {
9540 this.templatize(this._userTemplate);
9541 } else {
9542 console.warn('iron-list requires a template to be provided in light-do m');
9543 }
9544 }
9545 },
9546
9547 /**
9548 * Implements extension point from Templatizer mixin.
9549 */
9550 _getStampedChildren: function() {
9551 return this._physicalItems;
9552 },
9553
9554 /**
9555 * Implements extension point from Templatizer
9556 * Called as a side effect of a template instance path change, responsible
9557 * for notifying items.<key-for-instance>.<path> change up to host.
9558 */
9559 _forwardInstancePath: function(inst, path, value) {
9560 if (path.indexOf(this.as + '.') === 0) {
9561 this.notifyPath('items.' + inst.__key__ + '.' +
9562 path.slice(this.as.length + 1), value);
9563 }
9564 },
9565
9566 /**
9567 * Implements extension point from Templatizer mixin
9568 * Called as side-effect of a host property change, responsible for
9569 * notifying parent path change on each row.
9570 */
9571 _forwardParentProp: function(prop, value) {
9572 if (this._physicalItems) {
9573 this._physicalItems.forEach(function(item) {
9574 item._templateInstance[prop] = value;
9575 }, this);
9576 }
9577 },
9578
9579 /**
9580 * Implements extension point from Templatizer
9581 * Called as side-effect of a host path change, responsible for
9582 * notifying parent.<path> path change on each row.
9583 */
9584 _forwardParentPath: function(path, value) {
9585 if (this._physicalItems) {
9586 this._physicalItems.forEach(function(item) {
9587 item._templateInstance.notifyPath(path, value, true);
9588 }, this);
9589 }
9590 },
9591
9592 /**
9593 * Called as a side effect of a host items.<key>.<path> path change,
9594 * responsible for notifying item.<path> changes to row for key.
9595 */
9596 _forwardItemPath: function(path, value) {
9597 if (this._physicalIndexForKey) {
9598 var dot = path.indexOf('.');
9599 var key = path.substring(0, dot < 0 ? path.length : dot);
9600 var idx = this._physicalIndexForKey[key];
9601 var row = this._physicalItems[idx];
9602 if (row) {
9603 var inst = row._templateInstance;
9604 if (dot >= 0) {
9605 path = this.as + '.' + path.substring(dot+1);
9606 inst.notifyPath(path, value, true);
9607 } else {
9608 inst[this.as] = value;
9609 }
9610 }
9611 }
9612 },
9613
9614 /**
9615 * Called when the items have changed. That is, ressignments
9616 * to `items`, splices or updates to a single item.
9617 */
9618 _itemsChanged: function(change) {
9619 if (change.path === 'items') {
9620 // render the new set
9621 this._itemsRendered = false;
9622
9623 // update the whole set
9624 this._virtualStartVal = 0;
9625 this._physicalTop = 0;
9626 this._virtualCount = this.items ? this.items.length : 0;
9627 this._collection = this.items ? Polymer.Collection.get(this.items) : nul l;
9628 this._physicalIndexForKey = {};
9629
9630 // scroll to the top
9631 this._resetScrollPosition(0);
9632
9633 // create the initial physical items
9634 if (!this._physicalItems) {
9635 this._physicalCount = Math.max(1, Math.min(DEFAULT_PHYSICAL_COUNT, thi s._virtualCount));
9636 this._physicalItems = this._createPool(this._physicalCount);
9637 this._physicalSizes = new Array(this._physicalCount);
9638 }
9639
9640 this.debounce('refresh', this._render);
9641
9642 } else if (change.path === 'items.splices') {
9643 // render the new set
9644 this._itemsRendered = false;
9645
9646 this._adjustVirtualIndex(change.value.indexSplices);
9647 this._virtualCount = this.items ? this.items.length : 0;
9648
9649 this.debounce('refresh', this._render);
9650
9651 } else {
9652 // update a single item
9653 this._forwardItemPath(change.path.split('.').slice(1).join('.'), change. value);
9654 }
9655 },
9656
9657 /**
9658 * @param {!Array<!PolymerSplice>} splices
9659 */
9660 _adjustVirtualIndex: function(splices) {
9661 var i, splice, idx;
9662
9663 for (i = 0; i < splices.length; i++) {
9664 splice = splices[i];
9665
9666 // deselect removed items
9667 splice.removed.forEach(this.$.selector.deselect, this.$.selector);
9668
9669 idx = splice.index;
9670 // We only need to care about changes happening above the current positi on
9671 if (idx >= this._virtualStartVal) {
9672 break;
9673 }
9674
9675 this._virtualStart = this._virtualStart +
9676 Math.max(splice.addedCount - splice.removed.length, idx - this._virt ualStartVal);
9677 }
9678 },
9679
9680 _scrollHandler: function() {
9681 this._refresh();
9682 },
9683
9684 /**
9685 * Executes a provided function per every physical index in `itemSet`
9686 * `itemSet` default value is equivalent to the entire set of physical index es.
9687 *
9688 * @param {!function(number, number)} fn
9689 * @param {!Array<number>=} itemSet
9690 */
9691 _iterateItems: function(fn, itemSet) {
9692 var pidx, vidx, rtn, i;
9693
9694 if (arguments.length === 2 && itemSet) {
9695 for (i = 0; i < itemSet.length; i++) {
9696 pidx = itemSet[i];
9697 if (pidx >= this._physicalStart) {
9698 vidx = this._virtualStartVal + (pidx - this._physicalStart);
9699 } else {
9700 vidx = this._virtualStartVal + (this._physicalCount - this._physical Start) + pidx;
9701 }
9702 if ((rtn = fn.call(this, pidx, vidx)) != null) {
9703 return rtn;
9704 }
9705 }
9706 } else {
9707 pidx = this._physicalStart;
9708 vidx = this._virtualStartVal;
9709
9710 for (; pidx < this._physicalCount; pidx++, vidx++) {
9711 if ((rtn = fn.call(this, pidx, vidx)) != null) {
9712 return rtn;
9713 }
9714 }
9715
9716 pidx = 0;
9717
9718 for (; pidx < this._physicalStart; pidx++, vidx++) {
9719 if ((rtn = fn.call(this, pidx, vidx)) != null) {
9720 return rtn;
9721 }
9722 }
9723 }
9724 },
9725
9726 /**
9727 * Assigns the data models to a given set of items.
9728 * @param {!Array<number>=} itemSet
9729 */
9730 _assignModels: function(itemSet) {
9731 this._iterateItems(function(pidx, vidx) {
9732 var el = this._physicalItems[pidx];
9733 var inst = el._templateInstance;
9734 var item = this.items && this.items[vidx];
9735
9736 if (item) {
9737 inst[this.as] = item;
9738 inst.__key__ = this._collection.getKey(item);
9739 inst[this.selectedAs] =
9740 /** @type {!ArraySelectorElement} */ (this.$.selector).isSelected(it em);
9741 inst[this.indexAs] = vidx;
9742 el.removeAttribute('hidden');
9743 this._physicalIndexForKey[inst.__key__] = pidx;
9744 } else {
9745 inst.__key__ = null;
9746 el.setAttribute('hidden', '');
9747 }
9748
9749 }, itemSet);
9750 },
9751
9752 /**
9753 * Updates the height for a given set of items.
9754 *
9755 * @param {!Array<number>=} itemSet
9756 */
9757 _updateMetrics: function(itemSet) {
9758 var newPhysicalSize = 0;
9759 var oldPhysicalSize = 0;
9760 var prevAvgCount = this._physicalAverageCount;
9761 var prevPhysicalAvg = this._physicalAverage;
9762 // Make sure we distributed all the physical items
9763 // so we can measure them
9764 Polymer.dom.flush();
9765
9766 this._iterateItems(function(pidx, vidx) {
9767 oldPhysicalSize += this._physicalSizes[pidx] || 0;
9768 this._physicalSizes[pidx] = this._physicalItems[pidx].offsetHeight;
9769 newPhysicalSize += this._physicalSizes[pidx];
9770 this._physicalAverageCount += this._physicalSizes[pidx] ? 1 : 0;
9771 }, itemSet);
9772
9773 this._physicalSize = this._physicalSize + newPhysicalSize - oldPhysicalSiz e;
9774 this._viewportSize = this._scroller.offsetHeight;
9775
9776 // update the average if we measured something
9777 if (this._physicalAverageCount !== prevAvgCount) {
9778 this._physicalAverage = Math.round(
9779 ((prevPhysicalAvg * prevAvgCount) + newPhysicalSize) /
9780 this._physicalAverageCount);
9781 }
9782 },
9783
9784 /**
9785 * Updates the position of the physical items.
9786 */
9787 _positionItems: function() {
9788 this._adjustScrollPosition();
9789
9790 var y = this._physicalTop;
9791
9792 this._iterateItems(function(pidx) {
9793
9794 this.transform('translate3d(0, ' + y + 'px, 0)', this._physicalItems[pid x]);
9795 y += this._physicalSizes[pidx];
9796
9797 });
9798 },
9799
9800 /**
9801 * Adjusts the scroll position when it was overestimated.
9802 */
9803 _adjustScrollPosition: function() {
9804 var deltaHeight = this._virtualStartVal === 0 ? this._physicalTop :
9805 Math.min(this._scrollPosition + this._physicalTop, 0);
9806
9807 if (deltaHeight) {
9808 this._physicalTop = this._physicalTop - deltaHeight;
9809
9810 // juking scroll position during interial scrolling on iOS is no bueno
9811 if (!IOS_TOUCH_SCROLLING) {
9812 this._resetScrollPosition(this._scroller.scrollTop - deltaHeight);
9813 }
9814 }
9815 },
9816
9817 /**
9818 * Sets the position of the scroll.
9819 */
9820 _resetScrollPosition: function(pos) {
9821 if (this._scroller) {
9822 this._scroller.scrollTop = pos;
9823 this._scrollPosition = this._scroller.scrollTop;
9824 }
9825 },
9826
9827 /**
9828 * Sets the scroll height, that's the height of the content,
9829 *
9830 * @param {boolean=} forceUpdate If true, updates the height no matter what.
9831 */
9832 _updateScrollerSize: function(forceUpdate) {
9833 this._estScrollHeight = (this._physicalBottom +
9834 Math.max(this._virtualCount - this._physicalCount - this._virtualStart Val, 0) * this._physicalAverage);
9835
9836 forceUpdate = forceUpdate || this._scrollHeight === 0;
9837 forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight - this._physicalSize;
9838
9839 // amortize height adjustment, so it won't trigger repaints very often
9840 if (forceUpdate || Math.abs(this._estScrollHeight - this._scrollHeight) >= this._optPhysicalSize) {
9841 this.$.items.style.height = this._estScrollHeight + 'px';
9842 this._scrollHeight = this._estScrollHeight;
9843 }
9844 },
9845
9846 /**
9847 * Scroll to a specific item in the virtual list regardless
9848 * of the physical items in the DOM tree.
9849 *
9850 * @method scrollToIndex
9851 * @param {number} idx The index of the item
9852 */
9853 scrollToIndex: function(idx) {
9854 if (typeof idx !== 'number') {
9855 return;
9856 }
9857
9858 var firstVisible = this.firstVisibleIndex;
9859
9860 idx = Math.min(Math.max(idx, 0), this._virtualCount-1);
9861
9862 // start at the previous virtual item
9863 // so we have a item above the first visible item
9864 this._virtualStart = idx - 1;
9865
9866 // assign new models
9867 this._assignModels();
9868
9869 // measure the new sizes
9870 this._updateMetrics();
9871
9872 // estimate new physical offset
9873 this._physicalTop = this._virtualStart * this._physicalAverage;
9874
9875 var currentTopItem = this._physicalStart;
9876 var currentVirtualItem = this._virtualStart;
9877 var targetOffsetTop = 0;
9878 var hiddenContentSize = this._hiddenContentSize;
9879
9880 // scroll to the item as much as we can
9881 while (currentVirtualItem !== idx && targetOffsetTop < hiddenContentSize) {
9882 targetOffsetTop = targetOffsetTop + this._physicalSizes[currentTopItem];
9883 currentTopItem = (currentTopItem + 1) % this._physicalCount;
9884 currentVirtualItem++;
9885 }
9886
9887 // update the scroller size
9888 this._updateScrollerSize(true);
9889
9890 // update the position of the items
9891 this._positionItems();
9892
9893 // set the new scroll position
9894 this._resetScrollPosition(this._physicalTop + targetOffsetTop + 1);
9895
9896 // increase the pool of physical items if needed
9897 if (this._increasePoolIfNeeded()) {
9898 // set models to the new items
9899 this.async(this._update);
9900 }
9901
9902 // clear cached visible index
9903 this._firstVisibleIndexVal = null;
9904 },
9905
9906 /**
9907 * Reset the physical average and the average count.
9908 */
9909 _resetAverage: function() {
9910 this._physicalAverage = 0;
9911 this._physicalAverageCount = 0;
9912 },
9913
9914 /**
9915 * A handler for the `iron-resize` event triggered by `IronResizableBehavior `
9916 * when the element is resized.
9917 */
9918 _resizeHandler: function() {
9919 this.debounce('resize', function() {
9920 this._render();
9921 if (this._itemsRendered && this._physicalItems && this._isVisible) {
9922 this._resetAverage();
9923 this.updateViewportBoundaries();
9924 this.scrollToIndex(this.firstVisibleIndex);
9925 }
9926 });
9927 },
9928
9929 _getModelFromItem: function(item) {
9930 var key = this._collection.getKey(item);
9931 var pidx = this._physicalIndexForKey[key];
9932
9933 if (pidx !== undefined) {
9934 return this._physicalItems[pidx]._templateInstance;
9935 }
9936 return null;
9937 },
9938
9939 /**
9940 * Gets a valid item instance from its index or the object value.
9941 *
9942 * @param {(Object|number)} item The item object or its index
9943 */
9944 _getNormalizedItem: function(item) {
9945 if (typeof item === 'number') {
9946 item = this.items[item];
9947 if (!item) {
9948 throw new RangeError('<item> not found');
9949 }
9950 } else if (this._collection.getKey(item) === undefined) {
9951 throw new TypeError('<item> should be a valid item');
9952 }
9953 return item;
9954 },
9955
9956 /**
9957 * Select the list item at the given index.
9958 *
9959 * @method selectItem
9960 * @param {(Object|number)} item The item object or its index
9961 */
9962 selectItem: function(item) {
9963 item = this._getNormalizedItem(item);
9964 var model = this._getModelFromItem(item);
9965
9966 if (!this.multiSelection && this.selectedItem) {
9967 this.deselectItem(this.selectedItem);
9968 }
9969 if (model) {
9970 model[this.selectedAs] = true;
9971 }
9972 this.$.selector.select(item);
9973 },
9974
9975 /**
9976 * Deselects the given item list if it is already selected.
9977 *
9978
9979 * @method deselect
9980 * @param {(Object|number)} item The item object or its index
9981 */
9982 deselectItem: function(item) {
9983 item = this._getNormalizedItem(item);
9984 var model = this._getModelFromItem(item);
9985
9986 if (model) {
9987 model[this.selectedAs] = false;
9988 }
9989 this.$.selector.deselect(item);
9990 },
9991
9992 /**
9993 * Select or deselect a given item depending on whether the item
9994 * has already been selected.
9995 *
9996 * @method toggleSelectionForItem
9997 * @param {(Object|number)} item The item object or its index
9998 */
9999 toggleSelectionForItem: function(item) {
10000 item = this._getNormalizedItem(item);
10001 if (/** @type {!ArraySelectorElement} */ (this.$.selector).isSelected(item )) {
10002 this.deselectItem(item);
10003 } else {
10004 this.selectItem(item);
10005 }
10006 },
10007
10008 /**
10009 * Clears the current selection state of the list.
10010 *
10011 * @method clearSelection
10012 */
10013 clearSelection: function() {
10014 function unselect(item) {
10015 var model = this._getModelFromItem(item);
10016 if (model) {
10017 model[this.selectedAs] = false;
10018 }
10019 }
10020
10021 if (Array.isArray(this.selectedItems)) {
10022 this.selectedItems.forEach(unselect, this);
10023 } else if (this.selectedItem) {
10024 unselect.call(this, this.selectedItem);
10025 }
10026
10027 /** @type {!ArraySelectorElement} */ (this.$.selector).clearSelection();
10028 },
10029
10030 /**
10031 * Add an event listener to `tap` if `selectionEnabled` is true,
10032 * it will remove the listener otherwise.
10033 */
10034 _selectionEnabledChanged: function(selectionEnabled) {
10035 if (selectionEnabled) {
10036 this.listen(this, 'tap', '_selectionHandler');
10037 this.listen(this, 'keypress', '_selectionHandler');
10038 } else {
10039 this.unlisten(this, 'tap', '_selectionHandler');
10040 this.unlisten(this, 'keypress', '_selectionHandler');
10041 }
10042 },
10043
10044 /**
10045 * Select an item from an event object.
10046 */
10047 _selectionHandler: function(e) {
10048 if (e.type !== 'keypress' || e.keyCode === 13) {
10049 var model = this.modelForElement(e.target);
10050 if (model) {
10051 this.toggleSelectionForItem(model[this.as]);
10052 }
10053 }
10054 },
10055
10056 _multiSelectionChanged: function(multiSelection) {
10057 this.clearSelection();
10058 this.$.selector.multi = multiSelection;
10059 },
10060
10061 /**
10062 * Updates the size of an item.
10063 *
10064 * @method updateSizeForItem
10065 * @param {(Object|number)} item The item object or its index
10066 */
10067 updateSizeForItem: function(item) {
10068 item = this._getNormalizedItem(item);
10069 var key = this._collection.getKey(item);
10070 var pidx = this._physicalIndexForKey[key];
10071
10072 if (pidx !== undefined) {
10073 this._updateMetrics([pidx]);
10074 this._positionItems();
10075 }
10076 }
10077 });
10078
10079 })();
8774 (function() { 10080 (function() {
8775 10081
8776 'use strict'; 10082 'use strict';
8777 10083
8778 var SHADOW_WHEN_SCROLLING = 1; 10084 var SHADOW_WHEN_SCROLLING = 1;
8779 var SHADOW_ALWAYS = 2; 10085 var SHADOW_ALWAYS = 2;
8780 10086
8781 10087
8782 var MODE_CONFIGS = { 10088 var MODE_CONFIGS = {
8783 10089
(...skipping 227 matching lines...) Expand 10 before | Expand all | Expand 10 after
9011 }, 10317 },
9012 10318
9013 _getScrollerForMode: function(mode) { 10319 _getScrollerForMode: function(mode) {
9014 return MODE_CONFIGS.outerScroll[mode] ? 10320 return MODE_CONFIGS.outerScroll[mode] ?
9015 this : this.$.mainContainer; 10321 this : this.$.mainContainer;
9016 } 10322 }
9017 10323
9018 }); 10324 });
9019 10325
9020 })(); 10326 })();
10327 (function() {
10328
10329 // monostate data
10330 var metaDatas = {};
10331 var metaArrays = {};
10332
10333 Polymer.IronMeta = Polymer({
10334
10335 is: 'iron-meta',
10336
10337 properties: {
10338
10339 /**
10340 * The type of meta-data. All meta-data of the same type is stored
10341 * together.
10342 */
10343 type: {
10344 type: String,
10345 value: 'default',
10346 observer: '_typeChanged'
10347 },
10348
10349 /**
10350 * The key used to store `value` under the `type` namespace.
10351 */
10352 key: {
10353 type: String,
10354 observer: '_keyChanged'
10355 },
10356
10357 /**
10358 * The meta-data to store or retrieve.
10359 */
10360 value: {
10361 type: Object,
10362 notify: true,
10363 observer: '_valueChanged'
10364 },
10365
10366 /**
10367 * If true, `value` is set to the iron-meta instance itself.
10368 */
10369 self: {
10370 type: Boolean,
10371 observer: '_selfChanged'
10372 },
10373
10374 /**
10375 * Array of all meta-data values for the given type.
10376 */
10377 list: {
10378 type: Array,
10379 notify: true
10380 }
10381
10382 },
10383
10384 /**
10385 * Only runs if someone invokes the factory/constructor directly
10386 * e.g. `new Polymer.IronMeta()`
10387 */
10388 factoryImpl: function(config) {
10389 if (config) {
10390 for (var n in config) {
10391 switch(n) {
10392 case 'type':
10393 case 'key':
10394 case 'value':
10395 this[n] = config[n];
10396 break;
10397 }
10398 }
10399 }
10400 },
10401
10402 created: function() {
10403 // TODO(sjmiles): good for debugging?
10404 this._metaDatas = metaDatas;
10405 this._metaArrays = metaArrays;
10406 },
10407
10408 _keyChanged: function(key, old) {
10409 this._resetRegistration(old);
10410 },
10411
10412 _valueChanged: function(value) {
10413 this._resetRegistration(this.key);
10414 },
10415
10416 _selfChanged: function(self) {
10417 if (self) {
10418 this.value = this;
10419 }
10420 },
10421
10422 _typeChanged: function(type) {
10423 this._unregisterKey(this.key);
10424 if (!metaDatas[type]) {
10425 metaDatas[type] = {};
10426 }
10427 this._metaData = metaDatas[type];
10428 if (!metaArrays[type]) {
10429 metaArrays[type] = [];
10430 }
10431 this.list = metaArrays[type];
10432 this._registerKeyValue(this.key, this.value);
10433 },
10434
10435 /**
10436 * Retrieves meta data value by key.
10437 *
10438 * @method byKey
10439 * @param {string} key The key of the meta-data to be returned.
10440 * @return {*}
10441 */
10442 byKey: function(key) {
10443 return this._metaData && this._metaData[key];
10444 },
10445
10446 _resetRegistration: function(oldKey) {
10447 this._unregisterKey(oldKey);
10448 this._registerKeyValue(this.key, this.value);
10449 },
10450
10451 _unregisterKey: function(key) {
10452 this._unregister(key, this._metaData, this.list);
10453 },
10454
10455 _registerKeyValue: function(key, value) {
10456 this._register(key, value, this._metaData, this.list);
10457 },
10458
10459 _register: function(key, value, data, list) {
10460 if (key && data && value !== undefined) {
10461 data[key] = value;
10462 list.push(value);
10463 }
10464 },
10465
10466 _unregister: function(key, data, list) {
10467 if (key && data) {
10468 if (key in data) {
10469 var value = data[key];
10470 delete data[key];
10471 this.arrayDelete(list, value);
10472 }
10473 }
10474 }
10475
10476 });
10477
10478 /**
10479 `iron-meta-query` can be used to access infomation stored in `iron-meta`.
10480
10481 Examples:
10482
10483 If I create an instance like this:
10484
10485 <iron-meta key="info" value="foo/bar"></iron-meta>
10486
10487 Note that value="foo/bar" is the metadata I've defined. I could define more
10488 attributes or use child nodes to define additional metadata.
10489
10490 Now I can access that element (and it's metadata) from any `iron-meta-query` instance:
10491
10492 var value = new Polymer.IronMetaQuery({key: 'info'}).value;
10493
10494 @group Polymer Iron Elements
10495 @element iron-meta-query
10496 */
10497 Polymer.IronMetaQuery = Polymer({
10498
10499 is: 'iron-meta-query',
10500
10501 properties: {
10502
10503 /**
10504 * The type of meta-data. All meta-data of the same type is stored
10505 * together.
10506 */
10507 type: {
10508 type: String,
10509 value: 'default',
10510 observer: '_typeChanged'
10511 },
10512
10513 /**
10514 * Specifies a key to use for retrieving `value` from the `type`
10515 * namespace.
10516 */
10517 key: {
10518 type: String,
10519 observer: '_keyChanged'
10520 },
10521
10522 /**
10523 * The meta-data to store or retrieve.
10524 */
10525 value: {
10526 type: Object,
10527 notify: true,
10528 readOnly: true
10529 },
10530
10531 /**
10532 * Array of all meta-data values for the given type.
10533 */
10534 list: {
10535 type: Array,
10536 notify: true
10537 }
10538
10539 },
10540
10541 /**
10542 * Actually a factory method, not a true constructor. Only runs if
10543 * someone invokes it directly (via `new Polymer.IronMeta()`);
10544 */
10545 factoryImpl: function(config) {
10546 if (config) {
10547 for (var n in config) {
10548 switch(n) {
10549 case 'type':
10550 case 'key':
10551 this[n] = config[n];
10552 break;
10553 }
10554 }
10555 }
10556 },
10557
10558 created: function() {
10559 // TODO(sjmiles): good for debugging?
10560 this._metaDatas = metaDatas;
10561 this._metaArrays = metaArrays;
10562 },
10563
10564 _keyChanged: function(key) {
10565 this._setValue(this._metaData && this._metaData[key]);
10566 },
10567
10568 _typeChanged: function(type) {
10569 this._metaData = metaDatas[type];
10570 this.list = metaArrays[type];
10571 if (this.key) {
10572 this._keyChanged(this.key);
10573 }
10574 },
10575
10576 /**
10577 * Retrieves meta data value by key.
10578 * @param {string} key The key of the meta-data to be returned.
10579 * @return {*}
10580 */
10581 byKey: function(key) {
10582 return this._metaData && this._metaData[key];
10583 }
10584
10585 });
10586
10587 })();
10588 Polymer({
10589
10590 is: 'iron-icon',
10591
10592 properties: {
10593
10594 /**
10595 * The name of the icon to use. The name should be of the form:
10596 * `iconset_name:icon_name`.
10597 */
10598 icon: {
10599 type: String,
10600 observer: '_iconChanged'
10601 },
10602
10603 /**
10604 * The name of the theme to used, if one is specified by the
10605 * iconset.
10606 */
10607 theme: {
10608 type: String,
10609 observer: '_updateIcon'
10610 },
10611
10612 /**
10613 * If using iron-icon without an iconset, you can set the src to be
10614 * the URL of an individual icon image file. Note that this will take
10615 * precedence over a given icon attribute.
10616 */
10617 src: {
10618 type: String,
10619 observer: '_srcChanged'
10620 },
10621
10622 /**
10623 * @type {!Polymer.IronMeta}
10624 */
10625 _meta: {
10626 value: Polymer.Base.create('iron-meta', {type: 'iconset'})
10627 }
10628
10629 },
10630
10631 _DEFAULT_ICONSET: 'icons',
10632
10633 _iconChanged: function(icon) {
10634 var parts = (icon || '').split(':');
10635 this._iconName = parts.pop();
10636 this._iconsetName = parts.pop() || this._DEFAULT_ICONSET;
10637 this._updateIcon();
10638 },
10639
10640 _srcChanged: function(src) {
10641 this._updateIcon();
10642 },
10643
10644 _usesIconset: function() {
10645 return this.icon || !this.src;
10646 },
10647
10648 /** @suppress {visibility} */
10649 _updateIcon: function() {
10650 if (this._usesIconset()) {
10651 if (this._iconsetName) {
10652 this._iconset = /** @type {?Polymer.Iconset} */ (
10653 this._meta.byKey(this._iconsetName));
10654 if (this._iconset) {
10655 this._iconset.applyIcon(this, this._iconName, this.theme);
10656 this.unlisten(window, 'iron-iconset-added', '_updateIcon');
10657 } else {
10658 this.listen(window, 'iron-iconset-added', '_updateIcon');
10659 }
10660 }
10661 } else {
10662 if (!this._img) {
10663 this._img = document.createElement('img');
10664 this._img.style.width = '100%';
10665 this._img.style.height = '100%';
10666 this._img.draggable = false;
10667 }
10668 this._img.src = this.src;
10669 Polymer.dom(this.root).appendChild(this._img);
10670 }
10671 }
10672
10673 });
10674 /**
10675 * The `iron-iconset-svg` element allows users to define their own icon sets
10676 * that contain svg icons. The svg icon elements should be children of the
10677 * `iron-iconset-svg` element. Multiple icons should be given distinct id's.
10678 *
10679 * Using svg elements to create icons has a few advantages over traditional
10680 * bitmap graphics like jpg or png. Icons that use svg are vector based so the y
10681 * are resolution independent and should look good on any device. They are
10682 * stylable via css. Icons can be themed, colorized, and even animated.
10683 *
10684 * Example:
10685 *
10686 * <iron-iconset-svg name="my-svg-icons" size="24">
10687 * <svg>
10688 * <defs>
10689 * <g id="shape">
10690 * <rect x="50" y="50" width="50" height="50" />
10691 * <circle cx="50" cy="50" r="50" />
10692 * </g>
10693 * </defs>
10694 * </svg>
10695 * </iron-iconset-svg>
10696 *
10697 * This will automatically register the icon set "my-svg-icons" to the iconset
10698 * database. To use these icons from within another element, make a
10699 * `iron-iconset` element and call the `byId` method
10700 * to retrieve a given iconset. To apply a particular icon inside an
10701 * element use the `applyIcon` method. For example:
10702 *
10703 * iconset.applyIcon(iconNode, 'car');
10704 *
10705 * @element iron-iconset-svg
10706 * @demo demo/index.html
10707 */
10708 Polymer({
10709
10710 is: 'iron-iconset-svg',
10711
10712 properties: {
10713
10714 /**
10715 * The name of the iconset.
10716 *
10717 * @attribute name
10718 * @type string
10719 */
10720 name: {
10721 type: String,
10722 observer: '_nameChanged'
10723 },
10724
10725 /**
10726 * The size of an individual icon. Note that icons must be square.
10727 *
10728 * @attribute iconSize
10729 * @type number
10730 * @default 24
10731 */
10732 size: {
10733 type: Number,
10734 value: 24
10735 }
10736
10737 },
10738
10739 /**
10740 * Construct an array of all icon names in this iconset.
10741 *
10742 * @return {!Array} Array of icon names.
10743 */
10744 getIconNames: function() {
10745 this._icons = this._createIconMap();
10746 return Object.keys(this._icons).map(function(n) {
10747 return this.name + ':' + n;
10748 }, this);
10749 },
10750
10751 /**
10752 * Applies an icon to the given element.
10753 *
10754 * An svg icon is prepended to the element's shadowRoot if it exists,
10755 * otherwise to the element itself.
10756 *
10757 * @method applyIcon
10758 * @param {Element} element Element to which the icon is applied.
10759 * @param {string} iconName Name of the icon to apply.
10760 * @return {Element} The svg element which renders the icon.
10761 */
10762 applyIcon: function(element, iconName) {
10763 // insert svg element into shadow root, if it exists
10764 element = element.root || element;
10765 // Remove old svg element
10766 this.removeIcon(element);
10767 // install new svg element
10768 var svg = this._cloneIcon(iconName);
10769 if (svg) {
10770 var pde = Polymer.dom(element);
10771 pde.insertBefore(svg, pde.childNodes[0]);
10772 return element._svgIcon = svg;
10773 }
10774 return null;
10775 },
10776
10777 /**
10778 * Remove an icon from the given element by undoing the changes effected
10779 * by `applyIcon`.
10780 *
10781 * @param {Element} element The element from which the icon is removed.
10782 */
10783 removeIcon: function(element) {
10784 // Remove old svg element
10785 if (element._svgIcon) {
10786 Polymer.dom(element).removeChild(element._svgIcon);
10787 element._svgIcon = null;
10788 }
10789 },
10790
10791 /**
10792 *
10793 * When name is changed, register iconset metadata
10794 *
10795 */
10796 _nameChanged: function() {
10797 new Polymer.IronMeta({type: 'iconset', key: this.name, value: this});
10798 this.async(function() {
10799 this.fire('iron-iconset-added', this, {node: window});
10800 });
10801 },
10802
10803 /**
10804 * Create a map of child SVG elements by id.
10805 *
10806 * @return {!Object} Map of id's to SVG elements.
10807 */
10808 _createIconMap: function() {
10809 // Objects chained to Object.prototype (`{}`) have members. Specifically,
10810 // on FF there is a `watch` method that confuses the icon map, so we
10811 // need to use a null-based object here.
10812 var icons = Object.create(null);
10813 Polymer.dom(this).querySelectorAll('[id]')
10814 .forEach(function(icon) {
10815 icons[icon.id] = icon;
10816 });
10817 return icons;
10818 },
10819
10820 /**
10821 * Produce installable clone of the SVG element matching `id` in this
10822 * iconset, or `undefined` if there is no matching element.
10823 *
10824 * @return {Element} Returns an installable clone of the SVG element
10825 * matching `id`.
10826 */
10827 _cloneIcon: function(id) {
10828 // create the icon map on-demand, since the iconset itself has no discrete
10829 // signal to know when it's children are fully parsed
10830 this._icons = this._icons || this._createIconMap();
10831 return this._prepareSvgClone(this._icons[id], this.size);
10832 },
10833
10834 /**
10835 * @param {Element} sourceSvg
10836 * @param {number} size
10837 * @return {Element}
10838 */
10839 _prepareSvgClone: function(sourceSvg, size) {
10840 if (sourceSvg) {
10841 var content = sourceSvg.cloneNode(true),
10842 svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'),
10843 viewBox = content.getAttribute('viewBox') || '0 0 ' + size + ' ' + s ize;
10844 svg.setAttribute('viewBox', viewBox);
10845 svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
10846 // TODO(dfreedm): `pointer-events: none` works around https://crbug.com/ 370136
10847 // TODO(sjmiles): inline style may not be ideal, but avoids requiring a shadow-root
10848 svg.style.cssText = 'pointer-events: none; display: block; width: 100%; height: 100%;';
10849 svg.appendChild(content).removeAttribute('id');
10850 return svg;
10851 }
10852 return null;
10853 }
10854
10855 });
9021 Polymer({ 10856 Polymer({
9022 is: 'paper-material', 10857 is: 'paper-material',
9023 10858
9024 properties: { 10859 properties: {
9025 10860
9026 /** 10861 /**
9027 * The z-depth of this element, from 0-5. Setting to 0 will remove the 10862 * The z-depth of this element, from 0-5. Setting to 0 will remove the
9028 * shadow, and each increasing number greater than 0 will be "deeper" 10863 * shadow, and each increasing number greater than 0 will be "deeper"
9029 * than the last. 10864 * than the last.
9030 * 10865 *
(...skipping 800 matching lines...) Expand 10 before | Expand all | Expand 10 after
9831 /** 11666 /**
9832 * If true, the ripple will remain in the "down" state until `holdDown` 11667 * If true, the ripple will remain in the "down" state until `holdDown`
9833 * is set to false again. 11668 * is set to false again.
9834 */ 11669 */
9835 holdDown: { 11670 holdDown: {
9836 type: Boolean, 11671 type: Boolean,
9837 value: false, 11672 value: false,
9838 observer: '_holdDownChanged' 11673 observer: '_holdDownChanged'
9839 }, 11674 },
9840 11675
11676 /**
11677 * If true, the ripple will not generate a ripple effect
11678 * via pointer interaction.
11679 * Calling ripple's imperative api like `simulatedRipple` will
11680 * still generate the ripple effect.
11681 */
11682 noink: {
11683 type: Boolean,
11684 value: false
11685 },
11686
9841 _animating: { 11687 _animating: {
9842 type: Boolean 11688 type: Boolean
9843 }, 11689 },
9844 11690
9845 _boundAnimate: { 11691 _boundAnimate: {
9846 type: Function, 11692 type: Function,
9847 value: function() { 11693 value: function() {
9848 return this.animate.bind(this); 11694 return this.animate.bind(this);
9849 } 11695 }
9850 } 11696 }
9851 }, 11697 },
9852 11698
11699 observers: [
11700 '_noinkChanged(noink, isAttached)'
11701 ],
11702
9853 get target () { 11703 get target () {
9854 var ownerRoot = Polymer.dom(this).getOwnerRoot(); 11704 var ownerRoot = Polymer.dom(this).getOwnerRoot();
9855 var target; 11705 var target;
9856 11706
9857 if (this.parentNode.nodeType == 11) { // DOCUMENT_FRAGMENT_NODE 11707 if (this.parentNode.nodeType == 11) { // DOCUMENT_FRAGMENT_NODE
9858 target = ownerRoot.host; 11708 target = ownerRoot.host;
9859 } else { 11709 } else {
9860 target = this.parentNode; 11710 target = this.parentNode;
9861 } 11711 }
9862 11712
9863 return target; 11713 return target;
9864 }, 11714 },
9865 11715
9866 keyBindings: { 11716 keyBindings: {
9867 'enter:keydown': '_onEnterKeydown', 11717 'enter:keydown': '_onEnterKeydown',
9868 'space:keydown': '_onSpaceKeydown', 11718 'space:keydown': '_onSpaceKeydown',
9869 'space:keyup': '_onSpaceKeyup' 11719 'space:keyup': '_onSpaceKeyup'
9870 }, 11720 },
9871 11721
9872 attached: function() { 11722 attached: function() {
9873 this.listen(this.target, 'up', 'upAction'); 11723 this.listen(this.target, 'up', 'uiUpAction');
9874 this.listen(this.target, 'down', 'downAction'); 11724 this.listen(this.target, 'down', 'uiDownAction');
9875 11725 },
9876 if (!this.target.hasAttribute('noink')) { 11726
9877 this.keyEventTarget = this.target; 11727 detached: function() {
9878 } 11728 this.unlisten(this.target, 'up', 'uiUpAction');
11729 this.unlisten(this.target, 'down', 'uiDownAction');
9879 }, 11730 },
9880 11731
9881 get shouldKeepAnimating () { 11732 get shouldKeepAnimating () {
9882 for (var index = 0; index < this.ripples.length; ++index) { 11733 for (var index = 0; index < this.ripples.length; ++index) {
9883 if (!this.ripples[index].isAnimationComplete) { 11734 if (!this.ripples[index].isAnimationComplete) {
9884 return true; 11735 return true;
9885 } 11736 }
9886 } 11737 }
9887 11738
9888 return false; 11739 return false;
9889 }, 11740 },
9890 11741
9891 simulatedRipple: function() { 11742 simulatedRipple: function() {
9892 this.downAction(null); 11743 this.downAction(null);
9893 11744
9894 // Please see polymer/polymer#1305 11745 // Please see polymer/polymer#1305
9895 this.async(function() { 11746 this.async(function() {
9896 this.upAction(); 11747 this.upAction();
9897 }, 1); 11748 }, 1);
9898 }, 11749 },
9899 11750
9900 /** @param {Event=} event */ 11751 /**
11752 * Provokes a ripple down effect via a UI event,
11753 * respecting the `noink` property.
11754 * @param {Event=} event
11755 */
11756 uiDownAction: function(event) {
11757 if (!this.noink) {
11758 this.downAction(event);
11759 }
11760 },
11761
11762 /**
11763 * Provokes a ripple down effect via a UI event,
11764 * *not* respecting the `noink` property.
11765 * @param {Event=} event
11766 */
9901 downAction: function(event) { 11767 downAction: function(event) {
9902 if (this.holdDown && this.ripples.length > 0) { 11768 if (this.holdDown && this.ripples.length > 0) {
9903 return; 11769 return;
9904 } 11770 }
9905 11771
9906 var ripple = this.addRipple(); 11772 var ripple = this.addRipple();
9907 11773
9908 ripple.downAction(event); 11774 ripple.downAction(event);
9909 11775
9910 if (!this._animating) { 11776 if (!this._animating) {
9911 this.animate(); 11777 this.animate();
9912 } 11778 }
9913 }, 11779 },
9914 11780
9915 /** @param {Event=} event */ 11781 /**
11782 * Provokes a ripple up effect via a UI event,
11783 * respecting the `noink` property.
11784 * @param {Event=} event
11785 */
11786 uiUpAction: function(event) {
11787 if (!this.noink) {
11788 this.upAction(event);
11789 }
11790 },
11791
11792 /**
11793 * Provokes a ripple up effect via a UI event,
11794 * *not* respecting the `noink` property.
11795 * @param {Event=} event
11796 */
9916 upAction: function(event) { 11797 upAction: function(event) {
9917 if (this.holdDown) { 11798 if (this.holdDown) {
9918 return; 11799 return;
9919 } 11800 }
9920 11801
9921 this.ripples.forEach(function(ripple) { 11802 this.ripples.forEach(function(ripple) {
9922 ripple.upAction(event); 11803 ripple.upAction(event);
9923 }); 11804 });
9924 11805
9925 this.animate(); 11806 this.animate();
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
9978 } 11859 }
9979 11860
9980 if (!this.shouldKeepAnimating && this.ripples.length === 0) { 11861 if (!this.shouldKeepAnimating && this.ripples.length === 0) {
9981 this.onAnimationComplete(); 11862 this.onAnimationComplete();
9982 } else { 11863 } else {
9983 window.requestAnimationFrame(this._boundAnimate); 11864 window.requestAnimationFrame(this._boundAnimate);
9984 } 11865 }
9985 }, 11866 },
9986 11867
9987 _onEnterKeydown: function() { 11868 _onEnterKeydown: function() {
9988 this.downAction(); 11869 this.uiDownAction();
9989 this.async(this.upAction, 1); 11870 this.async(this.uiUpAction, 1);
9990 }, 11871 },
9991 11872
9992 _onSpaceKeydown: function() { 11873 _onSpaceKeydown: function() {
9993 this.downAction(); 11874 this.uiDownAction();
9994 }, 11875 },
9995 11876
9996 _onSpaceKeyup: function() { 11877 _onSpaceKeyup: function() {
9997 this.upAction(); 11878 this.uiUpAction();
9998 }, 11879 },
9999 11880
10000 _holdDownChanged: function(holdDown) { 11881 // note: holdDown does not respect noink since it can be a focus based
10001 if (holdDown) { 11882 // effect.
11883 _holdDownChanged: function(newVal, oldVal) {
11884 if (oldVal === undefined) {
11885 return;
11886 }
11887 if (newVal) {
10002 this.downAction(); 11888 this.downAction();
10003 } else { 11889 } else {
10004 this.upAction(); 11890 this.upAction();
10005 } 11891 }
11892 },
11893
11894 _noinkChanged: function(noink, attached) {
11895 if (attached) {
11896 this.keyEventTarget = noink ? this : this.target;
11897 }
10006 } 11898 }
10007 }); 11899 });
10008 })(); 11900 })();
10009 /** 11901 /**
10010 * @demo demo/index.html 11902 * @demo demo/index.html
10011 * @polymerBehavior 11903 * @polymerBehavior
10012 */ 11904 */
10013 Polymer.IronControlState = { 11905 Polymer.IronControlState = {
10014 11906
10015 properties: { 11907 properties: {
(...skipping 185 matching lines...) Expand 10 before | Expand all | Expand 10 after
10201 12093
10202 // to emulate native checkbox, (de-)activations from a user interaction fire 12094 // to emulate native checkbox, (de-)activations from a user interaction fire
10203 // 'change' events 12095 // 'change' events
10204 _userActivate: function(active) { 12096 _userActivate: function(active) {
10205 if (this.active !== active) { 12097 if (this.active !== active) {
10206 this.active = active; 12098 this.active = active;
10207 this.fire('change'); 12099 this.fire('change');
10208 } 12100 }
10209 }, 12101 },
10210 12102
10211 _eventSourceIsPrimaryInput: function(event) {
10212 event = event.detail.sourceEvent || event;
10213
10214 // Always true for non-mouse events....
10215 if (!this._mouseEventRe.test(event.type)) {
10216 return true;
10217 }
10218
10219 // http://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
10220 if ('buttons' in event) {
10221 return event.buttons === 1;
10222 }
10223
10224 // http://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/which
10225 if (typeof event.which === 'number') {
10226 return event.which < 2;
10227 }
10228
10229 // http://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
10230 return event.button < 1;
10231 },
10232
10233 _downHandler: function(event) { 12103 _downHandler: function(event) {
10234 if (!this._eventSourceIsPrimaryInput(event)) {
10235 return;
10236 }
10237
10238 this._setPointerDown(true); 12104 this._setPointerDown(true);
10239 this._setPressed(true); 12105 this._setPressed(true);
10240 this._setReceivedFocusFromKeyboard(false); 12106 this._setReceivedFocusFromKeyboard(false);
10241 }, 12107 },
10242 12108
10243 _upHandler: function() { 12109 _upHandler: function() {
10244 this._setPointerDown(false); 12110 this._setPointerDown(false);
10245 this._setPressed(false); 12111 this._setPressed(false);
10246 }, 12112 },
10247 12113
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after
10305 } 12171 }
10306 } 12172 }
10307 12173
10308 }; 12174 };
10309 12175
10310 /** @polymerBehavior */ 12176 /** @polymerBehavior */
10311 Polymer.IronButtonState = [ 12177 Polymer.IronButtonState = [
10312 Polymer.IronA11yKeysBehavior, 12178 Polymer.IronA11yKeysBehavior,
10313 Polymer.IronButtonStateImpl 12179 Polymer.IronButtonStateImpl
10314 ]; 12180 ];
10315 /** @polymerBehavior */ 12181 /**
12182 * `Polymer.PaperRippleBehavior` dynamically implements a ripple
12183 * when the element has focus via pointer or keyboard.
12184 *
12185 * NOTE: This behavior is intended to be used in conjunction with and after
12186 * `Polymer.IronButtonState` and `Polymer.IronControlState`.
12187 *
12188 * @polymerBehavior Polymer.PaperRippleBehavior
12189 */
12190 Polymer.PaperRippleBehavior = {
12191
12192 properties: {
12193 /**
12194 * If true, the element will not produce a ripple effect when interacted
12195 * with via the pointer.
12196 */
12197 noink: {
12198 type: Boolean,
12199 observer: '_noinkChanged'
12200 }
12201 },
12202
12203 /**
12204 * Ensures a `<paper-ripple>` element is available when the element is
12205 * focused.
12206 */
12207 _buttonStateChanged: function() {
12208 if (this.focused) {
12209 this.ensureRipple();
12210 }
12211 },
12212
12213 /**
12214 * In addition to the functionality provided in `IronButtonState`, ensures
12215 * a ripple effect is created when the element is in a `pressed` state.
12216 */
12217 _downHandler: function(event) {
12218 Polymer.IronButtonStateImpl._downHandler.call(this, event);
12219 if (this.pressed) {
12220 this.ensureRipple(event);
12221 }
12222 },
12223
12224 /**
12225 * Ensures this element contains a ripple effect. For startup efficiency
12226 * the ripple effect is dynamically on demand when needed.
12227 * @param {!Event=} opt_triggeringEvent (optional) event that triggered the
12228 * ripple.
12229 */
12230 ensureRipple: function(opt_triggeringEvent) {
12231 if (!this.hasRipple()) {
12232 this._ripple = this._createRipple();
12233 this._ripple.noink = this.noink;
12234 var rippleContainer = this._rippleContainer || this.root;
12235 if (rippleContainer) {
12236 Polymer.dom(rippleContainer).appendChild(this._ripple);
12237 }
12238 var domContainer = rippleContainer === this.shadyRoot ? this :
12239 rippleContainer;
12240 if (opt_triggeringEvent &&
12241 domContainer.contains(opt_triggeringEvent.target)) {
12242 this._ripple.uiDownAction(opt_triggeringEvent);
12243 }
12244 }
12245 },
12246
12247 /**
12248 * Returns the `<paper-ripple>` element used by this element to create
12249 * ripple effects. The element's ripple is created on demand, when
12250 * necessary, and calling this method will force the
12251 * ripple to be created.
12252 */
12253 getRipple: function() {
12254 this.ensureRipple();
12255 return this._ripple;
12256 },
12257
12258 /**
12259 * Returns true if this element currently contains a ripple effect.
12260 * @return {boolean}
12261 */
12262 hasRipple: function() {
12263 return Boolean(this._ripple);
12264 },
12265
12266 /**
12267 * Create the element's ripple effect via creating a `<paper-ripple>`.
12268 * Override this method to customize the ripple element.
12269 * @return {element} Returns a `<paper-ripple>` element.
12270 */
12271 _createRipple: function() {
12272 return document.createElement('paper-ripple');
12273 },
12274
12275 _noinkChanged: function(noink) {
12276 if (this.hasRipple()) {
12277 this._ripple.noink = noink;
12278 }
12279 }
12280
12281 };
12282 /** @polymerBehavior Polymer.PaperButtonBehavior */
10316 Polymer.PaperButtonBehaviorImpl = { 12283 Polymer.PaperButtonBehaviorImpl = {
10317 12284
10318 properties: { 12285 properties: {
10319 12286
10320 _elevation: { 12287 /**
10321 type: Number 12288 * The z-depth of this element, from 0-5. Setting to 0 will remove the
12289 * shadow, and each increasing number greater than 0 will be "deeper"
12290 * than the last.
12291 *
12292 * @attribute elevation
12293 * @type number
12294 * @default 1
12295 */
12296 elevation: {
12297 type: Number,
12298 reflectToAttribute: true,
12299 readOnly: true
10322 } 12300 }
10323 12301
10324 }, 12302 },
10325 12303
10326 observers: [ 12304 observers: [
10327 '_calculateElevation(focused, disabled, active, pressed, receivedFocusFrom Keyboard)' 12305 '_calculateElevation(focused, disabled, active, pressed, receivedFocusFrom Keyboard)',
12306 '_computeKeyboardClass(receivedFocusFromKeyboard)'
10328 ], 12307 ],
10329 12308
10330 hostAttributes: { 12309 hostAttributes: {
10331 role: 'button', 12310 role: 'button',
10332 tabindex: '0' 12311 tabindex: '0',
12312 animated: true
10333 }, 12313 },
10334 12314
10335 _calculateElevation: function() { 12315 _calculateElevation: function() {
10336 var e = 1; 12316 var e = 1;
10337 if (this.disabled) { 12317 if (this.disabled) {
10338 e = 0; 12318 e = 0;
10339 } else if (this.active || this.pressed) { 12319 } else if (this.active || this.pressed) {
10340 e = 4; 12320 e = 4;
10341 } else if (this.receivedFocusFromKeyboard) { 12321 } else if (this.receivedFocusFromKeyboard) {
10342 e = 3; 12322 e = 3;
10343 } 12323 }
10344 this._elevation = e; 12324 this._setElevation(e);
12325 },
12326
12327 _computeKeyboardClass: function(receivedFocusFromKeyboard) {
12328 this.classList.toggle('keyboard-focus', receivedFocusFromKeyboard);
12329 },
12330
12331 /**
12332 * In addition to `IronButtonState` behavior, when space key goes down,
12333 * create a ripple down effect.
12334 */
12335 _spaceKeyDownHandler: function(event) {
12336 Polymer.IronButtonStateImpl._spaceKeyDownHandler.call(this, event);
12337 if (this.hasRipple()) {
12338 this._ripple.uiDownAction();
12339 }
12340 },
12341
12342 /**
12343 * In addition to `IronButtonState` behavior, when space key goes up,
12344 * create a ripple up effect.
12345 */
12346 _spaceKeyUpHandler: function(event) {
12347 Polymer.IronButtonStateImpl._spaceKeyUpHandler.call(this, event);
12348 if (this.hasRipple()) {
12349 this._ripple.uiUpAction();
12350 }
10345 } 12351 }
12352
10346 }; 12353 };
10347 12354
10348 /** @polymerBehavior */ 12355 /** @polymerBehavior */
10349 Polymer.PaperButtonBehavior = [ 12356 Polymer.PaperButtonBehavior = [
10350 Polymer.IronButtonState, 12357 Polymer.IronButtonState,
10351 Polymer.IronControlState, 12358 Polymer.IronControlState,
12359 Polymer.PaperRippleBehavior,
10352 Polymer.PaperButtonBehaviorImpl 12360 Polymer.PaperButtonBehaviorImpl
10353 ]; 12361 ];
10354 Polymer({ 12362 Polymer({
10355 is: 'paper-button', 12363 is: 'paper-button',
10356 12364
10357 behaviors: [ 12365 behaviors: [
10358 Polymer.PaperButtonBehavior 12366 Polymer.PaperButtonBehavior
10359 ], 12367 ],
10360 12368
10361 properties: { 12369 properties: {
10362 /** 12370 /**
10363 * If true, the button should be styled with a shadow. 12371 * If true, the button should be styled with a shadow.
10364 */ 12372 */
10365 raised: { 12373 raised: {
10366 type: Boolean, 12374 type: Boolean,
10367 reflectToAttribute: true, 12375 reflectToAttribute: true,
10368 value: false, 12376 value: false,
10369 observer: '_calculateElevation' 12377 observer: '_calculateElevation'
10370 } 12378 }
10371 }, 12379 },
10372 12380
10373 _calculateElevation: function() { 12381 _calculateElevation: function() {
10374 if (!this.raised) { 12382 if (!this.raised) {
10375 this._elevation = 0; 12383 this.elevation = 0;
10376 } else { 12384 } else {
10377 Polymer.PaperButtonBehaviorImpl._calculateElevation.apply(this); 12385 Polymer.PaperButtonBehaviorImpl._calculateElevation.apply(this);
10378 } 12386 }
10379 },
10380
10381 _computeContentClass: function(receivedFocusFromKeyboard) {
10382 var className = 'content ';
10383 if (receivedFocusFromKeyboard) {
10384 className += ' keyboard-focus';
10385 }
10386 return className;
10387 } 12387 }
10388 }); 12388 });
10389 /** 12389 /**
10390 * `iron-range-behavior` provides the behavior for something with a minimum to m aximum range. 12390 * `iron-range-behavior` provides the behavior for something with a minimum to m aximum range.
10391 * 12391 *
10392 * @demo demo/index.html 12392 * @demo demo/index.html
10393 * @polymerBehavior 12393 * @polymerBehavior
10394 */ 12394 */
10395 Polymer.IronRangeBehavior = { 12395 Polymer.IronRangeBehavior = {
10396 12396
(...skipping 183 matching lines...) Expand 10 before | Expand all | Expand 10 after
10580 12580
10581 }); 12581 });
10582 // Copyright 2015 The Chromium Authors. All rights reserved. 12582 // Copyright 2015 The Chromium Authors. All rights reserved.
10583 // Use of this source code is governed by a BSD-style license that can be 12583 // Use of this source code is governed by a BSD-style license that can be
10584 // found in the LICENSE file. 12584 // found in the LICENSE file.
10585 12585
10586 cr.define('downloads', function() { 12586 cr.define('downloads', function() {
10587 var Item = Polymer({ 12587 var Item = Polymer({
10588 is: 'downloads-item', 12588 is: 'downloads-item',
10589 12589
10590 /**
10591 * @param {!downloads.ThrottledIconLoader} iconLoader
10592 */
10593 factoryImpl: function(iconLoader) {
10594 /** @private {!downloads.ThrottledIconLoader} */
10595 this.iconLoader_ = iconLoader;
10596 },
10597
10598 properties: { 12590 properties: {
10599 data: { 12591 data: {
10600 type: Object, 12592 type: Object,
10601 }, 12593 },
10602 12594
10603 hideDate: { 12595 hideDate: {
10604 type: Boolean, 12596 type: Boolean,
10605 value: true, 12597 value: true,
10606 }, 12598 },
10607 12599
10608 readyPromise: {
10609 type: Object,
10610 value: function() {
10611 return new Promise(function(resolve, reject) {
10612 this.resolveReadyPromise_ = resolve;
10613 }.bind(this));
10614 },
10615 },
10616
10617 completelyOnDisk_: { 12600 completelyOnDisk_: {
10618 computed: 'computeCompletelyOnDisk_(' + 12601 computed: 'computeCompletelyOnDisk_(' +
10619 'data.state, data.file_externally_removed)', 12602 'data.state, data.file_externally_removed)',
10620 type: Boolean, 12603 type: Boolean,
10621 value: true, 12604 value: true,
10622 }, 12605 },
10623 12606
10624 controlledBy_: { 12607 controlledBy_: {
10625 computed: 'computeControlledBy_(data.by_ext_id, data.by_ext_name)', 12608 computed: 'computeControlledBy_(data.by_ext_id, data.by_ext_name)',
10626 type: String, 12609 type: String,
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
10679 computed: 'computeIsMalware_(isDangerous_, data.danger_type)', 12662 computed: 'computeIsMalware_(isDangerous_, data.danger_type)',
10680 type: Boolean, 12663 type: Boolean,
10681 value: false, 12664 value: false,
10682 }, 12665 },
10683 }, 12666 },
10684 12667
10685 observers: [ 12668 observers: [
10686 // TODO(dbeam): this gets called way more when I observe data.by_ext_id 12669 // TODO(dbeam): this gets called way more when I observe data.by_ext_id
10687 // and data.by_ext_name directly. Why? 12670 // and data.by_ext_name directly. Why?
10688 'observeControlledBy_(controlledBy_)', 12671 'observeControlledBy_(controlledBy_)',
12672 'observeIsDangerous_(isDangerous_, data.file_path)',
10689 ], 12673 ],
10690 12674
10691 ready: function() { 12675 ready: function() {
10692 this.content = this.$.content; 12676 this.content = this.$.content;
10693 this.resolveReadyPromise_();
10694 },
10695
10696 /** @param {!downloads.Data} data */
10697 update: function(data) {
10698 this.data = data;
10699
10700 if (!this.isDangerous_) {
10701 var icon = 'chrome://fileicon/' + encodeURIComponent(data.file_path);
10702 this.iconLoader_.loadScaledIcon(this.$['file-icon'], icon);
10703 }
10704 }, 12677 },
10705 12678
10706 /** @private */ 12679 /** @private */
10707 computeClass_: function() { 12680 computeClass_: function() {
10708 var classes = []; 12681 var classes = [];
10709 12682
10710 if (this.isActive_) 12683 if (this.isActive_)
10711 classes.push('is-active'); 12684 classes.push('is-active');
10712 12685
10713 if (this.isDangerous_) 12686 if (this.isDangerous_)
(...skipping 15 matching lines...) Expand all
10729 computeControlledBy_: function() { 12702 computeControlledBy_: function() {
10730 if (!this.data.by_ext_id || !this.data.by_ext_name) 12703 if (!this.data.by_ext_id || !this.data.by_ext_name)
10731 return ''; 12704 return '';
10732 12705
10733 var url = 'chrome://extensions#' + this.data.by_ext_id; 12706 var url = 'chrome://extensions#' + this.data.by_ext_id;
10734 var name = this.data.by_ext_name; 12707 var name = this.data.by_ext_name;
10735 return loadTimeData.getStringF('controlledByUrl', url, name); 12708 return loadTimeData.getStringF('controlledByUrl', url, name);
10736 }, 12709 },
10737 12710
10738 /** @private */ 12711 /** @private */
12712 computeDangerIcon_: function() {
12713 if (!this.isDangerous_)
12714 return '';
12715
12716 switch (this.data.danger_type) {
12717 case downloads.DangerType.DANGEROUS_CONTENT:
12718 case downloads.DangerType.DANGEROUS_HOST:
12719 case downloads.DangerType.DANGEROUS_URL:
12720 case downloads.DangerType.POTENTIALLY_UNWANTED:
12721 case downloads.DangerType.UNCOMMON_CONTENT:
12722 return 'remove-circle';
12723 default:
12724 return 'warning';
12725 }
12726 },
12727
12728 /** @private */
10739 computeDate_: function() { 12729 computeDate_: function() {
10740 if (this.hideDate) 12730 if (this.hideDate)
10741 return ''; 12731 return '';
10742 return assert(this.data.since_string || this.data.date_string); 12732 return assert(this.data.since_string || this.data.date_string);
10743 }, 12733 },
10744 12734
10745 /** @private */ 12735 /** @private */
10746 computeDescription_: function() { 12736 computeDescription_: function() {
10747 var data = this.data; 12737 var data = this.data;
10748 12738
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after
10837 isIndeterminate_: function() { 12827 isIndeterminate_: function() {
10838 return this.data.percent == -1; 12828 return this.data.percent == -1;
10839 }, 12829 },
10840 12830
10841 /** @private */ 12831 /** @private */
10842 observeControlledBy_: function() { 12832 observeControlledBy_: function() {
10843 this.$['controlled-by'].innerHTML = this.controlledBy_; 12833 this.$['controlled-by'].innerHTML = this.controlledBy_;
10844 }, 12834 },
10845 12835
10846 /** @private */ 12836 /** @private */
12837 observeIsDangerous_: function() {
12838 if (this.data && !this.isDangerous_) {
12839 var filePath = encodeURIComponent(this.data.file_path);
12840 this.$['file-icon'].src = 'chrome://fileicon/' + filePath;
12841 }
12842 },
12843
12844 /** @private */
10847 onCancelTap_: function() { 12845 onCancelTap_: function() {
10848 downloads.ActionService.getInstance().cancel(this.data.id); 12846 downloads.ActionService.getInstance().cancel(this.data.id);
10849 }, 12847 },
10850 12848
10851 /** @private */ 12849 /** @private */
10852 onDiscardDangerousTap_: function() { 12850 onDiscardDangerousTap_: function() {
10853 downloads.ActionService.getInstance().discardDangerous(this.data.id); 12851 downloads.ActionService.getInstance().discardDangerous(this.data.id);
10854 }, 12852 },
10855 12853
10856 /** 12854 /**
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
10897 }, 12895 },
10898 12896
10899 /** @private */ 12897 /** @private */
10900 onShowTap_: function() { 12898 onShowTap_: function() {
10901 downloads.ActionService.getInstance().show(this.data.id); 12899 downloads.ActionService.getInstance().show(this.data.id);
10902 }, 12900 },
10903 }); 12901 });
10904 12902
10905 return {Item: Item}; 12903 return {Item: Item};
10906 }); 12904 });
10907 (function() {
10908
10909 // monostate data
10910 var metaDatas = {};
10911 var metaArrays = {};
10912
10913 Polymer.IronMeta = Polymer({
10914
10915 is: 'iron-meta',
10916
10917 properties: {
10918
10919 /**
10920 * The type of meta-data. All meta-data of the same type is stored
10921 * together.
10922 */
10923 type: {
10924 type: String,
10925 value: 'default',
10926 observer: '_typeChanged'
10927 },
10928
10929 /**
10930 * The key used to store `value` under the `type` namespace.
10931 */
10932 key: {
10933 type: String,
10934 observer: '_keyChanged'
10935 },
10936
10937 /**
10938 * The meta-data to store or retrieve.
10939 */
10940 value: {
10941 type: Object,
10942 notify: true,
10943 observer: '_valueChanged'
10944 },
10945
10946 /**
10947 * If true, `value` is set to the iron-meta instance itself.
10948 */
10949 self: {
10950 type: Boolean,
10951 observer: '_selfChanged'
10952 },
10953
10954 /**
10955 * Array of all meta-data values for the given type.
10956 */
10957 list: {
10958 type: Array,
10959 notify: true
10960 }
10961
10962 },
10963
10964 /**
10965 * Only runs if someone invokes the factory/constructor directly
10966 * e.g. `new Polymer.IronMeta()`
10967 */
10968 factoryImpl: function(config) {
10969 if (config) {
10970 for (var n in config) {
10971 switch(n) {
10972 case 'type':
10973 case 'key':
10974 case 'value':
10975 this[n] = config[n];
10976 break;
10977 }
10978 }
10979 }
10980 },
10981
10982 created: function() {
10983 // TODO(sjmiles): good for debugging?
10984 this._metaDatas = metaDatas;
10985 this._metaArrays = metaArrays;
10986 },
10987
10988 _keyChanged: function(key, old) {
10989 this._resetRegistration(old);
10990 },
10991
10992 _valueChanged: function(value) {
10993 this._resetRegistration(this.key);
10994 },
10995
10996 _selfChanged: function(self) {
10997 if (self) {
10998 this.value = this;
10999 }
11000 },
11001
11002 _typeChanged: function(type) {
11003 this._unregisterKey(this.key);
11004 if (!metaDatas[type]) {
11005 metaDatas[type] = {};
11006 }
11007 this._metaData = metaDatas[type];
11008 if (!metaArrays[type]) {
11009 metaArrays[type] = [];
11010 }
11011 this.list = metaArrays[type];
11012 this._registerKeyValue(this.key, this.value);
11013 },
11014
11015 /**
11016 * Retrieves meta data value by key.
11017 *
11018 * @method byKey
11019 * @param {string} key The key of the meta-data to be returned.
11020 * @return {*}
11021 */
11022 byKey: function(key) {
11023 return this._metaData && this._metaData[key];
11024 },
11025
11026 _resetRegistration: function(oldKey) {
11027 this._unregisterKey(oldKey);
11028 this._registerKeyValue(this.key, this.value);
11029 },
11030
11031 _unregisterKey: function(key) {
11032 this._unregister(key, this._metaData, this.list);
11033 },
11034
11035 _registerKeyValue: function(key, value) {
11036 this._register(key, value, this._metaData, this.list);
11037 },
11038
11039 _register: function(key, value, data, list) {
11040 if (key && data && value !== undefined) {
11041 data[key] = value;
11042 list.push(value);
11043 }
11044 },
11045
11046 _unregister: function(key, data, list) {
11047 if (key && data) {
11048 if (key in data) {
11049 var value = data[key];
11050 delete data[key];
11051 this.arrayDelete(list, value);
11052 }
11053 }
11054 }
11055
11056 });
11057
11058 /**
11059 `iron-meta-query` can be used to access infomation stored in `iron-meta`.
11060
11061 Examples:
11062
11063 If I create an instance like this:
11064
11065 <iron-meta key="info" value="foo/bar"></iron-meta>
11066
11067 Note that value="foo/bar" is the metadata I've defined. I could define more
11068 attributes or use child nodes to define additional metadata.
11069
11070 Now I can access that element (and it's metadata) from any `iron-meta-query` instance:
11071
11072 var value = new Polymer.IronMetaQuery({key: 'info'}).value;
11073
11074 @group Polymer Iron Elements
11075 @element iron-meta-query
11076 */
11077 Polymer.IronMetaQuery = Polymer({
11078
11079 is: 'iron-meta-query',
11080
11081 properties: {
11082
11083 /**
11084 * The type of meta-data. All meta-data of the same type is stored
11085 * together.
11086 */
11087 type: {
11088 type: String,
11089 value: 'default',
11090 observer: '_typeChanged'
11091 },
11092
11093 /**
11094 * Specifies a key to use for retrieving `value` from the `type`
11095 * namespace.
11096 */
11097 key: {
11098 type: String,
11099 observer: '_keyChanged'
11100 },
11101
11102 /**
11103 * The meta-data to store or retrieve.
11104 */
11105 value: {
11106 type: Object,
11107 notify: true,
11108 readOnly: true
11109 },
11110
11111 /**
11112 * Array of all meta-data values for the given type.
11113 */
11114 list: {
11115 type: Array,
11116 notify: true
11117 }
11118
11119 },
11120
11121 /**
11122 * Actually a factory method, not a true constructor. Only runs if
11123 * someone invokes it directly (via `new Polymer.IronMeta()`);
11124 */
11125 factoryImpl: function(config) {
11126 if (config) {
11127 for (var n in config) {
11128 switch(n) {
11129 case 'type':
11130 case 'key':
11131 this[n] = config[n];
11132 break;
11133 }
11134 }
11135 }
11136 },
11137
11138 created: function() {
11139 // TODO(sjmiles): good for debugging?
11140 this._metaDatas = metaDatas;
11141 this._metaArrays = metaArrays;
11142 },
11143
11144 _keyChanged: function(key) {
11145 this._setValue(this._metaData && this._metaData[key]);
11146 },
11147
11148 _typeChanged: function(type) {
11149 this._metaData = metaDatas[type];
11150 this.list = metaArrays[type];
11151 if (this.key) {
11152 this._keyChanged(this.key);
11153 }
11154 },
11155
11156 /**
11157 * Retrieves meta data value by key.
11158 * @param {string} key The key of the meta-data to be returned.
11159 * @return {*}
11160 */
11161 byKey: function(key) {
11162 return this._metaData && this._metaData[key];
11163 }
11164
11165 });
11166
11167 })();
11168 Polymer({
11169
11170 is: 'iron-icon',
11171
11172 properties: {
11173
11174 /**
11175 * The name of the icon to use. The name should be of the form:
11176 * `iconset_name:icon_name`.
11177 */
11178 icon: {
11179 type: String,
11180 observer: '_iconChanged'
11181 },
11182
11183 /**
11184 * The name of the theme to used, if one is specified by the
11185 * iconset.
11186 */
11187 theme: {
11188 type: String,
11189 observer: '_updateIcon'
11190 },
11191
11192 /**
11193 * If using iron-icon without an iconset, you can set the src to be
11194 * the URL of an individual icon image file. Note that this will take
11195 * precedence over a given icon attribute.
11196 */
11197 src: {
11198 type: String,
11199 observer: '_srcChanged'
11200 },
11201
11202 /**
11203 * @type {!Polymer.IronMeta}
11204 */
11205 _meta: {
11206 value: Polymer.Base.create('iron-meta', {type: 'iconset'})
11207 }
11208
11209 },
11210
11211 _DEFAULT_ICONSET: 'icons',
11212
11213 _iconChanged: function(icon) {
11214 var parts = (icon || '').split(':');
11215 this._iconName = parts.pop();
11216 this._iconsetName = parts.pop() || this._DEFAULT_ICONSET;
11217 this._updateIcon();
11218 },
11219
11220 _srcChanged: function(src) {
11221 this._updateIcon();
11222 },
11223
11224 _usesIconset: function() {
11225 return this.icon || !this.src;
11226 },
11227
11228 /** @suppress {visibility} */
11229 _updateIcon: function() {
11230 if (this._usesIconset()) {
11231 if (this._iconsetName) {
11232 this._iconset = /** @type {?Polymer.Iconset} */ (
11233 this._meta.byKey(this._iconsetName));
11234 if (this._iconset) {
11235 this._iconset.applyIcon(this, this._iconName, this.theme);
11236 this.unlisten(window, 'iron-iconset-added', '_updateIcon');
11237 } else {
11238 this.listen(window, 'iron-iconset-added', '_updateIcon');
11239 }
11240 }
11241 } else {
11242 if (!this._img) {
11243 this._img = document.createElement('img');
11244 this._img.style.width = '100%';
11245 this._img.style.height = '100%';
11246 this._img.draggable = false;
11247 }
11248 this._img.src = this.src;
11249 Polymer.dom(this.root).appendChild(this._img);
11250 }
11251 }
11252
11253 });
11254 /**
11255 * The `iron-iconset-svg` element allows users to define their own icon sets
11256 * that contain svg icons. The svg icon elements should be children of the
11257 * `iron-iconset-svg` element. Multiple icons should be given distinct id's.
11258 *
11259 * Using svg elements to create icons has a few advantages over traditional
11260 * bitmap graphics like jpg or png. Icons that use svg are vector based so the y
11261 * are resolution independent and should look good on any device. They are
11262 * stylable via css. Icons can be themed, colorized, and even animated.
11263 *
11264 * Example:
11265 *
11266 * <iron-iconset-svg name="my-svg-icons" size="24">
11267 * <svg>
11268 * <defs>
11269 * <g id="shape">
11270 * <rect x="50" y="50" width="50" height="50" />
11271 * <circle cx="50" cy="50" r="50" />
11272 * </g>
11273 * </defs>
11274 * </svg>
11275 * </iron-iconset-svg>
11276 *
11277 * This will automatically register the icon set "my-svg-icons" to the iconset
11278 * database. To use these icons from within another element, make a
11279 * `iron-iconset` element and call the `byId` method
11280 * to retrieve a given iconset. To apply a particular icon inside an
11281 * element use the `applyIcon` method. For example:
11282 *
11283 * iconset.applyIcon(iconNode, 'car');
11284 *
11285 * @element iron-iconset-svg
11286 * @demo demo/index.html
11287 */
11288 Polymer({
11289
11290 is: 'iron-iconset-svg',
11291
11292 properties: {
11293
11294 /**
11295 * The name of the iconset.
11296 *
11297 * @attribute name
11298 * @type string
11299 */
11300 name: {
11301 type: String,
11302 observer: '_nameChanged'
11303 },
11304
11305 /**
11306 * The size of an individual icon. Note that icons must be square.
11307 *
11308 * @attribute iconSize
11309 * @type number
11310 * @default 24
11311 */
11312 size: {
11313 type: Number,
11314 value: 24
11315 }
11316
11317 },
11318
11319 /**
11320 * Construct an array of all icon names in this iconset.
11321 *
11322 * @return {!Array} Array of icon names.
11323 */
11324 getIconNames: function() {
11325 this._icons = this._createIconMap();
11326 return Object.keys(this._icons).map(function(n) {
11327 return this.name + ':' + n;
11328 }, this);
11329 },
11330
11331 /**
11332 * Applies an icon to the given element.
11333 *
11334 * An svg icon is prepended to the element's shadowRoot if it exists,
11335 * otherwise to the element itself.
11336 *
11337 * @method applyIcon
11338 * @param {Element} element Element to which the icon is applied.
11339 * @param {string} iconName Name of the icon to apply.
11340 * @return {Element} The svg element which renders the icon.
11341 */
11342 applyIcon: function(element, iconName) {
11343 // insert svg element into shadow root, if it exists
11344 element = element.root || element;
11345 // Remove old svg element
11346 this.removeIcon(element);
11347 // install new svg element
11348 var svg = this._cloneIcon(iconName);
11349 if (svg) {
11350 var pde = Polymer.dom(element);
11351 pde.insertBefore(svg, pde.childNodes[0]);
11352 return element._svgIcon = svg;
11353 }
11354 return null;
11355 },
11356
11357 /**
11358 * Remove an icon from the given element by undoing the changes effected
11359 * by `applyIcon`.
11360 *
11361 * @param {Element} element The element from which the icon is removed.
11362 */
11363 removeIcon: function(element) {
11364 // Remove old svg element
11365 if (element._svgIcon) {
11366 Polymer.dom(element).removeChild(element._svgIcon);
11367 element._svgIcon = null;
11368 }
11369 },
11370
11371 /**
11372 *
11373 * When name is changed, register iconset metadata
11374 *
11375 */
11376 _nameChanged: function() {
11377 new Polymer.IronMeta({type: 'iconset', key: this.name, value: this});
11378 this.async(function() {
11379 this.fire('iron-iconset-added', this, {node: window});
11380 });
11381 },
11382
11383 /**
11384 * Create a map of child SVG elements by id.
11385 *
11386 * @return {!Object} Map of id's to SVG elements.
11387 */
11388 _createIconMap: function() {
11389 // Objects chained to Object.prototype (`{}`) have members. Specifically,
11390 // on FF there is a `watch` method that confuses the icon map, so we
11391 // need to use a null-based object here.
11392 var icons = Object.create(null);
11393 Polymer.dom(this).querySelectorAll('[id]')
11394 .forEach(function(icon) {
11395 icons[icon.id] = icon;
11396 });
11397 return icons;
11398 },
11399
11400 /**
11401 * Produce installable clone of the SVG element matching `id` in this
11402 * iconset, or `undefined` if there is no matching element.
11403 *
11404 * @return {Element} Returns an installable clone of the SVG element
11405 * matching `id`.
11406 */
11407 _cloneIcon: function(id) {
11408 // create the icon map on-demand, since the iconset itself has no discrete
11409 // signal to know when it's children are fully parsed
11410 this._icons = this._icons || this._createIconMap();
11411 return this._prepareSvgClone(this._icons[id], this.size);
11412 },
11413
11414 /**
11415 * @param {Element} sourceSvg
11416 * @param {number} size
11417 * @return {Element}
11418 */
11419 _prepareSvgClone: function(sourceSvg, size) {
11420 if (sourceSvg) {
11421 var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
11422 svg.setAttribute('viewBox', ['0', '0', size, size].join(' '));
11423 svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
11424 // TODO(dfreedm): `pointer-events: none` works around https://crbug.com/ 370136
11425 // TODO(sjmiles): inline style may not be ideal, but avoids requiring a shadow-root
11426 svg.style.cssText = 'pointer-events: none; display: block; width: 100%; height: 100%;';
11427 svg.appendChild(sourceSvg.cloneNode(true)).removeAttribute('id');
11428 return svg;
11429 }
11430 return null;
11431 }
11432
11433 });
11434 Polymer({ 12905 Polymer({
11435 is: 'paper-item', 12906 is: 'paper-item',
11436 12907
11437 hostAttributes: { 12908 hostAttributes: {
11438 role: 'listitem', 12909 role: 'listitem',
11439 tabindex: '0' 12910 tabindex: '0'
11440 }, 12911 },
11441 12912
11442 behaviors: [ 12913 behaviors: [
11443 Polymer.IronControlState, 12914 Polymer.IronControlState,
(...skipping 191 matching lines...) Expand 10 before | Expand all | Expand 10 after
11635 value: null 13106 value: null
11636 }, 13107 },
11637 13108
11638 /** 13109 /**
11639 * The set of excluded elements where the key is the `localName` 13110 * The set of excluded elements where the key is the `localName`
11640 * of the element that will be ignored from the item list. 13111 * of the element that will be ignored from the item list.
11641 * 13112 *
11642 * @type {object} 13113 * @type {object}
11643 * @default {template: 1} 13114 * @default {template: 1}
11644 */ 13115 */
11645 excludedLocalNames: { 13116 _excludedLocalNames: {
11646 type: Object, 13117 type: Object,
11647 value: function() { 13118 value: function() {
11648 return { 13119 return {
11649 'template': 1 13120 'template': 1
11650 }; 13121 };
11651 } 13122 }
11652 } 13123 }
11653 }, 13124 },
11654 13125
11655 observers: [ 13126 observers: [
11656 '_updateSelected(attrForSelected, selected)' 13127 '_updateSelected(attrForSelected, selected)'
11657 ], 13128 ],
11658 13129
11659 created: function() { 13130 created: function() {
11660 this._bindFilterItem = this._filterItem.bind(this); 13131 this._bindFilterItem = this._filterItem.bind(this);
11661 this._selection = new Polymer.IronSelection(this._applySelection.bind(this )); 13132 this._selection = new Polymer.IronSelection(this._applySelection.bind(this ));
13133 // TODO(cdata): When polymer/polymer#2535 lands, we do not need to do this
13134 // book keeping anymore:
13135 this.__listeningForActivate = false;
11662 }, 13136 },
11663 13137
11664 attached: function() { 13138 attached: function() {
11665 this._observer = this._observeItems(this); 13139 this._observer = this._observeItems(this);
11666 this._contentObserver = this._observeContent(this); 13140 this._contentObserver = this._observeContent(this);
11667 if (!this.selectedItem && this.selected) { 13141 if (!this.selectedItem && this.selected) {
11668 this._updateSelected(this.attrForSelected,this.selected) 13142 this._updateSelected(this.attrForSelected,this.selected)
11669 } 13143 }
13144 this._addListener(this.activateEvent);
11670 }, 13145 },
11671 13146
11672 detached: function() { 13147 detached: function() {
11673 if (this._observer) { 13148 if (this._observer) {
11674 this._observer.disconnect(); 13149 this._observer.disconnect();
11675 } 13150 }
11676 if (this._contentObserver) { 13151 if (this._contentObserver) {
11677 this._contentObserver.disconnect(); 13152 this._contentObserver.disconnect();
11678 } 13153 }
11679 this._removeListener(this.activateEvent); 13154 this._removeListener(this.activateEvent);
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
11726 * Selects the next item. 13201 * Selects the next item.
11727 * 13202 *
11728 * @method selectNext 13203 * @method selectNext
11729 */ 13204 */
11730 selectNext: function() { 13205 selectNext: function() {
11731 var index = (Number(this._valueToIndex(this.selected)) + 1) % this.items.l ength; 13206 var index = (Number(this._valueToIndex(this.selected)) + 1) % this.items.l ength;
11732 this.selected = this._indexToValue(index); 13207 this.selected = this._indexToValue(index);
11733 }, 13208 },
11734 13209
11735 _addListener: function(eventName) { 13210 _addListener: function(eventName) {
13211 if (!this.isAttached || this.__listeningForActivate) {
13212 return;
13213 }
13214
13215 this.__listeningForActivate = true;
11736 this.listen(this, eventName, '_activateHandler'); 13216 this.listen(this, eventName, '_activateHandler');
11737 }, 13217 },
11738 13218
11739 _removeListener: function(eventName) { 13219 _removeListener: function(eventName) {
11740 this.unlisten(this, eventName, '_activateHandler'); 13220 this.unlisten(this, eventName, '_activateHandler');
13221 this.__listeningForActivate = false;
11741 }, 13222 },
11742 13223
11743 _activateEventChanged: function(eventName, old) { 13224 _activateEventChanged: function(eventName, old) {
11744 this._removeListener(old); 13225 this._removeListener(old);
11745 this._addListener(eventName); 13226 this._addListener(eventName);
11746 }, 13227 },
11747 13228
11748 _updateSelected: function() { 13229 _updateSelected: function() {
11749 this._selectSelected(this.selected); 13230 this._selectSelected(this.selected);
11750 }, 13231 },
11751 13232
11752 _selectSelected: function(selected) { 13233 _selectSelected: function(selected) {
11753 this._selection.select(this._valueToItem(this.selected)); 13234 this._selection.select(this._valueToItem(this.selected));
11754 }, 13235 },
11755 13236
11756 _filterItem: function(node) { 13237 _filterItem: function(node) {
11757 return !this.excludedLocalNames[node.localName]; 13238 return !this._excludedLocalNames[node.localName];
11758 }, 13239 },
11759 13240
11760 _valueToItem: function(value) { 13241 _valueToItem: function(value) {
11761 return (value == null) ? null : this.items[this._valueToIndex(value)]; 13242 return (value == null) ? null : this.items[this._valueToIndex(value)];
11762 }, 13243 },
11763 13244
11764 _valueToIndex: function(value) { 13245 _valueToIndex: function(value) {
11765 if (this.attrForSelected) { 13246 if (this.attrForSelected) {
11766 for (var i = 0, item; item = this.items[i]; i++) { 13247 for (var i = 0, item; item = this.items[i]; i++) {
11767 if (this._valueForItem(item) == value) { 13248 if (this._valueForItem(item) == value) {
(...skipping 490 matching lines...) Expand 10 before | Expand all | Expand 10 after
12258 is: 'paper-menu', 13739 is: 'paper-menu',
12259 13740
12260 behaviors: [ 13741 behaviors: [
12261 Polymer.IronMenuBehavior 13742 Polymer.IronMenuBehavior
12262 ] 13743 ]
12263 13744
12264 }); 13745 });
12265 13746
12266 })(); 13747 })();
12267 /** 13748 /**
12268 * `IronResizableBehavior` is a behavior that can be used in Polymer elements to
12269 * coordinate the flow of resize events between "resizers" (elements that cont rol the
12270 * size or hidden state of their children) and "resizables" (elements that nee d to be
12271 * notified when they are resized or un-hidden by their parents in order to ta ke
12272 * action on their new measurements).
12273 * Elements that perform measurement should add the `IronResizableBehavior` be havior to
12274 * their element definition and listen for the `iron-resize` event on themselv es.
12275 * This event will be fired when they become showing after having been hidden,
12276 * when they are resized explicitly by another resizable, or when the window h as been
12277 * resized.
12278 * Note, the `iron-resize` event is non-bubbling.
12279 *
12280 * @polymerBehavior Polymer.IronResizableBehavior
12281 * @demo demo/index.html
12282 **/
12283 Polymer.IronResizableBehavior = {
12284 properties: {
12285 /**
12286 * The closest ancestor element that implements `IronResizableBehavior`.
12287 */
12288 _parentResizable: {
12289 type: Object,
12290 observer: '_parentResizableChanged'
12291 },
12292
12293 /**
12294 * True if this element is currently notifying its descedant elements of
12295 * resize.
12296 */
12297 _notifyingDescendant: {
12298 type: Boolean,
12299 value: false
12300 }
12301 },
12302
12303 listeners: {
12304 'iron-request-resize-notifications': '_onIronRequestResizeNotifications'
12305 },
12306
12307 created: function() {
12308 // We don't really need property effects on these, and also we want them
12309 // to be created before the `_parentResizable` observer fires:
12310 this._interestedResizables = [];
12311 this._boundNotifyResize = this.notifyResize.bind(this);
12312 },
12313
12314 attached: function() {
12315 this.fire('iron-request-resize-notifications', null, {
12316 node: this,
12317 bubbles: true,
12318 cancelable: true
12319 });
12320
12321 if (!this._parentResizable) {
12322 window.addEventListener('resize', this._boundNotifyResize);
12323 this.notifyResize();
12324 }
12325 },
12326
12327 detached: function() {
12328 if (this._parentResizable) {
12329 this._parentResizable.stopResizeNotificationsFor(this);
12330 } else {
12331 window.removeEventListener('resize', this._boundNotifyResize);
12332 }
12333
12334 this._parentResizable = null;
12335 },
12336
12337 /**
12338 * Can be called to manually notify a resizable and its descendant
12339 * resizables of a resize change.
12340 */
12341 notifyResize: function() {
12342 if (!this.isAttached) {
12343 return;
12344 }
12345
12346 this._interestedResizables.forEach(function(resizable) {
12347 if (this.resizerShouldNotify(resizable)) {
12348 this._notifyDescendant(resizable);
12349 }
12350 }, this);
12351
12352 this._fireResize();
12353 },
12354
12355 /**
12356 * Used to assign the closest resizable ancestor to this resizable
12357 * if the ancestor detects a request for notifications.
12358 */
12359 assignParentResizable: function(parentResizable) {
12360 this._parentResizable = parentResizable;
12361 },
12362
12363 /**
12364 * Used to remove a resizable descendant from the list of descendants
12365 * that should be notified of a resize change.
12366 */
12367 stopResizeNotificationsFor: function(target) {
12368 var index = this._interestedResizables.indexOf(target);
12369
12370 if (index > -1) {
12371 this._interestedResizables.splice(index, 1);
12372 this.unlisten(target, 'iron-resize', '_onDescendantIronResize');
12373 }
12374 },
12375
12376 /**
12377 * This method can be overridden to filter nested elements that should or
12378 * should not be notified by the current element. Return true if an element
12379 * should be notified, or false if it should not be notified.
12380 *
12381 * @param {HTMLElement} element A candidate descendant element that
12382 * implements `IronResizableBehavior`.
12383 * @return {boolean} True if the `element` should be notified of resize.
12384 */
12385 resizerShouldNotify: function(element) { return true; },
12386
12387 _onDescendantIronResize: function(event) {
12388 if (this._notifyingDescendant) {
12389 event.stopPropagation();
12390 return;
12391 }
12392
12393 // NOTE(cdata): In ShadowDOM, event retargetting makes echoing of the
12394 // otherwise non-bubbling event "just work." We do it manually here for
12395 // the case where Polymer is not using shadow roots for whatever reason:
12396 if (!Polymer.Settings.useShadow) {
12397 this._fireResize();
12398 }
12399 },
12400
12401 _fireResize: function() {
12402 this.fire('iron-resize', null, {
12403 node: this,
12404 bubbles: false
12405 });
12406 },
12407
12408 _onIronRequestResizeNotifications: function(event) {
12409 var target = event.path ? event.path[0] : event.target;
12410
12411 if (target === this) {
12412 return;
12413 }
12414
12415 if (this._interestedResizables.indexOf(target) === -1) {
12416 this._interestedResizables.push(target);
12417 this.listen(target, 'iron-resize', '_onDescendantIronResize');
12418 }
12419
12420 target.assignParentResizable(this);
12421 this._notifyDescendant(target);
12422
12423 event.stopPropagation();
12424 },
12425
12426 _parentResizableChanged: function(parentResizable) {
12427 if (parentResizable) {
12428 window.removeEventListener('resize', this._boundNotifyResize);
12429 }
12430 },
12431
12432 _notifyDescendant: function(descendant) {
12433 // NOTE(cdata): In IE10, attached is fired on children first, so it's
12434 // important not to notify them if the parent is not attached yet (or
12435 // else they will get redundantly notified when the parent attaches).
12436 if (!this.isAttached) {
12437 return;
12438 }
12439
12440 this._notifyingDescendant = true;
12441 descendant.notifyResize();
12442 this._notifyingDescendant = false;
12443 }
12444 };
12445 /**
12446 Polymer.IronFitBehavior fits an element in another element using `max-height` an d `max-width`, and 13749 Polymer.IronFitBehavior fits an element in another element using `max-height` an d `max-width`, and
12447 optionally centers it in the window or another element. 13750 optionally centers it in the window or another element.
12448 13751
12449 The element will only be sized and/or positioned if it has not already been size d and/or positioned 13752 The element will only be sized and/or positioned if it has not already been size d and/or positioned
12450 by CSS. 13753 by CSS.
12451 13754
12452 CSS properties | Action 13755 CSS properties | Action
12453 -----------------------------|------------------------------------------- 13756 -----------------------------|-------------------------------------------
12454 `position` set | Element is not centered horizontally or verticall y 13757 `position` set | Element is not centered horizontally or verticall y
12455 `top` or `bottom` set | Element is not vertically centered 13758 `top` or `bottom` set | Element is not vertically centered
(...skipping 394 matching lines...) Expand 10 before | Expand all | Expand 10 after
12850 on top of other content. It includes an optional backdrop, and can be used to im plement a variety 14153 on top of other content. It includes an optional backdrop, and can be used to im plement a variety
12851 of UI controls including dialogs and drop downs. Multiple overlays may be displa yed at once. 14154 of UI controls including dialogs and drop downs. Multiple overlays may be displa yed at once.
12852 14155
12853 ### Closing and canceling 14156 ### Closing and canceling
12854 14157
12855 A dialog may be hidden by closing or canceling. The difference between close and cancel is user 14158 A dialog may be hidden by closing or canceling. The difference between close and cancel is user
12856 intent. Closing generally implies that the user acknowledged the content on the overlay. By default, 14159 intent. Closing generally implies that the user acknowledged the content on the overlay. By default,
12857 it will cancel whenever the user taps outside it or presses the escape key. This behavior is 14160 it will cancel whenever the user taps outside it or presses the escape key. This behavior is
12858 configurable with the `no-cancel-on-esc-key` and the `no-cancel-on-outside-click ` properties. 14161 configurable with the `no-cancel-on-esc-key` and the `no-cancel-on-outside-click ` properties.
12859 `close()` should be called explicitly by the implementer when the user interacts with a control 14162 `close()` should be called explicitly by the implementer when the user interacts with a control
12860 in the overlay element. 14163 in the overlay element. When the dialog is canceled, the overlay fires an 'iron- overlay-canceled'
14164 event. Call `preventDefault` on this event to prevent the overlay from closing.
12861 14165
12862 ### Positioning 14166 ### Positioning
12863 14167
12864 By default the element is sized and positioned to fit and centered inside the wi ndow. You can 14168 By default the element is sized and positioned to fit and centered inside the wi ndow. You can
12865 position and size it manually using CSS. See `Polymer.IronFitBehavior`. 14169 position and size it manually using CSS. See `Polymer.IronFitBehavior`.
12866 14170
12867 ### Backdrop 14171 ### Backdrop
12868 14172
12869 Set the `with-backdrop` attribute to display a backdrop behind the overlay. The backdrop is 14173 Set the `with-backdrop` attribute to display a backdrop behind the overlay. The backdrop is
12870 appended to `<body>` and is of type `<iron-overlay-backdrop>`. See its doc page for styling 14174 appended to `<body>` and is of type `<iron-overlay-backdrop>`. See its doc page for styling
(...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after
13021 */ 14325 */
13022 close: function() { 14326 close: function() {
13023 this.opened = false; 14327 this.opened = false;
13024 this._setCanceled(false); 14328 this._setCanceled(false);
13025 }, 14329 },
13026 14330
13027 /** 14331 /**
13028 * Cancels the overlay. 14332 * Cancels the overlay.
13029 */ 14333 */
13030 cancel: function() { 14334 cancel: function() {
14335 var cancelEvent = this.fire('iron-overlay-canceled', undefined, {cancelabl e: true});
14336 if (cancelEvent.defaultPrevented) {
14337 return;
14338 }
14339
13031 this.opened = false; 14340 this.opened = false;
13032 this._setCanceled(true); 14341 this._setCanceled(true);
13033 }, 14342 },
13034 14343
13035 _ensureSetup: function() { 14344 _ensureSetup: function() {
13036 if (this._overlaySetup) { 14345 if (this._overlaySetup) {
13037 return; 14346 return;
13038 } 14347 }
13039 this._overlaySetup = true; 14348 this._overlaySetup = true;
13040 this.style.outline = 'none'; 14349 this.style.outline = 'none';
(...skipping 1528 matching lines...) Expand 10 before | Expand all | Expand 10 after
14569 }); 15878 });
14570 15879
14571 PaperMenuButton.ANIMATION_CUBIC_BEZIER = 'cubic-bezier(.3,.95,.5,1)'; 15880 PaperMenuButton.ANIMATION_CUBIC_BEZIER = 'cubic-bezier(.3,.95,.5,1)';
14572 PaperMenuButton.MAX_ANIMATION_TIME_MS = 400; 15881 PaperMenuButton.MAX_ANIMATION_TIME_MS = 400;
14573 15882
14574 Polymer.PaperMenuButton = PaperMenuButton; 15883 Polymer.PaperMenuButton = PaperMenuButton;
14575 })(); 15884 })();
14576 /** 15885 /**
14577 * `Polymer.PaperInkyFocusBehavior` implements a ripple when the element has k eyboard focus. 15886 * `Polymer.PaperInkyFocusBehavior` implements a ripple when the element has k eyboard focus.
14578 * 15887 *
14579 * @polymerBehavior Polymer.PaperInkyFocusBehavior 15888 * @polymerBehavior Polymer.PaperInkyFocusBehaviorImpl
14580 */ 15889 */
14581 Polymer.PaperInkyFocusBehaviorImpl = { 15890 Polymer.PaperInkyFocusBehaviorImpl = {
14582 15891
14583 observers: [ 15892 observers: [
14584 '_focusedChanged(receivedFocusFromKeyboard)' 15893 '_focusedChanged(receivedFocusFromKeyboard)'
14585 ], 15894 ],
14586 15895
14587 _focusedChanged: function(receivedFocusFromKeyboard) { 15896 _focusedChanged: function(receivedFocusFromKeyboard) {
14588 if (!this.$.ink) { 15897 if (receivedFocusFromKeyboard) {
14589 return; 15898 this.ensureRipple();
14590 } 15899 }
15900 if (this.hasRipple()) {
15901 this._ripple.holdDown = receivedFocusFromKeyboard;
15902 }
15903 },
14591 15904
14592 this.$.ink.holdDown = receivedFocusFromKeyboard; 15905 _createRipple: function() {
15906 var ripple = Polymer.PaperRippleBehavior._createRipple();
15907 ripple.id = 'ink';
15908 ripple.setAttribute('center', '');
15909 ripple.classList.add('circle');
15910 return ripple;
14593 } 15911 }
14594 15912
14595 }; 15913 };
14596 15914
14597 /** @polymerBehavior Polymer.PaperInkyFocusBehavior */ 15915 /** @polymerBehavior Polymer.PaperInkyFocusBehavior */
14598 Polymer.PaperInkyFocusBehavior = [ 15916 Polymer.PaperInkyFocusBehavior = [
14599 Polymer.IronButtonState, 15917 Polymer.IronButtonState,
14600 Polymer.IronControlState, 15918 Polymer.IronControlState,
15919 Polymer.PaperRippleBehavior,
14601 Polymer.PaperInkyFocusBehaviorImpl 15920 Polymer.PaperInkyFocusBehaviorImpl
14602 ]; 15921 ];
14603 Polymer({ 15922 Polymer({
14604 is: 'paper-icon-button', 15923 is: 'paper-icon-button',
14605 15924
14606 hostAttributes: { 15925 hostAttributes: {
14607 role: 'button', 15926 role: 'button',
14608 tabindex: '0' 15927 tabindex: '0'
14609 },
14610
14611 behaviors: [
14612 Polymer.PaperInkyFocusBehavior
14613 ],
14614
14615 properties: {
14616 /**
14617 * The URL of an image for the icon. If the src property is specified,
14618 * the icon property should not be.
14619 */
14620 src: {
14621 type: String
14622 }, 15928 },
14623 15929
14624 /** 15930 behaviors: [
14625 * Specifies the icon name or index in the set of icons available in 15931 Polymer.PaperInkyFocusBehavior
14626 * the icon's icon set. If the icon property is specified, 15932 ],
14627 * the src property should not be. 15933
14628 */ 15934 properties: {
14629 icon: { 15935 /**
14630 type: String 15936 * The URL of an image for the icon. If the src property is specified,
15937 * the icon property should not be.
15938 */
15939 src: {
15940 type: String
15941 },
15942
15943 /**
15944 * Specifies the icon name or index in the set of icons available in
15945 * the icon's icon set. If the icon property is specified,
15946 * the src property should not be.
15947 */
15948 icon: {
15949 type: String
15950 },
15951
15952 /**
15953 * Specifies the alternate text for the button, for accessibility.
15954 */
15955 alt: {
15956 type: String,
15957 observer: "_altChanged"
15958 }
14631 }, 15959 },
14632 15960
14633 /** 15961 _altChanged: function(newValue, oldValue) {
14634 * Specifies the alternate text for the button, for accessibility. 15962 var label = this.getAttribute('aria-label');
14635 */ 15963
14636 alt: { 15964 // Don't stomp over a user-set aria-label.
14637 type: String, 15965 if (!label || oldValue == label) {
14638 observer: "_altChanged" 15966 this.setAttribute('aria-label', newValue);
15967 }
14639 } 15968 }
14640 }, 15969 });
14641
14642 _altChanged: function(newValue, oldValue) {
14643 var label = this.getAttribute('aria-label');
14644
14645 // Don't stomp over a user-set aria-label.
14646 if (!label || oldValue == label) {
14647 this.setAttribute('aria-label', newValue);
14648 }
14649 }
14650 });
14651 /** 15970 /**
14652 * Use `Polymer.IronValidatableBehavior` to implement an element that validate s user input. 15971 * Use `Polymer.IronValidatableBehavior` to implement an element that validate s user input.
14653 * 15972 *
14654 * ### Accessibility 15973 * ### Accessibility
14655 * 15974 *
14656 * Changing the `invalid` property, either manually or by calling `validate()` will update the 15975 * Changing the `invalid` property, either manually or by calling `validate()` will update the
14657 * `aria-invalid` attribute. 15976 * `aria-invalid` attribute.
14658 * 15977 *
14659 * @demo demo/index.html 15978 * @demo demo/index.html
14660 * @polymerBehavior 15979 * @polymerBehavior
(...skipping 777 matching lines...) Expand 10 before | Expand all | Expand 10 after
15438 16757
15439 cr.define('downloads', function() { 16758 cr.define('downloads', function() {
15440 var Manager = Polymer({ 16759 var Manager = Polymer({
15441 is: 'downloads-manager', 16760 is: 'downloads-manager',
15442 16761
15443 properties: { 16762 properties: {
15444 hasDownloads_: { 16763 hasDownloads_: {
15445 type: Boolean, 16764 type: Boolean,
15446 value: false, 16765 value: false,
15447 }, 16766 },
16767
16768 items_: {
16769 type: Array,
16770 },
15448 }, 16771 },
15449 16772
15450 /** 16773 /**
15451 * @return {number} A guess at how many items could be visible at once.
15452 * @private
15453 */
15454 guesstimateNumberOfVisibleItems_: function() {
15455 var toolbarHeight = this.$.toolbar.offsetHeight;
15456 return Math.floor((window.innerHeight - toolbarHeight) / 46) + 1;
15457 },
15458
15459 /**
15460 * @param {Event} e 16774 * @param {Event} e
15461 * @private 16775 * @private
15462 */ 16776 */
15463 onCanExecute_: function(e) { 16777 onCanExecute_: function(e) {
15464 e = /** @type {cr.ui.CanExecuteEvent} */(e); 16778 e = /** @type {cr.ui.CanExecuteEvent} */(e);
15465 switch (e.command.id) { 16779 switch (e.command.id) {
15466 case 'undo-command': 16780 case 'undo-command':
15467 e.canExecute = this.$.toolbar.canUndo(); 16781 e.canExecute = this.$.toolbar.canUndo();
15468 break; 16782 break;
15469 case 'clear-all-command': 16783 case 'clear-all-command':
(...skipping 16 matching lines...) Expand all
15486 /** @private */ 16800 /** @private */
15487 onLoad_: function() { 16801 onLoad_: function() {
15488 cr.ui.decorate('command', cr.ui.Command); 16802 cr.ui.decorate('command', cr.ui.Command);
15489 document.addEventListener('canExecute', this.onCanExecute_.bind(this)); 16803 document.addEventListener('canExecute', this.onCanExecute_.bind(this));
15490 document.addEventListener('command', this.onCommand_.bind(this)); 16804 document.addEventListener('command', this.onCommand_.bind(this));
15491 16805
15492 // Shows all downloads. 16806 // Shows all downloads.
15493 downloads.ActionService.getInstance().search(''); 16807 downloads.ActionService.getInstance().search('');
15494 }, 16808 },
15495 16809
15496 /** @private */
15497 rebuildFocusGrid_: function() {
15498 var activeElement = this.shadowRoot.activeElement;
15499
15500 var activeItem;
15501 if (activeElement && activeElement.tagName == 'downloads-item')
15502 activeItem = activeElement;
15503
15504 var activeControl = activeItem && activeItem.shadowRoot.activeElement;
15505
15506 /** @private {!cr.ui.FocusGrid} */
15507 this.focusGrid_ = this.focusGrid_ || new cr.ui.FocusGrid;
15508 this.focusGrid_.destroy();
15509
15510 var boundary = this.$['downloads-list'];
15511
15512 this.items_.forEach(function(item) {
15513 var focusRow = new downloads.FocusRow(item.content, boundary);
15514 this.focusGrid_.addRow(focusRow);
15515
15516 if (item == activeItem && !cr.ui.FocusRow.isFocusable(activeControl))
15517 focusRow.getEquivalentElement(activeControl).focus();
15518 }, this);
15519
15520 this.focusGrid_.ensureRowActive();
15521 },
15522
15523 /** 16810 /**
15524 * @return {number} The number of downloads shown on the page. 16811 * @return {number} The number of downloads shown on the page.
15525 * @private 16812 * @private
15526 */ 16813 */
15527 size_: function() { 16814 size_: function() {
15528 return this.items_.length; 16815 return this.items_.length;
15529 }, 16816 },
15530 16817
15531 /** 16818 /**
15532 * Called when all items need to be updated. 16819 * Called when all items need to be updated.
15533 * @param {!Array<!downloads.Data>} list A list of new download data. 16820 * @param {!Array<!downloads.Data>} list A list of new download data.
15534 * @private 16821 * @private
15535 */ 16822 */
15536 updateAll_: function(list) { 16823 updateAll_: function(list) {
15537 var oldIdMap = this.idMap_ || {}; 16824 /** @private {!Object<number>} */
16825 this.idToIndex_ = {};
15538 16826
15539 /** @private {!Object<!downloads.Item>} */ 16827 var items = [];
15540 this.idMap_ = {};
15541
15542 /** @private {!Array<!downloads.Item>} */
15543 this.items_ = [];
15544
15545 if (!this.iconLoader_) {
15546 var guesstimate = Math.max(this.guesstimateNumberOfVisibleItems_(), 1);
15547 /** @private {downloads.ThrottledIconLoader} */
15548 this.iconLoader_ = new downloads.ThrottledIconLoader(guesstimate);
15549 }
15550 16828
15551 for (var i = 0; i < list.length; ++i) { 16829 for (var i = 0; i < list.length; ++i) {
15552 var data = list[i]; 16830 var data = list[i];
15553 var id = data.id; 16831 var id = data.id;
15554 16832
15555 // Re-use old items when possible (saves work, preserves focus). 16833 this.idToIndex_[id] = i;
15556 var item = oldIdMap[id] || new downloads.Item(this.iconLoader_);
15557 16834
15558 this.idMap_[id] = item; // Associated by ID for fast lookup. 16835 var prev = list[i - 1];
15559 this.items_.push(item); // Add to sorted list for order.
15560 16836
15561 // Render |item| but don't actually add to the DOM yet. |this.items_| 16837 items.push({
15562 // must be fully created to be able to find the right spot to insert. 16838 index: i,
15563 item.update(data); 16839 item: data,
15564 16840 hideDate: !!prev && prev.date_string == data.date_string,
15565 // Collapse redundant dates. 16841 });
15566 var prev = list[i - 1];
15567 item.hideDate = !!prev && prev.date_string == data.date_string;
15568
15569 delete oldIdMap[id];
15570 } 16842 }
15571 16843
15572 // Remove stale, previously rendered items from the DOM. 16844 // TODO(dbeam): this is a huge hack. Let's figure out a better way.
15573 for (var id in oldIdMap) { 16845 var resetScrollPosition = this.$['downloads-list']._resetScrollPosition;
15574 if (oldIdMap[id].parentNode) 16846 this.$['downloads-list']._resetScrollPosition = function() {};
15575 oldIdMap[id].parentNode.removeChild(oldIdMap[id]); 16847 this.items_ = items;
15576 delete oldIdMap[id]; 16848 this.$['downloads-list']._resetScrollPosition = resetScrollPosition;
15577 }
15578
15579 for (var i = 0; i < this.items_.length; ++i) {
15580 var item = this.items_[i];
15581 if (item.parentNode) // Already in the DOM; skip.
15582 continue;
15583
15584 var before = null;
15585 // Find the next rendered item after this one, and insert before it.
15586 for (var j = i + 1; !before && j < this.items_.length; ++j) {
15587 if (this.items_[j].parentNode)
15588 before = this.items_[j];
15589 }
15590 // If |before| is null, |item| will just get added at the end.
15591 this.$['downloads-list'].insertBefore(item, before);
15592 }
15593 16849
15594 var hasDownloads = this.size_() > 0; 16850 var hasDownloads = this.size_() > 0;
15595 if (!hasDownloads) { 16851 if (!hasDownloads) {
15596 var isSearching = downloads.ActionService.getInstance().isSearching(); 16852 var isSearching = downloads.ActionService.getInstance().isSearching();
15597 var messageToShow = isSearching ? 'noSearchResults' : 'noDownloads'; 16853 var messageToShow = isSearching ? 'noSearchResults' : 'noDownloads';
15598 this.$['no-downloads'].querySelector('span').textContent = 16854 this.$['no-downloads'].querySelector('span').textContent =
15599 loadTimeData.getString(messageToShow); 16855 loadTimeData.getString(messageToShow);
15600 } 16856 }
15601 this.hasDownloads_ = hasDownloads; 16857 this.hasDownloads_ = hasDownloads;
15602 16858
15603 if (loadTimeData.getBoolean('allowDeletingHistory')) 16859 if (loadTimeData.getBoolean('allowDeletingHistory'))
15604 this.$.toolbar.downloadsShowing = this.hasDownloads_; 16860 this.$.toolbar.downloadsShowing = this.hasDownloads_;
15605 16861
15606 this.$.panel.classList.remove('loading'); 16862 this.$.panel.classList.remove('loading');
15607
15608 var allReady = this.items_.map(function(i) { return i.readyPromise; });
15609 Promise.all(allReady).then(this.rebuildFocusGrid_.bind(this));
15610 }, 16863 },
15611 16864
15612 /** 16865 /**
15613 * @param {!downloads.Data} data 16866 * @param {!downloads.Data} data
15614 * @private 16867 * @private
15615 */ 16868 */
15616 updateItem_: function(data) { 16869 updateItem_: function(data) {
15617 var item = this.idMap_[data.id]; 16870 var index = this.idToIndex_[data.id];
15618 16871 this.set('items_.' + index + '.item', data);
15619 var activeControl = this.shadowRoot.activeElement == item ? 16872 this.$['downloads-list'].updateSizeForItem(index);
15620 item.shadowRoot.activeElement : null;
15621
15622 item.update(data);
15623
15624 this.async(function() {
15625 if (activeControl && !cr.ui.FocusRow.isFocusable(activeControl)) {
15626 var focusRow = this.focusGrid_.getRowForRoot(item.content);
15627 focusRow.getEquivalentElement(activeControl).focus();
15628 }
15629 }.bind(this));
15630 }, 16873 },
15631 }); 16874 });
15632 16875
15633 Manager.size = function() { 16876 Manager.size = function() {
15634 return document.querySelector('downloads-manager').size_(); 16877 return document.querySelector('downloads-manager').size_();
15635 }; 16878 };
15636 16879
15637 Manager.updateAll = function(list) { 16880 Manager.updateAll = function(list) {
15638 document.querySelector('downloads-manager').updateAll_(list); 16881 document.querySelector('downloads-manager').updateAll_(list);
15639 }; 16882 };
15640 16883
15641 Manager.updateItem = function(item) { 16884 Manager.updateItem = function(item) {
15642 document.querySelector('downloads-manager').updateItem_(item); 16885 document.querySelector('downloads-manager').updateItem_(item);
15643 }; 16886 };
15644 16887
15645 Manager.onLoad = function() { 16888 Manager.onLoad = function() {
15646 document.querySelector('downloads-manager').onLoad_(); 16889 document.querySelector('downloads-manager').onLoad_();
15647 }; 16890 };
15648 16891
15649 return {Manager: Manager}; 16892 return {Manager: Manager};
15650 }); 16893 });
15651 16894
15652 window.addEventListener('load', downloads.Manager.onLoad); 16895 window.addEventListener('load', downloads.Manager.onLoad);
OLDNEW
« no previous file with comments | « no previous file | chrome/browser/resources/md_downloads/item.css » ('j') | chrome/browser/resources/md_downloads/manager.js » ('J')

Powered by Google App Engine
This is Rietveld 408576698