| 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 MAX_PHYSICAL_COUNT = 500; | 6 var MAX_PHYSICAL_COUNT = 500; |
| 7 var HIDDEN_Y = '-10000px'; | 7 var HIDDEN_Y = '-10000px'; |
| 8 | 8 |
| 9 Polymer({ | 9 Polymer({ |
| 10 | 10 |
| (...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 94 '_setOverflow(scrollTarget)' | 94 '_setOverflow(scrollTarget)' |
| 95 ], | 95 ], |
| 96 | 96 |
| 97 behaviors: [ | 97 behaviors: [ |
| 98 Polymer.Templatizer, | 98 Polymer.Templatizer, |
| 99 Polymer.IronResizableBehavior, | 99 Polymer.IronResizableBehavior, |
| 100 Polymer.IronA11yKeysBehavior, | 100 Polymer.IronA11yKeysBehavior, |
| 101 Polymer.IronScrollTargetBehavior | 101 Polymer.IronScrollTargetBehavior |
| 102 ], | 102 ], |
| 103 | 103 |
| 104 listeners: { | |
| 105 'iron-resize': '_resizeHandler' | |
| 106 }, | |
| 107 | |
| 108 keyBindings: { | 104 keyBindings: { |
| 109 'up': '_didMoveUp', | 105 'up': '_didMoveUp', |
| 110 'down': '_didMoveDown', | 106 'down': '_didMoveDown', |
| 111 'enter': '_didEnter' | 107 'enter': '_didEnter' |
| 112 }, | 108 }, |
| 113 | 109 |
| 114 /** | 110 /** |
| 115 * The ratio of hidden tiles that should remain in the scroll direction. | 111 * The ratio of hidden tiles that should remain in the scroll direction. |
| 116 * Recommended value ~0.5, so it will distribute tiles evely in both directi
ons. | 112 * Recommended value ~0.5, so it will distribute tiles evely in both directi
ons. |
| 117 */ | 113 */ |
| (...skipping 283 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 401 return this; | 397 return this; |
| 402 }, | 398 }, |
| 403 | 399 |
| 404 ready: function() { | 400 ready: function() { |
| 405 this.addEventListener('focus', this._didFocus.bind(this), true); | 401 this.addEventListener('focus', this._didFocus.bind(this), true); |
| 406 }, | 402 }, |
| 407 | 403 |
| 408 attached: function() { | 404 attached: function() { |
| 409 this.updateViewportBoundaries(); | 405 this.updateViewportBoundaries(); |
| 410 this._render(); | 406 this._render(); |
| 407 // `iron-resize` is fired when the list is attached if the event is added |
| 408 // before attached causing unnecessary work. |
| 409 this.listen(this, 'iron-resize', '_resizeHandler'); |
| 411 }, | 410 }, |
| 412 | 411 |
| 413 detached: function() { | 412 detached: function() { |
| 414 this._itemsRendered = false; | 413 this._itemsRendered = false; |
| 414 this.unlisten(this, 'iron-resize', '_resizeHandler'); |
| 415 }, | 415 }, |
| 416 | 416 |
| 417 /** | 417 /** |
| 418 * Set the overflow property if this element has its own scrolling region | 418 * Set the overflow property if this element has its own scrolling region |
| 419 */ | 419 */ |
| 420 _setOverflow: function(scrollTarget) { | 420 _setOverflow: function(scrollTarget) { |
| 421 this.style.webkitOverflowScrolling = scrollTarget === this ? 'touch' : ''; | 421 this.style.webkitOverflowScrolling = scrollTarget === this ? 'touch' : ''; |
| 422 this.style.overflow = scrollTarget === this ? 'auto' : ''; | 422 this.style.overflow = scrollTarget === this ? 'auto' : ''; |
| 423 }, | 423 }, |
| 424 | 424 |
| (...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 525 currentRatio += tileHeight / hiddenContentSize; | 525 currentRatio += tileHeight / hiddenContentSize; |
| 526 | 526 |
| 527 this._physicalTop += tileHeight; | 527 this._physicalTop += tileHeight; |
| 528 recycledTileSet.push(kth); | 528 recycledTileSet.push(kth); |
| 529 recycledTiles++; | 529 recycledTiles++; |
| 530 kth = (kth + 1) % this._physicalCount; | 530 kth = (kth + 1) % this._physicalCount; |
| 531 } | 531 } |
| 532 } | 532 } |
| 533 | 533 |
| 534 if (recycledTiles === 0) { | 534 if (recycledTiles === 0) { |
| 535 // If the list ever reach this case, the physical average is not signifi
cant enough | 535 // Try to increase the pool if the list's client height isn't filled up
with physical items |
| 536 // to create all the items needed to cover the entire viewport. | |
| 537 // e.g. A few items have a height that differs from the average by serve
ral order of magnitude. | |
| 538 if (physicalBottom < scrollBottom || this._physicalTop > scrollTop) { | 536 if (physicalBottom < scrollBottom || this._physicalTop > scrollTop) { |
| 539 this.async(this._increasePool.bind(this, 1)); | 537 this._increasePoolIfNeeded(); |
| 540 } | 538 } |
| 541 } else { | 539 } else { |
| 542 this._virtualStart = this._virtualStart + recycledTiles; | 540 this._virtualStart = this._virtualStart + recycledTiles; |
| 543 this._physicalStart = this._physicalStart + recycledTiles; | 541 this._physicalStart = this._physicalStart + recycledTiles; |
| 544 this._update(recycledTileSet, movingUp); | 542 this._update(recycledTileSet, movingUp); |
| 545 } | 543 } |
| 546 }, | 544 }, |
| 547 | 545 |
| 548 /** | 546 /** |
| 549 * Update the list of items, starting from the `_virtualStart` item. | 547 * Update the list of items, starting from the `_virtualStart` item. |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 584 // First element child is item; Safari doesn't support children[0] | 582 // First element child is item; Safari doesn't support children[0] |
| 585 // on a doc fragment | 583 // on a doc fragment |
| 586 physicalItems[i] = inst.root.querySelector('*'); | 584 physicalItems[i] = inst.root.querySelector('*'); |
| 587 Polymer.dom(this).appendChild(inst.root); | 585 Polymer.dom(this).appendChild(inst.root); |
| 588 } | 586 } |
| 589 return physicalItems; | 587 return physicalItems; |
| 590 }, | 588 }, |
| 591 | 589 |
| 592 /** | 590 /** |
| 593 * Increases the pool of physical items only if needed. | 591 * Increases the pool of physical items only if needed. |
| 594 * This function will allocate additional physical items | 592 * |
| 595 * if the physical size is shorter than `_optPhysicalSize` | 593 * @return {boolean} True if the pool was increased. |
| 596 */ | 594 */ |
| 597 _increasePoolIfNeeded: function() { | 595 _increasePoolIfNeeded: function() { |
| 598 if (this._viewportSize === 0 || this._physicalSize >= this._optPhysicalSiz
e) { | 596 // Base case 1: the list has no size. |
| 597 if (this._viewportSize === 0) { |
| 599 return false; | 598 return false; |
| 600 } | 599 } |
| 601 // 0 <= `currentPage` <= `_maxPages` | 600 // Base case 2: If the physical size is optimal and the list's client heig
ht is full |
| 601 // with physical items, don't increase the pool. |
| 602 var isClientHeightFull = this._physicalBottom >= this._scrollBottom && thi
s._physicalTop <= this._scrollPosition; |
| 603 if (this._physicalSize >= this._optPhysicalSize && isClientHeightFull) { |
| 604 return false; |
| 605 } |
| 606 // this value should range between [0 <= `currentPage` <= `_maxPages`] |
| 602 var currentPage = Math.floor(this._physicalSize / this._viewportSize); | 607 var currentPage = Math.floor(this._physicalSize / this._viewportSize); |
| 608 |
| 603 if (currentPage === 0) { | 609 if (currentPage === 0) { |
| 604 // fill the first page | 610 // fill the first page |
| 605 this._debounceTemplate(this._increasePool.bind(this, Math.round(this._ph
ysicalCount * 0.5))); | 611 this._debounceTemplate(this._increasePool.bind(this, Math.round(this._ph
ysicalCount * 0.5))); |
| 606 } else if (this._lastPage !== currentPage) { | 612 } else if (this._lastPage !== currentPage && isClientHeightFull) { |
| 607 // paint the page and defer the next increase | 613 // paint the page and defer the next increase |
| 608 // wait 16ms which is rough enough to get paint cycle. | 614 // wait 16ms which is rough enough to get paint cycle. |
| 609 Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', this._increa
sePool.bind(this, 1), 16)); | 615 Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', this._increa
sePool.bind(this, 1), 16)); |
| 610 } else { | 616 } else { |
| 611 // fill the rest of the pages | 617 // fill the rest of the pages |
| 612 this._debounceTemplate(this._increasePool.bind(this, 1)); | 618 this._debounceTemplate(this._increasePool.bind(this, 1)); |
| 613 } | 619 } |
| 620 |
| 614 this._lastPage = currentPage; | 621 this._lastPage = currentPage; |
| 622 |
| 615 return true; | 623 return true; |
| 616 }, | 624 }, |
| 617 | 625 |
| 618 /** | 626 /** |
| 619 * Increases the pool size. | 627 * Increases the pool size. |
| 620 */ | 628 */ |
| 621 _increasePool: function(missingItems) { | 629 _increasePool: function(missingItems) { |
| 622 var nextPhysicalCount = Math.min( | 630 var nextPhysicalCount = Math.min( |
| 623 this._physicalCount + missingItems, | 631 this._physicalCount + missingItems, |
| 624 this._virtualCount - this._virtualStart, | 632 this._virtualCount - this._virtualStart, |
| (...skipping 25 matching lines...) Expand all Loading... |
| 650 /** | 658 /** |
| 651 * Render a new list of items. This method does exactly the same as `update`
, | 659 * Render a new list of items. This method does exactly the same as `update`
, |
| 652 * but it also ensures that only one `update` cycle is created. | 660 * but it also ensures that only one `update` cycle is created. |
| 653 */ | 661 */ |
| 654 _render: function() { | 662 _render: function() { |
| 655 var requiresUpdate = this._virtualCount > 0 || this._physicalCount > 0; | 663 var requiresUpdate = this._virtualCount > 0 || this._physicalCount > 0; |
| 656 | 664 |
| 657 if (this.isAttached && !this._itemsRendered && this._isVisible && requires
Update) { | 665 if (this.isAttached && !this._itemsRendered && this._isVisible && requires
Update) { |
| 658 this._lastPage = 0; | 666 this._lastPage = 0; |
| 659 this._update(); | 667 this._update(); |
| 660 this._scrollHandler(); | |
| 661 this._itemsRendered = true; | 668 this._itemsRendered = true; |
| 662 } | 669 } |
| 663 }, | 670 }, |
| 664 | 671 |
| 665 /** | 672 /** |
| 666 * Templetizes the user template. | 673 * Templetizes the user template. |
| 667 */ | 674 */ |
| 668 _ensureTemplatized: function() { | 675 _ensureTemplatized: function() { |
| 669 if (!this.ctor) { | 676 if (!this.ctor) { |
| 670 // Template instance props that should be excluded from forwarding | 677 // Template instance props that should be excluded from forwarding |
| (...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 745 var idx = this._physicalIndexForKey[key]; | 752 var idx = this._physicalIndexForKey[key]; |
| 746 var el = this._physicalItems[idx]; | 753 var el = this._physicalItems[idx]; |
| 747 | 754 |
| 748 | 755 |
| 749 if (idx === this._focusedIndex && this._offscreenFocusedItem) { | 756 if (idx === this._focusedIndex && this._offscreenFocusedItem) { |
| 750 el = this._offscreenFocusedItem; | 757 el = this._offscreenFocusedItem; |
| 751 } | 758 } |
| 752 if (!el) { | 759 if (!el) { |
| 753 return; | 760 return; |
| 754 } | 761 } |
| 755 | 762 |
| 756 inst = el._templateInstance; | 763 inst = el._templateInstance; |
| 757 | 764 |
| 758 if (inst.__key__ !== key) { | 765 if (inst.__key__ !== key) { |
| 759 return; | 766 return; |
| 760 } | 767 } |
| 761 if (dot >= 0) { | 768 if (dot >= 0) { |
| 762 path = this.as + '.' + path.substring(dot+1); | 769 path = this.as + '.' + path.substring(dot+1); |
| 763 inst.notifyPath(path, value, true); | 770 inst.notifyPath(path, value, true); |
| 764 } else { | 771 } else { |
| 765 inst[this.as] = value; | 772 inst[this.as] = value; |
| (...skipping 263 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1029 while (currentVirtualItem < idx && targetOffsetTop < hiddenContentSize) { | 1036 while (currentVirtualItem < idx && targetOffsetTop < hiddenContentSize) { |
| 1030 targetOffsetTop = targetOffsetTop + this._physicalSizes[currentTopItem]; | 1037 targetOffsetTop = targetOffsetTop + this._physicalSizes[currentTopItem]; |
| 1031 currentTopItem = (currentTopItem + 1) % this._physicalCount; | 1038 currentTopItem = (currentTopItem + 1) % this._physicalCount; |
| 1032 currentVirtualItem++; | 1039 currentVirtualItem++; |
| 1033 } | 1040 } |
| 1034 // update the scroller size | 1041 // update the scroller size |
| 1035 this._updateScrollerSize(true); | 1042 this._updateScrollerSize(true); |
| 1036 // update the position of the items | 1043 // update the position of the items |
| 1037 this._positionItems(); | 1044 this._positionItems(); |
| 1038 // set the new scroll position | 1045 // set the new scroll position |
| 1039 this._resetScrollPosition(this._physicalTop + this._scrollerPaddingTop + t
argetOffsetTop + 1); | 1046 this._resetScrollPosition(this._physicalTop + this._scrollerPaddingTop + t
argetOffsetTop); |
| 1040 // increase the pool of physical items if needed | 1047 // increase the pool of physical items if needed |
| 1041 this._increasePoolIfNeeded(); | 1048 this._increasePoolIfNeeded(); |
| 1042 // clear cached visible index | 1049 // clear cached visible index |
| 1043 this._firstVisibleIndexVal = null; | 1050 this._firstVisibleIndexVal = null; |
| 1044 this._lastVisibleIndexVal = null; | 1051 this._lastVisibleIndexVal = null; |
| 1045 }, | 1052 }, |
| 1046 | 1053 |
| 1047 /** | 1054 /** |
| 1048 * Reset the physical average and the average count. | 1055 * Reset the physical average and the average count. |
| 1049 */ | 1056 */ |
| 1050 _resetAverage: function() { | 1057 _resetAverage: function() { |
| 1051 this._physicalAverage = 0; | 1058 this._physicalAverage = 0; |
| 1052 this._physicalAverageCount = 0; | 1059 this._physicalAverageCount = 0; |
| 1053 }, | 1060 }, |
| 1054 | 1061 |
| 1055 /** | 1062 /** |
| 1056 * A handler for the `iron-resize` event triggered by `IronResizableBehavior
` | 1063 * A handler for the `iron-resize` event triggered by `IronResizableBehavior
` |
| 1057 * when the element is resized. | 1064 * when the element is resized. |
| 1058 */ | 1065 */ |
| 1059 _resizeHandler: function() { | 1066 _resizeHandler: function() { |
| 1060 // iOS fires the resize event when the address bar slides up | 1067 // iOS fires the resize event when the address bar slides up |
| 1061 if (IOS && Math.abs(this._viewportSize - this._scrollTargetHeight) < 100)
{ | 1068 if (IOS && Math.abs(this._viewportSize - this._scrollTargetHeight) < 100)
{ |
| 1062 return; | 1069 return; |
| 1063 } | 1070 } |
| 1064 this._debounceTemplate(function() { | 1071 // In Desktop Safari 9.0.3, if the scroll bars are always shown, |
| 1065 this._render(); | 1072 // changing the scroll position from a resize handler would result in |
| 1066 if (this._itemsRendered && this._physicalItems && this._isVisible) { | 1073 // the scroll position being reset. Waiting 1ms fixes the issue. |
| 1067 this._resetAverage(); | 1074 Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', |
| 1068 this.updateViewportBoundaries(); | 1075 function() { |
| 1069 this.scrollToIndex(this.firstVisibleIndex); | 1076 this._render(); |
| 1070 } | 1077 |
| 1071 }); | 1078 if (this._itemsRendered && this._physicalItems && this._isVisible) { |
| 1079 this._resetAverage(); |
| 1080 this.updateViewportBoundaries(); |
| 1081 this.scrollToIndex(this.firstVisibleIndex); |
| 1082 } |
| 1083 }.bind(this), 1)); |
| 1072 }, | 1084 }, |
| 1073 | 1085 |
| 1074 _getModelFromItem: function(item) { | 1086 _getModelFromItem: function(item) { |
| 1075 var key = this._collection.getKey(item); | 1087 var key = this._collection.getKey(item); |
| 1076 var pidx = this._physicalIndexForKey[key]; | 1088 var pidx = this._physicalIndexForKey[key]; |
| 1077 | 1089 |
| 1078 if (pidx != null) { | 1090 if (pidx != null) { |
| 1079 return this._physicalItems[pidx]._templateInstance; | 1091 return this._physicalItems[pidx]._templateInstance; |
| 1080 } | 1092 } |
| 1081 return null; | 1093 return null; |
| (...skipping 301 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1383 this._focusPhysicalItem(this._focusedIndex + 1); | 1395 this._focusPhysicalItem(this._focusedIndex + 1); |
| 1384 }, | 1396 }, |
| 1385 | 1397 |
| 1386 _didEnter: function(e) { | 1398 _didEnter: function(e) { |
| 1387 this._focusPhysicalItem(this._focusedIndex); | 1399 this._focusPhysicalItem(this._focusedIndex); |
| 1388 this._selectionHandler(e.detail.keyboardEvent); | 1400 this._selectionHandler(e.detail.keyboardEvent); |
| 1389 } | 1401 } |
| 1390 }); | 1402 }); |
| 1391 | 1403 |
| 1392 })(); | 1404 })(); |
| OLD | NEW |