Index: polymer_1.0.4/bower_components/iron-list/iron-list.html |
diff --git a/polymer_1.0.4/bower_components/iron-list/iron-list.html b/polymer_1.0.4/bower_components/iron-list/iron-list.html |
new file mode 100644 |
index 0000000000000000000000000000000000000000..190e484fca2ef40e0fc728274ca4e66e497e406d |
--- /dev/null |
+++ b/polymer_1.0.4/bower_components/iron-list/iron-list.html |
@@ -0,0 +1,969 @@ |
+<!-- |
+@license |
+Copyright (c) 2015 The Polymer Project Authors. All rights reserved. |
+This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt |
+The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
+The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt |
+Code distributed by Google as part of the polymer project is also |
+subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt |
+--> |
+ |
+<link rel="import" href="../polymer/polymer.html"> |
+<link rel="import" href="../iron-resizable-behavior/iron-resizable-behavior.html"> |
+ |
+<!-- |
+ |
+`iron-list` displays a virtual, 'infinite' list. The template inside |
+the iron-list element represents the DOM to create for each list item. |
+The `items` property specifies an array of list item data. |
+ |
+For performance reasons, not every item in the list is rendered at once; |
+instead a small subset of actual template elements *(enough to fill the viewport)* |
+are rendered and reused as the user scrolls. As such, it is important that all |
+state of the list template be bound to the model driving it, since the view may |
+be reused with a new model at any time. Particularly, any state that may change |
+as the result of a user interaction with the list item must be bound to the model |
+to avoid view state inconsistency. |
+ |
+__Important:__ `iron-list` must ether be explicitly sized, or delegate scrolling to an |
+explicitly sized parent. By "explicitly sized", we mean it either has an explicit |
+CSS `height` property set via a class or inline style, or else is sized by other |
+layout means (e.g. the `flex` or `fit` classes). |
+ |
+### Template model |
+ |
+List item templates should bind to template models of the following structure: |
+ |
+ { |
+ index: 0, // data index for this item |
+ item: { // user data corresponding to items[index] |
+ /* user item data */ |
+ } |
+ } |
+ |
+Alternatively, you can change the property name used as data index by changing the |
+`indexAs` property. The `as` property defines the name of the variable to add to the binding |
+scope for the array. |
+ |
+For example, given the following `data` array: |
+ |
+##### data.json |
+ |
+ [ |
+ {"name": "Bob"}, |
+ {"name": "Tim"}, |
+ {"name": "Mike"} |
+ ] |
+ |
+The following code would render the list (note the name and checked properties are |
+bound from the model object provided to the template scope): |
+ |
+ <template is="dom-bind"> |
+ <iron-ajax url="data.json" last-response="{{data}}" auto></iron-ajax> |
+ <iron-list items="[[data]]" as="item"> |
+ <template> |
+ <div> |
+ Name: <span>[[item.name]]</span> |
+ </div> |
+ </template> |
+ </iron-list> |
+ </template> |
+ |
+ |
+@group Iron Element |
+@element iron-list |
+@demo demo/index.html |
+--> |
+ |
+<dom-module id="iron-list"> |
+ <style> |
+ |
+ :host { |
+ display: block; |
+ will-change: transform; |
+ } |
+ |
+ :host(.has-scroller) { |
+ overflow: auto; |
+ } |
+ |
+ #items { |
+ position: relative; |
+ } |
+ |
+ #items > ::content > * { |
+ width: 100%; |
+ box-sizing: border-box; |
+ position: absolute; |
+ top: 0; |
+ } |
+ |
+ </style> |
+ <template> |
+ <div id="items"> |
+ <content></content> |
+ </div> |
+ </template> |
+</dom-module> |
+ |
+<script> |
+ |
+(function() { |
+ |
+ var IOS = navigator.userAgent.match(/iP(?:hone|ad;(?: U;)? CPU) OS (\d+)/); |
+ var IOS_TOUCH_SCROLLING = IOS && IOS[1] >= 8; |
+ var DEFAULT_PHYSICAL_COUNT = 20; |
+ var MAX_PHYSICAL_COUNT = 500; |
+ |
+ Polymer({ |
+ |
+ is: 'iron-list', |
+ |
+ properties: { |
+ |
+ /** |
+ * An array containing items determining how many instances of the template |
+ * to stamp and that that each template instance should bind to. |
+ */ |
+ items: { |
+ type: Array |
+ }, |
+ |
+ /** |
+ * The name of the variable to add to the binding scope for the array |
+ * element associated with a given template instance. |
+ */ |
+ as: { |
+ type: String, |
+ value: 'item' |
+ }, |
+ |
+ /** |
+ * The name of the variable to add to the binding scope with the index |
+ * for the row. If `sort` is provided, the index will reflect the |
+ * sorted order (rather than the original array order). |
+ */ |
+ indexAs: { |
+ type: String, |
+ value: 'index' |
+ } |
+ |
+ }, |
+ |
+ observers: [ |
+ '_itemsChanged(items.*)' |
+ ], |
+ |
+ behaviors: [ |
+ Polymer.Templatizer, |
+ Polymer.IronResizableBehavior |
+ ], |
+ |
+ listeners: { |
+ 'iron-resize': '_resizeHandler' |
+ }, |
+ |
+ /** |
+ * The ratio of hidden tiles that should remain in the scroll direction. |
+ * Recommended value ≈ 0.5, so it will distribute tiles evely in both directions. |
+ */ |
+ _ratio: 0.5, |
+ |
+ /** |
+ * The element that controls the scroll |
+ */ |
+ _scroller: null, |
+ |
+ /** |
+ * The padding-top value of the `scroller` element |
+ */ |
+ _scrollerPaddingTop: 0, |
+ |
+ /** |
+ * This value is the same as `scrollTop`. |
+ */ |
+ _scrollPosition: 0, |
+ |
+ /** |
+ * The number of tiles in the DOM. |
+ */ |
+ _physicalCount: DEFAULT_PHYSICAL_COUNT, |
+ |
+ /** |
+ * The k-th tile that is at the top of the scrolling list. |
+ */ |
+ _physicalStart: 0, |
+ |
+ /** |
+ * The k-th tile that is at the bottom of the scrolling list. |
+ */ |
+ _physicalEnd: 0, |
+ |
+ /** |
+ * The sum of the heights of all the tiles in the DOM. |
+ */ |
+ _physicalSize: 0, |
+ |
+ /** |
+ * The average `offsetHeight` of the tiles observed till now. |
+ */ |
+ _physicalAverage: 0, |
+ |
+ /** |
+ * The number of tiles which `offsetHeight` > 0 observed until now. |
+ */ |
+ _physicalAverageCount: 0, |
+ |
+ /** |
+ * The Y position of the item rendered in the `_physicalStart` |
+ * tile relative to the scrolling list. |
+ */ |
+ _physicalTop: 0, |
+ |
+ /** |
+ * The number of items in the list. |
+ */ |
+ _virtualCount: 0, |
+ |
+ /** |
+ * The n-th item rendered in the `_physicalStart` tile. |
+ */ |
+ _virtualStartVal: 0, |
+ |
+ /** |
+ * A map between an item key and its physical item index |
+ */ |
+ _physicalIndexForKey: {}, |
+ |
+ /** |
+ * The average scroll size |
+ */ |
+ _scrollSize: 0, |
+ |
+ /** |
+ * The size of the viewport |
+ */ |
+ _viewportSize: 0, |
+ |
+ /** |
+ * An array of DOM nodes that are currently in the tree |
+ */ |
+ _physicalItems: null, |
+ |
+ /** |
+ * An array of heights for each item in `_physicalItems` |
+ */ |
+ _physicalSizes: null, |
+ |
+ /** |
+ * A cached value for the visible index. |
+ * See `firstVisibleIndex` |
+ */ |
+ _firstVisibleIndexVal: null, |
+ |
+ /** |
+ * A Polymer collection for the items. |
+ */ |
+ _collection: null, |
+ |
+ /** |
+ * True if the current item list was rendered for the first time |
+ * after attached. |
+ */ |
+ _initRendered: false, |
+ |
+ /** |
+ * The bottom of the physical content. |
+ */ |
+ get _physicalBottom() { |
+ return this._physicalTop + this._physicalSize; |
+ }, |
+ |
+ /** |
+ * The n-th item rendered in the last physical item. |
+ */ |
+ get _virtualEnd() { |
+ return this._virtualStartVal + this._physicalCount - 1; |
+ }, |
+ |
+ /** |
+ * The lowest n-th value for an item such that it can be rendered in `_physicalStart`. |
+ */ |
+ _minVirtualStart: 0, |
+ |
+ /** |
+ * The largest n-th value for an item such that it can be rendered in `_physicalStart`. |
+ */ |
+ get _maxVirtualStart() { |
+ return this._virtualCount < this._physicalCount ? |
+ this._virtualCount : this._virtualCount - this._physicalCount; |
+ }, |
+ |
+ /** |
+ * The height of the physical content that isn't on the screen. |
+ */ |
+ get _hiddenContentSize() { |
+ return this._physicalSize - this._viewportSize; |
+ }, |
+ |
+ /** |
+ * The maximum scroll top value. |
+ */ |
+ get _maxScrollTop() { |
+ return this._scrollSize - this._viewportSize; |
+ }, |
+ |
+ /** |
+ * Sets the n-th item rendered in `_physicalStart` |
+ */ |
+ set _virtualStart(val) { |
+ // clamp the value so that _minVirtualStart <= val <= _maxVirtualStart |
+ this._virtualStartVal = Math.min(this._maxVirtualStart, Math.max(this._minVirtualStart, val)); |
+ this._physicalStart = this._virtualStartVal % this._physicalCount; |
+ this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this._physicalCount; |
+ }, |
+ |
+ /** |
+ * Gets the n-th item rendered in `_physicalStart` |
+ */ |
+ get _virtualStart() { |
+ return this._virtualStartVal; |
+ }, |
+ |
+ /** |
+ * An optimal physical size such that we will have enough physical items |
+ * to fill up the viewport and recycle when the user scrolls. |
+ * |
+ * This default value assumes that we will at least have the equivalent |
+ * to a viewport of physical items above and below the user's viewport. |
+ */ |
+ get _optPhysicalSize() { |
+ return this._viewportSize * 3; |
+ }, |
+ |
+ /** |
+ * Gets the first visible item in the viewport. |
+ * |
+ * @property firstVisibleIndex |
+ */ |
+ get firstVisibleIndex() { |
+ var physicalOffset; |
+ |
+ if (this._firstVisibleIndexVal === null) { |
+ physicalOffset = this._physicalTop; |
+ |
+ this._firstVisibleIndexVal = this._iterateItems( |
+ function(pidx, vidx) { |
+ physicalOffset += this._physicalSizes[pidx]; |
+ |
+ if (physicalOffset > this._scrollPosition) { |
+ return vidx; |
+ } |
+ }) || 0; |
+ } |
+ |
+ return this._firstVisibleIndexVal; |
+ }, |
+ |
+ /** |
+ * When the element has been attached to the DOM tree. |
+ */ |
+ attached: function() { |
+ // delegate to the parent's scroller |
+ // e.g. paper-scroll-header-panel |
+ var el = Polymer.dom(this); |
+ |
+ if (el.parentNode && el.parentNode.scroller) { |
+ this._scroller = el.parentNode.scroller; |
+ } else { |
+ this._scroller = this; |
+ this.classList.add('has-scroller'); |
+ } |
+ |
+ this.updateViewportBoundaries(); |
+ |
+ if (IOS_TOUCH_SCROLLING) { |
+ this._scroller.style.webkitOverflowScrolling = 'touch'; |
+ |
+ this._scroller.addEventListener('scroll', function() { |
+ requestAnimationFrame(this._scrollHandler.bind(this)); |
+ }.bind(this)); |
+ } else { |
+ this._scroller.addEventListener('scroll', this._scrollHandler.bind(this)); |
+ } |
+ |
+ // render the list of items if we haven't rendered them yet |
+ this._render(); |
+ }, |
+ |
+ /** |
+ * When the element has been removed from the DOM tree. |
+ */ |
+ detached: function() { |
+ this._initRendered = false; |
+ }, |
+ |
+ /** |
+ * Invoke this method if you dynamically update the viewport's |
+ * size or CSS padding. |
+ * |
+ * @method updateViewportBoundaries |
+ */ |
+ updateViewportBoundaries: function() { |
+ var scrollerStyle = window.getComputedStyle(this._scroller); |
+ this._scrollerPaddingTop = parseInt(scrollerStyle['padding-top']); |
+ this._viewportSize = this._scroller.offsetHeight; |
+ }, |
+ |
+ /** |
+ * Update the models, the position of the |
+ * items in the viewport and recycle tiles as needed. |
+ */ |
+ _refresh: function() { |
+ var SCROLL_DIRECTION_UP = -1; |
+ var SCROLL_DIRECTION_DOWN = 1; |
+ var SCROLL_DIRECTION_NONE = 0; |
+ |
+ // clamp the `scrollTop` value |
+ // IE 10|11 scrollTop may go above `_maxScrollTop` |
+ // iOS `scrollTop` may go below 0 and above `_maxScrollTop` |
+ var scrollTop = Math.max(0, Math.min(this._maxScrollTop, this._scroller.scrollTop)); |
+ |
+ var tileHeight, kth, recycledTileSet; |
+ var ratio = this._ratio; |
+ var delta = scrollTop - this._scrollPosition; |
+ var direction = SCROLL_DIRECTION_NONE; |
+ var recycledTiles = 0; |
+ var hiddenContentSize = this._hiddenContentSize; |
+ var currentRatio = ratio; |
+ var movingUp = []; |
+ |
+ // track the last `scrollTop` |
+ this._scrollPosition = scrollTop; |
+ |
+ // clear cached visible index |
+ this._firstVisibleIndexVal = null; |
+ |
+ // random access |
+ if (Math.abs(delta) > this._physicalSize) { |
+ this._physicalTop += delta; |
+ direction = SCROLL_DIRECTION_NONE; |
+ recycledTiles = Math.round(delta / this._physicalAverage); |
+ } |
+ // scroll up |
+ else if (delta < 0) { |
+ var topSpace = scrollTop - this._physicalTop; |
+ var virtualStart = this._virtualStart; |
+ |
+ direction = SCROLL_DIRECTION_UP; |
+ recycledTileSet = []; |
+ |
+ kth = this._physicalEnd; |
+ currentRatio = topSpace / hiddenContentSize; |
+ |
+ // move tiles from bottom to top |
+ while ( |
+ // approximate `currentRatio` to `ratio` |
+ currentRatio < ratio && |
+ // recycle less physical items than the total |
+ recycledTiles < this._physicalCount && |
+ // ensure that these recycled tiles are needed |
+ virtualStart - recycledTiles > 0 |
+ ) { |
+ |
+ tileHeight = this._physicalSizes[kth] || this._physicalAverage; |
+ currentRatio += tileHeight / hiddenContentSize; |
+ |
+ recycledTileSet.push(kth); |
+ recycledTiles++; |
+ kth = (kth === 0) ? this._physicalCount - 1 : kth - 1; |
+ } |
+ |
+ movingUp = recycledTileSet; |
+ recycledTiles = -recycledTiles; |
+ |
+ } |
+ // scroll down |
+ else if (delta > 0) { |
+ var bottomSpace = this._physicalBottom - (scrollTop + this._viewportSize); |
+ var virtualEnd = this._virtualEnd; |
+ var lastVirtualItemIndex = this._virtualCount-1; |
+ |
+ direction = SCROLL_DIRECTION_DOWN; |
+ recycledTileSet = []; |
+ |
+ kth = this._physicalStart; |
+ currentRatio = bottomSpace / hiddenContentSize; |
+ |
+ // move tiles from top to bottom |
+ while ( |
+ // approximate `currentRatio` to `ratio` |
+ currentRatio < ratio && |
+ // recycle less physical items than the total |
+ recycledTiles < this._physicalCount && |
+ // ensure that these recycled tiles are needed |
+ virtualEnd + recycledTiles < lastVirtualItemIndex |
+ ) { |
+ |
+ tileHeight = this._physicalSizes[kth] || this._physicalAverage; |
+ currentRatio += tileHeight / hiddenContentSize; |
+ |
+ this._physicalTop += tileHeight; |
+ recycledTileSet.push(kth); |
+ recycledTiles++; |
+ kth = (kth + 1) % this._physicalCount; |
+ } |
+ } |
+ |
+ if (recycledTiles !== 0) { |
+ this._virtualStart = this._virtualStart + recycledTiles; |
+ this._update(recycledTileSet, movingUp); |
+ } |
+ }, |
+ |
+ /** |
+ * Update the list of items, starting from the `_virtualStartVal` item. |
+ */ |
+ _update: function(itemSet, movingUp) { |
+ // update models |
+ this._assignModels(itemSet); |
+ |
+ // measure heights |
+ // TODO(blasten) pass `recycledTileSet` |
+ this._updateMetrics(); |
+ |
+ // adjust offset after measuring |
+ if (movingUp) { |
+ while (movingUp.length) { |
+ this._physicalTop -= this._physicalSizes[movingUp.pop()]; |
+ } |
+ } |
+ |
+ // update the position of the items |
+ this._positionItems(); |
+ |
+ // set the scroller size |
+ this._updateScrollerSize(); |
+ |
+ // increase the pool of physical items if needed |
+ if (itemSet = this._increasePoolIfNeeded()) { |
+ // set models to the new items |
+ this.async(this._update.bind(this, itemSet)); |
+ } |
+ }, |
+ |
+ /** |
+ * Creates a pool of DOM elements and attaches them to the local dom. |
+ */ |
+ _createPool: function(size) { |
+ var physicalItems = new Array(size); |
+ |
+ this._ensureTemplatized(); |
+ |
+ for (var i = 0; i < size; i++) { |
+ var inst = this.stamp(null); |
+ |
+ // First element child is item; Safari doesn't support children[0] |
+ // on a doc fragment |
+ physicalItems[i] = inst.root.querySelector('*'); |
+ Polymer.dom(this).appendChild(inst.root); |
+ } |
+ |
+ return physicalItems; |
+ }, |
+ |
+ /** |
+ * Increases the pool size. That is, the physical items in the DOM. |
+ * This function will allocate additional physical items |
+ * (limited by `MAX_PHYSICAL_COUNT`) if the content size is shorter than |
+ * `_optPhysicalSize` |
+ * |
+ * @return Array |
+ */ |
+ _increasePoolIfNeeded: function() { |
+ if (this._physicalSize > this._optPhysicalSize) { |
+ return null; |
+ } |
+ |
+ // the estimated number of physical items that we will need to reach |
+ // the cap established by `_optPhysicalSize`. |
+ var missingItems = Math.round( |
+ (this._optPhysicalSize - this._physicalSize) * 1.2 / this._physicalAverage |
+ ); |
+ |
+ // limit the size |
+ var nextPhysicalCount = Math.min( |
+ this._physicalCount + missingItems, |
+ this._virtualCount, |
+ MAX_PHYSICAL_COUNT |
+ ); |
+ |
+ var prevPhysicalCount = this._physicalCount; |
+ var delta = nextPhysicalCount - prevPhysicalCount; |
+ |
+ if (delta <= 0) { |
+ return null; |
+ } |
+ |
+ var newPhysicalItems = this._createPool(delta); |
+ var emptyArray = new Array(delta); |
+ |
+ [].push.apply(this._physicalItems, newPhysicalItems); |
+ [].push.apply(this._physicalSizes, emptyArray); |
+ |
+ this._physicalCount = prevPhysicalCount + delta; |
+ |
+ // fill the array with the new item pos |
+ while (delta > 0) { |
+ emptyArray[--delta] = prevPhysicalCount + delta; |
+ } |
+ |
+ return emptyArray; |
+ }, |
+ |
+ /** |
+ * Render a new list of items. This method does exactly the same as `update`, |
+ * but it also ensures that only one `update` cycle is created. |
+ */ |
+ _render: function() { |
+ if (this.isAttached && !this._initRendered && this.items) { |
+ // polymer/issues/2039 |
+ if (window.CustomElements) { |
+ window.CustomElements.takeRecords(); |
+ } |
+ this._update(); |
+ this._initRendered = true; |
+ } |
+ }, |
+ |
+ /** |
+ * Templetizes the user template. |
+ */ |
+ _ensureTemplatized: function() { |
+ if (!this.ctor) { |
+ // Template instance props that should be excluded from forwarding |
+ this._instanceProps = { |
+ __key__: true |
+ }; |
+ this._instanceProps[this.as] = true; |
+ this._instanceProps[this.indexAs] = true; |
+ this._userTemplate = Polymer.dom(this).querySelector('template'); |
+ if (this._userTemplate) { |
+ this.templatize(this._userTemplate); |
+ } else { |
+ console.warn('iron-list requires a template to be provided in light-dom'); |
+ } |
+ } |
+ }, |
+ |
+ /** |
+ * Implements extension point from Templatizer mixin. |
+ */ |
+ _getStampedChildren: function() { |
+ return this._physicalItems; |
+ }, |
+ |
+ /** |
+ * Implements extension point from Templatizer |
+ * Called as a side effect of a template instance path change, responsible |
+ * for notifying items.<key-for-instance>.<path> change up to host. |
+ */ |
+ _forwardInstancePath: function(inst, path, value) { |
+ if (path.indexOf(this.as + '.') === 0) { |
+ this.notifyPath('items.' + inst.__key__ + '.' + |
+ path.slice(this.as.length + 1), value); |
+ } |
+ }, |
+ |
+ /** |
+ * Implements extension point from Templatizer mixin |
+ * Called as side-effect of a host property change, responsible for |
+ * notifying parent path change on each row. |
+ */ |
+ _forwardParentProp: function(prop, value) { |
+ if (this._physicalItems) { |
+ this._physicalItems.forEach(function(item) { |
+ item._templateInstance[prop] = value; |
+ }, this); |
+ } |
+ }, |
+ |
+ /** |
+ * Implements extension point from Templatizer |
+ * Called as side-effect of a host path change, responsible for |
+ * notifying parent.<path> path change on each row. |
+ */ |
+ _forwardParentPath: function(path, value) { |
+ if (this._physicalItems) { |
+ this._physicalItems.forEach(function(item) { |
+ item._templateInstance.notifyPath(path, value, true); |
+ }, this); |
+ } |
+ }, |
+ |
+ /** |
+ * Called as a side effect of a host items.<key>.<path> path change, |
+ * responsible for notifying item.<path> changes to row for key. |
+ */ |
+ _forwardItemPath: function(path, value) { |
+ if (this._physicalIndexForKey) { |
+ var dot = path.indexOf('.'); |
+ var key = path.substring(0, dot < 0 ? path.length : dot); |
+ var idx = this._physicalIndexForKey[key]; |
+ var row = this._physicalItems[idx]; |
+ if (row) { |
+ var inst = row._templateInstance; |
+ if (dot >= 0) { |
+ path = this.as + '.' + path.substring(dot+1); |
+ inst.notifyPath(path, value, true); |
+ } else { |
+ inst[this.as] = value; |
+ } |
+ } |
+ } |
+ }, |
+ |
+ _itemsChanged: function(change) { |
+ if (change.path === 'items') { |
+ // render the new set |
+ this._initRendered = false; |
+ |
+ // update the whole set |
+ this._virtualStartVal = 0; |
+ this._physicalTop = 0; |
+ this._virtualCount = this.items ? this.items.length : 0; |
+ this._collection = this.items ? Polymer.Collection.get(this.items) : null; |
+ |
+ // scroll to the top |
+ this._resetScrollPosition(0); |
+ |
+ // create the initial physical items |
+ if (!this._physicalItems) { |
+ this._physicalItems = this._createPool(this._physicalCount); |
+ this._physicalSizes = new Array(this._physicalCount); |
+ } |
+ |
+ this.debounce('refresh', this._render); |
+ |
+ } else if (change.path === 'items.splices') { |
+ // render the new set |
+ this._initRendered = false; |
+ |
+ this._adjustVirtualIndex(change.value.indexSplices); |
+ this._virtualCount = this.items ? this.items.length : 0; |
+ |
+ this.debounce('refresh', this._render); |
+ |
+ } else { |
+ // update a single item |
+ this._forwardItemPath(change.path.split('.').slice(1).join('.'), change.value); |
+ } |
+ }, |
+ |
+ _adjustVirtualIndex: function(splices) { |
+ for (var i = 0; i < splices.length; i++) { |
+ var splice = splices[i]; |
+ var idx = splice.index; |
+ // We only need to care about changes happening above the current position |
+ if (idx >= this._virtualStartVal) { |
+ break; |
+ } |
+ |
+ this._virtualStart = this._virtualStart + |
+ Math.max(splice.addedCount - splice.removed.length, idx - this._virtualStartVal); |
+ } |
+ }, |
+ |
+ _scrollHandler: function() { |
+ this._refresh(); |
+ }, |
+ |
+ _iterateItems: function(fn, itemSet) { |
+ var pidx, vidx, rtn, i; |
+ |
+ if (arguments.length === 2 && itemSet) { |
+ for (i = 0; i < itemSet.length; i++) { |
+ pidx = itemSet[i]; |
+ if (pidx >= this._physicalStart) { |
+ vidx = this._virtualStartVal + (pidx - this._physicalStart); |
+ } else { |
+ vidx = this._virtualStartVal + (this._physicalCount - this._physicalStart) + pidx; |
+ } |
+ if ((rtn = fn.call(this, pidx, vidx)) != null) { |
+ return rtn; |
+ } |
+ } |
+ } else { |
+ pidx = this._physicalStart; |
+ vidx = this._virtualStartVal; |
+ |
+ for (; pidx < this._physicalCount; pidx++, vidx++) { |
+ if ((rtn = fn.call(this, pidx, vidx)) != null) { |
+ return rtn; |
+ } |
+ } |
+ |
+ pidx = 0; |
+ |
+ for (; pidx < this._physicalStart; pidx++, vidx++) { |
+ if ((rtn = fn.call(this, pidx, vidx)) != null) { |
+ return rtn; |
+ } |
+ } |
+ } |
+ }, |
+ |
+ _assignModels: function(itemSet) { |
+ this._iterateItems(function(pidx, vidx) { |
+ var el = this._physicalItems[pidx]; |
+ var inst = el._templateInstance; |
+ var item = this.items && this.items[vidx]; |
+ |
+ if (item) { |
+ inst[this.as] = item; |
+ inst.__key__ = this._collection.getKey(item); |
+ inst[this.indexAs] = vidx; |
+ el.removeAttribute('hidden'); |
+ this._physicalIndexForKey[inst.__key__] = pidx; |
+ } else { |
+ inst.__key__ = null; |
+ el.setAttribute('hidden', ''); |
+ } |
+ |
+ }, itemSet); |
+ }, |
+ |
+ _updateMetrics: function() { |
+ var total = 0; |
+ var prevAvgCount = this._physicalAverageCount; |
+ var prevPhysicalAvg = this._physicalAverage; |
+ |
+ for (var i = 0; i < this._physicalCount; i++) { |
+ this._physicalSizes[i] = this._physicalItems[i].offsetHeight; |
+ total += this._physicalSizes[i]; |
+ this._physicalAverageCount += this._physicalSizes[i] ? 1 : 0; |
+ } |
+ |
+ this._physicalSize = total; |
+ this._viewportSize = this._scroller.offsetHeight; |
+ |
+ if (this._physicalAverageCount !== prevAvgCount) { |
+ this._physicalAverage = Math.round( |
+ ((prevPhysicalAvg * prevAvgCount) + total) / |
+ this._physicalAverageCount); |
+ } |
+ }, |
+ |
+ _positionItems: function(itemSet) { |
+ this._adjustScrollPosition(); |
+ |
+ var y = this._physicalTop; |
+ |
+ this._iterateItems(function(pidx) { |
+ |
+ this.transform('translate3d(0, ' + y + 'px, 0)', this._physicalItems[pidx]); |
+ y += this._physicalSizes[pidx]; |
+ |
+ }, itemSet); |
+ }, |
+ |
+ _adjustScrollPosition: function() { |
+ var deltaHeight = this._virtualStartVal === 0 ? this._physicalTop : |
+ Math.min(this._scrollPosition + this._physicalTop, 0); |
+ |
+ if (deltaHeight) { |
+ this._physicalTop = this._physicalTop - deltaHeight; |
+ |
+ // juking scroll position during interial scrolling on iOS is no bueno |
+ if (!IOS_TOUCH_SCROLLING) { |
+ this._resetScrollPosition(this._scroller.scrollTop - deltaHeight); |
+ } |
+ } |
+ }, |
+ |
+ _resetScrollPosition: function(pos) { |
+ if (this._scroller) { |
+ this._scroller.scrollTop = pos; |
+ this._scrollPosition = this._scroller.scrollTop; |
+ } |
+ }, |
+ |
+ _updateScrollerSize: function() { |
+ this._scrollSize = (this._physicalBottom + |
+ Math.max(this._virtualCount - this._physicalCount - this._virtualStartVal, 0) * this._physicalAverage); |
+ |
+ this.$.items.style.height = this._scrollSize + 'px'; |
+ }, |
+ |
+ /** |
+ * Scroll to a specific item in the virtual list regardless |
+ * of the physical items in the DOM tree. |
+ * |
+ * @method scrollToIndex |
+ */ |
+ scrollToIndex: function(idx) { |
+ if (typeof idx !== 'number') { |
+ return; |
+ } |
+ |
+ var firstVisible = this.firstVisibleIndex; |
+ |
+ idx = Math.min(Math.max(idx, 0), this._virtualCount-1); |
+ |
+ // start at the previous virtual item |
+ // so we have a item above the first visible item |
+ this._virtualStart = idx - 1; |
+ |
+ // assign new models |
+ this._assignModels(); |
+ |
+ // measure the new sizes |
+ this._updateMetrics(); |
+ |
+ // estimate new physical offset |
+ this._physicalTop = this._virtualStart * this._physicalAverage; |
+ |
+ var currentTopItem = this._physicalStart; |
+ var currentVirtualItem = this._virtualStart; |
+ var targetOffsetTop = 0; |
+ var hiddenContentSize = this._hiddenContentSize; |
+ |
+ // scroll to the item as much as we can |
+ while (currentVirtualItem !== idx && targetOffsetTop < hiddenContentSize) { |
+ targetOffsetTop = targetOffsetTop + this._physicalSizes[currentTopItem]; |
+ currentTopItem = (currentTopItem + 1) % this._physicalCount; |
+ currentVirtualItem++; |
+ } |
+ |
+ // update the scroller size |
+ this._updateScrollerSize(); |
+ |
+ // update the position of the items |
+ this._positionItems(); |
+ |
+ // set the new scroll position |
+ this._resetScrollPosition(this._physicalTop + targetOffsetTop + 1); |
+ |
+ // clear cached visible index |
+ this._firstVisibleIndexVal = null; |
+ }, |
+ |
+ _resetAverage: function() { |
+ this._physicalAverage = 0; |
+ this._physicalAverageCount = 0; |
+ }, |
+ |
+ _resizeHandler: function() { |
+ if (this._physicalItems) { |
+ this.debounce('resize', function() { |
+ this._resetAverage(); |
+ this.updateViewportBoundaries(); |
+ this.scrollToIndex(this.firstVisibleIndex); |
+ }); |
+ } |
+ } |
+ }); |
+ |
+})(); |
+ |
+</script> |