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

Unified Diff: third_party/polymer/v1_0/components-chromium/iron-list/iron-list-extracted.js

Issue 1351623008: MD Settings: Languages model for language pages (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@SingletonPrefs
Patch Set: Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: third_party/polymer/v1_0/components-chromium/iron-list/iron-list-extracted.js
diff --git a/third_party/polymer/v1_0/components-chromium/iron-list/iron-list-extracted.js b/third_party/polymer/v1_0/components-chromium/iron-list/iron-list-extracted.js
new file mode 100644
index 0000000000000000000000000000000000000000..f3e261c78197d1a49a87964b83dcf204e8f830fb
--- /dev/null
+++ b/third_party/polymer/v1_0/components-chromium/iron-list/iron-list-extracted.js
@@ -0,0 +1,1122 @@
+
+
+(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'
+ },
+
+ /**
+ * The name of the variable to add to the binding scope to indicate
+ * if the row is selected.
+ */
+ selectedAs: {
+ type: String,
+ value: 'selected'
+ },
+
+ /**
+ * When true, tapping a row will select the item, placing its data model
+ * in the set of selected items retrievable via the selection property.
+ *
+ * Note that tapping focusable elements within the list item will not
+ * result in selection, since they are presumed to have their * own action.
+ */
+ selectionEnabled: {
+ type: Boolean,
+ value: false
+ },
+
+ /**
+ * When `multiSelection` is false, this is the currently selected item, or `null`
+ * if no item is selected.
+ */
+ selectedItem: {
+ type: Object,
+ notify: true
+ },
+
+ /**
+ * When `multiSelection` is true, this is an array that contains the selected items.
+ */
+ selectedItems: {
+ type: Object,
+ notify: true
+ },
+
+ /**
+ * When `true`, multiple items may be selected at once (in this case,
+ * `selected` is an array of currently selected items). When `false`,
+ * only one item may be selected at a time.
+ */
+ multiSelection: {
+ type: Boolean,
+ value: false
+ }
+ },
+
+ observers: [
+ '_itemsChanged(items.*)',
+ '_selectionEnabledChanged(selectionEnabled)',
+ '_multiSelectionChanged(multiSelection)'
+ ],
+
+ behaviors: [
+ Polymer.Templatizer,
+ Polymer.IronResizableBehavior
+ ],
+
+ listeners: {
+ 'iron-resize': '_resizeHandler'
+ },
+
+ /**
+ * The ratio of hidden tiles that should remain in the scroll direction.
+ * Recommended value ~0.5, so it will distribute tiles evely in both directions.
+ */
+ _ratio: 0.5,
+
+ /**
+ * The element that controls the scroll
+ */
+ _scroller: null,
+
+ /**
+ * The padding-top value of the `scroller` element
+ */
+ _scrollerPaddingTop: 0,
+
+ /**
+ * This value is the same as `scrollTop`.
+ */
+ _scrollPosition: 0,
+
+ /**
+ * The number of tiles in the DOM.
+ */
+ _physicalCount: 0,
+
+ /**
+ * The k-th tile that is at the top of the scrolling list.
+ */
+ _physicalStart: 0,
+
+ /**
+ * The k-th tile that is at the bottom of the scrolling list.
+ */
+ _physicalEnd: 0,
+
+ /**
+ * The sum of the heights of all the tiles in the DOM.
+ */
+ _physicalSize: 0,
+
+ /**
+ * The average `offsetHeight` of the tiles observed till now.
+ */
+ _physicalAverage: 0,
+
+ /**
+ * The number of tiles which `offsetHeight` > 0 observed until now.
+ */
+ _physicalAverageCount: 0,
+
+ /**
+ * The Y position of the item rendered in the `_physicalStart`
+ * tile relative to the scrolling list.
+ */
+ _physicalTop: 0,
+
+ /**
+ * The number of items in the list.
+ */
+ _virtualCount: 0,
+
+ /**
+ * The n-th item rendered in the `_physicalStart` tile.
+ */
+ _virtualStartVal: 0,
+
+ /**
+ * A map between an item key and its physical item index
+ */
+ _physicalIndexForKey: null,
+
+ /**
+ * The estimated scroll height based on `_physicalAverage`
+ */
+ _estScrollHeight: 0,
+
+ /**
+ * The scroll height of the dom node
+ */
+ _scrollHeight: 0,
+
+ /**
+ * The 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.
+ */
+ _itemsRendered: 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._estScrollHeight - this._viewportSize;
+ },
+
+ /**
+ * Sets the n-th item rendered in `_physicalStart`
+ */
+ set _virtualStart(val) {
+ // clamp the value so that _minVirtualStart <= val <= _maxVirtualStart
+ this._virtualStartVal = Math.min(this._maxVirtualStart, Math.max(this._minVirtualStart, val));
+ this._physicalStart = this._virtualStartVal % this._physicalCount;
+ this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this._physicalCount;
+ },
+
+ /**
+ * Gets the n-th item rendered in `_physicalStart`
+ */
+ get _virtualStart() {
+ return this._virtualStartVal;
+ },
+
+ /**
+ * An optimal physical size such that we will have enough physical items
+ * to fill up the viewport and recycle when the user scrolls.
+ *
+ * This default value assumes that we will at least have the equivalent
+ * to a viewport of physical items above and below the user's viewport.
+ */
+ get _optPhysicalSize() {
+ return this._viewportSize * 3;
+ },
+
+ /**
+ * True if the current list is visible.
+ */
+ get _isVisible() {
+ return this._scroller && Boolean(this._scroller.offsetWidth || this._scroller.offsetHeight);
+ },
+
+ /**
+ * 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;
+ },
+
+ ready: function() {
+ if (IOS_TOUCH_SCROLLING) {
+ this._scrollListener = function() {
+ requestAnimationFrame(this._scrollHandler.bind(this));
+ }.bind(this);
+ } else {
+ this._scrollListener = this._scrollHandler.bind(this);
+ }
+ },
+
+ /**
+ * When the element has been attached to the DOM tree.
+ */
+ attached: function() {
+ // delegate to the parent's scroller
+ // e.g. paper-scroll-header-panel
+ var el = Polymer.dom(this);
+
+ if (el.parentNode && el.parentNode.scroller) {
+ this._scroller = el.parentNode.scroller;
+ } else {
+ this._scroller = this;
+ this.classList.add('has-scroller');
+ }
+
+ if (IOS_TOUCH_SCROLLING) {
+ this._scroller.style.webkitOverflowScrolling = 'touch';
+ }
+
+ this._scroller.addEventListener('scroll', this._scrollListener);
+
+ this.updateViewportBoundaries();
+ this._render();
+ },
+
+ /**
+ * When the element has been removed from the DOM tree.
+ */
+ detached: function() {
+ this._itemsRendered = false;
+ if (this._scroller) {
+ this._scroller.removeEventListener('scroll', this._scrollListener);
+ }
+ },
+
+ /**
+ * Invoke this method if you dynamically update the viewport's
+ * size or CSS padding.
+ *
+ * @method updateViewportBoundaries
+ */
+ updateViewportBoundaries: function() {
+ var scrollerStyle = window.getComputedStyle(this._scroller);
+ this._scrollerPaddingTop = parseInt(scrollerStyle['padding-top']);
+ 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 || this._physicalAverage === 0) {
+ 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() {
+ var requiresUpdate = this._virtualCount > 0 || this._physicalCount > 0;
+
+ if (this.isAttached && !this._itemsRendered && this._isVisible && requiresUpdate) {
+ this._update();
+ this._itemsRendered = true;
+ }
+ },
+
+ /**
+ * Templetizes the user template.
+ */
+ _ensureTemplatized: function() {
+ if (!this.ctor) {
+ // Template instance props that should be excluded from forwarding
+ var props = {};
+
+ props.__key__ = true;
+ props[this.as] = true;
+ props[this.indexAs] = true;
+ props[this.selectedAs] = true;
+
+ this._instanceProps = props;
+ this._userTemplate = Polymer.dom(this).querySelector('template');
+
+ if (this._userTemplate) {
+ this.templatize(this._userTemplate);
+ } else {
+ console.warn('iron-list requires a template to be provided in light-dom');
+ }
+ }
+ },
+
+ /**
+ * Implements extension point from Templatizer mixin.
+ */
+ _getStampedChildren: function() {
+ return this._physicalItems;
+ },
+
+ /**
+ * Implements extension point from Templatizer
+ * Called as a side effect of a template instance path change, responsible
+ * for notifying items.<key-for-instance>.<path> change up to host.
+ */
+ _forwardInstancePath: function(inst, path, value) {
+ if (path.indexOf(this.as + '.') === 0) {
+ this.notifyPath('items.' + inst.__key__ + '.' +
+ path.slice(this.as.length + 1), value);
+ }
+ },
+
+ /**
+ * Implements extension point from Templatizer mixin
+ * Called as side-effect of a host property change, responsible for
+ * notifying parent path change on each row.
+ */
+ _forwardParentProp: function(prop, value) {
+ if (this._physicalItems) {
+ this._physicalItems.forEach(function(item) {
+ item._templateInstance[prop] = value;
+ }, this);
+ }
+ },
+
+ /**
+ * Implements extension point from Templatizer
+ * Called as side-effect of a host path change, responsible for
+ * notifying parent.<path> path change on each row.
+ */
+ _forwardParentPath: function(path, value) {
+ if (this._physicalItems) {
+ this._physicalItems.forEach(function(item) {
+ item._templateInstance.notifyPath(path, value, true);
+ }, this);
+ }
+ },
+
+ /**
+ * Called as a side effect of a host items.<key>.<path> path change,
+ * responsible for notifying item.<path> changes to row for key.
+ */
+ _forwardItemPath: function(path, value) {
+ if (this._physicalIndexForKey) {
+ var dot = path.indexOf('.');
+ var key = path.substring(0, dot < 0 ? path.length : dot);
+ var idx = this._physicalIndexForKey[key];
+ var row = this._physicalItems[idx];
+ if (row) {
+ var inst = row._templateInstance;
+ if (dot >= 0) {
+ path = this.as + '.' + path.substring(dot+1);
+ inst.notifyPath(path, value, true);
+ } else {
+ inst[this.as] = value;
+ }
+ }
+ }
+ },
+
+ /**
+ * Called when the items have changed. That is, ressignments
+ * to `items`, splices or updates to a single item.
+ */
+ _itemsChanged: function(change) {
+ if (change.path === 'items') {
+ // render the new set
+ this._itemsRendered = false;
+
+ // update the whole set
+ this._virtualStartVal = 0;
+ this._physicalTop = 0;
+ this._virtualCount = this.items ? this.items.length : 0;
+ this._collection = this.items ? Polymer.Collection.get(this.items) : null;
+ this._physicalIndexForKey = {};
+
+ // scroll to the top
+ this._resetScrollPosition(0);
+
+ // create the initial physical items
+ if (!this._physicalItems) {
+ this._physicalCount = Math.max(1, Math.min(DEFAULT_PHYSICAL_COUNT, this._virtualCount));
+ this._physicalItems = this._createPool(this._physicalCount);
+ this._physicalSizes = new Array(this._physicalCount);
+ }
+
+ this.debounce('refresh', this._render);
+
+ } else if (change.path === 'items.splices') {
+ // render the new set
+ this._itemsRendered = false;
+
+ this._adjustVirtualIndex(change.value.indexSplices);
+ this._virtualCount = this.items ? this.items.length : 0;
+
+ this.debounce('refresh', this._render);
+
+ } else {
+ // update a single item
+ this._forwardItemPath(change.path.split('.').slice(1).join('.'), change.value);
+ }
+ },
+
+ _adjustVirtualIndex: function(splices) {
+ var i, splice, idx;
+
+ for (i = 0; i < splices.length; i++) {
+ splice = splices[i];
+
+ // deselect removed items
+ splice.removed.forEach(this.$.selector.deselect, this.$.selector);
+
+ idx = splice.index;
+ // We only need to care about changes happening above the current position
+ if (idx >= this._virtualStartVal) {
+ break;
+ }
+
+ this._virtualStart = this._virtualStart +
+ Math.max(splice.addedCount - splice.removed.length, idx - this._virtualStartVal);
+ }
+ },
+
+ _scrollHandler: function() {
+ this._refresh();
+ },
+
+ /**
+ * Executes a provided function per every physical index in `itemSet`
+ * `itemSet` default value is equivalent to the entire set of physical indexes.
+ */
+ _iterateItems: function(fn, itemSet) {
+ var pidx, vidx, rtn, i;
+
+ if (arguments.length === 2 && itemSet) {
+ for (i = 0; i < itemSet.length; i++) {
+ pidx = itemSet[i];
+ if (pidx >= this._physicalStart) {
+ vidx = this._virtualStartVal + (pidx - this._physicalStart);
+ } else {
+ vidx = this._virtualStartVal + (this._physicalCount - this._physicalStart) + pidx;
+ }
+ if ((rtn = fn.call(this, pidx, vidx)) != null) {
+ return rtn;
+ }
+ }
+ } else {
+ pidx = this._physicalStart;
+ vidx = this._virtualStartVal;
+
+ for (; pidx < this._physicalCount; pidx++, vidx++) {
+ if ((rtn = fn.call(this, pidx, vidx)) != null) {
+ return rtn;
+ }
+ }
+
+ pidx = 0;
+
+ for (; pidx < this._physicalStart; pidx++, vidx++) {
+ if ((rtn = fn.call(this, pidx, vidx)) != null) {
+ return rtn;
+ }
+ }
+ }
+ },
+
+ /**
+ * Assigns the data models to a given set of items.
+ */
+ _assignModels: function(itemSet) {
+ this._iterateItems(function(pidx, vidx) {
+ var el = this._physicalItems[pidx];
+ var inst = el._templateInstance;
+ var item = this.items && this.items[vidx];
+
+ if (item) {
+ inst[this.as] = item;
+ inst.__key__ = this._collection.getKey(item);
+ inst[this.selectedAs] = this.$.selector.isSelected(item);
+ inst[this.indexAs] = vidx;
+ el.removeAttribute('hidden');
+ this._physicalIndexForKey[inst.__key__] = pidx;
+ } else {
+ inst.__key__ = null;
+ el.setAttribute('hidden', '');
+ }
+
+ }, itemSet);
+ },
+
+ /**
+ * Updates the height for a given set of items.
+ */
+ _updateMetrics: function() {
+ var total = 0;
+ var prevAvgCount = this._physicalAverageCount;
+ var prevPhysicalAvg = this._physicalAverage;
+
+ // Make sure we distributed all the physical items
+ // so we can measure them
+ Polymer.dom.flush();
+
+ 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);
+ }
+ },
+
+ /**
+ * Updates the position of the physical items.
+ */
+ _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);
+ },
+
+ /**
+ * Adjusts the scroll position when it was overestimated.
+ */
+ _adjustScrollPosition: function() {
+ var deltaHeight = this._virtualStartVal === 0 ? this._physicalTop :
+ Math.min(this._scrollPosition + this._physicalTop, 0);
+
+ if (deltaHeight) {
+ this._physicalTop = this._physicalTop - deltaHeight;
+
+ // juking scroll position during interial scrolling on iOS is no bueno
+ if (!IOS_TOUCH_SCROLLING) {
+ this._resetScrollPosition(this._scroller.scrollTop - deltaHeight);
+ }
+ }
+ },
+
+ /**
+ * Sets the position of the scroll.
+ */
+ _resetScrollPosition: function(pos) {
+ if (this._scroller) {
+ this._scroller.scrollTop = pos;
+ this._scrollPosition = this._scroller.scrollTop;
+ }
+ },
+
+ /**
+ * Sets the scroll height, that's the height of the content,
+ */
+ _updateScrollerSize: function(forceUpdate) {
+ this._estScrollHeight = (this._physicalBottom +
+ Math.max(this._virtualCount - this._physicalCount - this._virtualStartVal, 0) * this._physicalAverage);
+
+ forceUpdate = forceUpdate || this._scrollHeight === 0;
+ forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight - this._physicalSize;
+
+ // amortize height adjustment, so it won't trigger repaints very often
+ if (forceUpdate || Math.abs(this._estScrollHeight - this._scrollHeight) >= this._optPhysicalSize) {
+ this.$.items.style.height = this._estScrollHeight + 'px';
+ this._scrollHeight = this._estScrollHeight;
+ }
+ },
+
+ /**
+ * Scroll to a specific item in the virtual list regardless
+ * of the physical items in the DOM tree.
+ *
+ * @method scrollToIndex
+ * @param {number} idx The index of the item
+ */
+ scrollToIndex: function(idx) {
+ if (typeof idx !== 'number') {
+ return;
+ }
+
+ var itemSet;
+ var firstVisible = this.firstVisibleIndex;
+
+ idx = Math.min(Math.max(idx, 0), this._virtualCount-1);
+
+ // start at the previous virtual item
+ // so we have a item above the first visible item
+ this._virtualStart = idx - 1;
+
+ // assign new models
+ this._assignModels();
+
+ // measure the new sizes
+ this._updateMetrics();
+
+ // estimate new physical offset
+ this._physicalTop = this._virtualStart * this._physicalAverage;
+
+ var currentTopItem = this._physicalStart;
+ var currentVirtualItem = this._virtualStart;
+ var targetOffsetTop = 0;
+ var hiddenContentSize = this._hiddenContentSize;
+
+ // scroll to the item as much as we can
+ while (currentVirtualItem !== idx && targetOffsetTop < hiddenContentSize) {
+ targetOffsetTop = targetOffsetTop + this._physicalSizes[currentTopItem];
+ currentTopItem = (currentTopItem + 1) % this._physicalCount;
+ currentVirtualItem++;
+ }
+
+ // update the scroller size
+ this._updateScrollerSize(true);
+
+ // update the position of the items
+ this._positionItems();
+
+ // set the new scroll position
+ this._resetScrollPosition(this._physicalTop + targetOffsetTop + 1);
+
+ // increase the pool of physical items if needed
+ if (itemSet = this._increasePoolIfNeeded()) {
+ // set models to the new items
+ this.async(this._update.bind(this, itemSet));
+ }
+
+ // clear cached visible index
+ this._firstVisibleIndexVal = null;
+ },
+
+ /**
+ * Reset the physical average and the average count.
+ */
+ _resetAverage: function() {
+ this._physicalAverage = 0;
+ this._physicalAverageCount = 0;
+ },
+
+ /**
+ * A handler for the `resize` event triggered by `IronResizableBehavior`
+ * when the element is resized.
+ */
+ _resizeHandler: function() {
+ this.debounce('resize', function() {
+ this._render();
+ if (this._itemsRendered && this._physicalItems && this._isVisible) {
+ this._resetAverage();
+ this.updateViewportBoundaries();
+ this.scrollToIndex(this.firstVisibleIndex);
+ }
+ });
+ },
+
+ _getModelFromItem: function(item) {
+ var key = this._collection.getKey(item);
+ var pidx = this._physicalIndexForKey[key];
+
+ if (pidx !== undefined) {
+ return this._physicalItems[pidx]._templateInstance;
+ }
+ return null;
+ },
+
+ /**
+ * Select the list item at the given index.
+ *
+ * @method selectItem
+ * @param {(Object|number)} item the item object or its index
+ */
+ selectItem: function(item) {
+ if (typeof item === 'number') {
+ item = this.items[item];
+ if (!item) {
+ throw new RangeError('<item> not found');
+ }
+ } else {
+ if (this._collection.getKey(item) === undefined) {
+ throw new TypeError('<item> should be a valid item');
+ }
+ }
+
+ var model = this._getModelFromItem(item);
+
+ if (!this.multiSelection && this.selectedItem) {
+ this.deselectItem(this.selectedItem);
+ }
+ if (model) {
+ model[this.selectedAs] = true;
+ }
+ this.$.selector.select(item);
+ },
+
+ /**
+ * Deselects the given item list if it is already selected.
+ *
+ * @method deselect
+ * @param {(Object|number)} item the item object or its index
+ */
+ deselectItem: function(item) {
+ if (typeof item === 'number') {
+ item = this.items[item];
+ if (!item) {
+ throw new RangeError('<item> not found');
+ }
+ } else {
+ if (this._collection.getKey(item) === undefined) {
+ throw new TypeError('<item> should be a valid item');
+ }
+ }
+
+ var model = this._getModelFromItem(item);
+
+ if (model) {
+ model[this.selectedAs] = false;
+ }
+ this.$.selector.deselect(item);
+ },
+
+ /**
+ * Select or deselect a given item depending on whether the item
+ * has already been selected.
+ *
+ * @method toggleSelectionForItem
+ * @param {(Object|number)} item the item object or its index
+ */
+ toggleSelectionForItem: function(item) {
+ var item = typeof item === 'number' ? this.items[item] : item;
+ if (this.$.selector.isSelected(item)) {
+ this.deselectItem(item);
+ } else {
+ this.selectItem(item);
+ }
+ },
+
+ /**
+ * Clears the current selection state of the list.
+ *
+ * @method clearSelection
+ */
+ clearSelection: function() {
+ function unselect(item) {
+ var model = this._getModelFromItem(item);
+ if (model) {
+ model[this.selectedAs] = false;
+ }
+ }
+
+ if (Array.isArray(this.selectedItems)) {
+ this.selectedItems.forEach(unselect, this);
+ } else if (this.selectedItem) {
+ unselect.call(this, this.selectedItem);
+ }
+
+ this.$.selector.clearSelection();
+ },
+
+ /**
+ * Add an event listener to `tap` if `selectionEnabled` is true,
+ * it will remove the listener otherwise.
+ */
+ _selectionEnabledChanged: function(selectionEnabled) {
+ if (selectionEnabled) {
+ this.listen(this, 'tap', '_selectionHandler');
+ } else {
+ this.unlisten(this, 'tap', '_selectionHandler');
+ }
+ },
+
+ /**
+ * Select an item from an event object.
+ */
+ _selectionHandler: function(e) {
+ var model = this.modelForElement(e.target);
+ if (model) {
+ this.toggleSelectionForItem(model[this.as]);
+ }
+ },
+
+ _multiSelectionChanged: function(multiSelection) {
+ this.clearSelection();
+ this.$.selector.multi = multiSelection;
+ }
+ });
+
+})();
+

Powered by Google App Engine
This is Rietveld 408576698