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 |