Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(88)

Side by Side Diff: third_party/polymer/v1_0/components-chromium/iron-list/iron-list-extracted.js

Issue 1766433002: Roll Polymer to 1.3.1 (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: rebase Created 4 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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 100 matching lines...) Expand 10 before | Expand all | Expand 10 after
111 'enter': '_didEnter' 111 'enter': '_didEnter'
112 }, 112 },
113 113
114 /** 114 /**
115 * The ratio of hidden tiles that should remain in the scroll direction. 115 * 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. 116 * Recommended value ~0.5, so it will distribute tiles evely in both directi ons.
117 */ 117 */
118 _ratio: 0.5, 118 _ratio: 0.5,
119 119
120 /** 120 /**
121 * The padding-top value of the `scroller` element 121 * The padding-top value for the list.
122 */ 122 */
123 _scrollerPaddingTop: 0, 123 _scrollerPaddingTop: 0,
124 124
125 /** 125 /**
126 * This value is the same as `scrollTop`. 126 * This value is the same as `scrollTop`.
127 */ 127 */
128 _scrollPosition: 0, 128 _scrollPosition: 0,
129 129
130 /** 130 /**
131 * The number of tiles in the DOM.
132 */
133 _physicalCount: 0,
134
135 /**
136 * The k-th tile that is at the top of the scrolling list.
137 */
138 _physicalStart: 0,
139
140 /**
141 * The k-th tile that is at the bottom of the scrolling list.
142 */
143 _physicalEnd: 0,
144
145 /**
146 * The sum of the heights of all the tiles in the DOM. 131 * The sum of the heights of all the tiles in the DOM.
147 */ 132 */
148 _physicalSize: 0, 133 _physicalSize: 0,
149 134
150 /** 135 /**
151 * The average `F` of the tiles observed till now. 136 * The average `F` of the tiles observed till now.
152 */ 137 */
153 _physicalAverage: 0, 138 _physicalAverage: 0,
154 139
155 /** 140 /**
156 * The number of tiles which `offsetHeight` > 0 observed until now. 141 * The number of tiles which `offsetHeight` > 0 observed until now.
157 */ 142 */
158 _physicalAverageCount: 0, 143 _physicalAverageCount: 0,
159 144
160 /** 145 /**
161 * The Y position of the item rendered in the `_physicalStart` 146 * The Y position of the item rendered in the `_physicalStart`
162 * tile relative to the scrolling list. 147 * tile relative to the scrolling list.
163 */ 148 */
164 _physicalTop: 0, 149 _physicalTop: 0,
165 150
166 /** 151 /**
167 * The number of items in the list. 152 * The number of items in the list.
168 */ 153 */
169 _virtualCount: 0, 154 _virtualCount: 0,
170 155
171 /** 156 /**
172 * The n-th item rendered in the `_physicalStart` tile.
173 */
174 _virtualStartVal: 0,
175
176 /**
177 * A map between an item key and its physical item index 157 * A map between an item key and its physical item index
178 */ 158 */
179 _physicalIndexForKey: null, 159 _physicalIndexForKey: null,
180 160
181 /** 161 /**
182 * The estimated scroll height based on `_physicalAverage` 162 * The estimated scroll height based on `_physicalAverage`
183 */ 163 */
184 _estScrollHeight: 0, 164 _estScrollHeight: 0,
185 165
186 /** 166 /**
(...skipping 25 matching lines...) Expand all
212 */ 192 */
213 _firstVisibleIndexVal: null, 193 _firstVisibleIndexVal: null,
214 194
215 /** 195 /**
216 * A cached value for the last visible index. 196 * A cached value for the last visible index.
217 * See `lastVisibleIndex` 197 * See `lastVisibleIndex`
218 * @type {?number} 198 * @type {?number}
219 */ 199 */
220 _lastVisibleIndexVal: null, 200 _lastVisibleIndexVal: null,
221 201
222
223 /** 202 /**
224 * A Polymer collection for the items. 203 * A Polymer collection for the items.
225 * @type {?Polymer.Collection} 204 * @type {?Polymer.Collection}
226 */ 205 */
227 _collection: null, 206 _collection: null,
228 207
229 /** 208 /**
230 * True if the current item list was rendered for the first time 209 * True if the current item list was rendered for the first time
231 * after attached. 210 * after attached.
232 */ 211 */
233 _itemsRendered: false, 212 _itemsRendered: false,
234 213
235 /** 214 /**
236 * The page that is currently rendered. 215 * The page that is currently rendered.
237 */ 216 */
238 _lastPage: null, 217 _lastPage: null,
239 218
240 /** 219 /**
241 * The max number of pages to render. One page is equivalent to the height o f the list. 220 * The max number of pages to render. One page is equivalent to the height o f the list.
242 */ 221 */
243 _maxPages: 3, 222 _maxPages: 3,
244 223
245 /** 224 /**
246 * The currently focused item index. 225 * The currently focused physical item.
247 */ 226 */
248 _focusedIndex: 0, 227 _focusedItem: null,
228
229 /**
230 * The index of the `_focusedItem`.
231 */
232 _focusedIndex: -1,
249 233
250 /** 234 /**
251 * The the item that is focused if it is moved offscreen. 235 * The the item that is focused if it is moved offscreen.
252 * @private {?TemplatizerNode} 236 * @private {?TemplatizerNode}
253 */ 237 */
254 _offscreenFocusedItem: null, 238 _offscreenFocusedItem: null,
255 239
256 /** 240 /**
257 * The item that backfills the `_offscreenFocusedItem` in the physical items 241 * The item that backfills the `_offscreenFocusedItem` in the physical items
258 * list when that item is moved offscreen. 242 * list when that item is moved offscreen.
(...skipping 15 matching lines...) Expand all
274 }, 258 },
275 259
276 /** 260 /**
277 * The n-th item rendered in the last physical item. 261 * The n-th item rendered in the last physical item.
278 */ 262 */
279 get _virtualEnd() { 263 get _virtualEnd() {
280 return this._virtualStart + this._physicalCount - 1; 264 return this._virtualStart + this._physicalCount - 1;
281 }, 265 },
282 266
283 /** 267 /**
268 * The height of the physical content that isn't on the screen.
269 */
270 get _hiddenContentSize() {
271 return this._physicalSize - this._viewportSize;
272 },
273
274 /**
275 * The maximum scroll top value.
276 */
277 get _maxScrollTop() {
278 return this._estScrollHeight - this._viewportSize + this._scrollerPaddingT op;
279 },
280
281 /**
284 * The lowest n-th value for an item such that it can be rendered in `_physi calStart`. 282 * The lowest n-th value for an item such that it can be rendered in `_physi calStart`.
285 */ 283 */
286 _minVirtualStart: 0, 284 _minVirtualStart: 0,
287 285
288 /** 286 /**
289 * The largest n-th value for an item such that it can be rendered in `_phys icalStart`. 287 * The largest n-th value for an item such that it can be rendered in `_phys icalStart`.
290 */ 288 */
291 get _maxVirtualStart() { 289 get _maxVirtualStart() {
292 return Math.max(0, this._virtualCount - this._physicalCount); 290 return Math.max(0, this._virtualCount - this._physicalCount);
293 }, 291 },
294 292
295 /** 293 /**
296 * The height of the physical content that isn't on the screen. 294 * The n-th item rendered in the `_physicalStart` tile.
297 */ 295 */
298 get _hiddenContentSize() { 296 _virtualStartVal: 0,
299 return this._physicalSize - this._viewportSize; 297
298 set _virtualStart(val) {
299 this._virtualStartVal = Math.min(this._maxVirtualStart, Math.max(this._min VirtualStart, val));
300 },
301
302 get _virtualStart() {
303 return this._virtualStartVal || 0;
300 }, 304 },
301 305
302 /** 306 /**
303 * The maximum scroll top value. 307 * The k-th tile that is at the top of the scrolling list.
304 */ 308 */
305 get _maxScrollTop() { 309 _physicalStartVal: 0,
306 return this._estScrollHeight - this._viewportSize; 310
311 set _physicalStart(val) {
312 this._physicalStartVal = val % this._physicalCount;
313 if (this._physicalStartVal < 0) {
314 this._physicalStartVal = this._physicalCount + this._physicalStartVal;
315 }
316 this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this ._physicalCount;
317 },
318
319 get _physicalStart() {
320 return this._physicalStartVal || 0;
307 }, 321 },
308 322
309 /** 323 /**
310 * Sets the n-th item rendered in `_physicalStart` 324 * The number of tiles in the DOM.
311 */ 325 */
312 set _virtualStart(val) { 326 _physicalCountVal: 0,
313 // clamp the value so that _minVirtualStart <= val <= _maxVirtualStart 327
314 this._virtualStartVal = Math.min(this._maxVirtualStart, Math.max(this._min VirtualStart, val)); 328 set _physicalCount(val) {
315 if (this._physicalCount === 0) { 329 this._physicalCountVal = val;
316 this._physicalStart = 0; 330 this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this ._physicalCount;
317 this._physicalEnd = 0; 331 },
318 } else { 332
319 this._physicalStart = this._virtualStartVal % this._physicalCount; 333 get _physicalCount() {
320 this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % th is._physicalCount; 334 return this._physicalCountVal;
321 }
322 }, 335 },
323 336
324 /** 337 /**
325 * Gets the n-th item rendered in `_physicalStart` 338 * The k-th tile that is at the bottom of the scrolling list.
326 */ 339 */
327 get _virtualStart() { 340 _physicalEnd: 0,
328 return this._virtualStartVal;
329 },
330 341
331 /** 342 /**
332 * An optimal physical size such that we will have enough physical items 343 * An optimal physical size such that we will have enough physical items
333 * to fill up the viewport and recycle when the user scrolls. 344 * to fill up the viewport and recycle when the user scrolls.
334 * 345 *
335 * This default value assumes that we will at least have the equivalent 346 * This default value assumes that we will at least have the equivalent
336 * to a viewport of physical items above and below the user's viewport. 347 * to a viewport of physical items above and below the user's viewport.
337 */ 348 */
338 get _optPhysicalSize() { 349 get _optPhysicalSize() {
339 return this._viewportSize * this._maxPages; 350 return this._viewportSize * this._maxPages;
340 }, 351 },
341 352
342 /** 353 /**
343 * True if the current list is visible. 354 * True if the current list is visible.
344 */ 355 */
345 get _isVisible() { 356 get _isVisible() {
346 return this.scrollTarget && Boolean(this.scrollTarget.offsetWidth || this. scrollTarget.offsetHeight); 357 return this.scrollTarget && Boolean(this.scrollTarget.offsetWidth || this. scrollTarget.offsetHeight);
347 }, 358 },
348 359
349 /** 360 /**
350 * Gets the index of the first visible item in the viewport. 361 * Gets the index of the first visible item in the viewport.
351 * 362 *
352 * @type {number} 363 * @type {number}
353 */ 364 */
354 get firstVisibleIndex() { 365 get firstVisibleIndex() {
355 if (this._firstVisibleIndexVal === null) { 366 if (this._firstVisibleIndexVal === null) {
356 var physicalOffset = this._physicalTop; 367 var physicalOffset = this._physicalTop + this._scrollerPaddingTop;
357 368
358 this._firstVisibleIndexVal = this._iterateItems( 369 this._firstVisibleIndexVal = this._iterateItems(
359 function(pidx, vidx) { 370 function(pidx, vidx) {
360 physicalOffset += this._physicalSizes[pidx]; 371 physicalOffset += this._physicalSizes[pidx];
361
362 if (physicalOffset > this._scrollPosition) { 372 if (physicalOffset > this._scrollPosition) {
363 return vidx; 373 return vidx;
364 } 374 }
365 }) || 0; 375 }) || 0;
366 } 376 }
367 return this._firstVisibleIndexVal; 377 return this._firstVisibleIndexVal;
368 }, 378 },
369 379
370 /** 380 /**
371 * Gets the index of the last visible item in the viewport. 381 * Gets the index of the last visible item in the viewport.
372 * 382 *
373 * @type {number} 383 * @type {number}
374 */ 384 */
375 get lastVisibleIndex() { 385 get lastVisibleIndex() {
376 if (this._lastVisibleIndexVal === null) { 386 if (this._lastVisibleIndexVal === null) {
377 var physicalOffset = this._physicalTop; 387 var physicalOffset = this._physicalTop;
378 388
379 this._iterateItems(function(pidx, vidx) { 389 this._iterateItems(function(pidx, vidx) {
380 physicalOffset += this._physicalSizes[pidx]; 390 physicalOffset += this._physicalSizes[pidx];
381 391
382 if(physicalOffset <= this._scrollBottom) { 392 if (physicalOffset <= this._scrollBottom) {
383 this._lastVisibleIndexVal = vidx; 393 this._lastVisibleIndexVal = vidx;
384 } 394 }
385 }); 395 });
386 } 396 }
387 return this._lastVisibleIndexVal; 397 return this._lastVisibleIndexVal;
388 }, 398 },
389 399
400 get _defaultScrollTarget() {
401 return this;
402 },
403
390 ready: function() { 404 ready: function() {
391 this.addEventListener('focus', this._didFocus.bind(this), true); 405 this.addEventListener('focus', this._didFocus.bind(this), true);
392 }, 406 },
393 407
394 attached: function() { 408 attached: function() {
395 this.updateViewportBoundaries(); 409 this.updateViewportBoundaries();
396 this._render(); 410 this._render();
397 }, 411 },
398 412
399 detached: function() { 413 detached: function() {
400 this._itemsRendered = false; 414 this._itemsRendered = false;
401 }, 415 },
402 416
403 get _defaultScrollTarget() {
404 return this;
405 },
406
407 /** 417 /**
408 * 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
409 */ 419 */
410 _setOverflow: function(scrollTarget) { 420 _setOverflow: function(scrollTarget) {
411 this.style.webkitOverflowScrolling = scrollTarget === this ? 'touch' : ''; 421 this.style.webkitOverflowScrolling = scrollTarget === this ? 'touch' : '';
412 this.style.overflow = scrollTarget === this ? 'auto' : ''; 422 this.style.overflow = scrollTarget === this ? 'auto' : '';
413 }, 423 },
414 424
415 /** 425 /**
416 * Invoke this method if you dynamically update the viewport's 426 * Invoke this method if you dynamically update the viewport's
417 * size or CSS padding. 427 * size or CSS padding.
418 * 428 *
419 * @method updateViewportBoundaries 429 * @method updateViewportBoundaries
420 */ 430 */
421 updateViewportBoundaries: function() { 431 updateViewportBoundaries: function() {
422 var scrollerStyle = window.getComputedStyle(this.scrollTarget); 432 this._scrollerPaddingTop = this.scrollTarget === this ? 0 :
423 this._scrollerPaddingTop = parseInt(scrollerStyle['padding-top'], 10); 433 parseInt(window.getComputedStyle(this)['padding-top'], 10);
434
424 this._viewportSize = this._scrollTargetHeight; 435 this._viewportSize = this._scrollTargetHeight;
425 }, 436 },
426 437
427 /** 438 /**
428 * Update the models, the position of the 439 * Update the models, the position of the
429 * items in the viewport and recycle tiles as needed. 440 * items in the viewport and recycle tiles as needed.
430 */ 441 */
431 _scrollHandler: function() { 442 _scrollHandler: function() {
432 // clamp the `scrollTop` value 443 // clamp the `scrollTop` value
433 // IE 10|11 scrollTop may go above `_maxScrollTop`
434 // iOS `scrollTop` may go below 0 and above `_maxScrollTop`
435 var scrollTop = Math.max(0, Math.min(this._maxScrollTop, this._scrollTop)) ; 444 var scrollTop = Math.max(0, Math.min(this._maxScrollTop, this._scrollTop)) ;
445 var delta = scrollTop - this._scrollPosition;
436 var tileHeight, tileTop, kth, recycledTileSet, scrollBottom, physicalBotto m; 446 var tileHeight, tileTop, kth, recycledTileSet, scrollBottom, physicalBotto m;
437 var ratio = this._ratio; 447 var ratio = this._ratio;
438 var delta = scrollTop - this._scrollPosition;
439 var recycledTiles = 0; 448 var recycledTiles = 0;
440 var hiddenContentSize = this._hiddenContentSize; 449 var hiddenContentSize = this._hiddenContentSize;
441 var currentRatio = ratio; 450 var currentRatio = ratio;
442 var movingUp = []; 451 var movingUp = [];
443 452
444 // track the last `scrollTop` 453 // track the last `scrollTop`
445 this._scrollPosition = scrollTop; 454 this._scrollPosition = scrollTop;
446 455
447 // clear cached visible index 456 // clear cached visible indexes
448 this._firstVisibleIndexVal = null; 457 this._firstVisibleIndexVal = null;
449 this._lastVisibleIndexVal = null; 458 this._lastVisibleIndexVal = null;
450 459
451 scrollBottom = this._scrollBottom; 460 scrollBottom = this._scrollBottom;
452 physicalBottom = this._physicalBottom; 461 physicalBottom = this._physicalBottom;
453 462
454 // random access 463 // random access
455 if (Math.abs(delta) > this._physicalSize) { 464 if (Math.abs(delta) > this._physicalSize) {
456 this._physicalTop += delta; 465 this._physicalTop += delta;
457 recycledTiles = Math.round(delta / this._physicalAverage); 466 recycledTiles = Math.round(delta / this._physicalAverage);
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
524 533
525 if (recycledTiles === 0) { 534 if (recycledTiles === 0) {
526 // If the list ever reach this case, the physical average is not signifi cant enough 535 // If the list ever reach this case, the physical average is not signifi cant enough
527 // to create all the items needed to cover the entire viewport. 536 // to create all the items needed to cover the entire viewport.
528 // e.g. A few items have a height that differs from the average by serve ral order of magnitude. 537 // e.g. A few items have a height that differs from the average by serve ral order of magnitude.
529 if (physicalBottom < scrollBottom || this._physicalTop > scrollTop) { 538 if (physicalBottom < scrollBottom || this._physicalTop > scrollTop) {
530 this.async(this._increasePool.bind(this, 1)); 539 this.async(this._increasePool.bind(this, 1));
531 } 540 }
532 } else { 541 } else {
533 this._virtualStart = this._virtualStart + recycledTiles; 542 this._virtualStart = this._virtualStart + recycledTiles;
543 this._physicalStart = this._physicalStart + recycledTiles;
534 this._update(recycledTileSet, movingUp); 544 this._update(recycledTileSet, movingUp);
535 } 545 }
536 }, 546 },
537 547
538 /** 548 /**
539 * Update the list of items, starting from the `_virtualStart` item. 549 * Update the list of items, starting from the `_virtualStart` item.
540 * @param {!Array<number>=} itemSet 550 * @param {!Array<number>=} itemSet
541 * @param {!Array<number>=} movingUp 551 * @param {!Array<number>=} movingUp
542 */ 552 */
543 _update: function(itemSet, movingUp) { 553 _update: function(itemSet, movingUp) {
544 // manage focus 554 // manage focus
545 if (this._isIndexRendered(this._focusedIndex)) { 555 this._manageFocus();
546 this._restoreFocusedItem();
547 } else {
548 this._createFocusBackfillItem();
549 }
550 // update models 556 // update models
551 this._assignModels(itemSet); 557 this._assignModels(itemSet);
552 // measure heights 558 // measure heights
553 this._updateMetrics(itemSet); 559 this._updateMetrics(itemSet);
554 // adjust offset after measuring 560 // adjust offset after measuring
555 if (movingUp) { 561 if (movingUp) {
556 while (movingUp.length) { 562 while (movingUp.length) {
557 this._physicalTop -= this._physicalSizes[movingUp.pop()]; 563 this._physicalTop -= this._physicalSizes[movingUp.pop()];
558 } 564 }
559 } 565 }
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
606 this._debounceTemplate(this._increasePool.bind(this, 1)); 612 this._debounceTemplate(this._increasePool.bind(this, 1));
607 } 613 }
608 this._lastPage = currentPage; 614 this._lastPage = currentPage;
609 return true; 615 return true;
610 }, 616 },
611 617
612 /** 618 /**
613 * Increases the pool size. 619 * Increases the pool size.
614 */ 620 */
615 _increasePool: function(missingItems) { 621 _increasePool: function(missingItems) {
616 // limit the size
617 var nextPhysicalCount = Math.min( 622 var nextPhysicalCount = Math.min(
618 this._physicalCount + missingItems, 623 this._physicalCount + missingItems,
619 this._virtualCount - this._virtualStart, 624 this._virtualCount - this._virtualStart,
620 MAX_PHYSICAL_COUNT 625 MAX_PHYSICAL_COUNT
621 ); 626 );
622 var prevPhysicalCount = this._physicalCount; 627 var prevPhysicalCount = this._physicalCount;
623 var delta = nextPhysicalCount - prevPhysicalCount; 628 var delta = nextPhysicalCount - prevPhysicalCount;
624 629
625 if (delta > 0) { 630 if (delta <= 0) {
626 [].push.apply(this._physicalItems, this._createPool(delta)); 631 return;
627 [].push.apply(this._physicalSizes, new Array(delta)); 632 }
628 633
629 this._physicalCount = prevPhysicalCount + delta; 634 [].push.apply(this._physicalItems, this._createPool(delta));
630 // tail call 635 [].push.apply(this._physicalSizes, new Array(delta));
631 return this._update(); 636
637 this._physicalCount = prevPhysicalCount + delta;
638
639 // update the physical start if we need to preserve the model of the focus ed item.
640 // In this situation, the focused item is currently rendered and its model would
641 // have changed after increasing the pool if the physical start remained u nchanged.
642 if (this._physicalStart > this._physicalEnd &&
643 this._isIndexRendered(this._focusedIndex) &&
644 this._getPhysicalIndex(this._focusedIndex) < this._physicalEnd) {
645 this._physicalStart = this._physicalStart + delta;
632 } 646 }
647 this._update();
633 }, 648 },
634 649
635 /** 650 /**
636 * Render a new list of items. This method does exactly the same as `update` , 651 * Render a new list of items. This method does exactly the same as `update` ,
637 * but it also ensures that only one `update` cycle is created. 652 * but it also ensures that only one `update` cycle is created.
638 */ 653 */
639 _render: function() { 654 _render: function() {
640 var requiresUpdate = this._virtualCount > 0 || this._physicalCount > 0; 655 var requiresUpdate = this._virtualCount > 0 || this._physicalCount > 0;
641 656
642 if (this.isAttached && !this._itemsRendered && this._isVisible && requires Update) { 657 if (this.isAttached && !this._itemsRendered && this._isVisible && requires Update) {
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after
711 _forwardParentPath: function(path, value) { 726 _forwardParentPath: function(path, value) {
712 if (this._physicalItems) { 727 if (this._physicalItems) {
713 this._physicalItems.forEach(function(item) { 728 this._physicalItems.forEach(function(item) {
714 item._templateInstance.notifyPath(path, value, true); 729 item._templateInstance.notifyPath(path, value, true);
715 }, this); 730 }, this);
716 } 731 }
717 }, 732 },
718 733
719 /** 734 /**
720 * Called as a side effect of a host items.<key>.<path> path change, 735 * Called as a side effect of a host items.<key>.<path> path change,
721 * responsible for notifying item.<path> changes to row for key. 736 * responsible for notifying item.<path> changes.
722 */ 737 */
723 _forwardItemPath: function(path, value) { 738 _forwardItemPath: function(path, value) {
724 if (this._physicalIndexForKey) { 739 if (!this._physicalIndexForKey) {
725 var dot = path.indexOf('.'); 740 return;
726 var key = path.substring(0, dot < 0 ? path.length : dot); 741 }
727 var idx = this._physicalIndexForKey[key]; 742 var inst;
728 var row = this._physicalItems[idx]; 743 var dot = path.indexOf('.');
744 var key = path.substring(0, dot < 0 ? path.length : dot);
745 var idx = this._physicalIndexForKey[key];
746 var el = this._physicalItems[idx];
729 747
730 if (idx === this._focusedIndex && this._offscreenFocusedItem) { 748
731 row = this._offscreenFocusedItem; 749 if (idx === this._focusedIndex && this._offscreenFocusedItem) {
732 } 750 el = this._offscreenFocusedItem;
733 if (row) { 751 }
734 var inst = row._templateInstance; 752 if (!el) {
735 if (dot >= 0) { 753 return;
736 path = this.as + '.' + path.substring(dot+1); 754 }
737 inst.notifyPath(path, value, true); 755
738 } else { 756 inst = el._templateInstance;
739 inst[this.as] = value; 757
740 } 758 if (inst.__key__ !== key) {
741 } 759 return;
760 }
761 if (dot >= 0) {
762 path = this.as + '.' + path.substring(dot+1);
763 inst.notifyPath(path, value, true);
764 } else {
765 inst[this.as] = value;
742 } 766 }
743 }, 767 },
744 768
745 /** 769 /**
746 * Called when the items have changed. That is, ressignments 770 * Called when the items have changed. That is, ressignments
747 * to `items`, splices or updates to a single item. 771 * to `items`, splices or updates to a single item.
748 */ 772 */
749 _itemsChanged: function(change) { 773 _itemsChanged: function(change) {
750 if (change.path === 'items') { 774 if (change.path === 'items') {
751 775 // reset items
752 this._restoreFocusedItem();
753 // render the new set
754 this._itemsRendered = false;
755 // update the whole set
756 this._virtualStart = 0; 776 this._virtualStart = 0;
757 this._physicalTop = 0; 777 this._physicalTop = 0;
758 this._virtualCount = this.items ? this.items.length : 0; 778 this._virtualCount = this.items ? this.items.length : 0;
759 this._focusedIndex = 0;
760 this._collection = this.items ? Polymer.Collection.get(this.items) : nul l; 779 this._collection = this.items ? Polymer.Collection.get(this.items) : nul l;
761 this._physicalIndexForKey = {}; 780 this._physicalIndexForKey = {};
762 781
763 this._resetScrollPosition(0); 782 this._resetScrollPosition(0);
783 this._removeFocusedItem();
764 784
765 // create the initial physical items 785 // create the initial physical items
766 if (!this._physicalItems) { 786 if (!this._physicalItems) {
767 this._physicalCount = Math.max(1, Math.min(DEFAULT_PHYSICAL_COUNT, thi s._virtualCount)); 787 this._physicalCount = Math.max(1, Math.min(DEFAULT_PHYSICAL_COUNT, thi s._virtualCount));
768 this._physicalItems = this._createPool(this._physicalCount); 788 this._physicalItems = this._createPool(this._physicalCount);
769 this._physicalSizes = new Array(this._physicalCount); 789 this._physicalSizes = new Array(this._physicalCount);
770 } 790 }
771 this._debounceTemplate(this._render); 791
792 this._physicalStart = 0;
772 793
773 } else if (change.path === 'items.splices') { 794 } else if (change.path === 'items.splices') {
774 // render the new set
775 this._itemsRendered = false;
776 this._adjustVirtualIndex(change.value.indexSplices); 795 this._adjustVirtualIndex(change.value.indexSplices);
777 this._virtualCount = this.items ? this.items.length : 0; 796 this._virtualCount = this.items ? this.items.length : 0;
778 797
779 this._debounceTemplate(this._render);
780
781 if (this._focusedIndex < 0 || this._focusedIndex >= this._virtualCount) {
782 this._focusedIndex = 0;
783 }
784 this._debounceTemplate(this._render);
785
786 } else { 798 } else {
787 // update a single item 799 // update a single item
788 this._forwardItemPath(change.path.split('.').slice(1).join('.'), change. value); 800 this._forwardItemPath(change.path.split('.').slice(1).join('.'), change. value);
801 return;
789 } 802 }
803
804 this._itemsRendered = false;
805 this._debounceTemplate(this._render);
790 }, 806 },
791 807
792 /** 808 /**
793 * @param {!Array<!PolymerSplice>} splices 809 * @param {!Array<!PolymerSplice>} splices
794 */ 810 */
795 _adjustVirtualIndex: function(splices) { 811 _adjustVirtualIndex: function(splices) {
796 var i, splice, idx; 812 splices.forEach(function(splice) {
813 // deselect removed items
814 splice.removed.forEach(this._removeItem, this);
815 // We only need to care about changes happening above the current positi on
816 if (splice.index < this._virtualStart) {
817 var delta = Math.max(
818 splice.addedCount - splice.removed.length,
819 splice.index - this._virtualStart);
797 820
798 for (i = 0; i < splices.length; i++) { 821 this._virtualStart = this._virtualStart + delta;
799 splice = splices[i];
800 822
801 // deselect removed items 823 if (this._focusedIndex >= 0) {
802 splice.removed.forEach(this.$.selector.deselect, this.$.selector); 824 this._focusedIndex = this._focusedIndex + delta;
825 }
826 }
827 }, this);
828 },
803 829
804 idx = splice.index; 830 _removeItem: function(item) {
805 // We only need to care about changes happening above the current positi on 831 this.$.selector.deselect(item);
806 if (idx >= this._virtualStart) { 832 // remove the current focused item
807 break; 833 if (this._focusedItem && this._focusedItem._templateInstance[this.as] === item) {
808 } 834 this._removeFocusedItem();
809
810 this._virtualStart = this._virtualStart +
811 Math.max(splice.addedCount - splice.removed.length, idx - this._virt ualStart);
812 } 835 }
813 }, 836 },
814 837
815 /** 838 /**
816 * Executes a provided function per every physical index in `itemSet` 839 * Executes a provided function per every physical index in `itemSet`
817 * `itemSet` default value is equivalent to the entire set of physical index es. 840 * `itemSet` default value is equivalent to the entire set of physical index es.
818 * 841 *
819 * @param {!function(number, number)} fn 842 * @param {!function(number, number)} fn
820 * @param {!Array<number>=} itemSet 843 * @param {!Array<number>=} itemSet
821 */ 844 */
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
854 /** 877 /**
855 * Assigns the data models to a given set of items. 878 * Assigns the data models to a given set of items.
856 * @param {!Array<number>=} itemSet 879 * @param {!Array<number>=} itemSet
857 */ 880 */
858 _assignModels: function(itemSet) { 881 _assignModels: function(itemSet) {
859 this._iterateItems(function(pidx, vidx) { 882 this._iterateItems(function(pidx, vidx) {
860 var el = this._physicalItems[pidx]; 883 var el = this._physicalItems[pidx];
861 var inst = el._templateInstance; 884 var inst = el._templateInstance;
862 var item = this.items && this.items[vidx]; 885 var item = this.items && this.items[vidx];
863 886
864 if (item !== undefined && item !== null) { 887 if (item != null) {
865 inst[this.as] = item; 888 inst[this.as] = item;
866 inst.__key__ = this._collection.getKey(item); 889 inst.__key__ = this._collection.getKey(item);
867 inst[this.selectedAs] = /** @type {!ArraySelectorElement} */ (this.$.s elector).isSelected(item); 890 inst[this.selectedAs] = /** @type {!ArraySelectorElement} */ (this.$.s elector).isSelected(item);
868 inst[this.indexAs] = vidx; 891 inst[this.indexAs] = vidx;
869 inst.tabIndex = vidx === this._focusedIndex ? 0 : -1; 892 inst.tabIndex = this._focusedIndex === vidx ? 0 : -1;
893 this._physicalIndexForKey[inst.__key__] = pidx;
870 el.removeAttribute('hidden'); 894 el.removeAttribute('hidden');
871 this._physicalIndexForKey[inst.__key__] = pidx;
872 } else { 895 } else {
873 inst.__key__ = null; 896 inst.__key__ = null;
874 el.setAttribute('hidden', ''); 897 el.setAttribute('hidden', '');
875 } 898 }
876
877 }, itemSet); 899 }, itemSet);
878 }, 900 },
879 901
880 /** 902 /**
881 * Updates the height for a given set of items. 903 * Updates the height for a given set of items.
882 * 904 *
883 * @param {!Array<number>=} itemSet 905 * @param {!Array<number>=} itemSet
884 */ 906 */
885 _updateMetrics: function(itemSet) { 907 _updateMetrics: function(itemSet) {
886 // Make sure we distributed all the physical items 908 // Make sure we distributed all the physical items
(...skipping 27 matching lines...) Expand all
914 936
915 /** 937 /**
916 * Updates the position of the physical items. 938 * Updates the position of the physical items.
917 */ 939 */
918 _positionItems: function() { 940 _positionItems: function() {
919 this._adjustScrollPosition(); 941 this._adjustScrollPosition();
920 942
921 var y = this._physicalTop; 943 var y = this._physicalTop;
922 944
923 this._iterateItems(function(pidx) { 945 this._iterateItems(function(pidx) {
924
925 this.translate3d(0, y + 'px', 0, this._physicalItems[pidx]); 946 this.translate3d(0, y + 'px', 0, this._physicalItems[pidx]);
926 y += this._physicalSizes[pidx]; 947 y += this._physicalSizes[pidx];
927
928 }); 948 });
929 }, 949 },
930 950
931 /** 951 /**
932 * Adjusts the scroll position when it was overestimated. 952 * Adjusts the scroll position when it was overestimated.
933 */ 953 */
934 _adjustScrollPosition: function() { 954 _adjustScrollPosition: function() {
935 var deltaHeight = this._virtualStart === 0 ? this._physicalTop : 955 var deltaHeight = this._virtualStart === 0 ? this._physicalTop :
936 Math.min(this._scrollPosition + this._physicalTop, 0); 956 Math.min(this._scrollPosition + this._physicalTop, 0);
937 957
(...skipping 27 matching lines...) Expand all
965 985
966 forceUpdate = forceUpdate || this._scrollHeight === 0; 986 forceUpdate = forceUpdate || this._scrollHeight === 0;
967 forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight - this._physicalSize; 987 forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight - this._physicalSize;
968 988
969 // amortize height adjustment, so it won't trigger repaints very often 989 // amortize height adjustment, so it won't trigger repaints very often
970 if (forceUpdate || Math.abs(this._estScrollHeight - this._scrollHeight) >= this._optPhysicalSize) { 990 if (forceUpdate || Math.abs(this._estScrollHeight - this._scrollHeight) >= this._optPhysicalSize) {
971 this.$.items.style.height = this._estScrollHeight + 'px'; 991 this.$.items.style.height = this._estScrollHeight + 'px';
972 this._scrollHeight = this._estScrollHeight; 992 this._scrollHeight = this._estScrollHeight;
973 } 993 }
974 }, 994 },
975
976 /** 995 /**
977 * Scroll to a specific item in the virtual list regardless 996 * Scroll to a specific item in the virtual list regardless
978 * of the physical items in the DOM tree. 997 * of the physical items in the DOM tree.
979 * 998 *
980 * @method scrollToIndex 999 * @method scrollToIndex
981 * @param {number} idx The index of the item 1000 * @param {number} idx The index of the item
982 */ 1001 */
983 scrollToIndex: function(idx) { 1002 scrollToIndex: function(idx) {
984 if (typeof idx !== 'number') { 1003 if (typeof idx !== 'number') {
985 return; 1004 return;
986 } 1005 }
987 1006
988 Polymer.dom.flush(); 1007 Polymer.dom.flush();
989 1008
990 var firstVisible = this.firstVisibleIndex;
991 idx = Math.min(Math.max(idx, 0), this._virtualCount-1); 1009 idx = Math.min(Math.max(idx, 0), this._virtualCount-1);
992 1010 // update the virtual start only when needed
993 // start at the previous virtual item 1011 if (!this._isIndexRendered(idx) || idx >= this._maxVirtualStart) {
994 // so we have a item above the first visible item 1012 this._virtualStart = idx - 1;
995 this._virtualStart = idx - 1; 1013 }
1014 // manage focus
1015 this._manageFocus();
996 // assign new models 1016 // assign new models
997 this._assignModels(); 1017 this._assignModels();
998 // measure the new sizes 1018 // measure the new sizes
999 this._updateMetrics(); 1019 this._updateMetrics();
1000 // estimate new physical offset 1020 // estimate new physical offset
1001 this._physicalTop = this._virtualStart * this._physicalAverage; 1021 this._physicalTop = this._virtualStart * this._physicalAverage;
1002 1022
1003 var currentTopItem = this._physicalStart; 1023 var currentTopItem = this._physicalStart;
1004 var currentVirtualItem = this._virtualStart; 1024 var currentVirtualItem = this._virtualStart;
1005 var targetOffsetTop = 0; 1025 var targetOffsetTop = 0;
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
1048 this.updateViewportBoundaries(); 1068 this.updateViewportBoundaries();
1049 this.scrollToIndex(this.firstVisibleIndex); 1069 this.scrollToIndex(this.firstVisibleIndex);
1050 } 1070 }
1051 }); 1071 });
1052 }, 1072 },
1053 1073
1054 _getModelFromItem: function(item) { 1074 _getModelFromItem: function(item) {
1055 var key = this._collection.getKey(item); 1075 var key = this._collection.getKey(item);
1056 var pidx = this._physicalIndexForKey[key]; 1076 var pidx = this._physicalIndexForKey[key];
1057 1077
1058 if (pidx !== undefined) { 1078 if (pidx != null) {
1059 return this._physicalItems[pidx]._templateInstance; 1079 return this._physicalItems[pidx]._templateInstance;
1060 } 1080 }
1061 return null; 1081 return null;
1062 }, 1082 },
1063 1083
1064 /** 1084 /**
1065 * Gets a valid item instance from its index or the object value. 1085 * Gets a valid item instance from its index or the object value.
1066 * 1086 *
1067 * @param {(Object|number)} item The item object or its index 1087 * @param {(Object|number)} item The item object or its index
1068 */ 1088 */
(...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after
1186 * Updates the size of an item. 1206 * Updates the size of an item.
1187 * 1207 *
1188 * @method updateSizeForItem 1208 * @method updateSizeForItem
1189 * @param {(Object|number)} item The item object or its index 1209 * @param {(Object|number)} item The item object or its index
1190 */ 1210 */
1191 updateSizeForItem: function(item) { 1211 updateSizeForItem: function(item) {
1192 item = this._getNormalizedItem(item); 1212 item = this._getNormalizedItem(item);
1193 var key = this._collection.getKey(item); 1213 var key = this._collection.getKey(item);
1194 var pidx = this._physicalIndexForKey[key]; 1214 var pidx = this._physicalIndexForKey[key];
1195 1215
1196 if (pidx !== undefined) { 1216 if (pidx != null) {
1197 this._updateMetrics([pidx]); 1217 this._updateMetrics([pidx]);
1198 this._positionItems(); 1218 this._positionItems();
1199 } 1219 }
1200 }, 1220 },
1201 1221
1222 /**
1223 * Creates a temporary backfill item in the rendered pool of physical items
1224 * to replace the main focused item. The focused item has tabIndex = 0
1225 * and might be currently focused by the user.
1226 *
1227 * This dynamic replacement helps to preserve the focus state.
1228 */
1229 _manageFocus: function() {
1230 var fidx = this._focusedIndex;
1231
1232 if (fidx >= 0 && fidx < this._virtualCount) {
1233 // if it's a valid index, check if that index is rendered
1234 // in a physical item.
1235 if (this._isIndexRendered(fidx)) {
1236 this._restoreFocusedItem();
1237 } else {
1238 this._createFocusBackfillItem();
1239 }
1240 } else if (this._virtualCount > 0 && this._physicalCount > 0) {
1241 // otherwise, assign the initial focused index.
1242 this._focusedIndex = this._virtualStart;
1243 this._focusedItem = this._physicalItems[this._physicalStart];
1244 }
1245 },
1246
1202 _isIndexRendered: function(idx) { 1247 _isIndexRendered: function(idx) {
1203 return idx >= this._virtualStart && idx <= this._virtualEnd; 1248 return idx >= this._virtualStart && idx <= this._virtualEnd;
1204 }, 1249 },
1205 1250
1206 _getPhysicalItemForIndex: function(idx, force) { 1251 _isIndexVisible: function(idx) {
1207 if (!this._collection) { 1252 return idx >= this.firstVisibleIndex && idx <= this.lastVisibleIndex;
1208 return null; 1253 },
1209 }
1210 if (!this._isIndexRendered(idx)) {
1211 if (force) {
1212 this.scrollToIndex(idx);
1213 return this._getPhysicalItemForIndex(idx, false);
1214 }
1215 return null;
1216 }
1217 var item = this._getNormalizedItem(idx);
1218 var physicalItem = this._physicalItems[this._physicalIndexForKey[this._col lection.getKey(item)]];
1219 1254
1220 return physicalItem || null; 1255 _getPhysicalIndex: function(idx) {
1256 return this._physicalIndexForKey[this._collection.getKey(this._getNormaliz edItem(idx))];
1221 }, 1257 },
1222 1258
1223 _focusPhysicalItem: function(idx) { 1259 _focusPhysicalItem: function(idx) {
1224 this._restoreFocusedItem(); 1260 if (idx < 0 || idx >= this._virtualCount) {
1225
1226 var physicalItem = this._getPhysicalItemForIndex(idx, true);
1227 if (!physicalItem) {
1228 return; 1261 return;
1229 } 1262 }
1263 this._restoreFocusedItem();
1264 // scroll to index to make sure it's rendered
1265 if (!this._isIndexRendered(idx)) {
1266 this.scrollToIndex(idx);
1267 }
1268
1269 var physicalItem = this._physicalItems[this._getPhysicalIndex(idx)];
1230 var SECRET = ~(Math.random() * 100); 1270 var SECRET = ~(Math.random() * 100);
1231 var model = physicalItem._templateInstance; 1271 var model = physicalItem._templateInstance;
1232 var focusable; 1272 var focusable;
1233 1273
1274 // set a secret tab index
1234 model.tabIndex = SECRET; 1275 model.tabIndex = SECRET;
1235 // the focusable element could be the entire physical item 1276 // check if focusable element is the physical item
1236 if (physicalItem.tabIndex === SECRET) { 1277 if (physicalItem.tabIndex === SECRET) {
1237 focusable = physicalItem; 1278 focusable = physicalItem;
1238 } 1279 }
1239 // the focusable element could be somewhere within the physical item 1280 // search for the element which tabindex is bound to the secret tab index
1240 if (!focusable) { 1281 if (!focusable) {
1241 focusable = Polymer.dom(physicalItem).querySelector('[tabindex="' + SECR ET + '"]'); 1282 focusable = Polymer.dom(physicalItem).querySelector('[tabindex="' + SECR ET + '"]');
1242 } 1283 }
1243 // restore the tab index 1284 // restore the tab index
1244 model.tabIndex = 0; 1285 model.tabIndex = 0;
1286 // focus the focusable element
1287 this._focusedIndex = idx;
1245 focusable && focusable.focus(); 1288 focusable && focusable.focus();
1246 }, 1289 },
1247 1290
1248 _restoreFocusedItem: function() { 1291 _removeFocusedItem: function() {
1249 if (!this._offscreenFocusedItem) { 1292 if (this._offscreenFocusedItem) {
1250 return; 1293 Polymer.dom(this).removeChild(this._offscreenFocusedItem);
1251 }
1252 var item = this._getNormalizedItem(this._focusedIndex);
1253 var pidx = this._physicalIndexForKey[this._collection.getKey(item)];
1254
1255 if (pidx !== undefined) {
1256 this.translate3d(0, HIDDEN_Y, 0, this._physicalItems[pidx]);
1257 this._physicalItems[pidx] = this._offscreenFocusedItem;
1258 } 1294 }
1259 this._offscreenFocusedItem = null; 1295 this._offscreenFocusedItem = null;
1260 },
1261
1262 _removeFocusedItem: function() {
1263 if (!this._offscreenFocusedItem) {
1264 return;
1265 }
1266 Polymer.dom(this).removeChild(this._offscreenFocusedItem);
1267 this._offscreenFocusedItem = null;
1268 this._focusBackfillItem = null; 1296 this._focusBackfillItem = null;
1297 this._focusedItem = null;
1298 this._focusedIndex = -1;
1269 }, 1299 },
1270 1300
1271 _createFocusBackfillItem: function() { 1301 _createFocusBackfillItem: function() {
1272 if (this._offscreenFocusedItem) { 1302 var pidx, fidx = this._focusedIndex;
1303 if (this._offscreenFocusedItem || fidx < 0) {
1273 return; 1304 return;
1274 } 1305 }
1275 var item = this._getNormalizedItem(this._focusedIndex);
1276 var pidx = this._physicalIndexForKey[this._collection.getKey(item)];
1277
1278 this._offscreenFocusedItem = this._physicalItems[pidx];
1279 this.translate3d(0, HIDDEN_Y, 0, this._offscreenFocusedItem);
1280
1281 if (!this._focusBackfillItem) { 1306 if (!this._focusBackfillItem) {
1307 // create a physical item, so that it backfills the focused item.
1282 var stampedTemplate = this.stamp(null); 1308 var stampedTemplate = this.stamp(null);
1283 this._focusBackfillItem = stampedTemplate.root.querySelector('*'); 1309 this._focusBackfillItem = stampedTemplate.root.querySelector('*');
1284 Polymer.dom(this).appendChild(stampedTemplate.root); 1310 Polymer.dom(this).appendChild(stampedTemplate.root);
1285 } 1311 }
1286 this._physicalItems[pidx] = this._focusBackfillItem; 1312 // get the physical index for the focused index
1313 pidx = this._getPhysicalIndex(fidx);
1314
1315 if (pidx != null) {
1316 // set the offcreen focused physical item
1317 this._offscreenFocusedItem = this._physicalItems[pidx];
1318 // backfill the focused physical item
1319 this._physicalItems[pidx] = this._focusBackfillItem;
1320 // hide the focused physical
1321 this.translate3d(0, HIDDEN_Y, 0, this._offscreenFocusedItem);
1322 }
1323 },
1324
1325 _restoreFocusedItem: function() {
1326 var pidx, fidx = this._focusedIndex;
1327
1328 if (!this._offscreenFocusedItem || this._focusedIndex < 0) {
1329 return;
1330 }
1331 // assign models to the focused index
1332 this._assignModels();
1333 // get the new physical index for the focused index
1334 pidx = this._getPhysicalIndex(fidx);
1335
1336 if (pidx != null) {
1337 // flip the focus backfill
1338 this._focusBackfillItem = this._physicalItems[pidx];
1339 // restore the focused physical item
1340 this._physicalItems[pidx] = this._offscreenFocusedItem;
1341 // reset the offscreen focused item
1342 this._offscreenFocusedItem = null;
1343 // hide the physical item that backfills
1344 this.translate3d(0, HIDDEN_Y, 0, this._focusBackfillItem);
1345 }
1287 }, 1346 },
1288 1347
1289 _didFocus: function(e) { 1348 _didFocus: function(e) {
1290 var targetModel = this.modelForElement(e.target); 1349 var targetModel = this.modelForElement(e.target);
1350 var focusedModel = this._focusedItem ? this._focusedItem._templateInstance : null;
1351 var hasOffscreenFocusedItem = this._offscreenFocusedItem !== null;
1291 var fidx = this._focusedIndex; 1352 var fidx = this._focusedIndex;
1292 1353
1293 if (!targetModel) { 1354 if (!targetModel || !focusedModel) {
1294 return; 1355 return;
1295 } 1356 }
1296 this._restoreFocusedItem(); 1357 if (focusedModel === targetModel) {
1297 1358 // if the user focused the same item, then bring it into view if it's no t visible
1298 if (this.modelForElement(this._offscreenFocusedItem) === targetModel) { 1359 if (!this._isIndexVisible(fidx)) {
1299 this.scrollToIndex(fidx); 1360 this.scrollToIndex(fidx);
1361 }
1300 } else { 1362 } else {
1363 this._restoreFocusedItem();
1301 // restore tabIndex for the currently focused item 1364 // restore tabIndex for the currently focused item
1302 this._getModelFromItem(this._getNormalizedItem(fidx)).tabIndex = -1; 1365 focusedModel.tabIndex = -1;
1303 // set the tabIndex for the next focused item 1366 // set the tabIndex for the next focused item
1304 targetModel.tabIndex = 0; 1367 targetModel.tabIndex = 0;
1305 fidx = /** @type {{index: number}} */(targetModel).index; 1368 fidx = targetModel[this.indexAs];
1306 this._focusedIndex = fidx; 1369 this._focusedIndex = fidx;
1307 // bring the item into view 1370 this._focusedItem = this._physicalItems[this._getPhysicalIndex(fidx)];
1308 if (fidx < this.firstVisibleIndex || fidx > this.lastVisibleIndex) { 1371
1309 this.scrollToIndex(fidx); 1372 if (hasOffscreenFocusedItem && !this._offscreenFocusedItem) {
1310 } else {
1311 this._update(); 1373 this._update();
1312 } 1374 }
1313 } 1375 }
1314 }, 1376 },
1315 1377
1316 _didMoveUp: function() { 1378 _didMoveUp: function() {
1317 this._focusPhysicalItem(Math.max(0, this._focusedIndex - 1)); 1379 this._focusPhysicalItem(this._focusedIndex - 1);
1318 }, 1380 },
1319 1381
1320 _didMoveDown: function() { 1382 _didMoveDown: function() {
1321 this._focusPhysicalItem(Math.min(this._virtualCount, this._focusedIndex + 1)); 1383 this._focusPhysicalItem(this._focusedIndex + 1);
1322 }, 1384 },
1323 1385
1324 _didEnter: function(e) { 1386 _didEnter: function(e) {
1325 // focus the currently focused physical item
1326 this._focusPhysicalItem(this._focusedIndex); 1387 this._focusPhysicalItem(this._focusedIndex);
1327 // toggle selection 1388 this._selectionHandler(e.detail.keyboardEvent);
1328 this._selectionHandler(/** @type {{keyboardEvent: Event}} */(e.detail).key boardEvent);
1329 } 1389 }
1330 }); 1390 });
1331 1391
1332 })(); 1392 })();
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698