| 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 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 117 | 117 |
| 118 /** | 118 /** |
| 119 * @param {number} index | 119 * @param {number} index |
| 120 * @param {T} item | 120 * @param {T} item |
| 121 */ | 121 */ |
| 122 insertItemAtIndex(index, item) { | 122 insertItemAtIndex(index, item) { |
| 123 this.replaceItemsInRange(index, index, [item]); | 123 this.replaceItemsInRange(index, index, [item]); |
| 124 } | 124 } |
| 125 | 125 |
| 126 /** | 126 /** |
| 127 * @param {T} item |
| 128 * @param {function(T, T):number} comparator |
| 129 */ |
| 130 insertItemWithComparator(item, comparator) { |
| 131 var index = this._items.lowerBound(item, comparator); |
| 132 this.insertItemAtIndex(index, item); |
| 133 } |
| 134 |
| 135 /** |
| 136 * @param {T} item |
| 137 * @return {number} |
| 138 */ |
| 139 indexOfItem(item) { |
| 140 return this._items.indexOf(item); |
| 141 } |
| 142 |
| 143 /** |
| 127 * @param {number} index | 144 * @param {number} index |
| 128 * @return {T} | 145 * @return {T} |
| 129 */ | 146 */ |
| 130 removeItemAtIndex(index) { | 147 removeItemAtIndex(index) { |
| 131 var result = this._items[index]; | 148 var result = this._items[index]; |
| 132 this.replaceItemsInRange(index, index + 1, []); | 149 this.replaceItemsInRange(index, index + 1, []); |
| 133 return result; | 150 return result; |
| 134 } | 151 } |
| 135 | 152 |
| 136 /** | 153 /** |
| 137 * @param {T} item | 154 * @param {T} item |
| 138 */ | 155 */ |
| 139 removeItem(item) { | 156 removeItem(item) { |
| 140 var index = this._items.indexOf(item); | 157 var index = this._items.indexOf(item); |
| 141 if (index === -1) | 158 if (index === -1) { |
| 142 throw 'Attempt to remove non-existing item'; | 159 console.error('Attempt to remove non-existing item'); |
| 160 return; |
| 161 } |
| 143 this.removeItemAtIndex(index); | 162 this.removeItemAtIndex(index); |
| 144 } | 163 } |
| 145 | 164 |
| 146 /** | 165 /** |
| 147 * @param {number} from | 166 * @param {number} from |
| 148 * @param {number} to | 167 * @param {number} to |
| 149 * @param {!Array<T>} items | 168 * @param {!Array<T>} items |
| 150 */ | 169 */ |
| 151 replaceItemsInRange(from, to, items) { | 170 replaceItemsInRange(from, to, items) { |
| 152 var oldSelectedItem = this._selectedIndex !== -1 ? this._items[this._selecte
dIndex] : null; | 171 var oldSelectedItem = this._selectedIndex !== -1 ? this._items[this._selecte
dIndex] : null; |
| (...skipping 21 matching lines...) Expand all Loading... |
| 174 } | 193 } |
| 175 } | 194 } |
| 176 | 195 |
| 177 /** | 196 /** |
| 178 * @param {!Array<T>} items | 197 * @param {!Array<T>} items |
| 179 */ | 198 */ |
| 180 replaceAllItems(items) { | 199 replaceAllItems(items) { |
| 181 this.replaceItemsInRange(0, this._items.length, items); | 200 this.replaceItemsInRange(0, this._items.length, items); |
| 182 } | 201 } |
| 183 | 202 |
| 203 refreshAllItems() { |
| 204 this.refreshItemsInRange(0, this._items.length); |
| 205 } |
| 206 |
| 184 /** | 207 /** |
| 185 * @param {number} from | 208 * @param {number} from |
| 186 * @param {number} to | 209 * @param {number} to |
| 210 */ |
| 211 refreshItemsInRange(from, to) { |
| 212 for (var i = from; i < to; i++) |
| 213 this._itemToElement.delete(this._items[i]); |
| 214 this.invalidateRange(from, to); |
| 215 if (this._selectedIndex !== -1) |
| 216 this._select(this._selectedIndex, null, null); |
| 217 } |
| 218 |
| 219 /** |
| 220 * @param {number} from |
| 221 * @param {number} to |
| 187 */ | 222 */ |
| 188 invalidateRange(from, to) { | 223 invalidateRange(from, to) { |
| 189 this._invalidate(from, to, to - from); | 224 this._invalidate(from, to, to - from); |
| 190 } | 225 } |
| 191 | 226 |
| 192 viewportResized() { | 227 viewportResized() { |
| 193 if (this._mode === UI.ListMode.NonViewport) | 228 if (this._mode === UI.ListMode.NonViewport) |
| 194 return; | 229 return; |
| 195 // TODO(dgozman): try to keep visible scrollTop the same. | 230 // TODO(dgozman): try to keep visible scrollTop the same. |
| 196 var scrollTop = this.element.scrollTop; | 231 var scrollTop = this.element.scrollTop; |
| 197 var viewportHeight = this.element.offsetHeight; | 232 var viewportHeight = this.element.offsetHeight; |
| 198 this._clearViewport(); | 233 this._clearViewport(); |
| 199 this._updateViewport(Number.constrain(scrollTop, 0, this._totalHeight() - vi
ewportHeight), viewportHeight); | 234 this._updateViewport(Number.constrain(scrollTop, 0, this._totalHeight() - vi
ewportHeight), viewportHeight); |
| 200 } | 235 } |
| 201 | 236 |
| 202 invalidateItemHeight() { | 237 invalidateItemHeight() { |
| 203 if (this._mode !== UI.ListMode.EqualHeightItems) | 238 if (this._mode !== UI.ListMode.EqualHeightItems) { |
| 204 throw 'Only supported in equal height items mode'; | 239 console.error('Only supported in equal height items mode'); |
| 240 return; |
| 241 } |
| 205 this._fixedHeight = 0; | 242 this._fixedHeight = 0; |
| 206 if (this._items.length) { | 243 if (this._items.length) { |
| 207 this._itemToElement.clear(); | 244 this._itemToElement.clear(); |
| 208 this._invalidate(0, this._items.length, this._items.length); | 245 this._invalidate(0, this._items.length, this._items.length); |
| 209 } | 246 } |
| 210 } | 247 } |
| 211 | 248 |
| 212 /** | 249 /** |
| 213 * @param {?Node} node | 250 * @param {?Node} node |
| 214 * @return {?T} | 251 * @return {?T} |
| 215 */ | 252 */ |
| 216 itemForNode(node) { | 253 itemForNode(node) { |
| 217 while (node && node.parentNodeOrShadowHost() !== this.element) | 254 while (node && node.parentNodeOrShadowHost() !== this.element) |
| 218 node = node.parentNodeOrShadowHost(); | 255 node = node.parentNodeOrShadowHost(); |
| 219 if (!node) | 256 if (!node) |
| 220 return null; | 257 return null; |
| 221 var element = /** @type {!Element} */ (node); | 258 var element = /** @type {!Element} */ (node); |
| 222 var index = this._items.findIndex(item => this._itemToElement.get(item) ===
element); | 259 var index = this._items.findIndex(item => this._itemToElement.get(item) ===
element); |
| 223 return index !== -1 ? this._items[index] : null; | 260 return index !== -1 ? this._items[index] : null; |
| 224 } | 261 } |
| 225 | 262 |
| 226 /** | 263 /** |
| 227 * @param {T} item | 264 * @param {T} item |
| 228 * @param {boolean=} center | 265 * @param {boolean=} center |
| 229 */ | 266 */ |
| 230 scrollItemIntoView(item, center) { | 267 scrollItemIntoView(item, center) { |
| 231 var index = this._items.indexOf(item); | 268 var index = this._items.indexOf(item); |
| 232 if (index === -1) | 269 if (index === -1) { |
| 233 throw 'Attempt to scroll onto missing item'; | 270 console.error('Attempt to scroll onto missing item'); |
| 271 return; |
| 272 } |
| 234 this._scrollIntoView(index, center); | 273 this._scrollIntoView(index, center); |
| 235 } | 274 } |
| 236 | 275 |
| 237 /** | 276 /** |
| 238 * @return {?T} | 277 * @return {?T} |
| 239 */ | 278 */ |
| 240 selectedItem() { | 279 selectedItem() { |
| 241 return this._selectedIndex === -1 ? null : this._items[this._selectedIndex]; | 280 return this._selectedIndex === -1 ? null : this._items[this._selectedIndex]; |
| 242 } | 281 } |
| 243 | 282 |
| 244 /** | 283 /** |
| 284 * @return {number} |
| 285 */ |
| 286 selectedIndex() { |
| 287 return this._selectedIndex; |
| 288 } |
| 289 |
| 290 /** |
| 245 * @param {?T} item | 291 * @param {?T} item |
| 246 * @param {boolean=} center | 292 * @param {boolean=} center |
| 293 * @param {boolean=} dontScroll |
| 247 */ | 294 */ |
| 248 selectItem(item, center) { | 295 selectItem(item, center, dontScroll) { |
| 249 var index = -1; | 296 var index = -1; |
| 250 if (item !== null) { | 297 if (item !== null) { |
| 251 index = this._items.indexOf(item); | 298 index = this._items.indexOf(item); |
| 252 if (index === -1) | 299 if (index === -1) { |
| 253 throw 'Attempt to select missing item'; | 300 console.error('Attempt to select missing item'); |
| 254 if (!this._delegate.isItemSelectable(item)) | 301 return; |
| 255 throw 'Attempt to select non-selectable item'; | 302 } |
| 303 if (!this._delegate.isItemSelectable(item)) { |
| 304 console.error('Attempt to select non-selectable item'); |
| 305 return; |
| 306 } |
| 256 } | 307 } |
| 257 if (this._selectedIndex !== index) | 308 if (this._selectedIndex !== index) |
| 258 this._select(index); | 309 this._select(index); |
| 259 if (index !== -1) | 310 if (index !== -1 && !dontScroll) |
| 260 this._scrollIntoView(index, center); | 311 this._scrollIntoView(index, center); |
| 261 } | 312 } |
| 262 | 313 |
| 263 /** | 314 /** |
| 264 * @param {boolean=} canWrap | 315 * @param {boolean=} canWrap |
| 265 * @param {boolean=} center | 316 * @param {boolean=} center |
| 266 * @return {boolean} | 317 * @return {boolean} |
| 267 */ | 318 */ |
| 268 selectPreviousItem(canWrap, center) { | 319 selectPreviousItem(canWrap, center) { |
| 269 if (this._selectedIndex === -1 && !canWrap) | 320 if (this._selectedIndex === -1 && !canWrap) |
| (...skipping 318 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 588 var startElement = this._topElement; | 639 var startElement = this._topElement; |
| 589 for (var index = 0; index < start; index++) | 640 for (var index = 0; index < start; index++) |
| 590 startElement = startElement.nextElementSibling; | 641 startElement = startElement.nextElementSibling; |
| 591 while (remove--) | 642 while (remove--) |
| 592 startElement.nextElementSibling.remove(); | 643 startElement.nextElementSibling.remove(); |
| 593 while (add--) | 644 while (add--) |
| 594 this.element.insertBefore(this._elementAtIndex(start + add), startElement.
nextElementSibling); | 645 this.element.insertBefore(this._elementAtIndex(start + add), startElement.
nextElementSibling); |
| 595 } | 646 } |
| 596 | 647 |
| 597 _clearViewport() { | 648 _clearViewport() { |
| 598 if (this._mode === UI.ListMode.NonViewport) | 649 if (this._mode === UI.ListMode.NonViewport) { |
| 599 throw 'There should be no viewport updates in non-viewport mode'; | 650 console.error('There should be no viewport updates in non-viewport mode'); |
| 651 return; |
| 652 } |
| 600 this._firstIndex = 0; | 653 this._firstIndex = 0; |
| 601 this._lastIndex = 0; | 654 this._lastIndex = 0; |
| 602 this._renderedHeight = 0; | 655 this._renderedHeight = 0; |
| 603 this._topHeight = 0; | 656 this._topHeight = 0; |
| 604 this._bottomHeight = 0; | 657 this._bottomHeight = 0; |
| 605 this._clearContents(); | 658 this._clearContents(); |
| 606 } | 659 } |
| 607 | 660 |
| 608 _clearContents() { | 661 _clearContents() { |
| 609 // Note: this method should not force layout. Be careful. | 662 // Note: this method should not force layout. Be careful. |
| 610 this._topElement.style.height = '0'; | 663 this._topElement.style.height = '0'; |
| 611 this._bottomElement.style.height = '0'; | 664 this._bottomElement.style.height = '0'; |
| 612 this.element.removeChildren(); | 665 this.element.removeChildren(); |
| 613 this.element.appendChild(this._topElement); | 666 this.element.appendChild(this._topElement); |
| 614 this.element.appendChild(this._bottomElement); | 667 this.element.appendChild(this._bottomElement); |
| 615 } | 668 } |
| 616 | 669 |
| 617 /** | 670 /** |
| 618 * @param {number} scrollTop | 671 * @param {number} scrollTop |
| 619 * @param {number} viewportHeight | 672 * @param {number} viewportHeight |
| 620 */ | 673 */ |
| 621 _updateViewport(scrollTop, viewportHeight) { | 674 _updateViewport(scrollTop, viewportHeight) { |
| 622 // Note: this method should not force layout. Be careful. | 675 // Note: this method should not force layout. Be careful. |
| 623 if (this._mode === UI.ListMode.NonViewport) | 676 if (this._mode === UI.ListMode.NonViewport) { |
| 624 throw 'There should be no viewport updates in non-viewport mode'; | 677 console.error('There should be no viewport updates in non-viewport mode'); |
| 625 | 678 return; |
| 679 } |
| 626 var totalHeight = this._totalHeight(); | 680 var totalHeight = this._totalHeight(); |
| 627 if (!totalHeight) { | 681 if (!totalHeight) { |
| 628 this._firstIndex = 0; | 682 this._firstIndex = 0; |
| 629 this._lastIndex = 0; | 683 this._lastIndex = 0; |
| 630 this._topHeight = 0; | 684 this._topHeight = 0; |
| 631 this._bottomHeight = 0; | 685 this._bottomHeight = 0; |
| 632 this._renderedHeight = 0; | 686 this._renderedHeight = 0; |
| 633 this._topElement.style.height = '0'; | 687 this._topElement.style.height = '0'; |
| 634 this._bottomElement.style.height = '0'; | 688 this._bottomElement.style.height = '0'; |
| 635 return; | 689 return; |
| (...skipping 25 matching lines...) Expand all Loading... |
| 661 this._firstIndex = firstIndex; | 715 this._firstIndex = firstIndex; |
| 662 this._lastIndex = lastIndex; | 716 this._lastIndex = lastIndex; |
| 663 this._topHeight = this._offsetAtIndex(firstIndex); | 717 this._topHeight = this._offsetAtIndex(firstIndex); |
| 664 this._topElement.style.height = this._topHeight + 'px'; | 718 this._topElement.style.height = this._topHeight + 'px'; |
| 665 this._bottomHeight = totalHeight - this._offsetAtIndex(lastIndex); | 719 this._bottomHeight = totalHeight - this._offsetAtIndex(lastIndex); |
| 666 this._bottomElement.style.height = this._bottomHeight + 'px'; | 720 this._bottomElement.style.height = this._bottomHeight + 'px'; |
| 667 this._renderedHeight = totalHeight; | 721 this._renderedHeight = totalHeight; |
| 668 this.element.scrollTop = scrollTop; | 722 this.element.scrollTop = scrollTop; |
| 669 } | 723 } |
| 670 }; | 724 }; |
| OLD | NEW |