| 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
|
| index a7c75b88e84ab44b1ee88ad3d642fdcae8f86e4b..8a0ee8717684870bad9d3a5fb4e848f60407a7ad 100644
|
| --- 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
|
| @@ -4,6 +4,7 @@
|
| var IOS_TOUCH_SCROLLING = IOS && IOS[1] >= 8;
|
| var DEFAULT_PHYSICAL_COUNT = 3;
|
| var MAX_PHYSICAL_COUNT = 500;
|
| + var HIDDEN_Y = '-10000px';
|
|
|
| Polymer({
|
|
|
| @@ -89,18 +90,27 @@
|
| observers: [
|
| '_itemsChanged(items.*)',
|
| '_selectionEnabledChanged(selectionEnabled)',
|
| - '_multiSelectionChanged(multiSelection)'
|
| + '_multiSelectionChanged(multiSelection)',
|
| + '_setOverflow(scrollTarget)'
|
| ],
|
|
|
| behaviors: [
|
| Polymer.Templatizer,
|
| - Polymer.IronResizableBehavior
|
| + Polymer.IronResizableBehavior,
|
| + Polymer.IronA11yKeysBehavior,
|
| + Polymer.IronScrollTargetBehavior
|
| ],
|
|
|
| listeners: {
|
| 'iron-resize': '_resizeHandler'
|
| },
|
|
|
| + keyBindings: {
|
| + 'up': '_didMoveUp',
|
| + 'down': '_didMoveDown',
|
| + 'enter': '_didEnter'
|
| + },
|
| +
|
| /**
|
| * 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.
|
| @@ -108,12 +118,6 @@
|
| _ratio: 0.5,
|
|
|
| /**
|
| - * The element that controls the scroll
|
| - * @type {?Element}
|
| - */
|
| - _scroller: null,
|
| -
|
| - /**
|
| * The padding-top value of the `scroller` element
|
| */
|
| _scrollerPaddingTop: 0,
|
| @@ -144,7 +148,7 @@
|
| _physicalSize: 0,
|
|
|
| /**
|
| - * The average `offsetHeight` of the tiles observed till now.
|
| + * The average `F` of the tiles observed till now.
|
| */
|
| _physicalAverage: 0,
|
|
|
| @@ -202,13 +206,21 @@
|
| _physicalSizes: null,
|
|
|
| /**
|
| - * A cached value for the visible index.
|
| + * A cached value for the first visible index.
|
| * See `firstVisibleIndex`
|
| * @type {?number}
|
| */
|
| _firstVisibleIndexVal: null,
|
|
|
| /**
|
| + * A cached value for the last visible index.
|
| + * See `lastVisibleIndex`
|
| + * @type {?number}
|
| + */
|
| + _lastVisibleIndexVal: null,
|
| +
|
| +
|
| + /**
|
| * A Polymer collection for the items.
|
| * @type {?Polymer.Collection}
|
| */
|
| @@ -231,6 +243,23 @@
|
| _maxPages: 3,
|
|
|
| /**
|
| + * The currently focused item index.
|
| + */
|
| + _focusedIndex: 0,
|
| +
|
| + /**
|
| + * The the item that is focused if it is moved offscreen.
|
| + * @private {?TemplatizerNode}
|
| + */
|
| + _offscreenFocusedItem: null,
|
| +
|
| + /**
|
| + * The item that backfills the `_offscreenFocusedItem` in the physical items
|
| + * list when that item is moved offscreen.
|
| + */
|
| + _focusBackfillItem: null,
|
| +
|
| + /**
|
| * The bottom of the physical content.
|
| */
|
| get _physicalBottom() {
|
| @@ -248,7 +277,7 @@
|
| * The n-th item rendered in the last physical item.
|
| */
|
| get _virtualEnd() {
|
| - return this._virtualStartVal + this._physicalCount - 1;
|
| + return this._virtualStart + this._physicalCount - 1;
|
| },
|
|
|
| /**
|
| @@ -283,8 +312,13 @@
|
| 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;
|
| + if (this._physicalCount === 0) {
|
| + this._physicalStart = 0;
|
| + this._physicalEnd = 0;
|
| + } else {
|
| + this._physicalStart = this._virtualStartVal % this._physicalCount;
|
| + this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this._physicalCount;
|
| + }
|
| },
|
|
|
| /**
|
| @@ -309,7 +343,7 @@
|
| * True if the current list is visible.
|
| */
|
| get _isVisible() {
|
| - return this._scroller && Boolean(this._scroller.offsetWidth || this._scroller.offsetHeight);
|
| + return this.scrollTarget && Boolean(this.scrollTarget.offsetWidth || this.scrollTarget.offsetHeight);
|
| },
|
|
|
| /**
|
| @@ -318,10 +352,8 @@
|
| * @type {number}
|
| */
|
| get firstVisibleIndex() {
|
| - var physicalOffset;
|
| -
|
| if (this._firstVisibleIndexVal === null) {
|
| - physicalOffset = this._physicalTop;
|
| + var physicalOffset = this._physicalTop;
|
|
|
| this._firstVisibleIndexVal = this._iterateItems(
|
| function(pidx, vidx) {
|
| @@ -332,54 +364,52 @@
|
| }
|
| }) || 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.
|
| + * Gets the index of the last visible item in the viewport.
|
| + *
|
| + * @type {number}
|
| */
|
| - attached: function() {
|
| - // delegate to the parent's scroller
|
| - // e.g. paper-scroll-header-panel
|
| - var el = Polymer.dom(this);
|
| + get lastVisibleIndex() {
|
| + if (this._lastVisibleIndexVal === null) {
|
| + var physicalOffset = this._physicalTop;
|
|
|
| - var parentNode = /** @type {?{scroller: ?Element}} */ (el.parentNode);
|
| - if (parentNode && parentNode.scroller) {
|
| - this._scroller = parentNode.scroller;
|
| - } else {
|
| - this._scroller = this;
|
| - this.classList.add('has-scroller');
|
| - }
|
| + this._iterateItems(function(pidx, vidx) {
|
| + physicalOffset += this._physicalSizes[pidx];
|
|
|
| - if (IOS_TOUCH_SCROLLING) {
|
| - this._scroller.style.webkitOverflowScrolling = 'touch';
|
| + if(physicalOffset <= this._scrollBottom) {
|
| + this._lastVisibleIndexVal = vidx;
|
| + }
|
| + });
|
| }
|
| + return this._lastVisibleIndexVal;
|
| + },
|
|
|
| - this._scroller.addEventListener('scroll', this._scrollListener);
|
| + ready: function() {
|
| + this.addEventListener('focus', this._didFocus.bind(this), true);
|
| + },
|
|
|
| + attached: function() {
|
| 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);
|
| - }
|
| + },
|
| +
|
| + get _defaultScrollTarget() {
|
| + return this;
|
| + },
|
| +
|
| + /**
|
| + * Set the overflow property if this element has its own scrolling region
|
| + */
|
| + _setOverflow: function(scrollTarget) {
|
| + this.style.webkitOverflowScrolling = scrollTarget === this ? 'touch' : '';
|
| + this.style.overflow = scrollTarget === this ? 'auto' : '';
|
| },
|
|
|
| /**
|
| @@ -389,20 +419,20 @@
|
| * @method updateViewportBoundaries
|
| */
|
| updateViewportBoundaries: function() {
|
| - var scrollerStyle = window.getComputedStyle(this._scroller);
|
| + var scrollerStyle = window.getComputedStyle(this.scrollTarget);
|
| this._scrollerPaddingTop = parseInt(scrollerStyle['padding-top'], 10);
|
| - this._viewportSize = this._scroller.offsetHeight;
|
| + this._viewportSize = this._scrollTargetHeight;
|
| },
|
|
|
| /**
|
| * Update the models, the position of the
|
| * items in the viewport and recycle tiles as needed.
|
| */
|
| - _refresh: function() {
|
| + _scrollHandler: function() {
|
| // 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 scrollTop = Math.max(0, Math.min(this._maxScrollTop, this._scrollTop));
|
| var tileHeight, tileTop, kth, recycledTileSet, scrollBottom, physicalBottom;
|
| var ratio = this._ratio;
|
| var delta = scrollTop - this._scrollPosition;
|
| @@ -416,6 +446,7 @@
|
|
|
| // clear cached visible index
|
| this._firstVisibleIndexVal = null;
|
| + this._lastVisibleIndexVal = null;
|
|
|
| scrollBottom = this._scrollBottom;
|
| physicalBottom = this._physicalBottom;
|
| @@ -505,17 +536,21 @@
|
| },
|
|
|
| /**
|
| - * Update the list of items, starting from the `_virtualStartVal` item.
|
| + * Update the list of items, starting from the `_virtualStart` item.
|
| * @param {!Array<number>=} itemSet
|
| * @param {!Array<number>=} movingUp
|
| */
|
| _update: function(itemSet, movingUp) {
|
| + // manage focus
|
| + if (this._isIndexRendered(this._focusedIndex)) {
|
| + this._restoreFocusedItem();
|
| + } else {
|
| + this._createFocusBackfillItem();
|
| + }
|
| // update models
|
| this._assignModels(itemSet);
|
| -
|
| // measure heights
|
| this._updateMetrics(itemSet);
|
| -
|
| // adjust offset after measuring
|
| if (movingUp) {
|
| while (movingUp.length) {
|
| @@ -524,10 +559,8 @@
|
| }
|
| // update the position of the items
|
| this._positionItems();
|
| -
|
| // set the scroller size
|
| this._updateScrollerSize();
|
| -
|
| // increase the pool of physical items
|
| this._increasePoolIfNeeded();
|
| },
|
| @@ -547,7 +580,6 @@
|
| physicalItems[i] = inst.root.querySelector('*');
|
| Polymer.dom(this).appendChild(inst.root);
|
| }
|
| -
|
| return physicalItems;
|
| },
|
|
|
| @@ -557,24 +589,24 @@
|
| * if the physical size is shorter than `_optPhysicalSize`
|
| */
|
| _increasePoolIfNeeded: function() {
|
| - if (this._viewportSize !== 0 && this._physicalSize < this._optPhysicalSize) {
|
| - // 0 <= `currentPage` <= `_maxPages`
|
| - var currentPage = Math.floor(this._physicalSize / this._viewportSize);
|
| -
|
| - if (currentPage === 0) {
|
| - // fill the first page
|
| - this.async(this._increasePool.bind(this, Math.round(this._physicalCount * 0.5)));
|
| - } else if (this._lastPage !== currentPage) {
|
| - // once a page is filled up, paint it and defer the next increase
|
| - requestAnimationFrame(this._increasePool.bind(this, 1));
|
| - } else {
|
| - // fill the rest of the pages
|
| - this.async(this._increasePool.bind(this, 1));
|
| - }
|
| - this._lastPage = currentPage;
|
| - return true;
|
| + if (this._viewportSize === 0 || this._physicalSize >= this._optPhysicalSize) {
|
| + return false;
|
| + }
|
| + // 0 <= `currentPage` <= `_maxPages`
|
| + var currentPage = Math.floor(this._physicalSize / this._viewportSize);
|
| + if (currentPage === 0) {
|
| + // fill the first page
|
| + this._debounceTemplate(this._increasePool.bind(this, Math.round(this._physicalCount * 0.5)));
|
| + } else if (this._lastPage !== currentPage) {
|
| + // paint the page and defer the next increase
|
| + // wait 16ms which is rough enough to get paint cycle.
|
| + Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', this._increasePool.bind(this, 1), 16));
|
| + } else {
|
| + // fill the rest of the pages
|
| + this._debounceTemplate(this._increasePool.bind(this, 1));
|
| }
|
| - return false;
|
| + this._lastPage = currentPage;
|
| + return true;
|
| },
|
|
|
| /**
|
| @@ -584,7 +616,7 @@
|
| // limit the size
|
| var nextPhysicalCount = Math.min(
|
| this._physicalCount + missingItems,
|
| - this._virtualCount,
|
| + this._virtualCount - this._virtualStart,
|
| MAX_PHYSICAL_COUNT
|
| );
|
| var prevPhysicalCount = this._physicalCount;
|
| @@ -610,6 +642,7 @@
|
| if (this.isAttached && !this._itemsRendered && this._isVisible && requiresUpdate) {
|
| this._lastPage = 0;
|
| this._update();
|
| + this._scrollHandler();
|
| this._itemsRendered = true;
|
| }
|
| },
|
| @@ -621,11 +654,11 @@
|
| 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;
|
| + props.tabIndex = true;
|
|
|
| this._instanceProps = props;
|
| this._userTemplate = Polymer.dom(this).querySelector('template');
|
| @@ -693,6 +726,10 @@
|
| var key = path.substring(0, dot < 0 ? path.length : dot);
|
| var idx = this._physicalIndexForKey[key];
|
| var row = this._physicalItems[idx];
|
| +
|
| + if (idx === this._focusedIndex && this._offscreenFocusedItem) {
|
| + row = this._offscreenFocusedItem;
|
| + }
|
| if (row) {
|
| var inst = row._templateInstance;
|
| if (dot >= 0) {
|
| @@ -711,17 +748,18 @@
|
| */
|
| _itemsChanged: function(change) {
|
| if (change.path === 'items') {
|
| +
|
| + this._restoreFocusedItem();
|
| // render the new set
|
| this._itemsRendered = false;
|
| -
|
| // update the whole set
|
| - this._virtualStartVal = 0;
|
| + this._virtualStart = 0;
|
| this._physicalTop = 0;
|
| this._virtualCount = this.items ? this.items.length : 0;
|
| + this._focusedIndex = 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
|
| @@ -730,17 +768,20 @@
|
| this._physicalItems = this._createPool(this._physicalCount);
|
| this._physicalSizes = new Array(this._physicalCount);
|
| }
|
| -
|
| - this.debounce('refresh', this._render);
|
| + this._debounceTemplate(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);
|
| + this._debounceTemplate(this._render);
|
| +
|
| + if (this._focusedIndex < 0 || this._focusedIndex >= this._virtualCount) {
|
| + this._focusedIndex = 0;
|
| + }
|
| + this._debounceTemplate(this._render);
|
|
|
| } else {
|
| // update a single item
|
| @@ -762,19 +803,15 @@
|
|
|
| idx = splice.index;
|
| // We only need to care about changes happening above the current position
|
| - if (idx >= this._virtualStartVal) {
|
| + if (idx >= this._virtualStart) {
|
| break;
|
| }
|
|
|
| this._virtualStart = this._virtualStart +
|
| - Math.max(splice.addedCount - splice.removed.length, idx - this._virtualStartVal);
|
| + Math.max(splice.addedCount - splice.removed.length, idx - this._virtualStart);
|
| }
|
| },
|
|
|
| - _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.
|
| @@ -789,9 +826,9 @@
|
| for (i = 0; i < itemSet.length; i++) {
|
| pidx = itemSet[i];
|
| if (pidx >= this._physicalStart) {
|
| - vidx = this._virtualStartVal + (pidx - this._physicalStart);
|
| + vidx = this._virtualStart + (pidx - this._physicalStart);
|
| } else {
|
| - vidx = this._virtualStartVal + (this._physicalCount - this._physicalStart) + pidx;
|
| + vidx = this._virtualStart + (this._physicalCount - this._physicalStart) + pidx;
|
| }
|
| if ((rtn = fn.call(this, pidx, vidx)) != null) {
|
| return rtn;
|
| @@ -799,17 +836,14 @@
|
| }
|
| } else {
|
| pidx = this._physicalStart;
|
| - vidx = this._virtualStartVal;
|
| + vidx = this._virtualStart;
|
|
|
| for (; pidx < this._physicalCount; pidx++, vidx++) {
|
| if ((rtn = fn.call(this, pidx, vidx)) != null) {
|
| return rtn;
|
| }
|
| }
|
| -
|
| - pidx = 0;
|
| -
|
| - for (; pidx < this._physicalStart; pidx++, vidx++) {
|
| + for (pidx = 0; pidx < this._physicalStart; pidx++, vidx++) {
|
| if ((rtn = fn.call(this, pidx, vidx)) != null) {
|
| return rtn;
|
| }
|
| @@ -827,12 +861,12 @@
|
| var inst = el._templateInstance;
|
| var item = this.items && this.items[vidx];
|
|
|
| - if (item) {
|
| + if (item !== undefined && item !== null) {
|
| inst[this.as] = item;
|
| inst.__key__ = this._collection.getKey(item);
|
| - inst[this.selectedAs] =
|
| - /** @type {!ArraySelectorElement} */ (this.$.selector).isSelected(item);
|
| + inst[this.selectedAs] = /** @type {!ArraySelectorElement} */ (this.$.selector).isSelected(item);
|
| inst[this.indexAs] = vidx;
|
| + inst.tabIndex = vidx === this._focusedIndex ? 0 : -1;
|
| el.removeAttribute('hidden');
|
| this._physicalIndexForKey[inst.__key__] = pidx;
|
| } else {
|
| @@ -849,23 +883,26 @@
|
| * @param {!Array<number>=} itemSet
|
| */
|
| _updateMetrics: function(itemSet) {
|
| + // Make sure we distributed all the physical items
|
| + // so we can measure them
|
| + Polymer.dom.flush();
|
| +
|
| 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();
|
|
|
| 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 = this._physicalSize + newPhysicalSize - oldPhysicalSize;
|
| - this._viewportSize = this._scroller.offsetHeight;
|
| + this._viewportSize = this._scrollTargetHeight;
|
|
|
| // update the average if we measured something
|
| if (this._physicalAverageCount !== prevAvgCount) {
|
| @@ -885,7 +922,7 @@
|
|
|
| this._iterateItems(function(pidx) {
|
|
|
| - this.transform('translate3d(0, ' + y + 'px, 0)', this._physicalItems[pidx]);
|
| + this.translate3d(0, y + 'px', 0, this._physicalItems[pidx]);
|
| y += this._physicalSizes[pidx];
|
|
|
| });
|
| @@ -895,15 +932,14 @@
|
| * Adjusts the scroll position when it was overestimated.
|
| */
|
| _adjustScrollPosition: function() {
|
| - var deltaHeight = this._virtualStartVal === 0 ? this._physicalTop :
|
| + var deltaHeight = this._virtualStart === 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);
|
| + this._resetScrollPosition(this._scrollTop - deltaHeight);
|
| }
|
| }
|
| },
|
| @@ -912,9 +948,9 @@
|
| * Sets the position of the scroll.
|
| */
|
| _resetScrollPosition: function(pos) {
|
| - if (this._scroller) {
|
| - this._scroller.scrollTop = pos;
|
| - this._scrollPosition = this._scroller.scrollTop;
|
| + if (this.scrollTarget) {
|
| + this._scrollTop = pos;
|
| + this._scrollPosition = this._scrollTop;
|
| }
|
| },
|
|
|
| @@ -925,7 +961,7 @@
|
| */
|
| _updateScrollerSize: function(forceUpdate) {
|
| this._estScrollHeight = (this._physicalBottom +
|
| - Math.max(this._virtualCount - this._physicalCount - this._virtualStartVal, 0) * this._physicalAverage);
|
| + Math.max(this._virtualCount - this._physicalCount - this._virtualStart, 0) * this._physicalAverage);
|
|
|
| forceUpdate = forceUpdate || this._scrollHeight === 0;
|
| forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight - this._physicalSize;
|
| @@ -949,20 +985,18 @@
|
| return;
|
| }
|
|
|
| - var firstVisible = this.firstVisibleIndex;
|
| + Polymer.dom.flush();
|
|
|
| + 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;
|
|
|
| @@ -977,21 +1011,17 @@
|
| 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);
|
| -
|
| + this._resetScrollPosition(this._physicalTop + this._scrollerPaddingTop + targetOffsetTop + 1);
|
| // increase the pool of physical items if needed
|
| this._increasePoolIfNeeded();
|
| -
|
| // clear cached visible index
|
| this._firstVisibleIndexVal = null;
|
| + this._lastVisibleIndexVal = null;
|
| },
|
|
|
| /**
|
| @@ -1007,7 +1037,11 @@
|
| * when the element is resized.
|
| */
|
| _resizeHandler: function() {
|
| - this.debounce('resize', function() {
|
| + // iOS fires the resize event when the address bar slides up
|
| + if (IOS && Math.abs(this._viewportSize - this._scrollTargetHeight) < 100) {
|
| + return;
|
| + }
|
| + this._debounceTemplate(function() {
|
| this._render();
|
| if (this._itemsRendered && this._physicalItems && this._isVisible) {
|
| this._resetAverage();
|
| @@ -1033,12 +1067,14 @@
|
| * @param {(Object|number)} item The item object or its index
|
| */
|
| _getNormalizedItem: function(item) {
|
| - if (typeof item === 'number') {
|
| - item = this.items[item];
|
| - if (!item) {
|
| - throw new RangeError('<item> not found');
|
| + if (this._collection.getKey(item) === undefined) {
|
| + if (typeof item === 'number') {
|
| + item = this.items[item];
|
| + if (!item) {
|
| + throw new RangeError('<item> not found');
|
| + }
|
| + return item;
|
| }
|
| - } else if (this._collection.getKey(item) === undefined) {
|
| throw new TypeError('<item> should be a valid item');
|
| }
|
| return item;
|
| @@ -1061,6 +1097,7 @@
|
| model[this.selectedAs] = true;
|
| }
|
| this.$.selector.select(item);
|
| + this.updateSizeForItem(item);
|
| },
|
|
|
| /**
|
| @@ -1078,6 +1115,7 @@
|
| model[this.selectedAs] = false;
|
| }
|
| this.$.selector.deselect(item);
|
| + this.updateSizeForItem(item);
|
| },
|
|
|
| /**
|
| @@ -1123,20 +1161,15 @@
|
| * it will remove the listener otherwise.
|
| */
|
| _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');
|
| - }
|
| + var handler = selectionEnabled ? this.listen : this.unlisten;
|
| + handler.call(this, this, 'tap', '_selectionHandler');
|
| },
|
|
|
| /**
|
| * Select an item from an event object.
|
| */
|
| _selectionHandler: function(e) {
|
| - if (e.type !== 'keypress' || e.keyCode === 13) {
|
| + if (this.selectionEnabled) {
|
| var model = this.modelForElement(e.target);
|
| if (model) {
|
| this.toggleSelectionForItem(model[this.as]);
|
| @@ -1164,7 +1197,136 @@
|
| this._updateMetrics([pidx]);
|
| this._positionItems();
|
| }
|
| + },
|
| +
|
| + _isIndexRendered: function(idx) {
|
| + return idx >= this._virtualStart && idx <= this._virtualEnd;
|
| + },
|
| +
|
| + _getPhysicalItemForIndex: function(idx, force) {
|
| + if (!this._collection) {
|
| + return null;
|
| + }
|
| + if (!this._isIndexRendered(idx)) {
|
| + if (force) {
|
| + this.scrollToIndex(idx);
|
| + return this._getPhysicalItemForIndex(idx, false);
|
| + }
|
| + return null;
|
| + }
|
| + var item = this._getNormalizedItem(idx);
|
| + var physicalItem = this._physicalItems[this._physicalIndexForKey[this._collection.getKey(item)]];
|
| +
|
| + return physicalItem || null;
|
| + },
|
| +
|
| + _focusPhysicalItem: function(idx) {
|
| + this._restoreFocusedItem();
|
| +
|
| + var physicalItem = this._getPhysicalItemForIndex(idx, true);
|
| + if (!physicalItem) {
|
| + return;
|
| + }
|
| + var SECRET = ~(Math.random() * 100);
|
| + var model = physicalItem._templateInstance;
|
| + var focusable;
|
| +
|
| + model.tabIndex = SECRET;
|
| + // the focusable element could be the entire physical item
|
| + if (physicalItem.tabIndex === SECRET) {
|
| + focusable = physicalItem;
|
| + }
|
| + // the focusable element could be somewhere within the physical item
|
| + if (!focusable) {
|
| + focusable = Polymer.dom(physicalItem).querySelector('[tabindex="' + SECRET + '"]');
|
| + }
|
| + // restore the tab index
|
| + model.tabIndex = 0;
|
| + focusable && focusable.focus();
|
| + },
|
| +
|
| + _restoreFocusedItem: function() {
|
| + if (!this._offscreenFocusedItem) {
|
| + return;
|
| + }
|
| + var item = this._getNormalizedItem(this._focusedIndex);
|
| + var pidx = this._physicalIndexForKey[this._collection.getKey(item)];
|
| +
|
| + if (pidx !== undefined) {
|
| + this.translate3d(0, HIDDEN_Y, 0, this._physicalItems[pidx]);
|
| + this._physicalItems[pidx] = this._offscreenFocusedItem;
|
| + }
|
| + this._offscreenFocusedItem = null;
|
| + },
|
| +
|
| + _removeFocusedItem: function() {
|
| + if (!this._offscreenFocusedItem) {
|
| + return;
|
| + }
|
| + Polymer.dom(this).removeChild(this._offscreenFocusedItem);
|
| + this._offscreenFocusedItem = null;
|
| + this._focusBackfillItem = null;
|
| + },
|
| +
|
| + _createFocusBackfillItem: function() {
|
| + if (this._offscreenFocusedItem) {
|
| + return;
|
| + }
|
| + var item = this._getNormalizedItem(this._focusedIndex);
|
| + var pidx = this._physicalIndexForKey[this._collection.getKey(item)];
|
| +
|
| + this._offscreenFocusedItem = this._physicalItems[pidx];
|
| + this.translate3d(0, HIDDEN_Y, 0, this._offscreenFocusedItem);
|
| +
|
| + if (!this._focusBackfillItem) {
|
| + var stampedTemplate = this.stamp(null);
|
| + this._focusBackfillItem = stampedTemplate.root.querySelector('*');
|
| + Polymer.dom(this).appendChild(stampedTemplate.root);
|
| + }
|
| + this._physicalItems[pidx] = this._focusBackfillItem;
|
| + },
|
| +
|
| + _didFocus: function(e) {
|
| + var targetModel = this.modelForElement(e.target);
|
| + var fidx = this._focusedIndex;
|
| +
|
| + if (!targetModel) {
|
| + return;
|
| + }
|
| + this._restoreFocusedItem();
|
| +
|
| + if (this.modelForElement(this._offscreenFocusedItem) === targetModel) {
|
| + this.scrollToIndex(fidx);
|
| + } else {
|
| + // restore tabIndex for the currently focused item
|
| + this._getModelFromItem(this._getNormalizedItem(fidx)).tabIndex = -1;
|
| + // set the tabIndex for the next focused item
|
| + targetModel.tabIndex = 0;
|
| + fidx = /** @type {{index: number}} */(targetModel).index;
|
| + this._focusedIndex = fidx;
|
| + // bring the item into view
|
| + if (fidx < this.firstVisibleIndex || fidx > this.lastVisibleIndex) {
|
| + this.scrollToIndex(fidx);
|
| + } else {
|
| + this._update();
|
| + }
|
| + }
|
| + },
|
| +
|
| + _didMoveUp: function() {
|
| + this._focusPhysicalItem(Math.max(0, this._focusedIndex - 1));
|
| + },
|
| +
|
| + _didMoveDown: function() {
|
| + this._focusPhysicalItem(Math.min(this._virtualCount, this._focusedIndex + 1));
|
| + },
|
| +
|
| + _didEnter: function(e) {
|
| + // focus the currently focused physical item
|
| + this._focusPhysicalItem(this._focusedIndex);
|
| + // toggle selection
|
| + this._selectionHandler(/** @type {{keyboardEvent: Event}} */(e.detail).keyboardEvent);
|
| }
|
| });
|
|
|
| -})();
|
| +})();
|
|
|