Chromium Code Reviews| 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); |
|
Dan Beam
2016/02/09 04:27:59
this is a local change, but was mentioned on the o
|
| } |
| }); |
| -})(); |
| +})(); |