| OLD | NEW |
| 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 Loading... |
| 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 6374 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 8801 this._prepBehaviors(); | 8740 this._prepBehaviors(); |
| 8802 this._prepConfigure(); | 8741 this._prepConfigure(); |
| 8803 this._prepBindings(); | 8742 this._prepBindings(); |
| 8804 Polymer.Base._initFeatures.call(this); | 8743 Polymer.Base._initFeatures.call(this); |
| 8805 this._children = Array.prototype.slice.call(this.root.childNodes); | 8744 this._children = Array.prototype.slice.call(this.root.childNodes); |
| 8806 } | 8745 } |
| 8807 this._insertChildren(); | 8746 this._insertChildren(); |
| 8808 this.fire('dom-change'); | 8747 this.fire('dom-change'); |
| 8809 } | 8748 } |
| 8810 }); | 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 }; |
| 8811 (function() { | 8928 (function() { |
| 8812 | 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 })(); |
| 10080 (function() { |
| 10081 |
| 8813 'use strict'; | 10082 'use strict'; |
| 8814 | 10083 |
| 8815 var SHADOW_WHEN_SCROLLING = 1; | 10084 var SHADOW_WHEN_SCROLLING = 1; |
| 8816 var SHADOW_ALWAYS = 2; | 10085 var SHADOW_ALWAYS = 2; |
| 8817 | 10086 |
| 8818 | 10087 |
| 8819 var MODE_CONFIGS = { | 10088 var MODE_CONFIGS = { |
| 8820 | 10089 |
| 8821 outerScroll: { | 10090 outerScroll: { |
| 8822 'scroll': true | 10091 'scroll': true |
| (...skipping 2488 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 11311 | 12580 |
| 11312 }); | 12581 }); |
| 11313 // Copyright 2015 The Chromium Authors. All rights reserved. | 12582 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 11314 // 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 |
| 11315 // found in the LICENSE file. | 12584 // found in the LICENSE file. |
| 11316 | 12585 |
| 11317 cr.define('downloads', function() { | 12586 cr.define('downloads', function() { |
| 11318 var Item = Polymer({ | 12587 var Item = Polymer({ |
| 11319 is: 'downloads-item', | 12588 is: 'downloads-item', |
| 11320 | 12589 |
| 11321 /** | |
| 11322 * @param {!downloads.ThrottledIconLoader} iconLoader | |
| 11323 */ | |
| 11324 factoryImpl: function(iconLoader) { | |
| 11325 /** @private {!downloads.ThrottledIconLoader} */ | |
| 11326 this.iconLoader_ = iconLoader; | |
| 11327 }, | |
| 11328 | |
| 11329 properties: { | 12590 properties: { |
| 11330 data: { | 12591 data: { |
| 11331 type: Object, | 12592 type: Object, |
| 11332 }, | 12593 }, |
| 11333 | 12594 |
| 11334 hideDate: { | 12595 hideDate: { |
| 11335 type: Boolean, | 12596 type: Boolean, |
| 11336 value: true, | 12597 value: true, |
| 11337 }, | 12598 }, |
| 11338 | 12599 |
| 11339 readyPromise: { | |
| 11340 type: Object, | |
| 11341 value: function() { | |
| 11342 return new Promise(function(resolve, reject) { | |
| 11343 this.resolveReadyPromise_ = resolve; | |
| 11344 }.bind(this)); | |
| 11345 }, | |
| 11346 }, | |
| 11347 | |
| 11348 completelyOnDisk_: { | 12600 completelyOnDisk_: { |
| 11349 computed: 'computeCompletelyOnDisk_(' + | 12601 computed: 'computeCompletelyOnDisk_(' + |
| 11350 'data.state, data.file_externally_removed)', | 12602 'data.state, data.file_externally_removed)', |
| 11351 type: Boolean, | 12603 type: Boolean, |
| 11352 value: true, | 12604 value: true, |
| 11353 }, | 12605 }, |
| 11354 | 12606 |
| 11355 controlledBy_: { | 12607 controlledBy_: { |
| 11356 computed: 'computeControlledBy_(data.by_ext_id, data.by_ext_name)', | 12608 computed: 'computeControlledBy_(data.by_ext_id, data.by_ext_name)', |
| 11357 type: String, | 12609 type: String, |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 11410 computed: 'computeIsMalware_(isDangerous_, data.danger_type)', | 12662 computed: 'computeIsMalware_(isDangerous_, data.danger_type)', |
| 11411 type: Boolean, | 12663 type: Boolean, |
| 11412 value: false, | 12664 value: false, |
| 11413 }, | 12665 }, |
| 11414 }, | 12666 }, |
| 11415 | 12667 |
| 11416 observers: [ | 12668 observers: [ |
| 11417 // 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 |
| 11418 // and data.by_ext_name directly. Why? | 12670 // and data.by_ext_name directly. Why? |
| 11419 'observeControlledBy_(controlledBy_)', | 12671 'observeControlledBy_(controlledBy_)', |
| 12672 'observeIsDangerous_(isDangerous_, data.file_path)', |
| 11420 ], | 12673 ], |
| 11421 | 12674 |
| 11422 ready: function() { | 12675 ready: function() { |
| 11423 this.content = this.$.content; | 12676 this.content = this.$.content; |
| 11424 this.resolveReadyPromise_(); | |
| 11425 }, | |
| 11426 | |
| 11427 /** @param {!downloads.Data} data */ | |
| 11428 update: function(data) { | |
| 11429 this.data = data; | |
| 11430 | |
| 11431 if (!this.isDangerous_) { | |
| 11432 var icon = 'chrome://fileicon/' + encodeURIComponent(data.file_path); | |
| 11433 this.iconLoader_.loadScaledIcon(this.$['file-icon'], icon); | |
| 11434 } | |
| 11435 }, | 12677 }, |
| 11436 | 12678 |
| 11437 /** @private */ | 12679 /** @private */ |
| 11438 computeClass_: function() { | 12680 computeClass_: function() { |
| 11439 var classes = []; | 12681 var classes = []; |
| 11440 | 12682 |
| 11441 if (this.isActive_) | 12683 if (this.isActive_) |
| 11442 classes.push('is-active'); | 12684 classes.push('is-active'); |
| 11443 | 12685 |
| 11444 if (this.isDangerous_) | 12686 if (this.isDangerous_) |
| (...skipping 140 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 11585 isIndeterminate_: function() { | 12827 isIndeterminate_: function() { |
| 11586 return this.data.percent == -1; | 12828 return this.data.percent == -1; |
| 11587 }, | 12829 }, |
| 11588 | 12830 |
| 11589 /** @private */ | 12831 /** @private */ |
| 11590 observeControlledBy_: function() { | 12832 observeControlledBy_: function() { |
| 11591 this.$['controlled-by'].innerHTML = this.controlledBy_; | 12833 this.$['controlled-by'].innerHTML = this.controlledBy_; |
| 11592 }, | 12834 }, |
| 11593 | 12835 |
| 11594 /** @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 */ |
| 11595 onCancelTap_: function() { | 12845 onCancelTap_: function() { |
| 11596 downloads.ActionService.getInstance().cancel(this.data.id); | 12846 downloads.ActionService.getInstance().cancel(this.data.id); |
| 11597 }, | 12847 }, |
| 11598 | 12848 |
| 11599 /** @private */ | 12849 /** @private */ |
| 11600 onDiscardDangerousTap_: function() { | 12850 onDiscardDangerousTap_: function() { |
| 11601 downloads.ActionService.getInstance().discardDangerous(this.data.id); | 12851 downloads.ActionService.getInstance().discardDangerous(this.data.id); |
| 11602 }, | 12852 }, |
| 11603 | 12853 |
| 11604 /** | 12854 /** |
| (...skipping 884 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 12489 is: 'paper-menu', | 13739 is: 'paper-menu', |
| 12490 | 13740 |
| 12491 behaviors: [ | 13741 behaviors: [ |
| 12492 Polymer.IronMenuBehavior | 13742 Polymer.IronMenuBehavior |
| 12493 ] | 13743 ] |
| 12494 | 13744 |
| 12495 }); | 13745 }); |
| 12496 | 13746 |
| 12497 })(); | 13747 })(); |
| 12498 /** | 13748 /** |
| 12499 * `IronResizableBehavior` is a behavior that can be used in Polymer elements
to | |
| 12500 * coordinate the flow of resize events between "resizers" (elements that cont
rol the | |
| 12501 * size or hidden state of their children) and "resizables" (elements that nee
d to be | |
| 12502 * notified when they are resized or un-hidden by their parents in order to ta
ke | |
| 12503 * action on their new measurements). | |
| 12504 * Elements that perform measurement should add the `IronResizableBehavior` be
havior to | |
| 12505 * their element definition and listen for the `iron-resize` event on themselv
es. | |
| 12506 * This event will be fired when they become showing after having been hidden, | |
| 12507 * when they are resized explicitly by another resizable, or when the window h
as been | |
| 12508 * resized. | |
| 12509 * Note, the `iron-resize` event is non-bubbling. | |
| 12510 * | |
| 12511 * @polymerBehavior Polymer.IronResizableBehavior | |
| 12512 * @demo demo/index.html | |
| 12513 **/ | |
| 12514 Polymer.IronResizableBehavior = { | |
| 12515 properties: { | |
| 12516 /** | |
| 12517 * The closest ancestor element that implements `IronResizableBehavior`. | |
| 12518 */ | |
| 12519 _parentResizable: { | |
| 12520 type: Object, | |
| 12521 observer: '_parentResizableChanged' | |
| 12522 }, | |
| 12523 | |
| 12524 /** | |
| 12525 * True if this element is currently notifying its descedant elements of | |
| 12526 * resize. | |
| 12527 */ | |
| 12528 _notifyingDescendant: { | |
| 12529 type: Boolean, | |
| 12530 value: false | |
| 12531 } | |
| 12532 }, | |
| 12533 | |
| 12534 listeners: { | |
| 12535 'iron-request-resize-notifications': '_onIronRequestResizeNotifications' | |
| 12536 }, | |
| 12537 | |
| 12538 created: function() { | |
| 12539 // We don't really need property effects on these, and also we want them | |
| 12540 // to be created before the `_parentResizable` observer fires: | |
| 12541 this._interestedResizables = []; | |
| 12542 this._boundNotifyResize = this.notifyResize.bind(this); | |
| 12543 }, | |
| 12544 | |
| 12545 attached: function() { | |
| 12546 this.fire('iron-request-resize-notifications', null, { | |
| 12547 node: this, | |
| 12548 bubbles: true, | |
| 12549 cancelable: true | |
| 12550 }); | |
| 12551 | |
| 12552 if (!this._parentResizable) { | |
| 12553 window.addEventListener('resize', this._boundNotifyResize); | |
| 12554 this.notifyResize(); | |
| 12555 } | |
| 12556 }, | |
| 12557 | |
| 12558 detached: function() { | |
| 12559 if (this._parentResizable) { | |
| 12560 this._parentResizable.stopResizeNotificationsFor(this); | |
| 12561 } else { | |
| 12562 window.removeEventListener('resize', this._boundNotifyResize); | |
| 12563 } | |
| 12564 | |
| 12565 this._parentResizable = null; | |
| 12566 }, | |
| 12567 | |
| 12568 /** | |
| 12569 * Can be called to manually notify a resizable and its descendant | |
| 12570 * resizables of a resize change. | |
| 12571 */ | |
| 12572 notifyResize: function() { | |
| 12573 if (!this.isAttached) { | |
| 12574 return; | |
| 12575 } | |
| 12576 | |
| 12577 this._interestedResizables.forEach(function(resizable) { | |
| 12578 if (this.resizerShouldNotify(resizable)) { | |
| 12579 this._notifyDescendant(resizable); | |
| 12580 } | |
| 12581 }, this); | |
| 12582 | |
| 12583 this._fireResize(); | |
| 12584 }, | |
| 12585 | |
| 12586 /** | |
| 12587 * Used to assign the closest resizable ancestor to this resizable | |
| 12588 * if the ancestor detects a request for notifications. | |
| 12589 */ | |
| 12590 assignParentResizable: function(parentResizable) { | |
| 12591 this._parentResizable = parentResizable; | |
| 12592 }, | |
| 12593 | |
| 12594 /** | |
| 12595 * Used to remove a resizable descendant from the list of descendants | |
| 12596 * that should be notified of a resize change. | |
| 12597 */ | |
| 12598 stopResizeNotificationsFor: function(target) { | |
| 12599 var index = this._interestedResizables.indexOf(target); | |
| 12600 | |
| 12601 if (index > -1) { | |
| 12602 this._interestedResizables.splice(index, 1); | |
| 12603 this.unlisten(target, 'iron-resize', '_onDescendantIronResize'); | |
| 12604 } | |
| 12605 }, | |
| 12606 | |
| 12607 /** | |
| 12608 * This method can be overridden to filter nested elements that should or | |
| 12609 * should not be notified by the current element. Return true if an element | |
| 12610 * should be notified, or false if it should not be notified. | |
| 12611 * | |
| 12612 * @param {HTMLElement} element A candidate descendant element that | |
| 12613 * implements `IronResizableBehavior`. | |
| 12614 * @return {boolean} True if the `element` should be notified of resize. | |
| 12615 */ | |
| 12616 resizerShouldNotify: function(element) { return true; }, | |
| 12617 | |
| 12618 _onDescendantIronResize: function(event) { | |
| 12619 if (this._notifyingDescendant) { | |
| 12620 event.stopPropagation(); | |
| 12621 return; | |
| 12622 } | |
| 12623 | |
| 12624 // NOTE(cdata): In ShadowDOM, event retargetting makes echoing of the | |
| 12625 // otherwise non-bubbling event "just work." We do it manually here for | |
| 12626 // the case where Polymer is not using shadow roots for whatever reason: | |
| 12627 if (!Polymer.Settings.useShadow) { | |
| 12628 this._fireResize(); | |
| 12629 } | |
| 12630 }, | |
| 12631 | |
| 12632 _fireResize: function() { | |
| 12633 this.fire('iron-resize', null, { | |
| 12634 node: this, | |
| 12635 bubbles: false | |
| 12636 }); | |
| 12637 }, | |
| 12638 | |
| 12639 _onIronRequestResizeNotifications: function(event) { | |
| 12640 var target = event.path ? event.path[0] : event.target; | |
| 12641 | |
| 12642 if (target === this) { | |
| 12643 return; | |
| 12644 } | |
| 12645 | |
| 12646 if (this._interestedResizables.indexOf(target) === -1) { | |
| 12647 this._interestedResizables.push(target); | |
| 12648 this.listen(target, 'iron-resize', '_onDescendantIronResize'); | |
| 12649 } | |
| 12650 | |
| 12651 target.assignParentResizable(this); | |
| 12652 this._notifyDescendant(target); | |
| 12653 | |
| 12654 event.stopPropagation(); | |
| 12655 }, | |
| 12656 | |
| 12657 _parentResizableChanged: function(parentResizable) { | |
| 12658 if (parentResizable) { | |
| 12659 window.removeEventListener('resize', this._boundNotifyResize); | |
| 12660 } | |
| 12661 }, | |
| 12662 | |
| 12663 _notifyDescendant: function(descendant) { | |
| 12664 // NOTE(cdata): In IE10, attached is fired on children first, so it's | |
| 12665 // important not to notify them if the parent is not attached yet (or | |
| 12666 // else they will get redundantly notified when the parent attaches). | |
| 12667 if (!this.isAttached) { | |
| 12668 return; | |
| 12669 } | |
| 12670 | |
| 12671 this._notifyingDescendant = true; | |
| 12672 descendant.notifyResize(); | |
| 12673 this._notifyingDescendant = false; | |
| 12674 } | |
| 12675 }; | |
| 12676 /** | |
| 12677 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 |
| 12678 optionally centers it in the window or another element. | 13750 optionally centers it in the window or another element. |
| 12679 | 13751 |
| 12680 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 |
| 12681 by CSS. | 13753 by CSS. |
| 12682 | 13754 |
| 12683 CSS properties | Action | 13755 CSS properties | Action |
| 12684 -----------------------------|------------------------------------------- | 13756 -----------------------------|------------------------------------------- |
| 12685 `position` set | Element is not centered horizontally or verticall
y | 13757 `position` set | Element is not centered horizontally or verticall
y |
| 12686 `top` or `bottom` set | Element is not vertically centered | 13758 `top` or `bottom` set | Element is not vertically centered |
| (...skipping 2998 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 15685 | 16757 |
| 15686 cr.define('downloads', function() { | 16758 cr.define('downloads', function() { |
| 15687 var Manager = Polymer({ | 16759 var Manager = Polymer({ |
| 15688 is: 'downloads-manager', | 16760 is: 'downloads-manager', |
| 15689 | 16761 |
| 15690 properties: { | 16762 properties: { |
| 15691 hasDownloads_: { | 16763 hasDownloads_: { |
| 15692 type: Boolean, | 16764 type: Boolean, |
| 15693 value: false, | 16765 value: false, |
| 15694 }, | 16766 }, |
| 16767 |
| 16768 items_: { |
| 16769 type: Array, |
| 16770 }, |
| 15695 }, | 16771 }, |
| 15696 | 16772 |
| 15697 /** | 16773 /** |
| 15698 * @return {number} A guess at how many items could be visible at once. | |
| 15699 * @private | |
| 15700 */ | |
| 15701 guesstimateNumberOfVisibleItems_: function() { | |
| 15702 var toolbarHeight = this.$.toolbar.offsetHeight; | |
| 15703 return Math.floor((window.innerHeight - toolbarHeight) / 46) + 1; | |
| 15704 }, | |
| 15705 | |
| 15706 /** | |
| 15707 * @param {Event} e | 16774 * @param {Event} e |
| 15708 * @private | 16775 * @private |
| 15709 */ | 16776 */ |
| 15710 onCanExecute_: function(e) { | 16777 onCanExecute_: function(e) { |
| 15711 e = /** @type {cr.ui.CanExecuteEvent} */(e); | 16778 e = /** @type {cr.ui.CanExecuteEvent} */(e); |
| 15712 switch (e.command.id) { | 16779 switch (e.command.id) { |
| 15713 case 'undo-command': | 16780 case 'undo-command': |
| 15714 e.canExecute = this.$.toolbar.canUndo(); | 16781 e.canExecute = this.$.toolbar.canUndo(); |
| 15715 break; | 16782 break; |
| 15716 case 'clear-all-command': | 16783 case 'clear-all-command': |
| (...skipping 16 matching lines...) Expand all Loading... |
| 15733 /** @private */ | 16800 /** @private */ |
| 15734 onLoad_: function() { | 16801 onLoad_: function() { |
| 15735 cr.ui.decorate('command', cr.ui.Command); | 16802 cr.ui.decorate('command', cr.ui.Command); |
| 15736 document.addEventListener('canExecute', this.onCanExecute_.bind(this)); | 16803 document.addEventListener('canExecute', this.onCanExecute_.bind(this)); |
| 15737 document.addEventListener('command', this.onCommand_.bind(this)); | 16804 document.addEventListener('command', this.onCommand_.bind(this)); |
| 15738 | 16805 |
| 15739 // Shows all downloads. | 16806 // Shows all downloads. |
| 15740 downloads.ActionService.getInstance().search(''); | 16807 downloads.ActionService.getInstance().search(''); |
| 15741 }, | 16808 }, |
| 15742 | 16809 |
| 15743 /** @private */ | |
| 15744 rebuildFocusGrid_: function() { | |
| 15745 var activeElement = this.shadowRoot.activeElement; | |
| 15746 | |
| 15747 var activeItem; | |
| 15748 if (activeElement && activeElement.tagName == 'downloads-item') | |
| 15749 activeItem = activeElement; | |
| 15750 | |
| 15751 var activeControl = activeItem && activeItem.shadowRoot.activeElement; | |
| 15752 | |
| 15753 /** @private {!cr.ui.FocusGrid} */ | |
| 15754 this.focusGrid_ = this.focusGrid_ || new cr.ui.FocusGrid; | |
| 15755 this.focusGrid_.destroy(); | |
| 15756 | |
| 15757 var boundary = this.$['downloads-list']; | |
| 15758 | |
| 15759 this.items_.forEach(function(item) { | |
| 15760 var focusRow = new downloads.FocusRow(item.content, boundary); | |
| 15761 this.focusGrid_.addRow(focusRow); | |
| 15762 | |
| 15763 if (item == activeItem && !cr.ui.FocusRow.isFocusable(activeControl)) | |
| 15764 focusRow.getEquivalentElement(activeControl).focus(); | |
| 15765 }, this); | |
| 15766 | |
| 15767 this.focusGrid_.ensureRowActive(); | |
| 15768 }, | |
| 15769 | |
| 15770 /** | 16810 /** |
| 15771 * @return {number} The number of downloads shown on the page. | 16811 * @return {number} The number of downloads shown on the page. |
| 15772 * @private | 16812 * @private |
| 15773 */ | 16813 */ |
| 15774 size_: function() { | 16814 size_: function() { |
| 15775 return this.items_.length; | 16815 return this.items_.length; |
| 15776 }, | 16816 }, |
| 15777 | 16817 |
| 15778 /** | 16818 /** |
| 15779 * Called when all items need to be updated. | 16819 * Called when all items need to be updated. |
| 15780 * @param {!Array<!downloads.Data>} list A list of new download data. | 16820 * @param {!Array<!downloads.Data>} list A list of new download data. |
| 15781 * @private | 16821 * @private |
| 15782 */ | 16822 */ |
| 15783 updateAll_: function(list) { | 16823 updateAll_: function(list) { |
| 15784 var oldIdMap = this.idMap_ || {}; | 16824 /** @private {!Object<number>} */ |
| 15785 | 16825 this.idToIndex_ = {}; |
| 15786 /** @private {!Object<!downloads.Item>} */ | |
| 15787 this.idMap_ = {}; | |
| 15788 | |
| 15789 /** @private {!Array<!downloads.Item>} */ | |
| 15790 this.items_ = []; | |
| 15791 | |
| 15792 if (!this.iconLoader_) { | |
| 15793 var guesstimate = Math.max(this.guesstimateNumberOfVisibleItems_(), 1); | |
| 15794 /** @private {downloads.ThrottledIconLoader} */ | |
| 15795 this.iconLoader_ = new downloads.ThrottledIconLoader(guesstimate); | |
| 15796 } | |
| 15797 | 16826 |
| 15798 for (var i = 0; i < list.length; ++i) { | 16827 for (var i = 0; i < list.length; ++i) { |
| 15799 var data = list[i]; | 16828 var data = list[i]; |
| 15800 var id = data.id; | |
| 15801 | 16829 |
| 15802 // Re-use old items when possible (saves work, preserves focus). | 16830 this.idToIndex_[data.id] = data.index = i; |
| 15803 var item = oldIdMap[id] || new downloads.Item(this.iconLoader_); | |
| 15804 | 16831 |
| 15805 this.idMap_[id] = item; // Associated by ID for fast lookup. | |
| 15806 this.items_.push(item); // Add to sorted list for order. | |
| 15807 | |
| 15808 // Render |item| but don't actually add to the DOM yet. |this.items_| | |
| 15809 // must be fully created to be able to find the right spot to insert. | |
| 15810 item.update(data); | |
| 15811 | |
| 15812 // Collapse redundant dates. | |
| 15813 var prev = list[i - 1]; | 16832 var prev = list[i - 1]; |
| 15814 item.hideDate = !!prev && prev.date_string == data.date_string; | 16833 data.hideDate = !!prev && prev.date_string == data.date_string; |
| 15815 | |
| 15816 delete oldIdMap[id]; | |
| 15817 } | 16834 } |
| 15818 | 16835 |
| 15819 // Remove stale, previously rendered items from the DOM. | 16836 // TODO(dbeam): this resets the scroll position, which is a huge bummer. |
| 15820 for (var id in oldIdMap) { | 16837 // Removing something from the bottom of the list should not scroll you |
| 15821 if (oldIdMap[id].parentNode) | 16838 // back to the top. The grand plan is to restructure how the C++ sends the |
| 15822 oldIdMap[id].parentNode.removeChild(oldIdMap[id]); | 16839 // JS data so that it only gets updates (rather than the most recent set |
| 15823 delete oldIdMap[id]; | 16840 // of items). TL;DR - we can't ship with this bug. |
| 15824 } | 16841 this.items_ = list; |
| 15825 | |
| 15826 for (var i = 0; i < this.items_.length; ++i) { | |
| 15827 var item = this.items_[i]; | |
| 15828 if (item.parentNode) // Already in the DOM; skip. | |
| 15829 continue; | |
| 15830 | |
| 15831 var before = null; | |
| 15832 // Find the next rendered item after this one, and insert before it. | |
| 15833 for (var j = i + 1; !before && j < this.items_.length; ++j) { | |
| 15834 if (this.items_[j].parentNode) | |
| 15835 before = this.items_[j]; | |
| 15836 } | |
| 15837 // If |before| is null, |item| will just get added at the end. | |
| 15838 this.$['downloads-list'].insertBefore(item, before); | |
| 15839 } | |
| 15840 | 16842 |
| 15841 var hasDownloads = this.size_() > 0; | 16843 var hasDownloads = this.size_() > 0; |
| 15842 if (!hasDownloads) { | 16844 if (!hasDownloads) { |
| 15843 var isSearching = downloads.ActionService.getInstance().isSearching(); | 16845 var isSearching = downloads.ActionService.getInstance().isSearching(); |
| 15844 var messageToShow = isSearching ? 'noSearchResults' : 'noDownloads'; | 16846 var messageToShow = isSearching ? 'noSearchResults' : 'noDownloads'; |
| 15845 this.$['no-downloads'].querySelector('span').textContent = | 16847 this.$['no-downloads'].querySelector('span').textContent = |
| 15846 loadTimeData.getString(messageToShow); | 16848 loadTimeData.getString(messageToShow); |
| 15847 } | 16849 } |
| 15848 this.hasDownloads_ = hasDownloads; | 16850 this.hasDownloads_ = hasDownloads; |
| 15849 | 16851 |
| 15850 if (loadTimeData.getBoolean('allowDeletingHistory')) | 16852 if (loadTimeData.getBoolean('allowDeletingHistory')) |
| 15851 this.$.toolbar.downloadsShowing = this.hasDownloads_; | 16853 this.$.toolbar.downloadsShowing = this.hasDownloads_; |
| 15852 | 16854 |
| 15853 this.$.panel.classList.remove('loading'); | 16855 this.$.panel.classList.remove('loading'); |
| 15854 | |
| 15855 var allReady = this.items_.map(function(i) { return i.readyPromise; }); | |
| 15856 Promise.all(allReady).then(this.rebuildFocusGrid_.bind(this)); | |
| 15857 }, | 16856 }, |
| 15858 | 16857 |
| 15859 /** | 16858 /** |
| 15860 * @param {!downloads.Data} data | 16859 * @param {!downloads.Data} data |
| 15861 * @private | 16860 * @private |
| 15862 */ | 16861 */ |
| 15863 updateItem_: function(data) { | 16862 updateItem_: function(data) { |
| 15864 var item = this.idMap_[data.id]; | 16863 var index = this.idToIndex_[data.id]; |
| 15865 | 16864 this.set('items_.' + index, data); |
| 15866 var activeControl = this.shadowRoot.activeElement == item ? | 16865 this.$['downloads-list'].updateSizeForItem(index); |
| 15867 item.shadowRoot.activeElement : null; | |
| 15868 | |
| 15869 item.update(data); | |
| 15870 | |
| 15871 this.async(function() { | |
| 15872 if (activeControl && !cr.ui.FocusRow.isFocusable(activeControl)) { | |
| 15873 var focusRow = this.focusGrid_.getRowForRoot(item.content); | |
| 15874 focusRow.getEquivalentElement(activeControl).focus(); | |
| 15875 } | |
| 15876 }.bind(this)); | |
| 15877 }, | 16866 }, |
| 15878 }); | 16867 }); |
| 15879 | 16868 |
| 15880 Manager.size = function() { | 16869 Manager.size = function() { |
| 15881 return document.querySelector('downloads-manager').size_(); | 16870 return document.querySelector('downloads-manager').size_(); |
| 15882 }; | 16871 }; |
| 15883 | 16872 |
| 15884 Manager.updateAll = function(list) { | 16873 Manager.updateAll = function(list) { |
| 15885 document.querySelector('downloads-manager').updateAll_(list); | 16874 document.querySelector('downloads-manager').updateAll_(list); |
| 15886 }; | 16875 }; |
| 15887 | 16876 |
| 15888 Manager.updateItem = function(item) { | 16877 Manager.updateItem = function(item) { |
| 15889 document.querySelector('downloads-manager').updateItem_(item); | 16878 document.querySelector('downloads-manager').updateItem_(item); |
| 15890 }; | 16879 }; |
| 15891 | 16880 |
| 15892 Manager.onLoad = function() { | 16881 Manager.onLoad = function() { |
| 15893 document.querySelector('downloads-manager').onLoad_(); | 16882 document.querySelector('downloads-manager').onLoad_(); |
| 15894 }; | 16883 }; |
| 15895 | 16884 |
| 15896 return {Manager: Manager}; | 16885 return {Manager: Manager}; |
| 15897 }); | 16886 }); |
| 15898 | 16887 |
| 15899 window.addEventListener('load', downloads.Manager.onLoad); | 16888 window.addEventListener('load', downloads.Manager.onLoad); |
| OLD | NEW |