| OLD | NEW |
| 1 (function() { | 1 (function() { |
| 2 | 2 |
| 3 var IOS = navigator.userAgent.match(/iP(?:hone|ad;(?: U;)? CPU) OS (\d+)/); | 3 var IOS = navigator.userAgent.match(/iP(?:hone|ad;(?: U;)? CPU) OS (\d+)/); |
| 4 var IOS_TOUCH_SCROLLING = IOS && IOS[1] >= 8; | 4 var IOS_TOUCH_SCROLLING = IOS && IOS[1] >= 8; |
| 5 var DEFAULT_PHYSICAL_COUNT = 3; | 5 var DEFAULT_PHYSICAL_COUNT = 3; |
| 6 var HIDDEN_Y = '-10000px'; | 6 var HIDDEN_Y = '-10000px'; |
| 7 var DEFAULT_GRID_SIZE = 200; | 7 var DEFAULT_GRID_SIZE = 200; |
| 8 var SECRET_TABINDEX = -100; | 8 var SECRET_TABINDEX = -100; |
| 9 | 9 |
| 10 Polymer({ | 10 Polymer({ |
| (...skipping 216 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 227 */ | 227 */ |
| 228 _lastVisibleIndexVal: null, | 228 _lastVisibleIndexVal: null, |
| 229 | 229 |
| 230 /** | 230 /** |
| 231 * A Polymer collection for the items. | 231 * A Polymer collection for the items. |
| 232 * @type {?Polymer.Collection} | 232 * @type {?Polymer.Collection} |
| 233 */ | 233 */ |
| 234 _collection: null, | 234 _collection: null, |
| 235 | 235 |
| 236 /** | 236 /** |
| 237 * True if the current item list was rendered for the first time | |
| 238 * after attached. | |
| 239 */ | |
| 240 _itemsRendered: false, | |
| 241 | |
| 242 /** | |
| 243 * The page that is currently rendered. | |
| 244 */ | |
| 245 _lastPage: null, | |
| 246 | |
| 247 /** | |
| 248 * The max number of pages to render. One page is equivalent to the height o
f the list. | 237 * The max number of pages to render. One page is equivalent to the height o
f the list. |
| 249 */ | 238 */ |
| 250 _maxPages: 3, | 239 _maxPages: 3, |
| 251 | 240 |
| 252 /** | 241 /** |
| 253 * The currently focused physical item. | 242 * The currently focused physical item. |
| 254 */ | 243 */ |
| 255 _focusedItem: null, | 244 _focusedItem: null, |
| 256 | 245 |
| 257 /** | 246 /** |
| (...skipping 22 matching lines...) Expand all Loading... |
| 280 * The width of each grid item | 269 * The width of each grid item |
| 281 */ | 270 */ |
| 282 _itemWidth: 0, | 271 _itemWidth: 0, |
| 283 | 272 |
| 284 /** | 273 /** |
| 285 * The height of the row in grid layout. | 274 * The height of the row in grid layout. |
| 286 */ | 275 */ |
| 287 _rowHeight: 0, | 276 _rowHeight: 0, |
| 288 | 277 |
| 289 /** | 278 /** |
| 279 * The cost of stamping a template in ms. |
| 280 */ |
| 281 _templateCost: 0, |
| 282 |
| 283 /** |
| 290 * The bottom of the physical content. | 284 * The bottom of the physical content. |
| 291 */ | 285 */ |
| 292 get _physicalBottom() { | 286 get _physicalBottom() { |
| 293 return this._physicalTop + this._physicalSize; | 287 return this._physicalTop + this._physicalSize; |
| 294 }, | 288 }, |
| 295 | 289 |
| 296 /** | 290 /** |
| 297 * The bottom of the scroll. | 291 * The bottom of the scroll. |
| 298 */ | 292 */ |
| 299 get _scrollBottom() { | 293 get _scrollBottom() { |
| (...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 398 }, | 392 }, |
| 399 | 393 |
| 400 get _optPhysicalCount() { | 394 get _optPhysicalCount() { |
| 401 return this._estRowsInView * this._itemsPerRow * this._maxPages; | 395 return this._estRowsInView * this._itemsPerRow * this._maxPages; |
| 402 }, | 396 }, |
| 403 | 397 |
| 404 /** | 398 /** |
| 405 * True if the current list is visible. | 399 * True if the current list is visible. |
| 406 */ | 400 */ |
| 407 get _isVisible() { | 401 get _isVisible() { |
| 408 return this.scrollTarget && Boolean(this.scrollTarget.offsetWidth || this.
scrollTarget.offsetHeight); | 402 return Boolean(this.offsetWidth || this.offsetHeight); |
| 409 }, | 403 }, |
| 410 | 404 |
| 411 /** | 405 /** |
| 412 * Gets the index of the first visible item in the viewport. | 406 * Gets the index of the first visible item in the viewport. |
| 413 * | 407 * |
| 414 * @type {number} | 408 * @type {number} |
| 415 */ | 409 */ |
| 416 get firstVisibleIndex() { | 410 get firstVisibleIndex() { |
| 417 if (this._firstVisibleIndexVal === null) { | 411 if (this._firstVisibleIndexVal === null) { |
| 418 var physicalOffset = Math.floor(this._physicalTop + this._scrollerPaddin
gTop); | 412 var physicalOffset = Math.floor(this._physicalTop + this._scrollerPaddin
gTop); |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 455 physicalOffset += this._getPhysicalSizeIncrement(pidx); | 449 physicalOffset += this._getPhysicalSizeIncrement(pidx); |
| 456 }); | 450 }); |
| 457 } | 451 } |
| 458 } | 452 } |
| 459 return this._lastVisibleIndexVal; | 453 return this._lastVisibleIndexVal; |
| 460 }, | 454 }, |
| 461 | 455 |
| 462 get _defaultScrollTarget() { | 456 get _defaultScrollTarget() { |
| 463 return this; | 457 return this; |
| 464 }, | 458 }, |
| 459 |
| 465 get _virtualRowCount() { | 460 get _virtualRowCount() { |
| 466 return Math.ceil(this._virtualCount / this._itemsPerRow); | 461 return Math.ceil(this._virtualCount / this._itemsPerRow); |
| 467 }, | 462 }, |
| 468 | 463 |
| 469 get _estRowsInView() { | 464 get _estRowsInView() { |
| 470 return Math.ceil(this._viewportHeight / this._rowHeight); | 465 return Math.ceil(this._viewportHeight / this._rowHeight); |
| 471 }, | 466 }, |
| 472 | 467 |
| 473 get _physicalRows() { | 468 get _physicalRows() { |
| 474 return Math.ceil(this._physicalCount / this._itemsPerRow); | 469 return Math.ceil(this._physicalCount / this._itemsPerRow); |
| 475 }, | 470 }, |
| 476 | 471 |
| 477 ready: function() { | 472 ready: function() { |
| 478 this.addEventListener('focus', this._didFocus.bind(this), true); | 473 this.addEventListener('focus', this._didFocus.bind(this), true); |
| 479 }, | 474 }, |
| 480 | 475 |
| 481 attached: function() { | 476 attached: function() { |
| 482 this.updateViewportBoundaries(); | 477 this.updateViewportBoundaries(); |
| 483 this._render(); | 478 if (this._physicalCount === 0) { |
| 479 this._debounceTemplate(this._render); |
| 480 } |
| 484 // `iron-resize` is fired when the list is attached if the event is added | 481 // `iron-resize` is fired when the list is attached if the event is added |
| 485 // before attached causing unnecessary work. | 482 // before attached causing unnecessary work. |
| 486 this.listen(this, 'iron-resize', '_resizeHandler'); | 483 this.listen(this, 'iron-resize', '_resizeHandler'); |
| 487 }, | 484 }, |
| 488 | 485 |
| 489 detached: function() { | 486 detached: function() { |
| 490 this._itemsRendered = false; | |
| 491 this.unlisten(this, 'iron-resize', '_resizeHandler'); | 487 this.unlisten(this, 'iron-resize', '_resizeHandler'); |
| 492 }, | 488 }, |
| 493 | 489 |
| 494 /** | 490 /** |
| 495 * Set the overflow property if this element has its own scrolling region | 491 * Set the overflow property if this element has its own scrolling region |
| 496 */ | 492 */ |
| 497 _setOverflow: function(scrollTarget) { | 493 _setOverflow: function(scrollTarget) { |
| 498 this.style.webkitOverflowScrolling = scrollTarget === this ? 'touch' : ''; | 494 this.style.webkitOverflowScrolling = scrollTarget === this ? 'touch' : ''; |
| 499 this.style.overflow = scrollTarget === this ? 'auto' : ''; | 495 this.style.overflow = scrollTarget === this ? 'auto' : ''; |
| 500 }, | 496 }, |
| (...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 605 currentRatio += tileHeight / hiddenContentSize; | 601 currentRatio += tileHeight / hiddenContentSize; |
| 606 | 602 |
| 607 this._physicalTop += tileHeight; | 603 this._physicalTop += tileHeight; |
| 608 recycledTileSet.push(kth); | 604 recycledTileSet.push(kth); |
| 609 recycledTiles++; | 605 recycledTiles++; |
| 610 kth = (kth + 1) % this._physicalCount; | 606 kth = (kth + 1) % this._physicalCount; |
| 611 } | 607 } |
| 612 } | 608 } |
| 613 | 609 |
| 614 if (recycledTiles === 0) { | 610 if (recycledTiles === 0) { |
| 615 // Try to increase the pool if the list's client height isn't filled up
with physical items | 611 // Try to increase the pool if the list's client isn't filled up with ph
ysical items |
| 616 if (physicalBottom < scrollBottom || this._physicalTop > scrollTop) { | 612 if (physicalBottom < scrollBottom || this._physicalTop > scrollTop) { |
| 617 this._increasePoolIfNeeded(); | 613 this._increasePoolIfNeeded(); |
| 618 } | 614 } |
| 619 } else { | 615 } else { |
| 620 this._virtualStart = this._virtualStart + recycledTiles; | 616 this._virtualStart = this._virtualStart + recycledTiles; |
| 621 this._physicalStart = this._physicalStart + recycledTiles; | 617 this._physicalStart = this._physicalStart + recycledTiles; |
| 622 this._update(recycledTileSet, movingUp); | 618 this._update(recycledTileSet, movingUp); |
| 623 } | 619 } |
| 624 }, | 620 }, |
| 625 | 621 |
| 626 /** | 622 /** |
| 627 * Update the list of items, starting from the `_virtualStart` item. | 623 * Update the list of items, starting from the `_virtualStart` item. |
| 628 * @param {!Array<number>=} itemSet | 624 * @param {!Array<number>=} itemSet |
| 629 * @param {!Array<number>=} movingUp | 625 * @param {!Array<number>=} movingUp |
| 630 */ | 626 */ |
| 631 _update: function(itemSet, movingUp) { | 627 _update: function(itemSet, movingUp) { |
| 632 // manage focus | |
| 633 this._manageFocus(); | 628 this._manageFocus(); |
| 634 // update models | |
| 635 this._assignModels(itemSet); | 629 this._assignModels(itemSet); |
| 636 // measure heights | |
| 637 this._updateMetrics(itemSet); | 630 this._updateMetrics(itemSet); |
| 638 // adjust offset after measuring | 631 // Adjust offset after measuring. |
| 639 if (movingUp) { | 632 if (movingUp) { |
| 640 while (movingUp.length) { | 633 while (movingUp.length) { |
| 641 var idx = movingUp.pop(); | 634 var idx = movingUp.pop(); |
| 642 this._physicalTop -= this._getPhysicalSizeIncrement(idx); | 635 this._physicalTop -= this._getPhysicalSizeIncrement(idx); |
| 643 } | 636 } |
| 644 } | 637 } |
| 645 // update the position of the items | |
| 646 this._positionItems(); | 638 this._positionItems(); |
| 647 // set the scroller size | |
| 648 this._updateScrollerSize(); | 639 this._updateScrollerSize(); |
| 649 // increase the pool of physical items | |
| 650 this._increasePoolIfNeeded(); | 640 this._increasePoolIfNeeded(); |
| 651 }, | 641 }, |
| 652 | 642 |
| 653 /** | 643 /** |
| 654 * Creates a pool of DOM elements and attaches them to the local dom. | 644 * Creates a pool of DOM elements and attaches them to the local dom. |
| 645 * |
| 646 * @param {number} size Size of the pool |
| 655 */ | 647 */ |
| 656 _createPool: function(size) { | 648 _createPool: function(size) { |
| 657 var physicalItems = new Array(size); | 649 var physicalItems = new Array(size); |
| 658 | 650 |
| 659 this._ensureTemplatized(); | 651 this._ensureTemplatized(); |
| 660 | 652 |
| 661 for (var i = 0; i < size; i++) { | 653 for (var i = 0; i < size; i++) { |
| 662 var inst = this.stamp(null); | 654 var inst = this.stamp(null); |
| 663 // First element child is item; Safari doesn't support children[0] | 655 // First element child is item; Safari doesn't support children[0] |
| 664 // on a doc fragment | 656 // on a doc fragment. |
| 665 physicalItems[i] = inst.root.querySelector('*'); | 657 physicalItems[i] = inst.root.querySelector('*'); |
| 666 Polymer.dom(this).appendChild(inst.root); | 658 Polymer.dom(this).appendChild(inst.root); |
| 667 } | 659 } |
| 668 return physicalItems; | 660 return physicalItems; |
| 669 }, | 661 }, |
| 670 | 662 |
| 671 /** | 663 /** |
| 672 * Increases the pool of physical items only if needed. | 664 * Increases the pool of physical items only if needed. |
| 673 * | 665 * |
| 674 * @return {boolean} True if the pool was increased. | 666 * @return {boolean} True if the pool was increased. |
| 675 */ | 667 */ |
| 676 _increasePoolIfNeeded: function() { | 668 _increasePoolIfNeeded: function() { |
| 677 // Base case 1: the list has no height. | 669 // Base case 1: the list has no height. |
| 678 if (this._viewportHeight === 0) { | 670 if (this._viewportHeight === 0) { |
| 679 return false; | 671 return false; |
| 680 } | 672 } |
| 681 // Base case 2: If the physical size is optimal and the list's client heig
ht is full | 673 var self = this; |
| 674 var isClientFull = this._physicalBottom >= this._scrollBottom && |
| 675 this._physicalTop <= this._scrollPosition; |
| 676 |
| 677 // Base case 2: if the physical size is optimal and the list's client heig
ht is full |
| 682 // with physical items, don't increase the pool. | 678 // with physical items, don't increase the pool. |
| 683 var isClientHeightFull = this._physicalBottom >= this._scrollBottom && thi
s._physicalTop <= this._scrollPosition; | 679 if (this._physicalSize >= this._optPhysicalSize && isClientFull) { |
| 684 if (this._physicalSize >= this._optPhysicalSize && isClientHeightFull) { | |
| 685 return false; | 680 return false; |
| 686 } | 681 } |
| 687 // this value should range between [0 <= `currentPage` <= `_maxPages`] | 682 var maxPoolSize = Math.round(this._physicalCount * 0.5); |
| 688 var currentPage = Math.floor(this._physicalSize / this._viewportHeight); | 683 // Increase the pool synchronously until the client is filled. |
| 684 if (!isClientFull) { |
| 685 this._debounceTemplate(this._increasePool.bind(this, maxPoolSize)); |
| 686 return true; |
| 687 } |
| 688 this._yield(function() { |
| 689 self._increasePool(Math.min(maxPoolSize, Math.max(1, Math.round(50 / sel
f._templateCost)))); |
| 690 }); |
| 691 return true; |
| 692 }, |
| 689 | 693 |
| 690 if (currentPage === 0) { | 694 _yield: function(cb) { |
| 691 // fill the first page | 695 var g = window; |
| 692 this._debounceTemplate(this._increasePool.bind(this, Math.round(this._ph
ysicalCount * 0.5))); | 696 var handle = g.requestIdleCallback ? g.requestIdleCallback(cb) : g.setTime
out(cb, 16); |
| 693 } else if (this._lastPage !== currentPage && isClientHeightFull) { | 697 // Polymer/issues/3895 |
| 694 // paint the page and defer the next increase | 698 Polymer.dom.addDebouncer(/** @type {!Polymer.Debouncer} */({ |
| 695 // wait 16ms which is rough enough to get paint cycle. | 699 complete: function() { |
| 696 Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', this._increa
sePool.bind(this, this._itemsPerRow), 16)); | 700 g.cancelIdleCallback ? g.cancelIdleCallback(handle) : g.clearTimeout(h
andle); |
| 697 } else { | 701 cb(); |
| 698 // fill the rest of the pages | 702 } |
| 699 this._debounceTemplate(this._increasePool.bind(this, this._itemsPerRow))
; | 703 })); |
| 700 } | |
| 701 | |
| 702 this._lastPage = currentPage; | |
| 703 | |
| 704 return true; | |
| 705 }, | 704 }, |
| 706 | 705 |
| 707 /** | 706 /** |
| 708 * Increases the pool size. | 707 * Increases the pool size. |
| 709 */ | 708 */ |
| 710 _increasePool: function(missingItems) { | 709 _increasePool: function(missingItems) { |
| 711 var nextPhysicalCount = Math.min( | 710 var nextPhysicalCount = Math.min( |
| 712 this._physicalCount + missingItems, | 711 this._physicalCount + missingItems, |
| 713 this._virtualCount - this._virtualStart, | 712 this._virtualCount - this._virtualStart, |
| 714 Math.max(this.maxPhysicalCount, DEFAULT_PHYSICAL_COUNT) | 713 Math.max(this.maxPhysicalCount, DEFAULT_PHYSICAL_COUNT) |
| 715 ); | 714 ); |
| 716 var prevPhysicalCount = this._physicalCount; | 715 var prevPhysicalCount = this._physicalCount; |
| 717 var delta = nextPhysicalCount - prevPhysicalCount; | 716 var delta = nextPhysicalCount - prevPhysicalCount; |
| 717 var ts = window.performance.now(); |
| 718 | 718 |
| 719 if (delta <= 0) { | 719 if (delta <= 0) { |
| 720 return; | 720 return; |
| 721 } | 721 } |
| 722 | 722 // Concat arrays in place. |
| 723 [].push.apply(this._physicalItems, this._createPool(delta)); | 723 [].push.apply(this._physicalItems, this._createPool(delta)); |
| 724 [].push.apply(this._physicalSizes, new Array(delta)); | 724 [].push.apply(this._physicalSizes, new Array(delta)); |
| 725 | |
| 726 this._physicalCount = prevPhysicalCount + delta; | 725 this._physicalCount = prevPhysicalCount + delta; |
| 727 | 726 // Update the physical start if it needs to preserve the model of the focu
sed item. |
| 728 // update the physical start if we need to preserve the model of the focus
ed item. | |
| 729 // In this situation, the focused item is currently rendered and its model
would | 727 // In this situation, the focused item is currently rendered and its model
would |
| 730 // have changed after increasing the pool if the physical start remained u
nchanged. | 728 // have changed after increasing the pool if the physical start remained u
nchanged. |
| 731 if (this._physicalStart > this._physicalEnd && | 729 if (this._physicalStart > this._physicalEnd && |
| 732 this._isIndexRendered(this._focusedIndex) && | 730 this._isIndexRendered(this._focusedIndex) && |
| 733 this._getPhysicalIndex(this._focusedIndex) < this._physicalEnd) { | 731 this._getPhysicalIndex(this._focusedIndex) < this._physicalEnd) { |
| 734 this._physicalStart = this._physicalStart + delta; | 732 this._physicalStart = this._physicalStart + delta; |
| 735 } | 733 } |
| 736 this._update(); | 734 this._update(); |
| 735 this._templateCost = (window.performance.now() - ts) / delta; |
| 737 }, | 736 }, |
| 738 | 737 |
| 739 /** | 738 /** |
| 740 * Render a new list of items. This method does exactly the same as `update`
, | 739 * Render a new list of items. |
| 741 * but it also ensures that only one `update` cycle is created. | |
| 742 */ | 740 */ |
| 743 _render: function() { | 741 _render: function() { |
| 744 var requiresUpdate = this._virtualCount > 0 || this._physicalCount > 0; | 742 if (this.isAttached && this._isVisible) { |
| 745 | 743 if (this._physicalCount === 0) { |
| 746 if (this.isAttached && !this._itemsRendered && this._isVisible && requires
Update) { | 744 this._increasePool(DEFAULT_PHYSICAL_COUNT); |
| 747 this._lastPage = 0; | 745 } else { |
| 748 this._update(); | 746 this._update(); |
| 749 this._itemsRendered = true; | 747 } |
| 750 } | 748 } |
| 751 }, | 749 }, |
| 752 | 750 |
| 753 /** | 751 /** |
| 754 * Templetizes the user template. | 752 * Templetizes the user template. |
| 755 */ | 753 */ |
| 756 _ensureTemplatized: function() { | 754 _ensureTemplatized: function() { |
| 757 if (!this.ctor) { | 755 if (!this.ctor) { |
| 758 // Template instance props that should be excluded from forwarding | 756 // Template instance props that should be excluded from forwarding |
| 759 var props = {}; | 757 var props = {}; |
| 760 props.__key__ = true; | 758 props.__key__ = true; |
| 761 props[this.as] = true; | 759 props[this.as] = true; |
| 762 props[this.indexAs] = true; | 760 props[this.indexAs] = true; |
| 763 props[this.selectedAs] = true; | 761 props[this.selectedAs] = true; |
| 764 props.tabIndex = true; | 762 props.tabIndex = true; |
| 765 | |
| 766 this._instanceProps = props; | 763 this._instanceProps = props; |
| 767 this._userTemplate = Polymer.dom(this).querySelector('template'); | 764 this._userTemplate = Polymer.dom(this).querySelector('template'); |
| 768 | 765 |
| 769 if (this._userTemplate) { | 766 if (this._userTemplate) { |
| 770 this.templatize(this._userTemplate); | 767 this.templatize(this._userTemplate); |
| 771 } else { | 768 } else { |
| 772 console.warn('iron-list requires a template to be provided in light-do
m'); | 769 console.warn('iron-list requires a template to be provided in light-do
m'); |
| 773 } | 770 } |
| 774 } | 771 } |
| 775 }, | 772 }, |
| (...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 856 el._templateInstance[this.as] = value; | 853 el._templateInstance[this.as] = value; |
| 857 } | 854 } |
| 858 }, | 855 }, |
| 859 | 856 |
| 860 /** | 857 /** |
| 861 * Called when the items have changed. That is, ressignments | 858 * Called when the items have changed. That is, ressignments |
| 862 * to `items`, splices or updates to a single item. | 859 * to `items`, splices or updates to a single item. |
| 863 */ | 860 */ |
| 864 _itemsChanged: function(change) { | 861 _itemsChanged: function(change) { |
| 865 if (change.path === 'items') { | 862 if (change.path === 'items') { |
| 866 // reset items | |
| 867 this._virtualStart = 0; | 863 this._virtualStart = 0; |
| 868 this._physicalTop = 0; | 864 this._physicalTop = 0; |
| 869 this._virtualCount = this.items ? this.items.length : 0; | 865 this._virtualCount = this.items ? this.items.length : 0; |
| 870 this._collection = this.items ? Polymer.Collection.get(this.items) : nul
l; | 866 this._collection = this.items ? Polymer.Collection.get(this.items) : nul
l; |
| 871 this._physicalIndexForKey = {}; | 867 this._physicalIndexForKey = {}; |
| 872 this._firstVisibleIndexVal = null; | 868 this._firstVisibleIndexVal = null; |
| 873 this._lastVisibleIndexVal = null; | 869 this._lastVisibleIndexVal = null; |
| 874 | 870 this._physicalCount = this._physicalCount || 0; |
| 871 this._physicalItems = this._physicalItems || []; |
| 872 this._physicalSizes = this._physicalSizes || []; |
| 873 this._physicalStart = 0; |
| 875 this._resetScrollPosition(0); | 874 this._resetScrollPosition(0); |
| 876 this._removeFocusedItem(); | 875 this._removeFocusedItem(); |
| 877 // create the initial physical items | 876 this._debounceTemplate(this._render); |
| 878 if (!this._physicalItems) { | |
| 879 this._physicalCount = Math.max(1, Math.min(DEFAULT_PHYSICAL_COUNT, thi
s._virtualCount)); | |
| 880 this._physicalItems = this._createPool(this._physicalCount); | |
| 881 this._physicalSizes = new Array(this._physicalCount); | |
| 882 } | |
| 883 | |
| 884 this._physicalStart = 0; | |
| 885 | 877 |
| 886 } else if (change.path === 'items.splices') { | 878 } else if (change.path === 'items.splices') { |
| 887 | |
| 888 this._adjustVirtualIndex(change.value.indexSplices); | 879 this._adjustVirtualIndex(change.value.indexSplices); |
| 889 this._virtualCount = this.items ? this.items.length : 0; | 880 this._virtualCount = this.items ? this.items.length : 0; |
| 881 this._debounceTemplate(this._render); |
| 890 | 882 |
| 891 } else { | 883 } else { |
| 892 // update a single item | |
| 893 this._forwardItemPath(change.path.split('.').slice(1).join('.'), change.
value); | 884 this._forwardItemPath(change.path.split('.').slice(1).join('.'), change.
value); |
| 894 return; | |
| 895 } | 885 } |
| 896 | |
| 897 this._itemsRendered = false; | |
| 898 this._debounceTemplate(this._render); | |
| 899 }, | 886 }, |
| 900 | 887 |
| 901 /** | 888 /** |
| 902 * @param {!Array<!PolymerSplice>} splices | 889 * @param {!Array<!PolymerSplice>} splices |
| 903 */ | 890 */ |
| 904 _adjustVirtualIndex: function(splices) { | 891 _adjustVirtualIndex: function(splices) { |
| 905 splices.forEach(function(splice) { | 892 splices.forEach(function(splice) { |
| 906 // deselect removed items | 893 // deselect removed items |
| 907 splice.removed.forEach(this._removeItem, this); | 894 splice.removed.forEach(this._removeItem, this); |
| 908 // We only need to care about changes happening above the current positi
on | 895 // We only need to care about changes happening above the current positi
on |
| (...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1001 }, itemSet); | 988 }, itemSet); |
| 1002 }, | 989 }, |
| 1003 | 990 |
| 1004 /** | 991 /** |
| 1005 * Updates the height for a given set of items. | 992 * Updates the height for a given set of items. |
| 1006 * | 993 * |
| 1007 * @param {!Array<number>=} itemSet | 994 * @param {!Array<number>=} itemSet |
| 1008 */ | 995 */ |
| 1009 _updateMetrics: function(itemSet) { | 996 _updateMetrics: function(itemSet) { |
| 1010 // Make sure we distributed all the physical items | 997 // Make sure we distributed all the physical items |
| 1011 // so we can measure them | 998 // so we can measure them. |
| 1012 Polymer.dom.flush(); | 999 Polymer.dom.flush(); |
| 1013 | 1000 |
| 1014 var newPhysicalSize = 0; | 1001 var newPhysicalSize = 0; |
| 1015 var oldPhysicalSize = 0; | 1002 var oldPhysicalSize = 0; |
| 1016 var prevAvgCount = this._physicalAverageCount; | 1003 var prevAvgCount = this._physicalAverageCount; |
| 1017 var prevPhysicalAvg = this._physicalAverage; | 1004 var prevPhysicalAvg = this._physicalAverage; |
| 1018 | 1005 |
| 1019 this._iterateItems(function(pidx, vidx) { | 1006 this._iterateItems(function(pidx, vidx) { |
| 1020 | 1007 |
| 1021 oldPhysicalSize += this._physicalSizes[pidx] || 0; | 1008 oldPhysicalSize += this._physicalSizes[pidx] || 0; |
| 1022 this._physicalSizes[pidx] = this._physicalItems[pidx].offsetHeight; | 1009 this._physicalSizes[pidx] = this._physicalItems[pidx].offsetHeight; |
| 1023 newPhysicalSize += this._physicalSizes[pidx]; | 1010 newPhysicalSize += this._physicalSizes[pidx]; |
| 1024 this._physicalAverageCount += this._physicalSizes[pidx] ? 1 : 0; | 1011 this._physicalAverageCount += this._physicalSizes[pidx] ? 1 : 0; |
| 1025 | 1012 |
| 1026 }, itemSet); | 1013 }, itemSet); |
| 1027 | 1014 |
| 1028 this._viewportHeight = this._scrollTargetHeight; | 1015 this._viewportHeight = this._scrollTargetHeight; |
| 1029 if (this.grid) { | 1016 if (this.grid) { |
| 1030 this._updateGridMetrics(); | 1017 this._updateGridMetrics(); |
| 1031 this._physicalSize = Math.ceil(this._physicalCount / this._itemsPerRow)
* this._rowHeight; | 1018 this._physicalSize = Math.ceil(this._physicalCount / this._itemsPerRow)
* this._rowHeight; |
| 1032 } else { | 1019 } else { |
| 1033 this._physicalSize = this._physicalSize + newPhysicalSize - oldPhysicalS
ize; | 1020 this._physicalSize = this._physicalSize + newPhysicalSize - oldPhysicalS
ize; |
| 1034 } | 1021 } |
| 1035 | 1022 |
| 1036 // update the average if we measured something | 1023 // Update the average if it measured something. |
| 1037 if (this._physicalAverageCount !== prevAvgCount) { | 1024 if (this._physicalAverageCount !== prevAvgCount) { |
| 1038 this._physicalAverage = Math.round( | 1025 this._physicalAverage = Math.round( |
| 1039 ((prevPhysicalAvg * prevAvgCount) + newPhysicalSize) / | 1026 ((prevPhysicalAvg * prevAvgCount) + newPhysicalSize) / |
| 1040 this._physicalAverageCount); | 1027 this._physicalAverageCount); |
| 1041 } | 1028 } |
| 1042 }, | 1029 }, |
| 1043 | 1030 |
| 1044 _updateGridMetrics: function() { | 1031 _updateGridMetrics: function() { |
| 1045 this._viewportWidth = this.$.items.offsetWidth; | 1032 this._viewportWidth = this.$.items.offsetWidth; |
| 1046 // Set item width to the value of the _physicalItems offsetWidth | 1033 // Set item width to the value of the _physicalItems offsetWidth |
| (...skipping 10 matching lines...) Expand all Loading... |
| 1057 _positionItems: function() { | 1044 _positionItems: function() { |
| 1058 this._adjustScrollPosition(); | 1045 this._adjustScrollPosition(); |
| 1059 | 1046 |
| 1060 var y = this._physicalTop; | 1047 var y = this._physicalTop; |
| 1061 | 1048 |
| 1062 if (this.grid) { | 1049 if (this.grid) { |
| 1063 var totalItemWidth = this._itemsPerRow * this._itemWidth; | 1050 var totalItemWidth = this._itemsPerRow * this._itemWidth; |
| 1064 var rowOffset = (this._viewportWidth - totalItemWidth) / 2; | 1051 var rowOffset = (this._viewportWidth - totalItemWidth) / 2; |
| 1065 | 1052 |
| 1066 this._iterateItems(function(pidx, vidx) { | 1053 this._iterateItems(function(pidx, vidx) { |
| 1067 | |
| 1068 var modulus = vidx % this._itemsPerRow; | 1054 var modulus = vidx % this._itemsPerRow; |
| 1069 var x = Math.floor((modulus * this._itemWidth) + rowOffset); | 1055 var x = Math.floor((modulus * this._itemWidth) + rowOffset); |
| 1070 | |
| 1071 this.translate3d(x + 'px', y + 'px', 0, this._physicalItems[pidx]); | 1056 this.translate3d(x + 'px', y + 'px', 0, this._physicalItems[pidx]); |
| 1072 | |
| 1073 if (this._shouldRenderNextRow(vidx)) { | 1057 if (this._shouldRenderNextRow(vidx)) { |
| 1074 y += this._rowHeight; | 1058 y += this._rowHeight; |
| 1075 } | 1059 } |
| 1076 | |
| 1077 }); | 1060 }); |
| 1078 } else { | 1061 } else { |
| 1079 this._iterateItems(function(pidx, vidx) { | 1062 this._iterateItems(function(pidx, vidx) { |
| 1080 | |
| 1081 this.translate3d(0, y + 'px', 0, this._physicalItems[pidx]); | 1063 this.translate3d(0, y + 'px', 0, this._physicalItems[pidx]); |
| 1082 y += this._physicalSizes[pidx]; | 1064 y += this._physicalSizes[pidx]; |
| 1083 | |
| 1084 }); | 1065 }); |
| 1085 } | 1066 } |
| 1086 }, | 1067 }, |
| 1087 | 1068 |
| 1088 _getPhysicalSizeIncrement: function(pidx) { | 1069 _getPhysicalSizeIncrement: function(pidx) { |
| 1089 if (!this.grid) { | 1070 if (!this.grid) { |
| 1090 return this._physicalSizes[pidx]; | 1071 return this._physicalSizes[pidx]; |
| 1091 } | 1072 } |
| 1092 if (this._computeVidx(pidx) % this._itemsPerRow !== this._itemsPerRow - 1)
{ | 1073 if (this._computeVidx(pidx) % this._itemsPerRow !== this._itemsPerRow - 1)
{ |
| 1093 return 0; | 1074 return 0; |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1143 this._estScrollHeight = this._virtualRowCount * this._rowHeight; | 1124 this._estScrollHeight = this._virtualRowCount * this._rowHeight; |
| 1144 } else { | 1125 } else { |
| 1145 this._estScrollHeight = (this._physicalBottom + | 1126 this._estScrollHeight = (this._physicalBottom + |
| 1146 Math.max(this._virtualCount - this._physicalCount - this._virtualSta
rt, 0) * this._physicalAverage); | 1127 Math.max(this._virtualCount - this._physicalCount - this._virtualSta
rt, 0) * this._physicalAverage); |
| 1147 } | 1128 } |
| 1148 | 1129 |
| 1149 forceUpdate = forceUpdate || this._scrollHeight === 0; | 1130 forceUpdate = forceUpdate || this._scrollHeight === 0; |
| 1150 forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight
- this._physicalSize; | 1131 forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight
- this._physicalSize; |
| 1151 forceUpdate = forceUpdate || this.grid && this.$.items.style.height < this
._estScrollHeight; | 1132 forceUpdate = forceUpdate || this.grid && this.$.items.style.height < this
._estScrollHeight; |
| 1152 | 1133 |
| 1153 // amortize height adjustment, so it won't trigger repaints very often | 1134 // Amortize height adjustment, so it won't trigger large repaints too ofte
n. |
| 1154 if (forceUpdate || Math.abs(this._estScrollHeight - this._scrollHeight) >=
this._optPhysicalSize) { | 1135 if (forceUpdate || Math.abs(this._estScrollHeight - this._scrollHeight) >=
this._optPhysicalSize) { |
| 1155 this.$.items.style.height = this._estScrollHeight + 'px'; | 1136 this.$.items.style.height = this._estScrollHeight + 'px'; |
| 1156 this._scrollHeight = this._estScrollHeight; | 1137 this._scrollHeight = this._estScrollHeight; |
| 1157 } | 1138 } |
| 1158 }, | 1139 }, |
| 1159 | 1140 |
| 1160 /** | 1141 /** |
| 1161 * Scroll to a specific item in the virtual list regardless | 1142 * Scroll to a specific item in the virtual list regardless |
| 1162 * of the physical items in the DOM tree. | 1143 * of the physical items in the DOM tree. |
| 1163 * | 1144 * |
| 1164 * @method scrollToItem | 1145 * @method scrollToItem |
| 1165 * @param {(Object)} item The item to be scrolled to | 1146 * @param {(Object)} item The item to be scrolled to |
| 1166 */ | 1147 */ |
| 1167 scrollToItem: function(item){ | 1148 scrollToItem: function(item){ |
| 1168 return this.scrollToIndex(this.items.indexOf(item)); | 1149 return this.scrollToIndex(this.items.indexOf(item)); |
| 1169 }, | 1150 }, |
| 1170 | 1151 |
| 1171 /** | 1152 /** |
| 1172 * Scroll to a specific index in the virtual list regardless | 1153 * Scroll to a specific index in the virtual list regardless |
| 1173 * of the physical items in the DOM tree. | 1154 * of the physical items in the DOM tree. |
| 1174 * | 1155 * |
| 1175 * @method scrollToIndex | 1156 * @method scrollToIndex |
| 1176 * @param {number} idx The index of the item | 1157 * @param {number} idx The index of the item |
| 1177 */ | 1158 */ |
| 1178 scrollToIndex: function(idx) { | 1159 scrollToIndex: function(idx) { |
| 1179 if (typeof idx !== 'number' || idx < 0 || idx > this.items.length - 1) { | 1160 if (typeof idx !== 'number' || idx < 0 || idx > this.items.length - 1) { |
| 1180 return; | 1161 return; |
| 1181 } | 1162 } |
| 1163 |
| 1182 Polymer.dom.flush(); | 1164 Polymer.dom.flush(); |
| 1183 // Items should have been rendered prior scrolling to an index. | 1165 // Items should have been rendered prior scrolling to an index. |
| 1184 if (!this._itemsRendered) { | 1166 if (this._physicalCount === 0) { |
| 1185 return; | 1167 return; |
| 1186 } | 1168 } |
| 1187 idx = Math.min(Math.max(idx, 0), this._virtualCount-1); | 1169 idx = Math.min(Math.max(idx, 0), this._virtualCount-1); |
| 1188 // update the virtual start only when needed | 1170 // Update the virtual start only when needed. |
| 1189 if (!this._isIndexRendered(idx) || idx >= this._maxVirtualStart) { | 1171 if (!this._isIndexRendered(idx) || idx >= this._maxVirtualStart) { |
| 1190 this._virtualStart = this.grid ? (idx - this._itemsPerRow * 2) : (idx -
1); | 1172 this._virtualStart = this.grid ? (idx - this._itemsPerRow * 2) : (idx -
1); |
| 1191 } | 1173 } |
| 1192 // manage focus | |
| 1193 this._manageFocus(); | 1174 this._manageFocus(); |
| 1194 // assign new models | |
| 1195 this._assignModels(); | 1175 this._assignModels(); |
| 1196 // measure the new sizes | |
| 1197 this._updateMetrics(); | 1176 this._updateMetrics(); |
| 1198 // estimate new physical offset | 1177 // Estimate new physical offset. |
| 1199 this._physicalTop = Math.floor(this._virtualStart / this._itemsPerRow) *
this._physicalAverage; | 1178 this._physicalTop = Math.floor(this._virtualStart / this._itemsPerRow) *
this._physicalAverage; |
| 1200 | 1179 |
| 1201 var currentTopItem = this._physicalStart; | 1180 var currentTopItem = this._physicalStart; |
| 1202 var currentVirtualItem = this._virtualStart; | 1181 var currentVirtualItem = this._virtualStart; |
| 1203 var targetOffsetTop = 0; | 1182 var targetOffsetTop = 0; |
| 1204 var hiddenContentSize = this._hiddenContentSize; | 1183 var hiddenContentSize = this._hiddenContentSize; |
| 1205 // scroll to the item as much as we can | 1184 // scroll to the item as much as we can. |
| 1206 while (currentVirtualItem < idx && targetOffsetTop <= hiddenContentSize) { | 1185 while (currentVirtualItem < idx && targetOffsetTop <= hiddenContentSize) { |
| 1207 targetOffsetTop = targetOffsetTop + this._getPhysicalSizeIncrement(curre
ntTopItem); | 1186 targetOffsetTop = targetOffsetTop + this._getPhysicalSizeIncrement(curre
ntTopItem); |
| 1208 currentTopItem = (currentTopItem + 1) % this._physicalCount; | 1187 currentTopItem = (currentTopItem + 1) % this._physicalCount; |
| 1209 currentVirtualItem++; | 1188 currentVirtualItem++; |
| 1210 } | 1189 } |
| 1211 // update the scroller size | |
| 1212 this._updateScrollerSize(true); | 1190 this._updateScrollerSize(true); |
| 1213 // update the position of the items | |
| 1214 this._positionItems(); | 1191 this._positionItems(); |
| 1215 // set the new scroll position | |
| 1216 this._resetScrollPosition(this._physicalTop + this._scrollerPaddingTop + t
argetOffsetTop); | 1192 this._resetScrollPosition(this._physicalTop + this._scrollerPaddingTop + t
argetOffsetTop); |
| 1217 // increase the pool of physical items if needed | |
| 1218 this._increasePoolIfNeeded(); | 1193 this._increasePoolIfNeeded(); |
| 1219 // clear cached visible index | 1194 // clear cached visible index. |
| 1220 this._firstVisibleIndexVal = null; | 1195 this._firstVisibleIndexVal = null; |
| 1221 this._lastVisibleIndexVal = null; | 1196 this._lastVisibleIndexVal = null; |
| 1222 }, | 1197 }, |
| 1223 | 1198 |
| 1224 /** | 1199 /** |
| 1225 * Reset the physical average and the average count. | 1200 * Reset the physical average and the average count. |
| 1226 */ | 1201 */ |
| 1227 _resetAverage: function() { | 1202 _resetAverage: function() { |
| 1228 this._physicalAverage = 0; | 1203 this._physicalAverage = 0; |
| 1229 this._physicalAverageCount = 0; | 1204 this._physicalAverageCount = 0; |
| 1230 }, | 1205 }, |
| 1231 | 1206 |
| 1232 /** | 1207 /** |
| 1233 * A handler for the `iron-resize` event triggered by `IronResizableBehavior
` | 1208 * A handler for the `iron-resize` event triggered by `IronResizableBehavior
` |
| 1234 * when the element is resized. | 1209 * when the element is resized. |
| 1235 */ | 1210 */ |
| 1236 _resizeHandler: function() { | 1211 _resizeHandler: function() { |
| 1237 // iOS fires the resize event when the address bar slides up | 1212 // iOS fires the resize event when the address bar slides up |
| 1238 if (IOS && Math.abs(this._viewportHeight - this._scrollTargetHeight) < 100
) { | 1213 if (IOS && Math.abs(this._viewportHeight - this._scrollTargetHeight) < 100
) { |
| 1239 return; | 1214 return; |
| 1240 } | 1215 } |
| 1241 // In Desktop Safari 9.0.3, if the scroll bars are always shown, | 1216 // In Desktop Safari 9.0.3, if the scroll bars are always shown, |
| 1242 // changing the scroll position from a resize handler would result in | 1217 // changing the scroll position from a resize handler would result in |
| 1243 // the scroll position being reset. Waiting 1ms fixes the issue. | 1218 // the scroll position being reset. Waiting 1ms fixes the issue. |
| 1244 Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', function() { | 1219 Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', function() { |
| 1245 this.updateViewportBoundaries(); | 1220 this.updateViewportBoundaries(); |
| 1246 this._render(); | 1221 this._render(); |
| 1247 | 1222 |
| 1248 if (this._itemsRendered && this._physicalItems && this._isVisible) { | 1223 if (this._physicalCount > 0 && this._isVisible) { |
| 1249 this._resetAverage(); | 1224 this._resetAverage(); |
| 1250 this.scrollToIndex(this.firstVisibleIndex); | 1225 this.scrollToIndex(this.firstVisibleIndex); |
| 1251 } | 1226 } |
| 1252 }.bind(this), 1)); | 1227 }.bind(this), 1)); |
| 1253 }, | 1228 }, |
| 1254 | 1229 |
| 1255 _getModelFromItem: function(item) { | 1230 _getModelFromItem: function(item) { |
| 1256 var key = this._collection.getKey(item); | 1231 var key = this._collection.getKey(item); |
| 1257 var pidx = this._physicalIndexForKey[key]; | 1232 var pidx = this._physicalIndexForKey[key]; |
| 1258 | 1233 |
| (...skipping 327 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1586 this._focusPhysicalItem(this._focusedIndex + 1); | 1561 this._focusPhysicalItem(this._focusedIndex + 1); |
| 1587 }, | 1562 }, |
| 1588 | 1563 |
| 1589 _didEnter: function(e) { | 1564 _didEnter: function(e) { |
| 1590 this._focusPhysicalItem(this._focusedIndex); | 1565 this._focusPhysicalItem(this._focusedIndex); |
| 1591 this._selectionHandler(e.detail.keyboardEvent); | 1566 this._selectionHandler(e.detail.keyboardEvent); |
| 1592 } | 1567 } |
| 1593 }); | 1568 }); |
| 1594 | 1569 |
| 1595 })(); | 1570 })(); |
| OLD | NEW |