OLD | NEW |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * @template T | 6 * @template T |
7 * @interface | 7 * @interface |
8 */ | 8 */ |
9 UI.ListDelegate = function() {}; | 9 UI.ListDelegate = function() {}; |
10 | 10 |
(...skipping 19 matching lines...) Expand all Loading... |
30 /** | 30 /** |
31 * @param {?T} from | 31 * @param {?T} from |
32 * @param {?T} to | 32 * @param {?T} to |
33 * @param {?Element} fromElement | 33 * @param {?Element} fromElement |
34 * @param {?Element} toElement | 34 * @param {?Element} toElement |
35 */ | 35 */ |
36 selectedItemChanged(from, to, fromElement, toElement) {}, | 36 selectedItemChanged(from, to, fromElement, toElement) {}, |
37 }; | 37 }; |
38 | 38 |
39 /** @enum {symbol} */ | 39 /** @enum {symbol} */ |
40 UI.ListHeightMode = { | 40 UI.ListMode = { |
41 Fixed: Symbol('UI.ListHeightMode.Fixed'), | 41 Grow: Symbol('UI.ListMode.Grow'), |
42 Measured: Symbol('UI.ListHeightMode.Measured'), | 42 ViewportFixedItems: Symbol('UI.ListMode.ViewportFixedItems'), |
43 Variable: Symbol('UI.ListHeightMode.Variable') | 43 ViewportFixedItemsMeasured: Symbol('UI.ListMode.ViewportFixedItemsMeasured'), |
| 44 ViewportVariableItems: Symbol('UI.ListMode.ViewportVariableItems') |
44 }; | 45 }; |
45 | 46 |
46 /** | 47 /** |
47 * @template T | 48 * @template T |
48 */ | 49 */ |
49 UI.ListControl = class { | 50 UI.ListControl = class { |
50 /** | 51 /** |
51 * @param {!UI.ListDelegate<T>} delegate | 52 * @param {!UI.ListDelegate<T>} delegate |
| 53 * @param {!UI.ListMode=} mode |
52 */ | 54 */ |
53 constructor(delegate) { | 55 constructor(delegate, mode) { |
54 this.element = createElement('div'); | 56 this.element = createElement('div'); |
55 this.element.style.overflow = 'auto'; | 57 this.element.style.overflow = 'auto'; |
56 this._topElement = this.element.createChild('div'); | 58 this._topElement = this.element.createChild('div'); |
57 this._bottomElement = this.element.createChild('div'); | 59 this._bottomElement = this.element.createChild('div'); |
58 this._firstIndex = 0; | 60 this._firstIndex = 0; |
59 this._lastIndex = 0; | 61 this._lastIndex = 0; |
60 this._renderedHeight = 0; | 62 this._renderedHeight = 0; |
61 this._topHeight = 0; | 63 this._topHeight = 0; |
62 this._bottomHeight = 0; | 64 this._bottomHeight = 0; |
63 this._clearViewport(); | |
64 | 65 |
65 /** @type {!Array<T>} */ | 66 /** @type {!Array<T>} */ |
66 this._items = []; | 67 this._items = []; |
67 /** @type {!Map<T, !Element>} */ | 68 /** @type {!Map<T, !Element>} */ |
68 this._itemToElement = new Map(); | 69 this._itemToElement = new Map(); |
69 this._selectedIndex = -1; | 70 this._selectedIndex = -1; |
70 | 71 |
71 this._boundKeyDown = event => { | 72 this._boundKeyDown = event => { |
72 if (this.onKeyDown(event)) | 73 if (this.onKeyDown(event)) |
73 event.consume(true); | 74 event.consume(true); |
74 }; | 75 }; |
75 this._boundClick = event => { | 76 this._boundClick = event => { |
76 if (this.onClick(event)) | 77 if (this.onClick(event)) |
77 event.consume(true); | 78 event.consume(true); |
78 }; | 79 }; |
| 80 this._boundScroll = event => { |
| 81 this._updateViewport(this.element.scrollTop, this.element.offsetHeight); |
| 82 }; |
79 | 83 |
80 this._delegate = delegate; | 84 this._delegate = delegate; |
81 this._heightMode = UI.ListHeightMode.Measured; | 85 this._mode = mode || UI.ListMode.ViewportFixedItemsMeasured; |
82 this._fixedHeight = 0; | 86 this._fixedHeight = 0; |
83 this._variableOffsets = new Int32Array(0); | 87 this._variableOffsets = new Int32Array(0); |
84 | 88 this._clearContents(); |
85 this.element.addEventListener('scroll', this._onScroll.bind(this), false); | 89 if (this._mode !== UI.ListMode.Grow) |
| 90 this.element.addEventListener('scroll', this._boundScroll, false); |
86 } | 91 } |
87 | 92 |
88 /** | 93 /** |
89 * @param {!UI.ListHeightMode} mode | |
90 */ | |
91 setHeightMode(mode) { | |
92 this._heightMode = mode; | |
93 this._fixedHeight = 0; | |
94 if (this._items.length) { | |
95 this._itemToElement.clear(); | |
96 this._invalidate(0, this._items.length, this._items.length); | |
97 } | |
98 } | |
99 | |
100 /** | |
101 * @param {boolean} handleInput | 94 * @param {boolean} handleInput |
102 */ | 95 */ |
103 setHandleInput(handleInput) { | 96 setHandleInput(handleInput) { |
104 if (handleInput) { | 97 if (handleInput) { |
105 this.element.addEventListener('keydown', this._boundKeyDown, false); | 98 this.element.addEventListener('keydown', this._boundKeyDown, false); |
106 this.element.addEventListener('click', this._boundClick, false); | 99 this.element.addEventListener('click', this._boundClick, false); |
107 } else { | 100 } else { |
108 this.element.removeEventListener('keydown', this._boundKeyDown, false); | 101 this.element.removeEventListener('keydown', this._boundKeyDown, false); |
109 this.element.removeEventListener('click', this._boundClick, false); | 102 this.element.removeEventListener('click', this._boundClick, false); |
110 } | 103 } |
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
174 // Splice may fail with too many arguments. | 167 // Splice may fail with too many arguments. |
175 var before = this._items.slice(0, from); | 168 var before = this._items.slice(0, from); |
176 var after = this._items.slice(to); | 169 var after = this._items.slice(to); |
177 this._items = [].concat(before, items, after); | 170 this._items = [].concat(before, items, after); |
178 } | 171 } |
179 this._invalidate(from, to, items.length); | 172 this._invalidate(from, to, items.length); |
180 | 173 |
181 if (this._selectedIndex >= to) { | 174 if (this._selectedIndex >= to) { |
182 this._selectedIndex += items.length - (to - from); | 175 this._selectedIndex += items.length - (to - from); |
183 } else if (this._selectedIndex >= from) { | 176 } else if (this._selectedIndex >= from) { |
184 var index = this._findClosestSelectable(from + items.length, +1, 0, false)
; | 177 var index = this._findFirstSelectable(from + items.length, +1, false); |
185 if (index === -1) | 178 if (index === -1) |
186 index = this._findClosestSelectable(from - 1, -1, 0, false); | 179 index = this._findFirstSelectable(from - 1, -1, false); |
187 this._select(index, oldSelectedItem, oldSelectedElement); | 180 this._select(index, oldSelectedItem, oldSelectedElement); |
188 } | 181 } |
189 } | 182 } |
190 | 183 |
191 /** | 184 /** |
192 * @param {!Array<T>} items | 185 * @param {!Array<T>} items |
193 */ | 186 */ |
194 replaceAllItems(items) { | 187 replaceAllItems(items) { |
195 this.replaceItemsInRange(0, this._items.length, items); | 188 this.replaceItemsInRange(0, this._items.length, items); |
196 } | 189 } |
197 | 190 |
198 /** | 191 /** |
199 * @param {number} from | 192 * @param {number} from |
200 * @param {number} to | 193 * @param {number} to |
201 */ | 194 */ |
202 invalidateRange(from, to) { | 195 invalidateRange(from, to) { |
203 this._invalidate(from, to, to - from); | 196 this._invalidate(from, to, to - from); |
204 } | 197 } |
205 | 198 |
206 viewportResized() { | 199 viewportResized() { |
207 // TODO(dgozman): try to keep the visible scrollTop the same | 200 if (this._mode === UI.ListMode.Grow) |
208 // when invalidating after firstIndex but before first visible element. | 201 return; |
| 202 // TODO(dgozman): try to keep visible scrollTop the same. |
209 var scrollTop = this.element.scrollTop; | 203 var scrollTop = this.element.scrollTop; |
210 var viewportHeight = this.element.offsetHeight; | 204 var viewportHeight = this.element.offsetHeight; |
211 this._clearViewport(); | 205 this._clearViewport(); |
212 this._updateViewport(Number.constrain(scrollTop, 0, this._totalHeight() - vi
ewportHeight), viewportHeight); | 206 this._updateViewport(Number.constrain(scrollTop, 0, this._totalHeight() - vi
ewportHeight), viewportHeight); |
213 } | 207 } |
214 | 208 |
| 209 fixedHeightChanged() { |
| 210 if (this._mode !== UI.ListMode.ViewportFixedItemsMeasured && this._mode !==
UI.ListMode.ViewportFixedItems) |
| 211 throw 'Only supported in fixed height items modes'; |
| 212 this._fixedHeight = 0; |
| 213 if (this._items.length) { |
| 214 this._itemToElement.clear(); |
| 215 this._invalidate(0, this._items.length, this._items.length); |
| 216 } |
| 217 } |
| 218 |
215 /** | 219 /** |
216 * @param {number} index | 220 * @param {number} index |
217 */ | 221 */ |
218 scrollItemAtIndexIntoView(index) { | 222 scrollItemAtIndexIntoView(index) { |
| 223 if (this._mode === UI.ListMode.Grow) { |
| 224 this._elementAtIndex(index).scrollIntoViewIfNeeded(false); |
| 225 return; |
| 226 } |
219 var top = this._offsetAtIndex(index); | 227 var top = this._offsetAtIndex(index); |
220 var bottom = this._offsetAtIndex(index + 1); | 228 var bottom = this._offsetAtIndex(index + 1); |
221 var scrollTop = this.element.scrollTop; | 229 var scrollTop = this.element.scrollTop; |
222 var viewportHeight = this.element.offsetHeight; | 230 var viewportHeight = this.element.offsetHeight; |
223 if (top < scrollTop) | 231 if (top < scrollTop) |
224 this._updateViewport(top, viewportHeight); | 232 this._updateViewport(top, viewportHeight); |
225 else if (bottom > scrollTop + viewportHeight) | 233 else if (bottom > scrollTop + viewportHeight) |
226 this._updateViewport(bottom - viewportHeight, viewportHeight); | 234 this._updateViewport(bottom - viewportHeight, viewportHeight); |
227 } | 235 } |
228 | 236 |
(...skipping 24 matching lines...) Expand all Loading... |
253 } | 261 } |
254 | 262 |
255 /** | 263 /** |
256 * @param {!Event} event | 264 * @param {!Event} event |
257 * @return {boolean} | 265 * @return {boolean} |
258 */ | 266 */ |
259 onKeyDown(event) { | 267 onKeyDown(event) { |
260 var index = -1; | 268 var index = -1; |
261 switch (event.key) { | 269 switch (event.key) { |
262 case 'ArrowUp': | 270 case 'ArrowUp': |
263 index = this._selectedIndex === -1 ? this._findClosestSelectable(this._i
tems.length - 1, -1, 0, true) : | 271 index = this._selectedIndex === -1 ? this._items.length - 1 : this._sele
ctedIndex - 1; |
264 this._findClosestSelectable(this._s
electedIndex, -1, 1, true); | 272 index = this._findFirstSelectable(index, -1, true); |
265 break; | 273 break; |
266 case 'ArrowDown': | 274 case 'ArrowDown': |
267 index = this._selectedIndex === -1 ? this._findClosestSelectable(0, +1,
0, true) : | 275 index = this._selectedIndex === -1 ? 0 : this._selectedIndex + 1; |
268 this._findClosestSelectable(this._s
electedIndex, +1, 1, true); | 276 index = this._findFirstSelectable(index, +1, true); |
269 break; | 277 break; |
270 case 'PageUp': | 278 case 'PageUp': |
| 279 if (this._mode === UI.ListMode.Grow) |
| 280 return false; |
271 index = this._selectedIndex === -1 ? this._items.length - 1 : this._sele
ctedIndex; | 281 index = this._selectedIndex === -1 ? this._items.length - 1 : this._sele
ctedIndex; |
272 // Compensate for zoom rounding errors with -1. | 282 index = this._findPageSelectable(index, -1); |
273 index = this._findClosestSelectable(index, -1, this.element.offsetHeight
- 1, false); | |
274 break; | 283 break; |
275 case 'PageDown': | 284 case 'PageDown': |
| 285 if (this._mode === UI.ListMode.Grow) |
| 286 return false; |
276 index = this._selectedIndex === -1 ? 0 : this._selectedIndex; | 287 index = this._selectedIndex === -1 ? 0 : this._selectedIndex; |
277 // Compensate for zoom rounding errors with -1. | 288 index = this._findPageSelectable(index, +1); |
278 index = this._findClosestSelectable(index, +1, this.element.offsetHeight
- 1, false); | |
279 break; | 289 break; |
280 default: | 290 default: |
281 return false; | 291 return false; |
282 } | 292 } |
283 if (index !== -1) { | 293 if (index !== -1) { |
284 this.scrollItemAtIndexIntoView(index); | 294 this.scrollItemAtIndexIntoView(index); |
285 this._select(index); | 295 this._select(index); |
286 return true; | 296 return true; |
287 } | 297 } |
288 return false; | 298 return false; |
289 } | 299 } |
290 | 300 |
291 /** | 301 /** |
292 * @param {!Event} event | 302 * @param {!Event} event |
293 * @return {boolean} | 303 * @return {boolean} |
294 */ | 304 */ |
295 onClick(event) { | 305 onClick(event) { |
296 var node = event.target; | 306 var node = event.target; |
297 while (node && node.parentNodeOrShadowHost() !== this.element) | 307 while (node && node.parentNodeOrShadowHost() !== this.element) |
298 node = node.parentNodeOrShadowHost(); | 308 node = node.parentNodeOrShadowHost(); |
299 if (!node || node.nodeType !== Node.ELEMENT_NODE) | 309 if (!node) |
300 return false; | 310 return false; |
301 var offset = /** @type {!Element} */ (node).getBoundingClientRect().top; | 311 var index = this._items.findIndex(item => this._itemToElement.get(item) ===
node); |
302 offset -= this.element.getBoundingClientRect().top; | |
303 var index = this._indexAtOffset(offset + this.element.scrollTop); | |
304 if (index === -1 || !this._delegate.isItemSelectable(this._items[index])) | 312 if (index === -1 || !this._delegate.isItemSelectable(this._items[index])) |
305 return false; | 313 return false; |
306 this._select(index); | 314 this._select(index); |
307 return true; | 315 return true; |
308 } | 316 } |
309 | 317 |
310 /** | 318 /** |
311 * @return {number} | 319 * @return {number} |
312 */ | 320 */ |
313 _totalHeight() { | 321 _totalHeight() { |
314 return this._offsetAtIndex(this._items.length); | 322 return this._offsetAtIndex(this._items.length); |
315 } | 323 } |
316 | 324 |
317 /** | 325 /** |
318 * @param {number} offset | 326 * @param {number} offset |
319 * @return {number} | 327 * @return {number} |
320 */ | 328 */ |
321 _indexAtOffset(offset) { | 329 _indexAtOffset(offset) { |
| 330 if (this._mode === UI.ListMode.Grow) |
| 331 throw 'There should be no offset conversions in grow mode'; |
322 if (!this._items.length || offset < 0) | 332 if (!this._items.length || offset < 0) |
323 return 0; | 333 return 0; |
324 if (this._heightMode === UI.ListHeightMode.Variable) { | 334 if (this._mode === UI.ListMode.ViewportVariableItems) { |
325 return Math.min( | 335 return Math.min( |
326 this._items.length - 1, this._variableOffsets.lowerBound(offset, undef
ined, 0, this._items.length)); | 336 this._items.length - 1, this._variableOffsets.lowerBound(offset, undef
ined, 0, this._items.length)); |
327 } | 337 } |
328 if (!this._fixedHeight) | 338 if (!this._fixedHeight) |
329 this._measureHeight(); | 339 this._measureHeight(); |
330 return Math.min(this._items.length - 1, Math.floor(offset / this._fixedHeigh
t)); | 340 return Math.min(this._items.length - 1, Math.floor(offset / this._fixedHeigh
t)); |
331 } | 341 } |
332 | 342 |
333 /** | 343 /** |
334 * @param {number} index | 344 * @param {number} index |
335 * @return {!Element} | 345 * @return {!Element} |
336 */ | 346 */ |
337 _elementAtIndex(index) { | 347 _elementAtIndex(index) { |
338 var item = this._items[index]; | 348 var item = this._items[index]; |
339 var element = this._itemToElement.get(item); | 349 var element = this._itemToElement.get(item); |
340 if (!element) { | 350 if (!element) { |
341 element = this._delegate.createElementForItem(item); | 351 element = this._delegate.createElementForItem(item); |
342 this._itemToElement.set(item, element); | 352 this._itemToElement.set(item, element); |
343 } | 353 } |
344 return element; | 354 return element; |
345 } | 355 } |
346 | 356 |
347 /** | 357 /** |
348 * @param {number} index | 358 * @param {number} index |
349 * @return {number} | 359 * @return {number} |
350 */ | 360 */ |
351 _offsetAtIndex(index) { | 361 _offsetAtIndex(index) { |
| 362 if (this._mode === UI.ListMode.Grow) |
| 363 throw 'There should be no offset conversions in grow mode'; |
352 if (!this._items.length) | 364 if (!this._items.length) |
353 return 0; | 365 return 0; |
354 if (this._heightMode === UI.ListHeightMode.Variable) | 366 if (this._mode === UI.ListMode.ViewportVariableItems) |
355 return this._variableOffsets[index]; | 367 return this._variableOffsets[index]; |
356 if (!this._fixedHeight) | 368 if (!this._fixedHeight) |
357 this._measureHeight(); | 369 this._measureHeight(); |
358 return index * this._fixedHeight; | 370 return index * this._fixedHeight; |
359 } | 371 } |
360 | 372 |
361 _measureHeight() { | 373 _measureHeight() { |
362 if (this._heightMode === UI.ListHeightMode.Measured) | 374 if (this._mode === UI.ListMode.ViewportFixedItemsMeasured) |
363 this._fixedHeight = UI.measurePreferredSize(this._elementAtIndex(0), this.
element).height; | 375 this._fixedHeight = UI.measurePreferredSize(this._elementAtIndex(0), this.
element).height; |
364 else | 376 else |
365 this._fixedHeight = this._delegate.heightForItem(this._items[0]); | 377 this._fixedHeight = this._delegate.heightForItem(this._items[0]); |
366 } | 378 } |
367 | 379 |
368 /** | 380 /** |
369 * @param {number} index | 381 * @param {number} index |
370 * @param {?T=} oldItem | 382 * @param {?T=} oldItem |
371 * @param {?Element=} oldElement | 383 * @param {?Element=} oldElement |
372 */ | 384 */ |
373 _select(index, oldItem, oldElement) { | 385 _select(index, oldItem, oldElement) { |
374 if (oldItem === undefined) | 386 if (oldItem === undefined) |
375 oldItem = this._selectedIndex !== -1 ? this._items[this._selectedIndex] :
null; | 387 oldItem = this._selectedIndex !== -1 ? this._items[this._selectedIndex] :
null; |
376 if (oldElement === undefined) | 388 if (oldElement === undefined) |
377 oldElement = this._itemToElement.get(oldItem) || null; | 389 oldElement = this._itemToElement.get(oldItem) || null; |
378 this._selectedIndex = index; | 390 this._selectedIndex = index; |
379 var newItem = this._selectedIndex !== -1 ? this._items[this._selectedIndex]
: null; | 391 var newItem = this._selectedIndex !== -1 ? this._items[this._selectedIndex]
: null; |
380 var newElement = this._itemToElement.get(newItem) || null; | 392 var newElement = this._itemToElement.get(newItem) || null; |
381 this._delegate.selectedItemChanged(oldItem, newItem, /** @type {?Element} */
(oldElement), newElement); | 393 this._delegate.selectedItemChanged(oldItem, newItem, /** @type {?Element} */
(oldElement), newElement); |
382 } | 394 } |
383 | 395 |
384 /** | 396 /** |
385 * @param {number} index | 397 * @param {number} index |
386 * @param {number} direction | 398 * @param {number} direction |
387 * @param {number} minSkippedHeight | |
388 * @param {boolean} canWrap | 399 * @param {boolean} canWrap |
389 * @return {number} | 400 * @return {number} |
390 */ | 401 */ |
391 _findClosestSelectable(index, direction, minSkippedHeight, canWrap) { | 402 _findFirstSelectable(index, direction, canWrap) { |
392 var length = this._items.length; | 403 var length = this._items.length; |
393 if (!length) | 404 if (!length) |
394 return -1; | 405 return -1; |
395 | 406 for (var step = 0; step <= length; step++) { |
396 var lastSelectable = -1; | |
397 var start = -1; | |
398 var startOffset = this._offsetAtIndex(index); | |
399 while (true) { | |
400 if (index < 0 || index >= length) { | 407 if (index < 0 || index >= length) { |
401 if (!canWrap) | 408 if (!canWrap) |
402 return lastSelectable; | 409 return -1; |
403 index = (index + length) % length; | 410 index = (index + length) % length; |
404 } | 411 } |
| 412 if (this._delegate.isItemSelectable(this._items[index])) |
| 413 return index; |
| 414 index += direction; |
| 415 } |
| 416 return -1; |
| 417 } |
405 | 418 |
406 // Handle full wrap-around. | 419 /** |
407 if (index === start) | 420 * @param {number} index |
408 return lastSelectable; | 421 * @param {number} direction |
409 if (start === -1) { | 422 * @return {number} |
410 start = index; | 423 */ |
411 startOffset = this._offsetAtIndex(index); | 424 _findPageSelectable(index, direction) { |
412 } | 425 var lastSelectable = -1; |
413 | 426 var startOffset = this._offsetAtIndex(index); |
| 427 // Compensate for zoom rounding errors with -1. |
| 428 var viewportHeight = this.element.offsetHeight - 1; |
| 429 while (index >= 0 && index < this._items.length) { |
414 if (this._delegate.isItemSelectable(this._items[index])) { | 430 if (this._delegate.isItemSelectable(this._items[index])) { |
415 if (Math.abs(this._offsetAtIndex(index) - startOffset) >= minSkippedHeig
ht) | 431 if (Math.abs(this._offsetAtIndex(index) - startOffset) >= viewportHeight
) |
416 return index; | 432 return index; |
417 lastSelectable = index; | 433 lastSelectable = index; |
418 } | 434 } |
419 | |
420 index += direction; | 435 index += direction; |
421 } | 436 } |
| 437 return lastSelectable; |
422 } | 438 } |
423 | 439 |
424 /** | 440 /** |
425 * @param {number} length | 441 * @param {number} length |
426 * @param {number} copyTo | 442 * @param {number} copyTo |
427 */ | 443 */ |
428 _reallocateVariableOffsets(length, copyTo) { | 444 _reallocateVariableOffsets(length, copyTo) { |
429 if (this._variableOffsets.length < length) { | 445 if (this._variableOffsets.length < length) { |
430 var variableOffsets = new Int32Array(Math.max(length, this._variableOffset
s.length * 2)); | 446 var variableOffsets = new Int32Array(Math.max(length, this._variableOffset
s.length * 2)); |
431 variableOffsets.set(this._variableOffsets.slice(0, copyTo), 0); | 447 variableOffsets.set(this._variableOffsets.slice(0, copyTo), 0); |
432 this._variableOffsets = variableOffsets; | 448 this._variableOffsets = variableOffsets; |
433 } else if (this._variableOffsets.length >= 2 * length) { | 449 } else if (this._variableOffsets.length >= 2 * length) { |
434 var variableOffsets = new Int32Array(length); | 450 var variableOffsets = new Int32Array(length); |
435 variableOffsets.set(this._variableOffsets.slice(0, copyTo), 0); | 451 variableOffsets.set(this._variableOffsets.slice(0, copyTo), 0); |
436 this._variableOffsets = variableOffsets; | 452 this._variableOffsets = variableOffsets; |
437 } | 453 } |
438 } | 454 } |
439 | 455 |
440 /** | 456 /** |
441 * @param {number} from | 457 * @param {number} from |
442 * @param {number} to | 458 * @param {number} to |
443 * @param {number} inserted | 459 * @param {number} inserted |
444 */ | 460 */ |
445 _invalidate(from, to, inserted) { | 461 _invalidate(from, to, inserted) { |
446 if (this._heightMode === UI.ListHeightMode.Variable) { | 462 if (this._mode === UI.ListMode.Grow) { |
| 463 this._invalidateGrowMode(from, to - from, inserted); |
| 464 return; |
| 465 } |
| 466 |
| 467 if (this._mode === UI.ListMode.ViewportVariableItems) { |
447 this._reallocateVariableOffsets(this._items.length + 1, from + 1); | 468 this._reallocateVariableOffsets(this._items.length + 1, from + 1); |
448 for (var i = from + 1; i <= this._items.length; i++) | 469 for (var i = from + 1; i <= this._items.length; i++) |
449 this._variableOffsets[i] = this._variableOffsets[i - 1] + this._delegate
.heightForItem(this._items[i - 1]); | 470 this._variableOffsets[i] = this._variableOffsets[i - 1] + this._delegate
.heightForItem(this._items[i - 1]); |
450 } | 471 } |
451 | 472 |
452 var viewportHeight = this.element.offsetHeight; | 473 var viewportHeight = this.element.offsetHeight; |
453 var totalHeight = this._totalHeight(); | 474 var totalHeight = this._totalHeight(); |
454 var scrollTop = this.element.scrollTop; | 475 var scrollTop = this.element.scrollTop; |
455 | 476 |
456 if (this._renderedHeight < viewportHeight || totalHeight < viewportHeight) { | 477 if (this._renderedHeight < viewportHeight || totalHeight < viewportHeight) { |
(...skipping 16 matching lines...) Expand all Loading... |
473 } | 494 } |
474 | 495 |
475 if (from >= this._lastIndex) { | 496 if (from >= this._lastIndex) { |
476 var bottomHeight = this._bottomHeight + heightDelta; | 497 var bottomHeight = this._bottomHeight + heightDelta; |
477 this._bottomElement.style.height = bottomHeight + 'px'; | 498 this._bottomElement.style.height = bottomHeight + 'px'; |
478 this._bottomHeight = bottomHeight; | 499 this._bottomHeight = bottomHeight; |
479 this._renderedHeight = totalHeight; | 500 this._renderedHeight = totalHeight; |
480 return; | 501 return; |
481 } | 502 } |
482 | 503 |
483 // TODO(dgozman): try to keep the visible scrollTop the same | 504 // TODO(dgozman): try to keep visible scrollTop the same |
484 // when invalidating after firstIndex but before first visible element. | 505 // when invalidating after firstIndex but before first visible element. |
485 this._clearViewport(); | 506 this._clearViewport(); |
486 this._updateViewport(Number.constrain(scrollTop, 0, totalHeight - viewportHe
ight), viewportHeight); | 507 this._updateViewport(Number.constrain(scrollTop, 0, totalHeight - viewportHe
ight), viewportHeight); |
487 } | 508 } |
488 | 509 |
| 510 /** |
| 511 * @param {number} start |
| 512 * @param {number} remove |
| 513 * @param {number} add |
| 514 */ |
| 515 _invalidateGrowMode(start, remove, add) { |
| 516 var startElement = this._topElement; |
| 517 for (var index = 0; index < start; index++) |
| 518 startElement = startElement.nextElementSibling; |
| 519 while (remove--) |
| 520 startElement.nextElementSibling.remove(); |
| 521 while (add--) |
| 522 this.element.insertBefore(this._elementAtIndex(start + add), startElement.
nextElementSibling); |
| 523 } |
| 524 |
489 _clearViewport() { | 525 _clearViewport() { |
| 526 if (this._mode === UI.ListMode.Grow) |
| 527 throw 'There should be no viewport updates in grow mode'; |
490 this._firstIndex = 0; | 528 this._firstIndex = 0; |
491 this._lastIndex = 0; | 529 this._lastIndex = 0; |
492 this._renderedHeight = 0; | 530 this._renderedHeight = 0; |
493 this._topHeight = 0; | 531 this._topHeight = 0; |
494 this._bottomHeight = 0; | 532 this._bottomHeight = 0; |
| 533 this._clearContents(); |
| 534 } |
| 535 |
| 536 _clearContents() { |
| 537 // Note: this method should not force layout. Be careful. |
495 this._topElement.style.height = '0'; | 538 this._topElement.style.height = '0'; |
496 this._bottomElement.style.height = '0'; | 539 this._bottomElement.style.height = '0'; |
497 this.element.removeChildren(); | 540 this.element.removeChildren(); |
498 this.element.appendChild(this._topElement); | 541 this.element.appendChild(this._topElement); |
499 this.element.appendChild(this._bottomElement); | 542 this.element.appendChild(this._bottomElement); |
500 } | 543 } |
501 | 544 |
502 _onScroll() { | |
503 this._updateViewport(this.element.scrollTop, this.element.offsetHeight); | |
504 } | |
505 | |
506 /** | 545 /** |
507 * @param {number} scrollTop | 546 * @param {number} scrollTop |
508 * @param {number} viewportHeight | 547 * @param {number} viewportHeight |
509 */ | 548 */ |
510 _updateViewport(scrollTop, viewportHeight) { | 549 _updateViewport(scrollTop, viewportHeight) { |
511 // Note: this method should not force layout. Be careful. | 550 // Note: this method should not force layout. Be careful. |
| 551 if (this._mode === UI.ListMode.Grow) |
| 552 throw 'There should be no viewport updates in grow mode'; |
512 | 553 |
513 var totalHeight = this._totalHeight(); | 554 var totalHeight = this._totalHeight(); |
514 if (!totalHeight) { | 555 if (!totalHeight) { |
515 this._firstIndex = 0; | 556 this._firstIndex = 0; |
516 this._lastIndex = 0; | 557 this._lastIndex = 0; |
517 this._topHeight = 0; | 558 this._topHeight = 0; |
518 this._bottomHeight = 0; | 559 this._bottomHeight = 0; |
519 this._renderedHeight = 0; | 560 this._renderedHeight = 0; |
520 this._topElement.style.height = '0'; | 561 this._topElement.style.height = '0'; |
521 this._bottomElement.style.height = '0'; | 562 this._bottomElement.style.height = '0'; |
(...skipping 26 matching lines...) Expand all Loading... |
548 this._firstIndex = firstIndex; | 589 this._firstIndex = firstIndex; |
549 this._lastIndex = lastIndex; | 590 this._lastIndex = lastIndex; |
550 this._topHeight = this._offsetAtIndex(firstIndex); | 591 this._topHeight = this._offsetAtIndex(firstIndex); |
551 this._topElement.style.height = this._topHeight + 'px'; | 592 this._topElement.style.height = this._topHeight + 'px'; |
552 this._bottomHeight = totalHeight - this._offsetAtIndex(lastIndex); | 593 this._bottomHeight = totalHeight - this._offsetAtIndex(lastIndex); |
553 this._bottomElement.style.height = this._bottomHeight + 'px'; | 594 this._bottomElement.style.height = this._bottomHeight + 'px'; |
554 this._renderedHeight = totalHeight; | 595 this._renderedHeight = totalHeight; |
555 this.element.scrollTop = scrollTop; | 596 this.element.scrollTop = scrollTop; |
556 } | 597 } |
557 }; | 598 }; |
OLD | NEW |