| 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 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 43 NonViewport: Symbol('UI.ListMode.NonViewport'), | 43 NonViewport: Symbol('UI.ListMode.NonViewport'), |
| 44 EqualHeightItems: Symbol('UI.ListMode.EqualHeightItems'), | 44 EqualHeightItems: Symbol('UI.ListMode.EqualHeightItems'), |
| 45 VariousHeightItems: Symbol('UI.ListMode.VariousHeightItems') | 45 VariousHeightItems: Symbol('UI.ListMode.VariousHeightItems') |
| 46 }; | 46 }; |
| 47 | 47 |
| 48 /** | 48 /** |
| 49 * @template T | 49 * @template T |
| 50 */ | 50 */ |
| 51 UI.ListControl = class { | 51 UI.ListControl = class { |
| 52 /** | 52 /** |
| 53 * @param {!UI.ListModel<T>} model |
| 53 * @param {!UI.ListDelegate<T>} delegate | 54 * @param {!UI.ListDelegate<T>} delegate |
| 54 * @param {!UI.ListMode=} mode | 55 * @param {!UI.ListMode=} mode |
| 55 */ | 56 */ |
| 56 constructor(delegate, mode) { | 57 constructor(model, delegate, mode) { |
| 57 this.element = createElement('div'); | 58 this.element = createElement('div'); |
| 58 this.element.style.overflowY = 'auto'; | 59 this.element.style.overflowY = 'auto'; |
| 59 this._topElement = this.element.createChild('div'); | 60 this._topElement = this.element.createChild('div'); |
| 60 this._bottomElement = this.element.createChild('div'); | 61 this._bottomElement = this.element.createChild('div'); |
| 61 this._firstIndex = 0; | 62 this._firstIndex = 0; |
| 62 this._lastIndex = 0; | 63 this._lastIndex = 0; |
| 63 this._renderedHeight = 0; | 64 this._renderedHeight = 0; |
| 64 this._topHeight = 0; | 65 this._topHeight = 0; |
| 65 this._bottomHeight = 0; | 66 this._bottomHeight = 0; |
| 66 | 67 |
| 67 /** @type {!Array<T>} */ | 68 this._model = model; |
| 68 this._items = []; | 69 this._model.addEventListener(UI.ListModel.Events.ItemsReplaced, this._replac
edItemsInRange, this); |
| 69 /** @type {!Map<T, !Element>} */ | 70 /** @type {!Map<T, !Element>} */ |
| 70 this._itemToElement = new Map(); | 71 this._itemToElement = new Map(); |
| 71 this._selectedIndex = -1; | 72 this._selectedIndex = -1; |
| 73 /** @type {?T} */ |
| 74 this._selectedItem = null; |
| 72 | 75 |
| 73 this.element.tabIndex = -1; | 76 this.element.tabIndex = -1; |
| 74 this.element.addEventListener('click', this._onClick.bind(this), false); | 77 this.element.addEventListener('click', this._onClick.bind(this), false); |
| 75 this.element.addEventListener('keydown', this._onKeyDown.bind(this), false); | 78 this.element.addEventListener('keydown', this._onKeyDown.bind(this), false); |
| 76 | 79 |
| 77 this._delegate = delegate; | 80 this._delegate = delegate; |
| 78 this._mode = mode || UI.ListMode.EqualHeightItems; | 81 this._mode = mode || UI.ListMode.EqualHeightItems; |
| 79 this._fixedHeight = 0; | 82 this._fixedHeight = 0; |
| 80 this._variableOffsets = new Int32Array(0); | 83 this._variableOffsets = new Int32Array(0); |
| 81 this._clearContents(); | 84 this._clearContents(); |
| 82 | 85 |
| 83 if (this._mode !== UI.ListMode.NonViewport) { | 86 if (this._mode !== UI.ListMode.NonViewport) { |
| 84 this.element.addEventListener('scroll', () => { | 87 this.element.addEventListener('scroll', () => { |
| 85 this._updateViewport(this.element.scrollTop, this.element.offsetHeight); | 88 this._updateViewport(this.element.scrollTop, this.element.offsetHeight); |
| 86 }, false); | 89 }, false); |
| 87 } | 90 } |
| 88 } | 91 } |
| 89 | 92 |
| 90 /** | 93 /** |
| 91 * @return {number} | 94 * @param {!UI.ListModel<T>} model |
| 92 */ | 95 */ |
| 93 length() { | 96 setModel(model) { |
| 94 return this._items.length; | 97 this._itemToElement.clear(); |
| 98 var length = this._model.length(); |
| 99 this._model.removeEventListener(UI.ListModel.Events.ItemsReplaced, this._rep
lacedItemsInRange, this); |
| 100 this._model = model; |
| 101 this._model.addEventListener(UI.ListModel.Events.ItemsReplaced, this._replac
edItemsInRange, this); |
| 102 this.invalidateRange(0, length); |
| 95 } | 103 } |
| 96 | 104 |
| 97 /** | 105 /** |
| 98 * @param {number} index | 106 * @param {!Common.Event} event |
| 99 * @return {T} | |
| 100 */ | 107 */ |
| 101 itemAtIndex(index) { | 108 _replacedItemsInRange(event) { |
| 102 return this._items[index]; | 109 var data = /** @type {{index: number, removed: !Array<T>, inserted: number}}
*/ (event.data); |
| 103 } | 110 var from = data.index; |
| 111 var to = from + data.removed.length; |
| 104 | 112 |
| 105 /** | 113 var oldSelectedItem = this._selectedItem; |
| 106 * @param {T} item | |
| 107 */ | |
| 108 pushItem(item) { | |
| 109 this.replaceItemsInRange(this._items.length, this._items.length, [item]); | |
| 110 } | |
| 111 | |
| 112 /** | |
| 113 * @return {T} | |
| 114 */ | |
| 115 popItem() { | |
| 116 return this.removeItemAtIndex(this._items.length - 1); | |
| 117 } | |
| 118 | |
| 119 /** | |
| 120 * @param {number} index | |
| 121 * @param {T} item | |
| 122 */ | |
| 123 insertItemAtIndex(index, item) { | |
| 124 this.replaceItemsInRange(index, index, [item]); | |
| 125 } | |
| 126 | |
| 127 /** | |
| 128 * @param {T} item | |
| 129 * @param {function(T, T):number} comparator | |
| 130 */ | |
| 131 insertItemWithComparator(item, comparator) { | |
| 132 var index = this._items.lowerBound(item, comparator); | |
| 133 this.insertItemAtIndex(index, item); | |
| 134 } | |
| 135 | |
| 136 /** | |
| 137 * @param {T} item | |
| 138 * @return {number} | |
| 139 */ | |
| 140 indexOfItem(item) { | |
| 141 return this._items.indexOf(item); | |
| 142 } | |
| 143 | |
| 144 /** | |
| 145 * @param {number} index | |
| 146 * @return {T} | |
| 147 */ | |
| 148 removeItemAtIndex(index) { | |
| 149 var result = this._items[index]; | |
| 150 this.replaceItemsInRange(index, index + 1, []); | |
| 151 return result; | |
| 152 } | |
| 153 | |
| 154 /** | |
| 155 * @param {T} item | |
| 156 */ | |
| 157 removeItem(item) { | |
| 158 var index = this._items.indexOf(item); | |
| 159 if (index === -1) { | |
| 160 console.error('Attempt to remove non-existing item'); | |
| 161 return; | |
| 162 } | |
| 163 this.removeItemAtIndex(index); | |
| 164 } | |
| 165 | |
| 166 /** | |
| 167 * @param {number} from | |
| 168 * @param {number} to | |
| 169 * @param {!Array<T>} items | |
| 170 */ | |
| 171 replaceItemsInRange(from, to, items) { | |
| 172 var oldSelectedItem = this._selectedIndex !== -1 ? this._items[this._selecte
dIndex] : null; | |
| 173 var oldSelectedElement = oldSelectedItem ? (this._itemToElement.get(oldSelec
tedItem) || null) : null; | 114 var oldSelectedElement = oldSelectedItem ? (this._itemToElement.get(oldSelec
tedItem) || null) : null; |
| 174 | 115 for (var i = 0; i < data.removed.length; i++) |
| 175 for (var i = from; i < to; i++) | 116 this._itemToElement.delete(data.removed[i]); |
| 176 this._itemToElement.delete(this._items[i]); | 117 this._invalidate(from, to, data.inserted); |
| 177 if (items.length < 10000) { | |
| 178 this._items.splice.bind(this._items, from, to - from).apply(null, items); | |
| 179 } else { | |
| 180 // Splice may fail with too many arguments. | |
| 181 var before = this._items.slice(0, from); | |
| 182 var after = this._items.slice(to); | |
| 183 this._items = [].concat(before, items, after); | |
| 184 } | |
| 185 this._invalidate(from, to, items.length); | |
| 186 | 118 |
| 187 if (this._selectedIndex >= to) { | 119 if (this._selectedIndex >= to) { |
| 188 this._selectedIndex += items.length - (to - from); | 120 this._selectedIndex += data.inserted - (to - from); |
| 121 this._selectedItem = this._model.itemAtIndex(this._selectedIndex); |
| 189 } else if (this._selectedIndex >= from) { | 122 } else if (this._selectedIndex >= from) { |
| 190 var index = this._findFirstSelectable(from + items.length, +1, false); | 123 var index = this._findFirstSelectable(from + data.inserted, +1, false); |
| 191 if (index === -1) | 124 if (index === -1) |
| 192 index = this._findFirstSelectable(from - 1, -1, false); | 125 index = this._findFirstSelectable(from - 1, -1, false); |
| 193 this._select(index, oldSelectedItem, oldSelectedElement); | 126 this._select(index, oldSelectedItem, oldSelectedElement); |
| 194 } | 127 } |
| 195 } | 128 } |
| 196 | 129 |
| 197 /** | 130 /** |
| 198 * @param {!Array<T>} items | 131 * @param {T} item |
| 199 */ | 132 */ |
| 200 replaceAllItems(items) { | 133 refreshItem(item) { |
| 201 this.replaceItemsInRange(0, this._items.length, items); | 134 var index = this._model.indexOfItem(item); |
| 202 } | 135 if (index === -1) { |
| 203 | 136 console.error('Item to refresh is not present'); |
| 204 refreshAllItems() { | 137 return; |
| 205 this.refreshItemsInRange(0, this._items.length); | 138 } |
| 206 } | 139 this._itemToElement.delete(item); |
| 207 | 140 this.invalidateRange(index, index + 1); |
| 208 /** | |
| 209 * @param {number} from | |
| 210 * @param {number} to | |
| 211 */ | |
| 212 refreshItemsInRange(from, to) { | |
| 213 for (var i = from; i < to; i++) | |
| 214 this._itemToElement.delete(this._items[i]); | |
| 215 this.invalidateRange(from, to); | |
| 216 if (this._selectedIndex !== -1) | 141 if (this._selectedIndex !== -1) |
| 217 this._select(this._selectedIndex, null, null); | 142 this._select(this._selectedIndex, null, null); |
| 218 } | 143 } |
| 219 | 144 |
| 220 /** | 145 /** |
| 221 * @param {number} from | 146 * @param {number} from |
| 222 * @param {number} to | 147 * @param {number} to |
| 223 */ | 148 */ |
| 224 invalidateRange(from, to) { | 149 invalidateRange(from, to) { |
| 225 this._invalidate(from, to, to - from); | 150 this._invalidate(from, to, to - from); |
| 226 } | 151 } |
| 227 | 152 |
| 228 viewportResized() { | 153 viewportResized() { |
| 229 if (this._mode === UI.ListMode.NonViewport) | 154 if (this._mode === UI.ListMode.NonViewport) |
| 230 return; | 155 return; |
| 231 // TODO(dgozman): try to keep visible scrollTop the same. | 156 // TODO(dgozman): try to keep visible scrollTop the same. |
| 232 var scrollTop = this.element.scrollTop; | 157 var scrollTop = this.element.scrollTop; |
| 233 var viewportHeight = this.element.offsetHeight; | 158 var viewportHeight = this.element.offsetHeight; |
| 234 this._clearViewport(); | 159 this._clearViewport(); |
| 235 this._updateViewport(Number.constrain(scrollTop, 0, this._totalHeight() - vi
ewportHeight), viewportHeight); | 160 this._updateViewport(Number.constrain(scrollTop, 0, this._totalHeight() - vi
ewportHeight), viewportHeight); |
| 236 } | 161 } |
| 237 | 162 |
| 238 invalidateItemHeight() { | 163 invalidateItemHeight() { |
| 239 if (this._mode !== UI.ListMode.EqualHeightItems) { | 164 if (this._mode !== UI.ListMode.EqualHeightItems) { |
| 240 console.error('Only supported in equal height items mode'); | 165 console.error('Only supported in equal height items mode'); |
| 241 return; | 166 return; |
| 242 } | 167 } |
| 243 this._fixedHeight = 0; | 168 this._fixedHeight = 0; |
| 244 if (this._items.length) { | 169 if (this._model.length()) { |
| 245 this._itemToElement.clear(); | 170 this._itemToElement.clear(); |
| 246 this._invalidate(0, this._items.length, this._items.length); | 171 this._invalidate(0, this._model.length(), this._model.length()); |
| 247 } | 172 } |
| 248 } | 173 } |
| 249 | 174 |
| 250 /** | 175 /** |
| 251 * @param {?Node} node | 176 * @param {?Node} node |
| 252 * @return {?T} | 177 * @return {?T} |
| 253 */ | 178 */ |
| 254 itemForNode(node) { | 179 itemForNode(node) { |
| 255 while (node && node.parentNodeOrShadowHost() !== this.element) | 180 while (node && node.parentNodeOrShadowHost() !== this.element) |
| 256 node = node.parentNodeOrShadowHost(); | 181 node = node.parentNodeOrShadowHost(); |
| 257 if (!node) | 182 if (!node) |
| 258 return null; | 183 return null; |
| 259 var element = /** @type {!Element} */ (node); | 184 var element = /** @type {!Element} */ (node); |
| 260 var index = this._items.findIndex(item => this._itemToElement.get(item) ===
element); | 185 var index = this._model.findIndex(item => this._itemToElement.get(item) ===
element); |
| 261 return index !== -1 ? this._items[index] : null; | 186 return index !== -1 ? this._model.itemAtIndex(index) : null; |
| 262 } | 187 } |
| 263 | 188 |
| 264 /** | 189 /** |
| 265 * @param {T} item | 190 * @param {T} item |
| 266 * @param {boolean=} center | 191 * @param {boolean=} center |
| 267 */ | 192 */ |
| 268 scrollItemIntoView(item, center) { | 193 scrollItemIntoView(item, center) { |
| 269 var index = this._items.indexOf(item); | 194 var index = this._model.indexOfItem(item); |
| 270 if (index === -1) { | 195 if (index === -1) { |
| 271 console.error('Attempt to scroll onto missing item'); | 196 console.error('Attempt to scroll onto missing item'); |
| 272 return; | 197 return; |
| 273 } | 198 } |
| 274 this._scrollIntoView(index, center); | 199 this._scrollIntoView(index, center); |
| 275 } | 200 } |
| 276 | 201 |
| 277 /** | 202 /** |
| 278 * @return {?T} | 203 * @return {?T} |
| 279 */ | 204 */ |
| 280 selectedItem() { | 205 selectedItem() { |
| 281 return this._selectedIndex === -1 ? null : this._items[this._selectedIndex]; | 206 return this._selectedItem; |
| 282 } | 207 } |
| 283 | 208 |
| 284 /** | 209 /** |
| 285 * @return {number} | 210 * @return {number} |
| 286 */ | 211 */ |
| 287 selectedIndex() { | 212 selectedIndex() { |
| 288 return this._selectedIndex; | 213 return this._selectedIndex; |
| 289 } | 214 } |
| 290 | 215 |
| 291 /** | 216 /** |
| 292 * @param {?T} item | 217 * @param {?T} item |
| 293 * @param {boolean=} center | 218 * @param {boolean=} center |
| 294 * @param {boolean=} dontScroll | 219 * @param {boolean=} dontScroll |
| 295 */ | 220 */ |
| 296 selectItem(item, center, dontScroll) { | 221 selectItem(item, center, dontScroll) { |
| 297 var index = -1; | 222 var index = -1; |
| 298 if (item !== null) { | 223 if (item !== null) { |
| 299 index = this._items.indexOf(item); | 224 index = this._model.indexOfItem(item); |
| 300 if (index === -1) { | 225 if (index === -1) { |
| 301 console.error('Attempt to select missing item'); | 226 console.error('Attempt to select missing item'); |
| 302 return; | 227 return; |
| 303 } | 228 } |
| 304 if (!this._delegate.isItemSelectable(item)) { | 229 if (!this._delegate.isItemSelectable(item)) { |
| 305 console.error('Attempt to select non-selectable item'); | 230 console.error('Attempt to select non-selectable item'); |
| 306 return; | 231 return; |
| 307 } | 232 } |
| 308 } | 233 } |
| 309 if (this._selectedIndex !== index) | 234 if (this._selectedIndex !== index) |
| 310 this._select(index); | 235 this._select(index); |
| 311 if (index !== -1 && !dontScroll) | 236 if (index !== -1 && !dontScroll) |
| 312 this._scrollIntoView(index, center); | 237 this._scrollIntoView(index, center); |
| 313 } | 238 } |
| 314 | 239 |
| 315 /** | 240 /** |
| 316 * @param {boolean=} canWrap | 241 * @param {boolean=} canWrap |
| 317 * @param {boolean=} center | 242 * @param {boolean=} center |
| 318 * @return {boolean} | 243 * @return {boolean} |
| 319 */ | 244 */ |
| 320 selectPreviousItem(canWrap, center) { | 245 selectPreviousItem(canWrap, center) { |
| 321 if (this._selectedIndex === -1 && !canWrap) | 246 if (this._selectedIndex === -1 && !canWrap) |
| 322 return false; | 247 return false; |
| 323 var index = this._selectedIndex === -1 ? this._items.length - 1 : this._sele
ctedIndex - 1; | 248 var index = this._selectedIndex === -1 ? this._model.length() - 1 : this._se
lectedIndex - 1; |
| 324 index = this._findFirstSelectable(index, -1, !!canWrap); | 249 index = this._findFirstSelectable(index, -1, !!canWrap); |
| 325 if (index !== -1) { | 250 if (index !== -1) { |
| 326 this._scrollIntoView(index, center); | 251 this._scrollIntoView(index, center); |
| 327 this._select(index); | 252 this._select(index); |
| 328 return true; | 253 return true; |
| 329 } | 254 } |
| 330 return false; | 255 return false; |
| 331 } | 256 } |
| 332 | 257 |
| 333 /** | 258 /** |
| (...skipping 14 matching lines...) Expand all Loading... |
| 348 return false; | 273 return false; |
| 349 } | 274 } |
| 350 | 275 |
| 351 /** | 276 /** |
| 352 * @param {boolean=} center | 277 * @param {boolean=} center |
| 353 * @return {boolean} | 278 * @return {boolean} |
| 354 */ | 279 */ |
| 355 selectItemPreviousPage(center) { | 280 selectItemPreviousPage(center) { |
| 356 if (this._mode === UI.ListMode.NonViewport) | 281 if (this._mode === UI.ListMode.NonViewport) |
| 357 return false; | 282 return false; |
| 358 var index = this._selectedIndex === -1 ? this._items.length - 1 : this._sele
ctedIndex; | 283 var index = this._selectedIndex === -1 ? this._model.length() - 1 : this._se
lectedIndex; |
| 359 index = this._findPageSelectable(index, -1); | 284 index = this._findPageSelectable(index, -1); |
| 360 if (index !== -1) { | 285 if (index !== -1) { |
| 361 this._scrollIntoView(index, center); | 286 this._scrollIntoView(index, center); |
| 362 this._select(index); | 287 this._select(index); |
| 363 return true; | 288 return true; |
| 364 } | 289 } |
| 365 return false; | 290 return false; |
| 366 } | 291 } |
| 367 | 292 |
| 368 /** | 293 /** |
| (...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 437 break; | 362 break; |
| 438 } | 363 } |
| 439 if (selected) | 364 if (selected) |
| 440 event.consume(); | 365 event.consume(); |
| 441 } | 366 } |
| 442 | 367 |
| 443 /** | 368 /** |
| 444 * @return {number} | 369 * @return {number} |
| 445 */ | 370 */ |
| 446 _totalHeight() { | 371 _totalHeight() { |
| 447 return this._offsetAtIndex(this._items.length); | 372 return this._offsetAtIndex(this._model.length()); |
| 448 } | 373 } |
| 449 | 374 |
| 450 /** | 375 /** |
| 451 * @param {number} offset | 376 * @param {number} offset |
| 452 * @return {number} | 377 * @return {number} |
| 453 */ | 378 */ |
| 454 _indexAtOffset(offset) { | 379 _indexAtOffset(offset) { |
| 455 if (this._mode === UI.ListMode.NonViewport) | 380 if (this._mode === UI.ListMode.NonViewport) |
| 456 throw 'There should be no offset conversions in non-viewport mode'; | 381 throw 'There should be no offset conversions in non-viewport mode'; |
| 457 if (!this._items.length || offset < 0) | 382 if (!this._model.length() || offset < 0) |
| 458 return 0; | 383 return 0; |
| 459 if (this._mode === UI.ListMode.VariousHeightItems) { | 384 if (this._mode === UI.ListMode.VariousHeightItems) { |
| 460 return Math.min( | 385 return Math.min( |
| 461 this._items.length - 1, this._variableOffsets.lowerBound(offset, undef
ined, 0, this._items.length)); | 386 this._model.length() - 1, this._variableOffsets.lowerBound(offset, und
efined, 0, this._model.length())); |
| 462 } | 387 } |
| 463 if (!this._fixedHeight) | 388 if (!this._fixedHeight) |
| 464 this._measureHeight(); | 389 this._measureHeight(); |
| 465 return Math.min(this._items.length - 1, Math.floor(offset / this._fixedHeigh
t)); | 390 return Math.min(this._model.length() - 1, Math.floor(offset / this._fixedHei
ght)); |
| 466 } | 391 } |
| 467 | 392 |
| 468 /** | 393 /** |
| 469 * @param {number} index | 394 * @param {number} index |
| 470 * @return {!Element} | 395 * @return {!Element} |
| 471 */ | 396 */ |
| 472 _elementAtIndex(index) { | 397 _elementAtIndex(index) { |
| 473 var item = this._items[index]; | 398 var item = this._model.itemAtIndex(index); |
| 474 var element = this._itemToElement.get(item); | 399 var element = this._itemToElement.get(item); |
| 475 if (!element) { | 400 if (!element) { |
| 476 element = this._delegate.createElementForItem(item); | 401 element = this._delegate.createElementForItem(item); |
| 477 this._itemToElement.set(item, element); | 402 this._itemToElement.set(item, element); |
| 478 } | 403 } |
| 479 return element; | 404 return element; |
| 480 } | 405 } |
| 481 | 406 |
| 482 /** | 407 /** |
| 483 * @param {number} index | 408 * @param {number} index |
| 484 * @return {number} | 409 * @return {number} |
| 485 */ | 410 */ |
| 486 _offsetAtIndex(index) { | 411 _offsetAtIndex(index) { |
| 487 if (this._mode === UI.ListMode.NonViewport) | 412 if (this._mode === UI.ListMode.NonViewport) |
| 488 throw 'There should be no offset conversions in non-viewport mode'; | 413 throw 'There should be no offset conversions in non-viewport mode'; |
| 489 if (!this._items.length) | 414 if (!this._model.length()) |
| 490 return 0; | 415 return 0; |
| 491 if (this._mode === UI.ListMode.VariousHeightItems) | 416 if (this._mode === UI.ListMode.VariousHeightItems) |
| 492 return this._variableOffsets[index]; | 417 return this._variableOffsets[index]; |
| 493 if (!this._fixedHeight) | 418 if (!this._fixedHeight) |
| 494 this._measureHeight(); | 419 this._measureHeight(); |
| 495 return index * this._fixedHeight; | 420 return index * this._fixedHeight; |
| 496 } | 421 } |
| 497 | 422 |
| 498 _measureHeight() { | 423 _measureHeight() { |
| 499 this._fixedHeight = this._delegate.heightForItem(this._items[0]); | 424 this._fixedHeight = this._delegate.heightForItem(this._model.itemAtIndex(0))
; |
| 500 if (!this._fixedHeight) | 425 if (!this._fixedHeight) |
| 501 this._fixedHeight = UI.measurePreferredSize(this._elementAtIndex(0), this.
element).height; | 426 this._fixedHeight = UI.measurePreferredSize(this._elementAtIndex(0), this.
element).height; |
| 502 } | 427 } |
| 503 | 428 |
| 504 /** | 429 /** |
| 505 * @param {number} index | 430 * @param {number} index |
| 506 * @param {?T=} oldItem | 431 * @param {?T=} oldItem |
| 507 * @param {?Element=} oldElement | 432 * @param {?Element=} oldElement |
| 508 */ | 433 */ |
| 509 _select(index, oldItem, oldElement) { | 434 _select(index, oldItem, oldElement) { |
| 510 if (oldItem === undefined) | 435 if (oldItem === undefined) |
| 511 oldItem = this._selectedIndex !== -1 ? this._items[this._selectedIndex] :
null; | 436 oldItem = this._selectedItem; |
| 512 if (oldElement === undefined) | 437 if (oldElement === undefined) |
| 513 oldElement = this._itemToElement.get(oldItem) || null; | 438 oldElement = this._itemToElement.get(oldItem) || null; |
| 514 this._selectedIndex = index; | 439 this._selectedIndex = index; |
| 515 var newItem = this._selectedIndex !== -1 ? this._items[this._selectedIndex]
: null; | 440 this._selectedItem = index === -1 ? null : this._model.itemAtIndex(index); |
| 441 var newItem = this._selectedItem; |
| 516 var newElement = this._selectedIndex !== -1 ? this._elementAtIndex(index) :
null; | 442 var newElement = this._selectedIndex !== -1 ? this._elementAtIndex(index) :
null; |
| 517 this._delegate.selectedItemChanged(oldItem, newItem, /** @type {?Element} */
(oldElement), newElement); | 443 this._delegate.selectedItemChanged(oldItem, newItem, /** @type {?Element} */
(oldElement), newElement); |
| 518 } | 444 } |
| 519 | 445 |
| 520 /** | 446 /** |
| 521 * @param {number} index | 447 * @param {number} index |
| 522 * @param {number} direction | 448 * @param {number} direction |
| 523 * @param {boolean} canWrap | 449 * @param {boolean} canWrap |
| 524 * @return {number} | 450 * @return {number} |
| 525 */ | 451 */ |
| 526 _findFirstSelectable(index, direction, canWrap) { | 452 _findFirstSelectable(index, direction, canWrap) { |
| 527 var length = this._items.length; | 453 var length = this._model.length(); |
| 528 if (!length) | 454 if (!length) |
| 529 return -1; | 455 return -1; |
| 530 for (var step = 0; step <= length; step++) { | 456 for (var step = 0; step <= length; step++) { |
| 531 if (index < 0 || index >= length) { | 457 if (index < 0 || index >= length) { |
| 532 if (!canWrap) | 458 if (!canWrap) |
| 533 return -1; | 459 return -1; |
| 534 index = (index + length) % length; | 460 index = (index + length) % length; |
| 535 } | 461 } |
| 536 if (this._delegate.isItemSelectable(this._items[index])) | 462 if (this._delegate.isItemSelectable(this._model.itemAtIndex(index))) |
| 537 return index; | 463 return index; |
| 538 index += direction; | 464 index += direction; |
| 539 } | 465 } |
| 540 return -1; | 466 return -1; |
| 541 } | 467 } |
| 542 | 468 |
| 543 /** | 469 /** |
| 544 * @param {number} index | 470 * @param {number} index |
| 545 * @param {number} direction | 471 * @param {number} direction |
| 546 * @return {number} | 472 * @return {number} |
| 547 */ | 473 */ |
| 548 _findPageSelectable(index, direction) { | 474 _findPageSelectable(index, direction) { |
| 549 var lastSelectable = -1; | 475 var lastSelectable = -1; |
| 550 var startOffset = this._offsetAtIndex(index); | 476 var startOffset = this._offsetAtIndex(index); |
| 551 // Compensate for zoom rounding errors with -1. | 477 // Compensate for zoom rounding errors with -1. |
| 552 var viewportHeight = this.element.offsetHeight - 1; | 478 var viewportHeight = this.element.offsetHeight - 1; |
| 553 while (index >= 0 && index < this._items.length) { | 479 while (index >= 0 && index < this._model.length()) { |
| 554 if (this._delegate.isItemSelectable(this._items[index])) { | 480 if (this._delegate.isItemSelectable(this._model.itemAtIndex(index))) { |
| 555 if (Math.abs(this._offsetAtIndex(index) - startOffset) >= viewportHeight
) | 481 if (Math.abs(this._offsetAtIndex(index) - startOffset) >= viewportHeight
) |
| 556 return index; | 482 return index; |
| 557 lastSelectable = index; | 483 lastSelectable = index; |
| 558 } | 484 } |
| 559 index += direction; | 485 index += direction; |
| 560 } | 486 } |
| 561 return lastSelectable; | 487 return lastSelectable; |
| 562 } | 488 } |
| 563 | 489 |
| 564 /** | 490 /** |
| (...skipping 17 matching lines...) Expand all Loading... |
| 582 * @param {number} to | 508 * @param {number} to |
| 583 * @param {number} inserted | 509 * @param {number} inserted |
| 584 */ | 510 */ |
| 585 _invalidate(from, to, inserted) { | 511 _invalidate(from, to, inserted) { |
| 586 if (this._mode === UI.ListMode.NonViewport) { | 512 if (this._mode === UI.ListMode.NonViewport) { |
| 587 this._invalidateNonViewportMode(from, to - from, inserted); | 513 this._invalidateNonViewportMode(from, to - from, inserted); |
| 588 return; | 514 return; |
| 589 } | 515 } |
| 590 | 516 |
| 591 if (this._mode === UI.ListMode.VariousHeightItems) { | 517 if (this._mode === UI.ListMode.VariousHeightItems) { |
| 592 this._reallocateVariableOffsets(this._items.length + 1, from + 1); | 518 this._reallocateVariableOffsets(this._model.length() + 1, from + 1); |
| 593 for (var i = from + 1; i <= this._items.length; i++) | 519 for (var i = from + 1; i <= this._model.length(); i++) { |
| 594 this._variableOffsets[i] = this._variableOffsets[i - 1] + this._delegate
.heightForItem(this._items[i - 1]); | 520 this._variableOffsets[i] = |
| 521 this._variableOffsets[i - 1] + this._delegate.heightForItem(this._mo
del.itemAtIndex(i - 1)); |
| 522 } |
| 595 } | 523 } |
| 596 | 524 |
| 597 var viewportHeight = this.element.offsetHeight; | 525 var viewportHeight = this.element.offsetHeight; |
| 598 var totalHeight = this._totalHeight(); | 526 var totalHeight = this._totalHeight(); |
| 599 var scrollTop = this.element.scrollTop; | 527 var scrollTop = this.element.scrollTop; |
| 600 | 528 |
| 601 if (this._renderedHeight < viewportHeight || totalHeight < viewportHeight) { | 529 if (this._renderedHeight < viewportHeight || totalHeight < viewportHeight) { |
| 602 this._clearViewport(); | 530 this._clearViewport(); |
| 603 this._updateViewport(Number.constrain(scrollTop, 0, totalHeight - viewport
Height), viewportHeight); | 531 this._updateViewport(Number.constrain(scrollTop, 0, totalHeight - viewport
Height), viewportHeight); |
| 604 return; | 532 return; |
| (...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 716 this._firstIndex = firstIndex; | 644 this._firstIndex = firstIndex; |
| 717 this._lastIndex = lastIndex; | 645 this._lastIndex = lastIndex; |
| 718 this._topHeight = this._offsetAtIndex(firstIndex); | 646 this._topHeight = this._offsetAtIndex(firstIndex); |
| 719 this._topElement.style.height = this._topHeight + 'px'; | 647 this._topElement.style.height = this._topHeight + 'px'; |
| 720 this._bottomHeight = totalHeight - this._offsetAtIndex(lastIndex); | 648 this._bottomHeight = totalHeight - this._offsetAtIndex(lastIndex); |
| 721 this._bottomElement.style.height = this._bottomHeight + 'px'; | 649 this._bottomElement.style.height = this._bottomHeight + 'px'; |
| 722 this._renderedHeight = totalHeight; | 650 this._renderedHeight = totalHeight; |
| 723 this.element.scrollTop = scrollTop; | 651 this.element.scrollTop = scrollTop; |
| 724 } | 652 } |
| 725 }; | 653 }; |
| OLD | NEW |