| 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 ITEM_WIDTH = 0; | 7 var ITEM_WIDTH = 0; |
| 8 var ITEM_HEIGHT = 1; | 8 var ITEM_HEIGHT = 1; |
| 9 var SECRET_TABINDEX = -100; | 9 var SECRET_TABINDEX = -100; |
| 10 | 10 |
| (...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 105 }, | 105 }, |
| 106 | 106 |
| 107 /** | 107 /** |
| 108 * When `true`, multiple items may be selected at once (in this case, | 108 * When `true`, multiple items may be selected at once (in this case, |
| 109 * `selected` is an array of currently selected items). When `false`, | 109 * `selected` is an array of currently selected items). When `false`, |
| 110 * only one item may be selected at a time. | 110 * only one item may be selected at a time. |
| 111 */ | 111 */ |
| 112 multiSelection: { | 112 multiSelection: { |
| 113 type: Boolean, | 113 type: Boolean, |
| 114 value: false | 114 value: false |
| 115 }, |
| 116 |
| 117 /** |
| 118 * The offset top from the scrolling element to the iron-list element. |
| 119 * This value can be computed using the position returned by `getBoundingC
lientRect()` |
| 120 * although it's preferred to use a constant value when possible. |
| 121 * |
| 122 * This property is useful when an external scrolling element is used and
there's |
| 123 * some offset between the scrolling element and the list. |
| 124 * For example: a header is placed above the list. |
| 125 */ |
| 126 scrollOffset: { |
| 127 type: Number, |
| 128 value: 0 |
| 115 } | 129 } |
| 116 }, | 130 }, |
| 117 | 131 |
| 118 observers: [ | 132 observers: [ |
| 119 '_itemsChanged(items.*)', | 133 '_itemsChanged(items.*)', |
| 120 '_selectionEnabledChanged(selectionEnabled)', | 134 '_selectionEnabledChanged(selectionEnabled)', |
| 121 '_multiSelectionChanged(multiSelection)', | 135 '_multiSelectionChanged(multiSelection)', |
| 122 '_setOverflow(scrollTarget)' | 136 '_setOverflow(scrollTarget, scrollOffset)' |
| 123 ], | 137 ], |
| 124 | 138 |
| 125 behaviors: [ | 139 behaviors: [ |
| 126 Polymer.Templatizer, | 140 Polymer.Templatizer, |
| 127 Polymer.IronResizableBehavior, | 141 Polymer.IronResizableBehavior, |
| 128 Polymer.IronA11yKeysBehavior, | 142 Polymer.IronA11yKeysBehavior, |
| 129 Polymer.IronScrollTargetBehavior | 143 Polymer.IronScrollTargetBehavior |
| 130 ], | 144 ], |
| 131 | 145 |
| 132 keyBindings: { | 146 keyBindings: { |
| (...skipping 171 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 304 | 318 |
| 305 /** | 319 /** |
| 306 * The height of the physical content that isn't on the screen. | 320 * The height of the physical content that isn't on the screen. |
| 307 */ | 321 */ |
| 308 get _hiddenContentSize() { | 322 get _hiddenContentSize() { |
| 309 var size = this.grid ? this._physicalRows * this._rowHeight : this._physic
alSize; | 323 var size = this.grid ? this._physicalRows * this._rowHeight : this._physic
alSize; |
| 310 return size - this._viewportHeight; | 324 return size - this._viewportHeight; |
| 311 }, | 325 }, |
| 312 | 326 |
| 313 /** | 327 /** |
| 328 * The parent node for the _userTemplate. |
| 329 */ |
| 330 get _itemsParent() { |
| 331 return Polymer.dom(Polymer.dom(this._userTemplate).parentNode); |
| 332 }, |
| 333 |
| 334 /** |
| 314 * The maximum scroll top value. | 335 * The maximum scroll top value. |
| 315 */ | 336 */ |
| 316 get _maxScrollTop() { | 337 get _maxScrollTop() { |
| 317 return this._estScrollHeight - this._viewportHeight + this._scrollerPaddin
gTop; | 338 return this._estScrollHeight - this._viewportHeight + this._scrollOffset; |
| 318 }, | 339 }, |
| 319 | 340 |
| 320 /** | 341 /** |
| 321 * The lowest n-th value for an item such that it can be rendered in `_physi
calStart`. | 342 * The lowest n-th value for an item such that it can be rendered in `_physi
calStart`. |
| 322 */ | 343 */ |
| 323 _minVirtualStart: 0, | 344 _minVirtualStart: 0, |
| 324 | 345 |
| 325 /** | 346 /** |
| 326 * The largest n-th value for an item such that it can be rendered in `_phys
icalStart`. | 347 * The largest n-th value for an item such that it can be rendered in `_phys
icalStart`. |
| 327 */ | 348 */ |
| 328 get _maxVirtualStart() { | 349 get _maxVirtualStart() { |
| 329 return Math.max(0, this._virtualCount - this._physicalCount); | 350 return Math.max(0, this._virtualCount - this._physicalCount); |
| 330 }, | 351 }, |
| 331 | 352 |
| 332 /** | 353 /** |
| 333 * The n-th item rendered in the `_physicalStart` tile. | 354 * The n-th item rendered in the `_physicalStart` tile. |
| 334 */ | 355 */ |
| 335 _virtualStartVal: 0, | 356 _virtualStartVal: 0, |
| 336 | 357 |
| 337 set _virtualStart(val) { | 358 set _virtualStart(val) { |
| 338 this._virtualStartVal = Math.min(this._maxVirtualStart, Math.max(this._min
VirtualStart, val)); | 359 val = Math.min(this._maxVirtualStart, Math.max(this._minVirtualStart, val)
); |
| 360 if (this.grid) { |
| 361 val = val - (val % this._itemsPerRow); |
| 362 } |
| 363 this._virtualStartVal = val; |
| 339 }, | 364 }, |
| 340 | 365 |
| 341 get _virtualStart() { | 366 get _virtualStart() { |
| 342 return this._virtualStartVal || 0; | 367 return this._virtualStartVal || 0; |
| 343 }, | 368 }, |
| 344 | 369 |
| 345 /** | 370 /** |
| 346 * The k-th tile that is at the top of the scrolling list. | 371 * The k-th tile that is at the top of the scrolling list. |
| 347 */ | 372 */ |
| 348 _physicalStartVal: 0, | 373 _physicalStartVal: 0, |
| 349 | 374 |
| 350 set _physicalStart(val) { | 375 set _physicalStart(val) { |
| 351 this._physicalStartVal = val % this._physicalCount; | 376 val = val % this._physicalCount; |
| 352 if (this._physicalStartVal < 0) { | 377 if (val < 0) { |
| 353 this._physicalStartVal = this._physicalCount + this._physicalStartVal; | 378 val = this._physicalCount + val; |
| 354 } | 379 } |
| 380 if (this.grid) { |
| 381 val = val - (val % this._itemsPerRow); |
| 382 } |
| 383 this._physicalStartVal = val; |
| 355 this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this
._physicalCount; | 384 this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this
._physicalCount; |
| 356 }, | 385 }, |
| 357 | 386 |
| 358 get _physicalStart() { | 387 get _physicalStart() { |
| 359 return this._physicalStartVal || 0; | 388 return this._physicalStartVal || 0; |
| 360 }, | 389 }, |
| 361 | 390 |
| 362 /** | 391 /** |
| 363 * The number of tiles in the DOM. | 392 * The number of tiles in the DOM. |
| 364 */ | 393 */ |
| (...skipping 17 matching lines...) Expand all Loading... |
| 382 * An optimal physical size such that we will have enough physical items | 411 * An optimal physical size such that we will have enough physical items |
| 383 * to fill up the viewport and recycle when the user scrolls. | 412 * to fill up the viewport and recycle when the user scrolls. |
| 384 * | 413 * |
| 385 * This default value assumes that we will at least have the equivalent | 414 * This default value assumes that we will at least have the equivalent |
| 386 * to a viewport of physical items above and below the user's viewport. | 415 * to a viewport of physical items above and below the user's viewport. |
| 387 */ | 416 */ |
| 388 get _optPhysicalSize() { | 417 get _optPhysicalSize() { |
| 389 if (this.grid) { | 418 if (this.grid) { |
| 390 return this._estRowsInView * this._rowHeight * this._maxPages; | 419 return this._estRowsInView * this._rowHeight * this._maxPages; |
| 391 } | 420 } |
| 392 return this._viewportHeight * this._maxPages; | 421 return this._viewportHeight === 0 ? Infinity : this._viewportHeight * this
._maxPages; |
| 393 }, | 422 }, |
| 394 | 423 |
| 395 /** | 424 /** |
| 396 * True if the current list is visible. | 425 * True if the current list is visible. |
| 397 */ | 426 */ |
| 398 get _isVisible() { | 427 get _isVisible() { |
| 399 return Boolean(this.offsetWidth || this.offsetHeight); | 428 return Boolean(this.offsetWidth || this.offsetHeight); |
| 400 }, | 429 }, |
| 401 | 430 |
| 402 /** | 431 /** |
| 403 * Gets the index of the first visible item in the viewport. | 432 * Gets the index of the first visible item in the viewport. |
| 404 * | 433 * |
| 405 * @type {number} | 434 * @type {number} |
| 406 */ | 435 */ |
| 407 get firstVisibleIndex() { | 436 get firstVisibleIndex() { |
| 408 if (this._firstVisibleIndexVal === null) { | 437 var idx = this._firstVisibleIndexVal; |
| 409 var physicalOffset = Math.floor(this._physicalTop + this._scrollerPaddin
gTop); | 438 if (idx == null) { |
| 439 var physicalOffset = this._physicalTop + this._scrollOffset; |
| 410 | 440 |
| 411 this._firstVisibleIndexVal = this._iterateItems( | 441 idx = this._iterateItems(function(pidx, vidx) { |
| 412 function(pidx, vidx) { | 442 physicalOffset += this._getPhysicalSizeIncrement(pidx); |
| 413 physicalOffset += this._getPhysicalSizeIncrement(pidx); | |
| 414 | 443 |
| 415 if (physicalOffset > this._scrollPosition) { | 444 if (physicalOffset > this._scrollPosition) { |
| 416 return this.grid ? vidx - (vidx % this._itemsPerRow) : vidx; | 445 return this.grid ? vidx - (vidx % this._itemsPerRow) : vidx; |
| 417 } | 446 } |
| 418 // Handle a partially rendered final row in grid mode | 447 // Handle a partially rendered final row in grid mode |
| 419 if (this.grid && this._virtualCount - 1 === vidx) { | 448 if (this.grid && this._virtualCount - 1 === vidx) { |
| 420 return vidx - (vidx % this._itemsPerRow); | 449 return vidx - (vidx % this._itemsPerRow); |
| 421 } | 450 } |
| 422 }) || 0; | 451 }) || 0; |
| 452 this._firstVisibleIndexVal = idx; |
| 423 } | 453 } |
| 424 return this._firstVisibleIndexVal; | 454 return idx; |
| 425 }, | 455 }, |
| 426 | 456 |
| 427 /** | 457 /** |
| 428 * Gets the index of the last visible item in the viewport. | 458 * Gets the index of the last visible item in the viewport. |
| 429 * | 459 * |
| 430 * @type {number} | 460 * @type {number} |
| 431 */ | 461 */ |
| 432 get lastVisibleIndex() { | 462 get lastVisibleIndex() { |
| 433 if (this._lastVisibleIndexVal === null) { | 463 var idx = this._lastVisibleIndexVal; |
| 464 if (idx == null) { |
| 434 if (this.grid) { | 465 if (this.grid) { |
| 435 var lastIndex = this.firstVisibleIndex + this._estRowsInView * this._i
temsPerRow - 1; | 466 idx = Math.min(this._virtualCount, |
| 436 this._lastVisibleIndexVal = Math.min(this._virtualCount, lastIndex); | 467 this.firstVisibleIndex + this._estRowsInView * this._itemsPerRow -
1); |
| 437 } else { | 468 } else { |
| 438 var physicalOffset = this._physicalTop; | 469 var physicalOffset = this._physicalTop + this._scrollOffset; |
| 439 this._iterateItems(function(pidx, vidx) { | 470 this._iterateItems(function(pidx, vidx) { |
| 440 if (physicalOffset < this._scrollBottom) { | 471 if (physicalOffset < this._scrollBottom) { |
| 441 this._lastVisibleIndexVal = vidx; | 472 idx = vidx; |
| 442 } else { | |
| 443 // Break _iterateItems | |
| 444 return true; | |
| 445 } | 473 } |
| 446 physicalOffset += this._getPhysicalSizeIncrement(pidx); | 474 physicalOffset += this._getPhysicalSizeIncrement(pidx); |
| 447 }); | 475 }); |
| 448 } | 476 } |
| 477 this._lastVisibleIndexVal = idx; |
| 449 } | 478 } |
| 450 return this._lastVisibleIndexVal; | 479 return idx; |
| 451 }, | 480 }, |
| 452 | 481 |
| 453 get _defaultScrollTarget() { | 482 get _defaultScrollTarget() { |
| 454 return this; | 483 return this; |
| 455 }, | 484 }, |
| 456 | 485 |
| 457 get _virtualRowCount() { | 486 get _virtualRowCount() { |
| 458 return Math.ceil(this._virtualCount / this._itemsPerRow); | 487 return Math.ceil(this._virtualCount / this._itemsPerRow); |
| 459 }, | 488 }, |
| 460 | 489 |
| 461 get _estRowsInView() { | 490 get _estRowsInView() { |
| 462 return Math.ceil(this._viewportHeight / this._rowHeight); | 491 return Math.ceil(this._viewportHeight / this._rowHeight); |
| 463 }, | 492 }, |
| 464 | 493 |
| 465 get _physicalRows() { | 494 get _physicalRows() { |
| 466 return Math.ceil(this._physicalCount / this._itemsPerRow); | 495 return Math.ceil(this._physicalCount / this._itemsPerRow); |
| 467 }, | 496 }, |
| 468 | 497 |
| 498 get _scrollOffset() { |
| 499 return this._scrollerPaddingTop + this.scrollOffset; |
| 500 }, |
| 501 |
| 469 ready: function() { | 502 ready: function() { |
| 470 this.addEventListener('focus', this._didFocus.bind(this), true); | 503 this.addEventListener('focus', this._didFocus.bind(this), true); |
| 471 }, | 504 }, |
| 472 | 505 |
| 473 attached: function() { | 506 attached: function() { |
| 474 if (this._physicalCount === 0) { | 507 if (this._physicalCount === 0) { |
| 475 this._debounceTemplate(this._render); | 508 this._debounceTemplate(this._render); |
| 476 } | 509 } |
| 477 // `iron-resize` is fired when the list is attached if the event is added | 510 // `iron-resize` is fired when the list is attached if the event is added |
| 478 // before attached causing unnecessary work. | 511 // before attached causing unnecessary work. |
| 479 this.listen(this, 'iron-resize', '_resizeHandler'); | 512 this.listen(this, 'iron-resize', '_resizeHandler'); |
| 480 }, | 513 }, |
| 481 | 514 |
| 482 detached: function() { | 515 detached: function() { |
| 483 this.unlisten(this, 'iron-resize', '_resizeHandler'); | 516 this.unlisten(this, 'iron-resize', '_resizeHandler'); |
| 484 }, | 517 }, |
| 485 | 518 |
| 486 /** | 519 /** |
| 487 * Set the overflow property if this element has its own scrolling region | 520 * Set the overflow property if this element has its own scrolling region |
| 488 */ | 521 */ |
| 489 _setOverflow: function(scrollTarget) { | 522 _setOverflow: function(scrollTarget) { |
| 490 this.style.webkitOverflowScrolling = scrollTarget === this ? 'touch' : ''; | 523 this.style.webkitOverflowScrolling = scrollTarget === this ? 'touch' : ''; |
| 491 this.style.overflow = scrollTarget === this ? 'auto' : ''; | 524 this.style.overflow = scrollTarget === this ? 'auto' : ''; |
| 525 // Clear cache. |
| 526 this._lastVisibleIndexVal = null; |
| 527 this._firstVisibleIndexVal = null; |
| 528 this._debounceTemplate(this._render); |
| 492 }, | 529 }, |
| 493 | 530 |
| 494 /** | 531 /** |
| 495 * Invoke this method if you dynamically update the viewport's | 532 * Invoke this method if you dynamically update the viewport's |
| 496 * size or CSS padding. | 533 * size or CSS padding. |
| 497 * | 534 * |
| 498 * @method updateViewportBoundaries | 535 * @method updateViewportBoundaries |
| 499 */ | 536 */ |
| 500 updateViewportBoundaries: function() { | 537 updateViewportBoundaries: function() { |
| 538 var styles = window.getComputedStyle(this); |
| 501 this._scrollerPaddingTop = this.scrollTarget === this ? 0 : | 539 this._scrollerPaddingTop = this.scrollTarget === this ? 0 : |
| 502 parseInt(window.getComputedStyle(this)['padding-top'], 10); | 540 parseInt(styles['padding-top'], 10); |
| 541 this._isRTL = Boolean(styles.direction === 'rtl'); |
| 503 this._viewportWidth = this.$.items.offsetWidth; | 542 this._viewportWidth = this.$.items.offsetWidth; |
| 504 this._viewportHeight = this._scrollTargetHeight; | 543 this._viewportHeight = this._scrollTargetHeight; |
| 505 this.grid && this._updateGridMetrics(); | 544 this.grid && this._updateGridMetrics(); |
| 506 }, | 545 }, |
| 507 | 546 |
| 508 /** | 547 /** |
| 509 * Recycles the physical items when needed. | 548 * Recycles the physical items when needed. |
| 510 */ | 549 */ |
| 511 _scrollHandler: function() { | 550 _scrollHandler: function() { |
| 512 var scrollTop = Math.max(0, Math.min(this._maxScrollTop, this._scrollTop))
; | 551 var scrollTop = Math.max(0, Math.min(this._maxScrollTop, this._scrollTop))
; |
| 513 var delta = scrollTop - this._scrollPosition; | 552 var delta = scrollTop - this._scrollPosition; |
| 514 var isScrollingDown = delta >= 0; | 553 var isScrollingDown = delta >= 0; |
| 515 // Track the current scroll position. | 554 // Track the current scroll position. |
| 516 this._scrollPosition = scrollTop; | 555 this._scrollPosition = scrollTop; |
| 517 // Clear indexes. | 556 // Clear indexes. |
| 518 this._firstVisibleIndexVal = null; | 557 this._firstVisibleIndexVal = null; |
| 519 this._lastVisibleIndexVal = null; | 558 this._lastVisibleIndexVal = null; |
| 520 | 559 |
| 521 // Random access. | 560 // Random access. |
| 522 if (Math.abs(delta) > this._physicalSize) { | 561 if (Math.abs(delta) > this._physicalSize) { |
| 523 var idxAdjustment = Math.round(delta / this._physicalAverage) * this._i
temsPerRow | 562 delta = delta - this._scrollOffset; |
| 563 var idxAdjustment = Math.round(delta / this._physicalAverage) * this._it
emsPerRow; |
| 524 this._physicalTop = this._physicalTop + delta; | 564 this._physicalTop = this._physicalTop + delta; |
| 525 this._virtualStart = this._virtualStart + idxAdjustment; | 565 this._virtualStart = this._virtualStart + idxAdjustment; |
| 526 this._physicalStart = this._physicalStart + idxAdjustment; | 566 this._physicalStart = this._physicalStart + idxAdjustment; |
| 527 this._update(); | 567 this._update(); |
| 528 } else { | 568 } else { |
| 529 var reusables = this._getReusables(isScrollingDown); | 569 var reusables = this._getReusables(isScrollingDown); |
| 530 if (isScrollingDown) { | 570 if (isScrollingDown) { |
| 531 this._physicalTop = reusables.physicalTop; | 571 this._physicalTop = reusables.physicalTop; |
| 532 this._virtualStart = this._virtualStart + reusables.indexes.length; | 572 this._virtualStart = this._virtualStart + reusables.indexes.length; |
| 533 this._physicalStart = this._physicalStart + reusables.indexes.length; | 573 this._physicalStart = this._physicalStart + reusables.indexes.length; |
| (...skipping 15 matching lines...) Expand all Loading... |
| 549 * | 589 * |
| 550 * @param {boolean} fromTop If the potential reusable items are above the sc
rolling region. | 590 * @param {boolean} fromTop If the potential reusable items are above the sc
rolling region. |
| 551 */ | 591 */ |
| 552 _getReusables: function(fromTop) { | 592 _getReusables: function(fromTop) { |
| 553 var ith, lastIth, offsetContent, physicalItemHeight; | 593 var ith, lastIth, offsetContent, physicalItemHeight; |
| 554 var idxs = []; | 594 var idxs = []; |
| 555 var protectedOffsetContent = this._hiddenContentSize * this._ratio; | 595 var protectedOffsetContent = this._hiddenContentSize * this._ratio; |
| 556 var virtualStart = this._virtualStart; | 596 var virtualStart = this._virtualStart; |
| 557 var virtualEnd = this._virtualEnd; | 597 var virtualEnd = this._virtualEnd; |
| 558 var physicalCount = this._physicalCount; | 598 var physicalCount = this._physicalCount; |
| 559 var physicalTop = this._physicalTop + this._scrollerPaddingTop; | 599 var top = this._physicalTop + this._scrollOffset; |
| 600 var bottom = this._physicalBottom + this._scrollOffset; |
| 560 var scrollTop = this._scrollTop; | 601 var scrollTop = this._scrollTop; |
| 561 var scrollBottom = this._scrollBottom; | 602 var scrollBottom = this._scrollBottom; |
| 562 | 603 |
| 563 if (fromTop) { | 604 if (fromTop) { |
| 564 ith = this._physicalStart; | 605 ith = this._physicalStart; |
| 565 lastIth = this._physicalEnd; | 606 lastIth = this._physicalEnd; |
| 566 offsetContent = scrollTop - physicalTop; | 607 offsetContent = scrollTop - top; |
| 567 } else { | 608 } else { |
| 568 ith = this._physicalEnd; | 609 ith = this._physicalEnd; |
| 569 lastIth = this._physicalStart; | 610 lastIth = this._physicalStart; |
| 570 offsetContent = this._physicalBottom - scrollBottom; | 611 offsetContent = bottom - scrollBottom; |
| 571 } | 612 } |
| 572 while (true) { | 613 while (true) { |
| 573 physicalItemHeight = this._getPhysicalSizeIncrement(ith); | 614 physicalItemHeight = this._getPhysicalSizeIncrement(ith); |
| 574 offsetContent = offsetContent - physicalItemHeight; | 615 offsetContent = offsetContent - physicalItemHeight; |
| 575 if (idxs.length >= physicalCount || offsetContent <= protectedOffsetCont
ent) { | 616 if (idxs.length >= physicalCount || offsetContent <= protectedOffsetCont
ent) { |
| 576 break; | 617 break; |
| 577 } | 618 } |
| 578 if (fromTop) { | 619 if (fromTop) { |
| 579 // Check that index is within the valid range. | 620 // Check that index is within the valid range. |
| 580 if (virtualEnd + idxs.length + 1 >= this._virtualCount) { | 621 if (virtualEnd + idxs.length + 1 >= this._virtualCount) { |
| 581 break; | 622 break; |
| 582 } | 623 } |
| 583 // Check that the index is not visible. | 624 // Check that the index is not visible. |
| 584 if (physicalTop + physicalItemHeight >= scrollTop) { | 625 if (top + physicalItemHeight >= scrollTop - this._scrollOffset) { |
| 585 break; | 626 break; |
| 586 } | 627 } |
| 587 idxs.push(ith); | 628 idxs.push(ith); |
| 588 physicalTop = physicalTop + physicalItemHeight; | 629 top = top + physicalItemHeight; |
| 589 ith = (ith + 1) % physicalCount; | 630 ith = (ith + 1) % physicalCount; |
| 590 } else { | 631 } else { |
| 591 // Check that index is within the valid range. | 632 // Check that index is within the valid range. |
| 592 if (virtualStart - idxs.length <= 0) { | 633 if (virtualStart - idxs.length <= 0) { |
| 593 break; | 634 break; |
| 594 } | 635 } |
| 595 // Check that the index is not visible. | 636 // Check that the index is not visible. |
| 596 if (physicalTop + this._physicalSize - physicalItemHeight <= scrollBot
tom) { | 637 if (top + this._physicalSize - physicalItemHeight <= scrollBottom) { |
| 597 break; | 638 break; |
| 598 } | 639 } |
| 599 idxs.push(ith); | 640 idxs.push(ith); |
| 600 physicalTop = physicalTop - physicalItemHeight; | 641 top = top - physicalItemHeight; |
| 601 ith = (ith === 0) ? physicalCount - 1 : ith - 1; | 642 ith = (ith === 0) ? physicalCount - 1 : ith - 1; |
| 602 } | 643 } |
| 603 } | 644 } |
| 604 return { indexes: idxs, physicalTop: physicalTop - this._scrollerPaddingTo
p }; | 645 return { indexes: idxs, physicalTop: top - this._scrollOffset }; |
| 605 }, | 646 }, |
| 606 | 647 |
| 607 /** | 648 /** |
| 608 * Update the list of items, starting from the `_virtualStart` item. | 649 * Update the list of items, starting from the `_virtualStart` item. |
| 609 * @param {!Array<number>=} itemSet | 650 * @param {!Array<number>=} itemSet |
| 610 * @param {!Array<number>=} movingUp | 651 * @param {!Array<number>=} movingUp |
| 611 */ | 652 */ |
| 612 _update: function(itemSet, movingUp) { | 653 _update: function(itemSet, movingUp) { |
| 613 if (itemSet && itemSet.length === 0) { | 654 if (itemSet && itemSet.length === 0) { |
| 614 return; | 655 return; |
| (...skipping 21 matching lines...) Expand all Loading... |
| 636 _createPool: function(size) { | 677 _createPool: function(size) { |
| 637 var physicalItems = new Array(size); | 678 var physicalItems = new Array(size); |
| 638 | 679 |
| 639 this._ensureTemplatized(); | 680 this._ensureTemplatized(); |
| 640 | 681 |
| 641 for (var i = 0; i < size; i++) { | 682 for (var i = 0; i < size; i++) { |
| 642 var inst = this.stamp(null); | 683 var inst = this.stamp(null); |
| 643 // First element child is item; Safari doesn't support children[0] | 684 // First element child is item; Safari doesn't support children[0] |
| 644 // on a doc fragment. | 685 // on a doc fragment. |
| 645 physicalItems[i] = inst.root.querySelector('*'); | 686 physicalItems[i] = inst.root.querySelector('*'); |
| 646 Polymer.dom(this).appendChild(inst.root); | 687 this._itemsParent.appendChild(inst.root); |
| 647 } | 688 } |
| 648 return physicalItems; | 689 return physicalItems; |
| 649 }, | 690 }, |
| 650 | 691 |
| 651 /** | 692 /** |
| 652 * Increases the pool of physical items only if needed. | 693 * Increases the pool of physical items only if needed. |
| 653 * | 694 * |
| 654 * @return {boolean} True if the pool was increased. | 695 * @return {boolean} True if the pool was increased. |
| 655 */ | 696 */ |
| 656 _increasePoolIfNeeded: function() { | 697 _increasePoolIfNeeded: function() { |
| 657 // Base case 1: the list has no height. | |
| 658 if (this._viewportHeight === 0) { | |
| 659 return false; | |
| 660 } | |
| 661 var self = this; | 698 var self = this; |
| 662 var isClientFull = this._physicalBottom >= this._scrollBottom && | 699 var isClientFull = this._physicalBottom + this._scrollOffset >= this._scro
llBottom && |
| 663 this._physicalTop <= this._scrollPosition; | 700 this._physicalTop - this._scrollOffset <= this._scrollPosition; |
| 664 | 701 // Base case 1: if the physical size is optimal and the list's client heig
ht is full |
| 665 // Base case 2: if the physical size is optimal and the list's client heig
ht is full | |
| 666 // with physical items, don't increase the pool. | 702 // with physical items, don't increase the pool. |
| 667 if (this._physicalSize >= this._optPhysicalSize && isClientFull) { | 703 if (this._physicalSize >= this._optPhysicalSize && isClientFull) { |
| 668 return false; | 704 return false; |
| 669 } | 705 } |
| 670 var maxPoolSize = Math.round(this._physicalCount * 0.5); | 706 var maxPoolSize = Math.round(this._physicalCount * 0.5); |
| 671 // Increase the pool synchronously until the client is filled. | 707 // Increase the pool synchronously until the client is filled. |
| 672 if (!isClientFull) { | 708 if (!isClientFull) { |
| 673 this._debounceTemplate(this._increasePool.bind(this, maxPoolSize)); | 709 this._debounceTemplate(this._increasePool.bind(this, maxPoolSize)); |
| 674 return true; | 710 return true; |
| 675 } | 711 } |
| (...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 749 _ensureTemplatized: function() { | 785 _ensureTemplatized: function() { |
| 750 if (!this.ctor) { | 786 if (!this.ctor) { |
| 751 // Template instance props that should be excluded from forwarding | 787 // Template instance props that should be excluded from forwarding |
| 752 var props = {}; | 788 var props = {}; |
| 753 props.__key__ = true; | 789 props.__key__ = true; |
| 754 props[this.as] = true; | 790 props[this.as] = true; |
| 755 props[this.indexAs] = true; | 791 props[this.indexAs] = true; |
| 756 props[this.selectedAs] = true; | 792 props[this.selectedAs] = true; |
| 757 props.tabIndex = true; | 793 props.tabIndex = true; |
| 758 this._instanceProps = props; | 794 this._instanceProps = props; |
| 759 this._userTemplate = Polymer.dom(this).querySelector('template'); | 795 this._userTemplate = this.queryEffectiveChildren('template'); |
| 760 | 796 |
| 761 if (this._userTemplate) { | 797 if (this._userTemplate) { |
| 762 this.templatize(this._userTemplate); | 798 this.templatize(this._userTemplate); |
| 763 } else { | 799 } else { |
| 764 console.warn('iron-list requires a template to be provided in light-do
m'); | 800 console.warn('iron-list requires a template to be provided in light-do
m'); |
| 765 } | 801 } |
| 766 } | 802 } |
| 767 }, | 803 }, |
| 768 | 804 |
| 769 /** | 805 /** |
| (...skipping 14 matching lines...) Expand all Loading... |
| 784 path.slice(this.as.length + 1), value); | 820 path.slice(this.as.length + 1), value); |
| 785 } | 821 } |
| 786 }, | 822 }, |
| 787 | 823 |
| 788 /** | 824 /** |
| 789 * Implements extension point from Templatizer mixin | 825 * Implements extension point from Templatizer mixin |
| 790 * Called as side-effect of a host property change, responsible for | 826 * Called as side-effect of a host property change, responsible for |
| 791 * notifying parent path change on each row. | 827 * notifying parent path change on each row. |
| 792 */ | 828 */ |
| 793 _forwardParentProp: function(prop, value) { | 829 _forwardParentProp: function(prop, value) { |
| 794 if (this._physicalItems) { | 830 (this._physicalItems || []) |
| 795 this._physicalItems.forEach(function(item) { | 831 .concat([this._offscreenFocusedItem, this._focusBackfillItem]) |
| 796 item._templateInstance[prop] = value; | 832 .forEach(function(item) { |
| 797 }, this); | 833 if (item) { |
| 798 } | 834 item._templateInstance[prop] = value; |
| 835 } |
| 836 }); |
| 799 }, | 837 }, |
| 800 | 838 |
| 801 /** | 839 /** |
| 802 * Implements extension point from Templatizer | 840 * Implements extension point from Templatizer |
| 803 * Called as side-effect of a host path change, responsible for | 841 * Called as side-effect of a host path change, responsible for |
| 804 * notifying parent.<path> path change on each row. | 842 * notifying parent.<path> path change on each row. |
| 805 */ | 843 */ |
| 806 _forwardParentPath: function(path, value) { | 844 _forwardParentPath: function(path, value) { |
| 807 if (this._physicalItems) { | 845 (this._physicalItems || []) |
| 808 this._physicalItems.forEach(function(item) { | 846 .concat([this._offscreenFocusedItem, this._focusBackfillItem]) |
| 809 item._templateInstance.notifyPath(path, value, true); | 847 .forEach(function(item) { |
| 810 }, this); | 848 if (item) { |
| 811 } | 849 item._templateInstance.notifyPath(path, value, true); |
| 850 } |
| 851 }); |
| 812 }, | 852 }, |
| 813 | 853 |
| 814 /** | 854 /** |
| 815 * Called as a side effect of a host items.<key>.<path> path change, | 855 * Called as a side effect of a host items.<key>.<path> path change, |
| 816 * responsible for notifying item.<path> changes. | 856 * responsible for notifying item.<path> changes. |
| 817 */ | 857 */ |
| 818 _forwardItemPath: function(path, value) { | 858 _forwardItemPath: function(path, value) { |
| 819 if (!this._physicalIndexForKey) { | 859 if (!this._physicalIndexForKey) { |
| 820 return; | 860 return; |
| 821 } | 861 } |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 859 this._physicalTop = 0; | 899 this._physicalTop = 0; |
| 860 this._virtualCount = this.items ? this.items.length : 0; | 900 this._virtualCount = this.items ? this.items.length : 0; |
| 861 this._collection = this.items ? Polymer.Collection.get(this.items) : nul
l; | 901 this._collection = this.items ? Polymer.Collection.get(this.items) : nul
l; |
| 862 this._physicalIndexForKey = {}; | 902 this._physicalIndexForKey = {}; |
| 863 this._firstVisibleIndexVal = null; | 903 this._firstVisibleIndexVal = null; |
| 864 this._lastVisibleIndexVal = null; | 904 this._lastVisibleIndexVal = null; |
| 865 this._physicalCount = this._physicalCount || 0; | 905 this._physicalCount = this._physicalCount || 0; |
| 866 this._physicalItems = this._physicalItems || []; | 906 this._physicalItems = this._physicalItems || []; |
| 867 this._physicalSizes = this._physicalSizes || []; | 907 this._physicalSizes = this._physicalSizes || []; |
| 868 this._physicalStart = 0; | 908 this._physicalStart = 0; |
| 869 this._resetScrollPosition(0); | 909 if (this._scrollTop > this._scrollOffset) { |
| 910 this._resetScrollPosition(0); |
| 911 } |
| 870 this._removeFocusedItem(); | 912 this._removeFocusedItem(); |
| 871 this._debounceTemplate(this._render); | 913 this._debounceTemplate(this._render); |
| 872 | 914 |
| 873 } else if (change.path === 'items.splices') { | 915 } else if (change.path === 'items.splices') { |
| 874 this._adjustVirtualIndex(change.value.indexSplices); | 916 this._adjustVirtualIndex(change.value.indexSplices); |
| 875 this._virtualCount = this.items ? this.items.length : 0; | 917 this._virtualCount = this.items ? this.items.length : 0; |
| 876 | 918 |
| 877 this._debounceTemplate(this._render); | 919 this._debounceTemplate(this._render); |
| 878 | |
| 879 } else { | 920 } else { |
| 880 this._forwardItemPath(change.path.split('.').slice(1).join('.'), change.
value); | 921 this._forwardItemPath(change.path.split('.').slice(1).join('.'), change.
value); |
| 881 } | 922 } |
| 882 }, | 923 }, |
| 883 | 924 |
| 884 /** | 925 /** |
| 885 * @param {!Array<!PolymerSplice>} splices | 926 * @param {!Array<!PolymerSplice>} splices |
| 886 */ | 927 */ |
| 887 _adjustVirtualIndex: function(splices) { | 928 _adjustVirtualIndex: function(splices) { |
| 888 splices.forEach(function(splice) { | 929 splices.forEach(function(splice) { |
| (...skipping 144 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1033 | 1074 |
| 1034 var y = this._physicalTop; | 1075 var y = this._physicalTop; |
| 1035 | 1076 |
| 1036 if (this.grid) { | 1077 if (this.grid) { |
| 1037 var totalItemWidth = this._itemsPerRow * this._itemWidth; | 1078 var totalItemWidth = this._itemsPerRow * this._itemWidth; |
| 1038 var rowOffset = (this._viewportWidth - totalItemWidth) / 2; | 1079 var rowOffset = (this._viewportWidth - totalItemWidth) / 2; |
| 1039 | 1080 |
| 1040 this._iterateItems(function(pidx, vidx) { | 1081 this._iterateItems(function(pidx, vidx) { |
| 1041 var modulus = vidx % this._itemsPerRow; | 1082 var modulus = vidx % this._itemsPerRow; |
| 1042 var x = Math.floor((modulus * this._itemWidth) + rowOffset); | 1083 var x = Math.floor((modulus * this._itemWidth) + rowOffset); |
| 1084 if (this._isRTL) { |
| 1085 x = x * -1; |
| 1086 } |
| 1043 this.translate3d(x + 'px', y + 'px', 0, this._physicalItems[pidx]); | 1087 this.translate3d(x + 'px', y + 'px', 0, this._physicalItems[pidx]); |
| 1044 if (this._shouldRenderNextRow(vidx)) { | 1088 if (this._shouldRenderNextRow(vidx)) { |
| 1045 y += this._rowHeight; | 1089 y += this._rowHeight; |
| 1046 } | 1090 } |
| 1047 }); | 1091 }); |
| 1048 } else { | 1092 } else { |
| 1049 this._iterateItems(function(pidx, vidx) { | 1093 this._iterateItems(function(pidx, vidx) { |
| 1050 this.translate3d(0, y + 'px', 0, this._physicalItems[pidx]); | 1094 this.translate3d(0, y + 'px', 0, this._physicalItems[pidx]); |
| 1051 y += this._physicalSizes[pidx]; | 1095 y += this._physicalSizes[pidx]; |
| 1052 }); | 1096 }); |
| (...skipping 21 matching lines...) Expand all Loading... |
| 1074 _shouldRenderNextRow: function(vidx) { | 1118 _shouldRenderNextRow: function(vidx) { |
| 1075 return vidx % this._itemsPerRow === this._itemsPerRow - 1; | 1119 return vidx % this._itemsPerRow === this._itemsPerRow - 1; |
| 1076 }, | 1120 }, |
| 1077 | 1121 |
| 1078 /** | 1122 /** |
| 1079 * Adjusts the scroll position when it was overestimated. | 1123 * Adjusts the scroll position when it was overestimated. |
| 1080 */ | 1124 */ |
| 1081 _adjustScrollPosition: function() { | 1125 _adjustScrollPosition: function() { |
| 1082 var deltaHeight = this._virtualStart === 0 ? this._physicalTop : | 1126 var deltaHeight = this._virtualStart === 0 ? this._physicalTop : |
| 1083 Math.min(this._scrollPosition + this._physicalTop, 0); | 1127 Math.min(this._scrollPosition + this._physicalTop, 0); |
| 1084 | 1128 // Note: the delta can be positive or negative. |
| 1085 if (deltaHeight) { | 1129 if (deltaHeight !== 0) { |
| 1086 this._physicalTop = this._physicalTop - deltaHeight; | 1130 this._physicalTop = this._physicalTop - deltaHeight; |
| 1131 var scrollTop = this._scrollTop; |
| 1087 // juking scroll position during interial scrolling on iOS is no bueno | 1132 // juking scroll position during interial scrolling on iOS is no bueno |
| 1088 if (!IOS_TOUCH_SCROLLING && this._physicalTop !== 0) { | 1133 if (!IOS_TOUCH_SCROLLING && scrollTop > 0) { |
| 1089 this._resetScrollPosition(this._scrollTop - deltaHeight); | 1134 this._resetScrollPosition(scrollTop - deltaHeight); |
| 1090 } | 1135 } |
| 1091 } | 1136 } |
| 1092 }, | 1137 }, |
| 1093 | 1138 |
| 1094 /** | 1139 /** |
| 1095 * Sets the position of the scroll. | 1140 * Sets the position of the scroll. |
| 1096 */ | 1141 */ |
| 1097 _resetScrollPosition: function(pos) { | 1142 _resetScrollPosition: function(pos) { |
| 1098 if (this.scrollTarget) { | 1143 if (this.scrollTarget && pos >= 0) { |
| 1099 this._scrollTop = pos; | 1144 this._scrollTop = pos; |
| 1100 this._scrollPosition = this._scrollTop; | 1145 this._scrollPosition = this._scrollTop; |
| 1101 } | 1146 } |
| 1102 }, | 1147 }, |
| 1103 | 1148 |
| 1104 /** | 1149 /** |
| 1105 * Sets the scroll height, that's the height of the content, | 1150 * Sets the scroll height, that's the height of the content, |
| 1106 * | 1151 * |
| 1107 * @param {boolean=} forceUpdate If true, updates the height no matter what. | 1152 * @param {boolean=} forceUpdate If true, updates the height no matter what. |
| 1108 */ | 1153 */ |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1140 * Scroll to a specific index in the virtual list regardless | 1185 * Scroll to a specific index in the virtual list regardless |
| 1141 * of the physical items in the DOM tree. | 1186 * of the physical items in the DOM tree. |
| 1142 * | 1187 * |
| 1143 * @method scrollToIndex | 1188 * @method scrollToIndex |
| 1144 * @param {number} idx The index of the item | 1189 * @param {number} idx The index of the item |
| 1145 */ | 1190 */ |
| 1146 scrollToIndex: function(idx) { | 1191 scrollToIndex: function(idx) { |
| 1147 if (typeof idx !== 'number' || idx < 0 || idx > this.items.length - 1) { | 1192 if (typeof idx !== 'number' || idx < 0 || idx > this.items.length - 1) { |
| 1148 return; | 1193 return; |
| 1149 } | 1194 } |
| 1150 | |
| 1151 Polymer.dom.flush(); | 1195 Polymer.dom.flush(); |
| 1152 // Items should have been rendered prior scrolling to an index. | 1196 // Items should have been rendered prior scrolling to an index. |
| 1153 if (this._physicalCount === 0) { | 1197 if (this._physicalCount === 0) { |
| 1154 return; | 1198 return; |
| 1155 } | 1199 } |
| 1156 idx = Math.min(Math.max(idx, 0), this._virtualCount-1); | 1200 idx = Math.min(Math.max(idx, 0), this._virtualCount-1); |
| 1157 // Update the virtual start only when needed. | 1201 // Update the virtual start only when needed. |
| 1158 if (!this._isIndexRendered(idx) || idx >= this._maxVirtualStart) { | 1202 if (!this._isIndexRendered(idx) || idx >= this._maxVirtualStart) { |
| 1159 this._virtualStart = this.grid ? (idx - this._itemsPerRow * 2) : (idx -
1); | 1203 this._virtualStart = this.grid ? (idx - this._itemsPerRow * 2) : (idx -
1); |
| 1160 } | 1204 } |
| 1161 this._manageFocus(); | 1205 this._manageFocus(); |
| 1162 this._assignModels(); | 1206 this._assignModels(); |
| 1163 this._updateMetrics(); | 1207 this._updateMetrics(); |
| 1164 // Estimate new physical offset. | 1208 // Estimate new physical offset. |
| 1165 this._physicalTop = Math.floor(this._virtualStart / this._itemsPerRow) *
this._physicalAverage; | 1209 this._physicalTop = Math.floor(this._virtualStart / this._itemsPerRow) *
this._physicalAverage; |
| 1166 | 1210 |
| 1167 var currentTopItem = this._physicalStart; | 1211 var currentTopItem = this._physicalStart; |
| 1168 var currentVirtualItem = this._virtualStart; | 1212 var currentVirtualItem = this._virtualStart; |
| 1169 var targetOffsetTop = 0; | 1213 var targetOffsetTop = 0; |
| 1170 var hiddenContentSize = this._hiddenContentSize; | 1214 var hiddenContentSize = this._hiddenContentSize; |
| 1171 // scroll to the item as much as we can. | 1215 // scroll to the item as much as we can. |
| 1172 while (currentVirtualItem < idx && targetOffsetTop <= hiddenContentSize) { | 1216 while (currentVirtualItem < idx && targetOffsetTop <= hiddenContentSize) { |
| 1173 targetOffsetTop = targetOffsetTop + this._getPhysicalSizeIncrement(curre
ntTopItem); | 1217 targetOffsetTop = targetOffsetTop + this._getPhysicalSizeIncrement(curre
ntTopItem); |
| 1174 currentTopItem = (currentTopItem + 1) % this._physicalCount; | 1218 currentTopItem = (currentTopItem + 1) % this._physicalCount; |
| 1175 currentVirtualItem++; | 1219 currentVirtualItem++; |
| 1176 } | 1220 } |
| 1177 this._updateScrollerSize(true); | 1221 this._updateScrollerSize(true); |
| 1178 this._positionItems(); | 1222 this._positionItems(); |
| 1179 this._resetScrollPosition(this._physicalTop + this._scrollerPaddingTop + t
argetOffsetTop); | 1223 this._resetScrollPosition(this._physicalTop + this._scrollOffset + targetO
ffsetTop); |
| 1180 this._increasePoolIfNeeded(); | 1224 this._increasePoolIfNeeded(); |
| 1181 // clear cached visible index. | 1225 // clear cached visible index. |
| 1182 this._firstVisibleIndexVal = null; | 1226 this._firstVisibleIndexVal = null; |
| 1183 this._lastVisibleIndexVal = null; | 1227 this._lastVisibleIndexVal = null; |
| 1184 }, | 1228 }, |
| 1185 | 1229 |
| 1186 /** | 1230 /** |
| 1187 * Reset the physical average and the average count. | 1231 * Reset the physical average and the average count. |
| 1188 */ | 1232 */ |
| 1189 _resetAverage: function() { | 1233 _resetAverage: function() { |
| 1190 this._physicalAverage = 0; | 1234 this._physicalAverage = 0; |
| 1191 this._physicalAverageCount = 0; | 1235 this._physicalAverageCount = 0; |
| 1192 }, | 1236 }, |
| 1193 | 1237 |
| 1194 /** | 1238 /** |
| 1195 * A handler for the `iron-resize` event triggered by `IronResizableBehavior
` | 1239 * A handler for the `iron-resize` event triggered by `IronResizableBehavior
` |
| 1196 * when the element is resized. | 1240 * when the element is resized. |
| 1197 */ | 1241 */ |
| 1198 _resizeHandler: function() { | 1242 _resizeHandler: function() { |
| 1199 // iOS fires the resize event when the address bar slides up | 1243 this._debounceTemplate(function() { |
| 1200 var delta = Math.abs(this._viewportHeight - this._scrollTargetHeight); | 1244 // Skip the resize event on touch devices when the address bar slides up
. |
| 1201 if (IOS && delta > 0 && delta < 100) { | 1245 var delta = Math.abs(this._viewportHeight - this._scrollTargetHeight); |
| 1202 return; | |
| 1203 } | |
| 1204 // In Desktop Safari 9.0.3, if the scroll bars are always shown, | |
| 1205 // changing the scroll position from a resize handler would result in | |
| 1206 // the scroll position being reset. Waiting 1ms fixes the issue. | |
| 1207 Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', function() { | |
| 1208 this.updateViewportBoundaries(); | 1246 this.updateViewportBoundaries(); |
| 1209 this._render(); | 1247 if (('ontouchstart' in window || navigator.maxTouchPoints > 0) && delta
> 0 && delta < 100) { |
| 1248 return; |
| 1249 } |
| 1210 if (this._isVisible) { | 1250 if (this._isVisible) { |
| 1251 // Reinstall the scroll event listener. |
| 1211 this.toggleScrollListener(true); | 1252 this.toggleScrollListener(true); |
| 1212 if (this._physicalCount > 0) { | 1253 this._resetAverage(); |
| 1213 this._resetAverage(); | 1254 this._render(); |
| 1214 this.scrollToIndex(this.firstVisibleIndex); | |
| 1215 } | |
| 1216 } else { | 1255 } else { |
| 1256 // Uninstall the scroll event listener. |
| 1217 this.toggleScrollListener(false); | 1257 this.toggleScrollListener(false); |
| 1218 } | 1258 } |
| 1219 }.bind(this), 1)); | 1259 }.bind(this)); |
| 1220 }, | 1260 }, |
| 1221 | 1261 |
| 1222 _getModelFromItem: function(item) { | 1262 _getModelFromItem: function(item) { |
| 1223 var key = this._collection.getKey(item); | 1263 var key = this._collection.getKey(item); |
| 1224 var pidx = this._physicalIndexForKey[key]; | 1264 var pidx = this._physicalIndexForKey[key]; |
| 1225 | 1265 |
| 1226 if (pidx != null) { | 1266 if (pidx != null) { |
| 1227 return this._physicalItems[pidx]._templateInstance; | 1267 return this._physicalItems[pidx]._templateInstance; |
| 1228 } | 1268 } |
| 1229 return null; | 1269 return null; |
| (...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1336 /** | 1376 /** |
| 1337 * Select an item from an event object. | 1377 * Select an item from an event object. |
| 1338 */ | 1378 */ |
| 1339 _selectionHandler: function(e) { | 1379 _selectionHandler: function(e) { |
| 1340 var model = this.modelForElement(e.target); | 1380 var model = this.modelForElement(e.target); |
| 1341 if (!model) { | 1381 if (!model) { |
| 1342 return; | 1382 return; |
| 1343 } | 1383 } |
| 1344 var modelTabIndex, activeElTabIndex; | 1384 var modelTabIndex, activeElTabIndex; |
| 1345 var target = Polymer.dom(e).path[0]; | 1385 var target = Polymer.dom(e).path[0]; |
| 1346 var activeEl = Polymer.dom(this.domHost ? this.domHost.root : document).ac
tiveElement; | 1386 var itemsHost = this._itemsParent.node.domHost; |
| 1387 var activeEl = Polymer.dom(itemsHost ? itemsHost.root : document).activeEl
ement; |
| 1347 var physicalItem = this._physicalItems[this._getPhysicalIndex(model[this.i
ndexAs])]; | 1388 var physicalItem = this._physicalItems[this._getPhysicalIndex(model[this.i
ndexAs])]; |
| 1348 // Safari does not focus certain form controls via mouse | 1389 // Safari does not focus certain form controls via mouse |
| 1349 // https://bugs.webkit.org/show_bug.cgi?id=118043 | 1390 // https://bugs.webkit.org/show_bug.cgi?id=118043 |
| 1350 if (target.localName === 'input' || | 1391 if (target.localName === 'input' || |
| 1351 target.localName === 'button' || | 1392 target.localName === 'button' || |
| 1352 target.localName === 'select') { | 1393 target.localName === 'select') { |
| 1353 return; | 1394 return; |
| 1354 } | 1395 } |
| 1355 // Set a temporary tabindex | 1396 // Set a temporary tabindex |
| 1356 modelTabIndex = model.tabIndex; | 1397 modelTabIndex = model.tabIndex; |
| (...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1449 } | 1490 } |
| 1450 // restore the tab index | 1491 // restore the tab index |
| 1451 model.tabIndex = 0; | 1492 model.tabIndex = 0; |
| 1452 // focus the focusable element | 1493 // focus the focusable element |
| 1453 this._focusedIndex = idx; | 1494 this._focusedIndex = idx; |
| 1454 focusable && focusable.focus(); | 1495 focusable && focusable.focus(); |
| 1455 }, | 1496 }, |
| 1456 | 1497 |
| 1457 _removeFocusedItem: function() { | 1498 _removeFocusedItem: function() { |
| 1458 if (this._offscreenFocusedItem) { | 1499 if (this._offscreenFocusedItem) { |
| 1459 Polymer.dom(this).removeChild(this._offscreenFocusedItem); | 1500 this._itemsParent.removeChild(this._offscreenFocusedItem); |
| 1460 } | 1501 } |
| 1461 this._offscreenFocusedItem = null; | 1502 this._offscreenFocusedItem = null; |
| 1462 this._focusBackfillItem = null; | 1503 this._focusBackfillItem = null; |
| 1463 this._focusedItem = null; | 1504 this._focusedItem = null; |
| 1464 this._focusedIndex = -1; | 1505 this._focusedIndex = -1; |
| 1465 }, | 1506 }, |
| 1466 | 1507 |
| 1467 _createFocusBackfillItem: function() { | 1508 _createFocusBackfillItem: function() { |
| 1468 var fidx = this._focusedIndex; | 1509 var fidx = this._focusedIndex; |
| 1469 var pidx = this._getPhysicalIndex(fidx); | 1510 var pidx = this._getPhysicalIndex(fidx); |
| 1470 | 1511 |
| 1471 if (this._offscreenFocusedItem || pidx == null || fidx < 0) { | 1512 if (this._offscreenFocusedItem || pidx == null || fidx < 0) { |
| 1472 return; | 1513 return; |
| 1473 } | 1514 } |
| 1474 if (!this._focusBackfillItem) { | 1515 if (!this._focusBackfillItem) { |
| 1475 // Create a physical item. | 1516 // Create a physical item. |
| 1476 var stampedTemplate = this.stamp(null); | 1517 var stampedTemplate = this.stamp(null); |
| 1477 this._focusBackfillItem = stampedTemplate.root.querySelector('*'); | 1518 this._focusBackfillItem = stampedTemplate.root.querySelector('*'); |
| 1478 Polymer.dom(this).appendChild(stampedTemplate.root); | 1519 this._itemsParent.appendChild(stampedTemplate.root); |
| 1479 } | 1520 } |
| 1480 // Set the offcreen focused physical item. | 1521 // Set the offcreen focused physical item. |
| 1481 this._offscreenFocusedItem = this._physicalItems[pidx]; | 1522 this._offscreenFocusedItem = this._physicalItems[pidx]; |
| 1482 this._offscreenFocusedItem._templateInstance.tabIndex = 0; | 1523 this._offscreenFocusedItem._templateInstance.tabIndex = 0; |
| 1483 this._physicalItems[pidx] = this._focusBackfillItem; | 1524 this._physicalItems[pidx] = this._focusBackfillItem; |
| 1484 // Hide the focused physical. | 1525 // Hide the focused physical. |
| 1485 this.translate3d(0, HIDDEN_Y, 0, this._offscreenFocusedItem); | 1526 this.translate3d(0, HIDDEN_Y, 0, this._offscreenFocusedItem); |
| 1486 }, | 1527 }, |
| 1487 | 1528 |
| 1488 _restoreFocusedItem: function() { | 1529 _restoreFocusedItem: function() { |
| 1489 var pidx, fidx = this._focusedIndex; | 1530 var pidx, fidx = this._focusedIndex; |
| 1490 | 1531 |
| 1491 if (!this._offscreenFocusedItem || this._focusedIndex < 0) { | 1532 if (!this._offscreenFocusedItem || this._focusedIndex < 0) { |
| 1492 return; | 1533 return; |
| 1493 } | 1534 } |
| 1494 // Assign models to the focused index. | 1535 // Assign models to the focused index. |
| 1495 this._assignModels(); | 1536 this._assignModels(); |
| 1496 // Get the new physical index for the focused index. | 1537 // Get the new physical index for the focused index. |
| 1497 pidx = this._getPhysicalIndex(fidx); | 1538 pidx = this._getPhysicalIndex(fidx); |
| 1498 | 1539 |
| 1499 if (pidx != null) { | 1540 var onScreenItem = this._physicalItems[pidx]; |
| 1541 if (!onScreenItem) { |
| 1542 return; |
| 1543 } |
| 1544 var onScreenInstance = onScreenItem._templateInstance; |
| 1545 var offScreenInstance = this._offscreenFocusedItem._templateInstance; |
| 1546 // Restores the physical item only when it has the same model |
| 1547 // as the offscreen one. Use key for comparison since users can set |
| 1548 // a new item via set('items.idx'). |
| 1549 if (onScreenInstance.__key__ === offScreenInstance.__key__) { |
| 1500 // Flip the focus backfill. | 1550 // Flip the focus backfill. |
| 1501 this._focusBackfillItem = this._physicalItems[pidx]; | 1551 this._focusBackfillItem = onScreenItem; |
| 1502 this._focusBackfillItem._templateInstance.tabIndex = -1; | 1552 onScreenInstance.tabIndex = -1; |
| 1503 // Restore the focused physical item. | 1553 // Restore the focused physical item. |
| 1504 this._physicalItems[pidx] = this._offscreenFocusedItem; | 1554 this._physicalItems[pidx] = this._offscreenFocusedItem; |
| 1505 // Reset the offscreen focused item. | |
| 1506 this._offscreenFocusedItem = null; | |
| 1507 // Hide the physical item that backfills. | 1555 // Hide the physical item that backfills. |
| 1508 this.translate3d(0, HIDDEN_Y, 0, this._focusBackfillItem); | 1556 this.translate3d(0, HIDDEN_Y, 0, this._focusBackfillItem); |
| 1557 } else { |
| 1558 this._focusBackfillItem = null; |
| 1509 } | 1559 } |
| 1560 this._offscreenFocusedItem = null; |
| 1510 }, | 1561 }, |
| 1511 | 1562 |
| 1512 _didFocus: function(e) { | 1563 _didFocus: function(e) { |
| 1513 var targetModel = this.modelForElement(e.target); | 1564 var targetModel = this.modelForElement(e.target); |
| 1514 var focusedModel = this._focusedItem ? this._focusedItem._templateInstance
: null; | 1565 var focusedModel = this._focusedItem ? this._focusedItem._templateInstance
: null; |
| 1515 var hasOffscreenFocusedItem = this._offscreenFocusedItem !== null; | 1566 var hasOffscreenFocusedItem = this._offscreenFocusedItem !== null; |
| 1516 var fidx = this._focusedIndex; | 1567 var fidx = this._focusedIndex; |
| 1517 | 1568 |
| 1518 if (!targetModel || !focusedModel) { | 1569 if (!targetModel || !focusedModel) { |
| 1519 return; | 1570 return; |
| (...skipping 28 matching lines...) Expand all Loading... |
| 1548 this._focusPhysicalItem(this._focusedIndex + 1); | 1599 this._focusPhysicalItem(this._focusedIndex + 1); |
| 1549 }, | 1600 }, |
| 1550 | 1601 |
| 1551 _didEnter: function(e) { | 1602 _didEnter: function(e) { |
| 1552 this._focusPhysicalItem(this._focusedIndex); | 1603 this._focusPhysicalItem(this._focusedIndex); |
| 1553 this._selectionHandler(e.detail.keyboardEvent); | 1604 this._selectionHandler(e.detail.keyboardEvent); |
| 1554 } | 1605 } |
| 1555 }); | 1606 }); |
| 1556 | 1607 |
| 1557 })(); | 1608 })(); |
| OLD | NEW |