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

Unified Diff: lib/src/iron-list/iron-list.html

Issue 1418513006: update elements and fix some bugs (Closed) Base URL: git@github.com:dart-lang/polymer_elements.git@master
Patch Set: code review updates Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « lib/src/iron-iconset/test/iron-iconset.html ('k') | lib/src/iron-list/test/different-heights.html » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: lib/src/iron-list/iron-list.html
diff --git a/lib/src/iron-list/iron-list.html b/lib/src/iron-list/iron-list.html
index aaeed9b8337ddb1c867d253140ad355d0474586a..60539b9177be2a2263409e67cc11b0a6026a4277 100644
--- a/lib/src/iron-list/iron-list.html
+++ b/lib/src/iron-list/iron-list.html
@@ -69,9 +69,19 @@ bound from the model object provided to the template scope):
</iron-list>
</template>
+### Styling
+
+Use the `--iron-list-items-container` mixin to style the container of items, e.g.
+
+ iron-list {
+ --iron-list-items-container: {
+ margin: auto;
+ };
+ }
+
### Resizing
-`iron-list` lays out the items when it recives a notification via the `resize` event.
+`iron-list` lays out the items when it recives a notification via the `iron-resize` event.
This event is fired by any element that implements `IronResizableBehavior`.
By default, elements such as `iron-pages`, `paper-tabs` or `paper-dialog` will trigger
@@ -79,46 +89,47 @@ this event automatically. If you hide the list manually (e.g. you use `display:
you might want to implement `IronResizableBehavior` or fire this event manually right
after the list became visible again. e.g.
- document.querySelector('iron-list').fire('resize');
+ document.querySelector('iron-list').fire('iron-resize');
@group Iron Element
@element iron-list
-@demo demo/index.html
+@demo demo/index.html Simple list
+@demo demo/selection.html Selection of items
+@demo demo/collapse.html Collapsable items
-->
<dom-module id="iron-list">
- <style>
-
- :host {
- display: block;
- }
-
- :host(.has-scroller) {
- overflow: auto;
- }
+ <template>
+ <style>
+ :host {
+ display: block;
+ }
- :host(:not(.has-scroller)) {
- position: relative;
- }
+ :host(.has-scroller) {
+ overflow: auto;
+ }
- #items {
- position: relative;
- }
+ :host(:not(.has-scroller)) {
+ position: relative;
+ }
- #items > ::content > * {
- width: 100%;
- box-sizing: border-box;
- position: absolute;
- top: 0;
- will-change: transform;
- }
+ #items {
+ @apply(--iron-list-items-container);
+ position: relative;
+ }
- </style>
- <template>
+ #items > ::content > * {
+ width: 100%;
+ box-sizing: border-box;
+ position: absolute;
+ top: 0;
+ will-change: transform;
+ }
+ </style>
<array-selector id="selector" items="{{items}}"
- selected="{{selectedItems}}" selected-item="{{selectedItem}}">
+ selected="{{selectedItems}}" selected-item="{{selectedItem}}">
</array-selector>
<div id="items">
@@ -162,8 +173,7 @@ after the list became visible again. e.g.
/**
* 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).
+ * for the row.
*/
indexAs: {
type: String,
@@ -242,6 +252,7 @@ after the list became visible again. e.g.
/**
* The element that controls the scroll
+ * @type {?Element}
*/
_scroller: null,
@@ -323,22 +334,26 @@ after the list became visible again. e.g.
/**
* An array of DOM nodes that are currently in the tree
+ * @type {?Array<!TemplatizerNode>}
*/
_physicalItems: null,
/**
* An array of heights for each item in `_physicalItems`
+ * @type {?Array<number>}
*/
_physicalSizes: null,
/**
* A cached value for the visible index.
* See `firstVisibleIndex`
+ * @type {?number}
*/
_firstVisibleIndexVal: null,
/**
* A Polymer collection for the items.
+ * @type {?Polymer.Collection}
*/
_collection: null,
@@ -356,6 +371,13 @@ after the list became visible again. e.g.
},
/**
+ * The bottom of the scroll.
+ */
+ get _scrollBottom() {
+ return this._scrollPosition + this._viewportSize;
+ },
+
+ /**
* The n-th item rendered in the last physical item.
*/
get _virtualEnd() {
@@ -371,8 +393,7 @@ after the list became visible again. e.g.
* 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;
+ return Math.max(0, this._virtualCount - this._physicalCount);
},
/**
@@ -425,9 +446,9 @@ after the list became visible again. e.g.
},
/**
- * Gets the first visible item in the viewport.
+ * Gets the index of the first visible item in the viewport.
*
- * @property firstVisibleIndex
+ * @type {number}
*/
get firstVisibleIndex() {
var physicalOffset;
@@ -466,8 +487,9 @@ after the list became visible again. e.g.
// e.g. paper-scroll-header-panel
var el = Polymer.dom(this);
- if (el.parentNode && el.parentNode.scroller) {
- this._scroller = el.parentNode.scroller;
+ var parentNode = /** @type {?{scroller: ?Element}} */ (el.parentNode);
+ if (parentNode && parentNode.scroller) {
+ this._scroller = parentNode.scroller;
} else {
this._scroller = this;
this.classList.add('has-scroller');
@@ -501,7 +523,7 @@ after the list became visible again. e.g.
*/
updateViewportBoundaries: function() {
var scrollerStyle = window.getComputedStyle(this._scroller);
- this._scrollerPaddingTop = parseInt(scrollerStyle['padding-top']);
+ this._scrollerPaddingTop = parseInt(scrollerStyle['padding-top'], 10);
this._viewportSize = this._scroller.offsetHeight;
},
@@ -510,19 +532,13 @@ after the list became visible again. e.g.
* 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 tileHeight, tileTop, kth, recycledTileSet, scrollBottom;
var ratio = this._ratio;
var delta = scrollTop - this._scrollPosition;
- var direction = SCROLL_DIRECTION_NONE;
var recycledTiles = 0;
var hiddenContentSize = this._hiddenContentSize;
var currentRatio = ratio;
@@ -534,18 +550,19 @@ after the list became visible again. e.g.
// clear cached visible index
this._firstVisibleIndexVal = null;
+ scrollBottom = this._scrollBottom;
+
// 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;
+ var physicalBottom = this._physicalBottom;
- direction = SCROLL_DIRECTION_UP;
recycledTileSet = [];
kth = this._physicalEnd;
@@ -558,12 +575,14 @@ after the list became visible again. e.g.
// recycle less physical items than the total
recycledTiles < this._physicalCount &&
// ensure that these recycled tiles are needed
- virtualStart - recycledTiles > 0
+ virtualStart - recycledTiles > 0 &&
+ // ensure that the tile is not visible
+ physicalBottom - this._physicalSizes[kth] > scrollBottom
) {
- tileHeight = this._physicalSizes[kth] || this._physicalAverage;
+ tileHeight = this._physicalSizes[kth];
currentRatio += tileHeight / hiddenContentSize;
-
+ physicalBottom -= tileHeight;
recycledTileSet.push(kth);
recycledTiles++;
kth = (kth === 0) ? this._physicalCount - 1 : kth - 1;
@@ -571,15 +590,13 @@ after the list became visible again. e.g.
movingUp = recycledTileSet;
recycledTiles = -recycledTiles;
-
}
// scroll down
else if (delta > 0) {
- var bottomSpace = this._physicalBottom - (scrollTop + this._viewportSize);
+ var bottomSpace = this._physicalBottom - scrollBottom;
var virtualEnd = this._virtualEnd;
var lastVirtualItemIndex = this._virtualCount-1;
- direction = SCROLL_DIRECTION_DOWN;
recycledTileSet = [];
kth = this._physicalStart;
@@ -592,10 +609,12 @@ after the list became visible again. e.g.
// recycle less physical items than the total
recycledTiles < this._physicalCount &&
// ensure that these recycled tiles are needed
- virtualEnd + recycledTiles < lastVirtualItemIndex
+ virtualEnd + recycledTiles < lastVirtualItemIndex &&
+ // ensure that the tile is not visible
+ this._physicalTop + this._physicalSizes[kth] < scrollTop
) {
- tileHeight = this._physicalSizes[kth] || this._physicalAverage;
+ tileHeight = this._physicalSizes[kth];
currentRatio += tileHeight / hiddenContentSize;
this._physicalTop += tileHeight;
@@ -605,7 +624,15 @@ after the list became visible again. e.g.
}
}
- if (recycledTiles !== 0) {
+ if (recycledTiles === 0) {
+ // If the list ever reach this case, the physical average is not significant enough
+ // to create all the items needed to cover the entire viewport.
+ // e.g. A few items have a height that differs from the average by serveral order of magnitude.
+ if (this._increasePoolIfNeeded()) {
+ // yield and set models to the new items
+ this.async(this._update);
+ }
+ } else {
this._virtualStart = this._virtualStart + recycledTiles;
this._update(recycledTileSet, movingUp);
}
@@ -613,14 +640,15 @@ after the list became visible again. e.g.
/**
* Update the list of items, starting from the `_virtualStartVal` item.
+ * @param {!Array<number>=} itemSet
+ * @param {!Array<number>=} movingUp
*/
_update: function(itemSet, movingUp) {
// update models
this._assignModels(itemSet);
// measure heights
- // TODO(blasten) pass `recycledTileSet`
- this._updateMetrics();
+ this._updateMetrics(itemSet);
// adjust offset after measuring
if (movingUp) {
@@ -628,7 +656,6 @@ after the list became visible again. e.g.
this._physicalTop -= this._physicalSizes[movingUp.pop()];
}
}
-
// update the position of the items
this._positionItems();
@@ -636,9 +663,9 @@ after the list became visible again. e.g.
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));
+ if (this._increasePoolIfNeeded()) {
+ // yield set models to the new items
+ this.async(this._update);
}
},
@@ -663,24 +690,30 @@ after the list became visible again. e.g.
},
/**
- * Increases the pool size. That is, the physical items in the DOM.
+ * Increases the pool of physical items only if needed.
* This function will allocate additional physical items
* (limited by `MAX_PHYSICAL_COUNT`) if the content size is shorter than
* `_optPhysicalSize`
*
- * @return Array
+ * @return boolean
*/
_increasePoolIfNeeded: function() {
- if (this._physicalSize >= this._optPhysicalSize || this._physicalAverage === 0) {
- return null;
+ if (this._physicalAverage === 0) {
+ return false;
}
+ if (this._physicalBottom < this._scrollBottom || this._physicalTop > this._scrollPosition) {
+ return this._increasePool(1);
+ }
+ if (this._physicalSize < this._optPhysicalSize) {
+ return this._increasePool(Math.round((this._optPhysicalSize - this._physicalSize) * 1.2 / this._physicalAverage));
+ }
+ return false;
+ },
- // 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
- );
-
+ /**
+ * Increases the pool size.
+ */
+ _increasePool: function(missingItems) {
// limit the size
var nextPhysicalCount = Math.min(
this._physicalCount + missingItems,
@@ -692,23 +725,15 @@ after the list became visible again. e.g.
var delta = nextPhysicalCount - prevPhysicalCount;
if (delta <= 0) {
- return null;
+ return false;
}
- var newPhysicalItems = this._createPool(delta);
- var emptyArray = new Array(delta);
-
- [].push.apply(this._physicalItems, newPhysicalItems);
- [].push.apply(this._physicalSizes, emptyArray);
+ [].push.apply(this._physicalItems, this._createPool(delta));
+ [].push.apply(this._physicalSizes, new Array(delta));
this._physicalCount = prevPhysicalCount + delta;
- // fill the array with the new item pos
- while (delta > 0) {
- emptyArray[--delta] = prevPhysicalCount + delta;
- }
-
- return emptyArray;
+ return true;
},
/**
@@ -858,6 +883,9 @@ after the list became visible again. e.g.
}
},
+ /**
+ * @param {!Array<!PolymerSplice>} splices
+ */
_adjustVirtualIndex: function(splices) {
var i, splice, idx;
@@ -885,6 +913,9 @@ after the list became visible again. e.g.
/**
* Executes a provided function per every physical index in `itemSet`
* `itemSet` default value is equivalent to the entire set of physical indexes.
+ *
+ * @param {!function(number, number)} fn
+ * @param {!Array<number>=} itemSet
*/
_iterateItems: function(fn, itemSet) {
var pidx, vidx, rtn, i;
@@ -923,6 +954,7 @@ after the list became visible again. e.g.
/**
* Assigns the data models to a given set of items.
+ * @param {!Array<number>=} itemSet
*/
_assignModels: function(itemSet) {
this._iterateItems(function(pidx, vidx) {
@@ -933,7 +965,8 @@ after the list became visible again. e.g.
if (item) {
inst[this.as] = item;
inst.__key__ = this._collection.getKey(item);
- inst[this.selectedAs] = this.$.selector.isSelected(item);
+ inst[this.selectedAs] =
+ /** @type {!ArraySelectorElement} */ (this.$.selector).isSelected(item);
inst[this.indexAs] = vidx;
el.removeAttribute('hidden');
this._physicalIndexForKey[inst.__key__] = pidx;
@@ -947,28 +980,32 @@ after the list became visible again. e.g.
/**
* Updates the height for a given set of items.
+ *
+ * @param {!Array<number>=} itemSet
*/
- _updateMetrics: function() {
- var total = 0;
+ _updateMetrics: function(itemSet) {
+ var newPhysicalSize = 0;
+ var oldPhysicalSize = 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._iterateItems(function(pidx, vidx) {
+ oldPhysicalSize += this._physicalSizes[pidx] || 0;
+ this._physicalSizes[pidx] = this._physicalItems[pidx].offsetHeight;
+ newPhysicalSize += this._physicalSizes[pidx];
+ this._physicalAverageCount += this._physicalSizes[pidx] ? 1 : 0;
+ }, itemSet);
- this._physicalSize = total;
+ this._physicalSize = this._physicalSize + newPhysicalSize - oldPhysicalSize;
this._viewportSize = this._scroller.offsetHeight;
+ // update the average if we measured something
if (this._physicalAverageCount !== prevAvgCount) {
this._physicalAverage = Math.round(
- ((prevPhysicalAvg * prevAvgCount) + total) /
+ ((prevPhysicalAvg * prevAvgCount) + newPhysicalSize) /
this._physicalAverageCount);
}
},
@@ -976,7 +1013,7 @@ after the list became visible again. e.g.
/**
* Updates the position of the physical items.
*/
- _positionItems: function(itemSet) {
+ _positionItems: function() {
this._adjustScrollPosition();
var y = this._physicalTop;
@@ -986,7 +1023,7 @@ after the list became visible again. e.g.
this.transform('translate3d(0, ' + y + 'px, 0)', this._physicalItems[pidx]);
y += this._physicalSizes[pidx];
- }, itemSet);
+ });
},
/**
@@ -1018,6 +1055,8 @@ after the list became visible again. e.g.
/**
* Sets the scroll height, that's the height of the content,
+ *
+ * @param {boolean=} forceUpdate If true, updates the height no matter what.
*/
_updateScrollerSize: function(forceUpdate) {
this._estScrollHeight = (this._physicalBottom +
@@ -1045,7 +1084,6 @@ after the list became visible again. e.g.
return;
}
- var itemSet;
var firstVisible = this.firstVisibleIndex;
idx = Math.min(Math.max(idx, 0), this._virtualCount-1);
@@ -1085,11 +1123,10 @@ after the list became visible again. e.g.
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));
+ if (this._increasePoolIfNeeded()) {
+ // yield set models to the new items
+ this.async(this._update);
}
-
// clear cached visible index
this._firstVisibleIndexVal = null;
},
@@ -1103,7 +1140,7 @@ after the list became visible again. e.g.
},
/**
- * A handler for the `resize` event triggered by `IronResizableBehavior`
+ * A handler for the `iron-resize` event triggered by `IronResizableBehavior`
* when the element is resized.
*/
_resizeHandler: function() {
@@ -1128,23 +1165,30 @@ after the list became visible again. e.g.
},
/**
- * Select the list item at the given index.
+ * Gets a valid item instance from its index or the object value.
*
- * @method selectItem
- * @param {(Object|number)} item the item object or its index
+ * @param {(Object|number)} item The item object or its index
*/
- selectItem: function(item) {
+ _getNormalizedItem: 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');
- }
+ } else if (this._collection.getKey(item) === undefined) {
+ throw new TypeError('<item> should be a valid item');
}
+ return item;
+ },
+ /**
+ * Select the list item at the given index.
+ *
+ * @method selectItem
+ * @param {(Object|number)} item The item object or its index
+ */
+ selectItem: function(item) {
+ item = this._getNormalizedItem(item);
var model = this._getModelFromItem(item);
if (!this.multiSelection && this.selectedItem) {
@@ -1159,21 +1203,12 @@ after the list became visible again. e.g.
/**
* Deselects the given item list if it is already selected.
*
+
* @method deselect
- * @param {(Object|number)} item the item object or its index
+ * @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');
- }
- }
-
+ item = this._getNormalizedItem(item);
var model = this._getModelFromItem(item);
if (model) {
@@ -1187,11 +1222,11 @@ after the list became visible again. e.g.
* has already been selected.
*
* @method toggleSelectionForItem
- * @param {(Object|number)} item the item object or its index
+ * @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)) {
+ item = this._getNormalizedItem(item);
+ if (/** @type {!ArraySelectorElement} */ (this.$.selector).isSelected(item)) {
this.deselectItem(item);
} else {
this.selectItem(item);
@@ -1217,7 +1252,7 @@ after the list became visible again. e.g.
unselect.call(this, this.selectedItem);
}
- this.$.selector.clearSelection();
+ /** @type {!ArraySelectorElement} */ (this.$.selector).clearSelection();
},
/**
@@ -1227,8 +1262,10 @@ after the list became visible again. e.g.
_selectionEnabledChanged: function(selectionEnabled) {
if (selectionEnabled) {
this.listen(this, 'tap', '_selectionHandler');
+ this.listen(this, 'keypress', '_selectionHandler');
} else {
this.unlisten(this, 'tap', '_selectionHandler');
+ this.unlisten(this, 'keypress', '_selectionHandler');
}
},
@@ -1236,15 +1273,34 @@ after the list became visible again. e.g.
* Select an item from an event object.
*/
_selectionHandler: function(e) {
- var model = this.modelForElement(e.target);
- if (model) {
- this.toggleSelectionForItem(model[this.as]);
+ if (e.type !== 'keypress' || e.keyCode === 13) {
+ var model = this.modelForElement(e.target);
+ if (model) {
+ this.toggleSelectionForItem(model[this.as]);
+ }
}
},
_multiSelectionChanged: function(multiSelection) {
this.clearSelection();
this.$.selector.multi = multiSelection;
+ },
+
+ /**
+ * Updates the size of an item.
+ *
+ * @method updateSizeForItem
+ * @param {(Object|number)} item The item object or its index
+ */
+ updateSizeForItem: function(item) {
+ item = this._getNormalizedItem(item);
+ var key = this._collection.getKey(item);
+ var pidx = this._physicalIndexForKey[key];
+
+ if (pidx !== undefined) {
+ this._updateMetrics([pidx]);
+ this._positionItems();
+ }
}
});
« no previous file with comments | « lib/src/iron-iconset/test/iron-iconset.html ('k') | lib/src/iron-list/test/different-heights.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698