in extensions/virtuallistview/src/VirtualListView.tsx [367:499]
private _handleItemListChange(props: VirtualListViewProps<ItemInfo>) {
// Build a new item map.
const newItemMap = new Map<string, number>();
let itemIndex = -1;
for (const item of props.itemList) {
itemIndex++;
// Make sure there are no duplicate keys.
if (newItemMap.has(item.key)) {
assert(false, 'Found a duplicate key: ' + item.key);
if (props.logInfo) {
props.logInfo('Item with key ' + item.key + ' is duplicated at positions ' + itemIndex +
' and ' + newItemMap.get(item.key)!);
}
}
newItemMap.set(item.key, itemIndex);
if (this.props && this.props.itemList) {
const cell = this._activeCells.get(item.key);
if (cell) {
const oldItemIndex = this._itemMap.get(item.key);
if (oldItemIndex === undefined) {
cell.shouldUpdate = true;
} else {
const oldItem = this.props.itemList[oldItemIndex];
if (this.props.skipRenderIfItemUnchanged || !_.isEqual(oldItem, item)) {
cell.shouldUpdate = true;
}
}
}
}
}
// Stop tracking the heights of deleted items.
const oldItems = (this.props && this.props.itemList) ? this.props.itemList : [];
itemIndex = -1;
for (const item of oldItems) {
itemIndex++;
if (!newItemMap.has(item.key)) {
// If we're deleting an item that's above the current render block,
// update the adjustment so we avoid an unnecessary scroll.
// Update focused item if it's the one removed, if we're unable to, reset focus
if (item.key === this.state.lastFocusedItemKey) {
if (!this._focusSubsequentItem(FocusDirection.Down, false, false) &&
!this._focusSubsequentItem(FocusDirection.Up, false, false)) {
this.setState({ lastFocusedItemKey: undefined });
}
}
if (itemIndex < this._itemsAboveRenderBlock) {
this._heightAboveRenderAdjustment += this._getHeightOfItem(oldItems[itemIndex]);
}
this._heightCache.delete(item.key);
this._pendingMeasurements.delete(item.key);
// Recycle any deleted active cells up front so they can be recycled below.
if (this._activeCells.has(item.key)) {
this._recycleCell(item.key);
}
}
}
const overdrawAmount = this._calcOverdrawAmount();
const renderBlockTopLimit = this._lastScrollTop - overdrawAmount;
const renderBlockBottomLimit = this._lastScrollTop + this._layoutHeight + overdrawAmount;
let yPosition = this._heightAboveRenderAdjustment;
let lookingForStartOfRenderBlock = true;
this._itemsAboveRenderBlock = 0;
this._itemsInRenderBlock = 0;
// Determine the new bounds of the render block.
itemIndex = -1;
for (const item of props.itemList) {
itemIndex++;
const itemHeight = this._getHeightOfItem(item);
yPosition += itemHeight;
if (yPosition <= renderBlockTopLimit) {
if (this._activeCells.has(item.key)) {
this._recycleCell(item.key);
}
} else {
if (lookingForStartOfRenderBlock) {
this._itemsAboveRenderBlock = itemIndex;
lookingForStartOfRenderBlock = false;
}
if (yPosition - itemHeight < renderBlockBottomLimit) {
// We're within the render block.
this._itemsInRenderBlock++;
if (this._activeCells.has(item.key)) {
this._setCellTopAndVisibility(item.key, this._shouldShowItem(item, props),
yPosition - itemHeight, !!props.animateChanges);
} else {
this._allocateCell(item.key, item.template, itemIndex, !item.measureHeight, item.height,
yPosition - itemHeight, this._shouldShowItem(item, props));
if (!this._isItemHeightKnown(item)) {
this._pendingMeasurements.add(item.key);
}
}
} else {
// We're past the render block.
if (this._activeCells.has(item.key)) {
this._recycleCell(item.key);
}
}
}
}
// Replace the item map with the updated version.
this._itemMap = newItemMap;
this._itemsBelowRenderBlock = props.itemList.length - this._itemsAboveRenderBlock -
this._itemsInRenderBlock;
this._heightAboveRenderBlock = this._calcHeightOfItems(props, 0, this._itemsAboveRenderBlock - 1);
this._heightOfRenderBlock = this._calcHeightOfItems(props, this._itemsAboveRenderBlock,
this._itemsAboveRenderBlock + this._itemsInRenderBlock - 1);
this._heightBelowRenderBlock = this._calcHeightOfItems(props,
this._itemsAboveRenderBlock + this._itemsInRenderBlock, props.itemList.length - 1);
// Pre-populate the container height with known values early - if there are dynamically sized items in the list, this will be
// corrected during the onLayout phase
if (this._containerHeight === 0) {
this._containerHeight = this._heightAboveRenderBlock + this._heightOfRenderBlock + this._heightBelowRenderBlock;
this._containerHeightValue.setValue(this._containerHeight);
}
}