private _handleItemListChange()

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);
        }
    }