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