private _calcNewRenderedItemState()

in extensions/virtuallistview/src/VirtualListView.tsx [682:902]


    private _calcNewRenderedItemState(props: VirtualListViewProps<ItemInfo>): void {
        if (this._layoutHeight === 0) {
            // Wait until we get a height before bothering.
            return;
        }

        if (props.itemList.length === 0) {
            // Can't possibly be rendering anything.
            return;
        }

        if (this._pendingMeasurements.size > 0) {
            // Don't bother if we're still measuring things. Wait for the last batch to end.
            return;
        }

        // What's the top/bottom line that we'll cull items that are wholly outside of?
        const cullMargin = Math.max(this._layoutHeight * this._cullFraction, this._minCullAmount);
        const topCullLine = this._lastScrollTop - cullMargin;
        const bottomCullLine = this._lastScrollTop + this._layoutHeight + cullMargin;

        // Do we need to cut anything out of the top because we've scrolled away from it?
        while (this._itemsInRenderBlock > 0) {
            const itemIndex = this._itemsAboveRenderBlock;
            const item = props.itemList[itemIndex];
            if (!this._isItemHeightKnown(item)) {
                break;
            }

            const itemHeight = this._getHeightOfItem(item);
            if (this._heightAboveRenderAdjustment + this._heightAboveRenderBlock + itemHeight >= topCullLine) {
                // We're rendering up to the top render line, so don't need to nuke any more.
                break;
            }

            this._itemsInRenderBlock--;
            this._heightOfRenderBlock -= itemHeight;
            this._itemsAboveRenderBlock++;
            this._heightAboveRenderBlock += itemHeight;
            this._recycleCell(item.key);

            if (props.logInfo) {
                props.logInfo('Culled Item From Top: ' + item.key);
            }
        }

        // Do we need to cut anything out of the bottom because we've scrolled away from it?
        while (this._itemsInRenderBlock > 0) {
            const itemIndex = this._itemsAboveRenderBlock + this._itemsInRenderBlock - 1;
            const item = props.itemList[itemIndex];
            if (!this._isItemHeightKnown(item)) {
                break;
            }

            const itemHeight = this._getHeightOfItem(item);
            if (this._heightAboveRenderAdjustment + this._heightAboveRenderBlock + this._heightOfRenderBlock
                - itemHeight <= bottomCullLine) {
                break;
            }

            this._itemsInRenderBlock--;
            this._heightOfRenderBlock -= itemHeight;
            this._itemsBelowRenderBlock++;
            this._heightBelowRenderBlock += itemHeight;
            this._recycleCell(item.key);

            if (props.logInfo) {
                props.logInfo('Culled Item From Bottom: ' + item.key);
            }
        }

        // Determine what the line is that we're rendering up to. If we haven't yet filled a screen,
        // first get the screen full before over-rendering.
        const overdrawAmount = this._calcOverdrawAmount();
        let renderMargin = this._isInitialFillComplete ? overdrawAmount : 0;
        let renderBlockTopLimit = this._lastScrollTop - renderMargin;
        let renderBlockBottomLimit = this._lastScrollTop + this._layoutHeight + renderMargin;

        if (this._itemsInRenderBlock === 0) {
            let yPosition = this._heightAboveRenderAdjustment;
            this._itemsAboveRenderBlock = 0;

            // Find the first item that's in the render block and add it.
            for (let i = 0; i < props.itemList.length; i++) {
                const item = props.itemList[i];
                const itemHeight = this._getHeightOfItem(item);

                yPosition += itemHeight;

                if (yPosition > renderBlockTopLimit) {
                    this._itemsAboveRenderBlock = i;
                    this._itemsInRenderBlock = 1;

                    this._allocateCell(item.key, item.template, i, !item.measureHeight, item.height,
                        yPosition - itemHeight, this._shouldShowItem(item, props));

                    if (!this._isItemHeightKnown(item)) {
                        this._pendingMeasurements.add(item.key);
                    }
                    break;
                }
            }

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

        // What is the whole height of the scroll region? We need this both for calculating bottom
        // offsets as well as for making the view render to the proper height since we're using
        // position: absolute for all placements.
        const itemBlockHeight = this._heightAboveRenderAdjustment + this._heightAboveRenderBlock +
            this._heightOfRenderBlock + this._heightBelowRenderBlock;
        const containerHeight = Math.max(itemBlockHeight, this._layoutHeight);

        // Render the actual items now!
        let yPosition = this._heightAboveRenderBlock + this._heightAboveRenderAdjustment;

        let topOfRenderBlockY = yPosition;

        // Start by checking heights/visibility of everything in the render block before we add to it.
        for (let i = 0; i < this._itemsInRenderBlock; i++) {
            const itemIndex = this._itemsAboveRenderBlock + i;
            const item = props.itemList[itemIndex];

            this._setCellTopAndVisibility(item.key, this._shouldShowItem(item, props),
                yPosition, !!this.props.animateChanges);

            const height = this._getHeightOfItem(item);
            yPosition += height;
        }

        let bottomOfRenderBlockY = yPosition;

        // See if the container height needs adjusting.
        if (containerHeight !== this._containerHeight) {
            if (props.logInfo) {
                props.logInfo('Container Height Change: ' + this._containerHeight + ' to ' + containerHeight);
            }
            this._containerHeight = containerHeight;
            this._containerHeightValue.setValue(containerHeight);
        }

        // Reuse an item-builder.
        const buildItem = (itemIndex: number, above: boolean) => {
            const item = props.itemList[itemIndex];
            const isHeightKnown = this._isItemHeightKnown(item);
            const itemHeight = this._getHeightOfItem(item);
            assert(itemHeight > 0, 'list items should always have non-zero height');

            this._itemsInRenderBlock++;
            this._heightOfRenderBlock += itemHeight;
            let yPlacement: number;
            if (above) {
                this._itemsAboveRenderBlock--;
                this._heightAboveRenderBlock -= itemHeight;
                topOfRenderBlockY -= itemHeight;
                yPlacement = topOfRenderBlockY;
            } else {
                this._itemsBelowRenderBlock--;
                this._heightBelowRenderBlock -= itemHeight;
                yPlacement = bottomOfRenderBlockY;
                bottomOfRenderBlockY += itemHeight;
            }

            if (!isHeightKnown) {
                this._pendingMeasurements.add(item.key);
            }

            this._allocateCell(item.key, item.template, itemIndex, !item.measureHeight, item.height,
                yPlacement, this._shouldShowItem(item, props));

            if (props.logInfo) {
                props.logInfo('New Item On ' + (above ? 'Top' : 'Bottom') + ': ' + item.key);
            }
        };

        // Try to add items to the bottom of the current render block.
        while (this._pendingMeasurements.size < _maxSimultaneousMeasures) {
            // Stop if we go beyond the bottom render limit.
            if (this._itemsBelowRenderBlock <= 0 ||
                this._heightAboveRenderAdjustment + this._heightAboveRenderBlock +
                this._heightOfRenderBlock >= renderBlockBottomLimit) {
                break;
            }

            buildItem(this._itemsAboveRenderBlock + this._itemsInRenderBlock, false);
        }

        // Try to add an item to the top of the current render block.
        while (this._pendingMeasurements.size < _maxSimultaneousMeasures) {
            if (this._itemsAboveRenderBlock <= 0 ||
                this._heightAboveRenderAdjustment + this._heightAboveRenderBlock <= renderBlockTopLimit) {
                break;
            }

            buildItem(this._itemsAboveRenderBlock - 1, true);
        }

        // See if we've filled the screen and rendered it, and we're not waiting on any measurements.
        if (!this._isInitialFillComplete && !this._isRenderDirty && this._pendingMeasurements.size === 0) {
            // Time for overrender. Recalc render lines.
            renderMargin = overdrawAmount;
            renderBlockTopLimit = this._lastScrollTop - renderMargin;
            renderBlockBottomLimit = this._lastScrollTop + this._layoutHeight + renderMargin;

            this._popInvisibleIntoView(props);

            // Render pass again!
            this._componentDidRender();
        }

        if (props.logInfo) {
            props.logInfo('CalcNewRenderedItemState: O:' + this._heightAboveRenderAdjustment +
                ' + A:' + this._heightAboveRenderBlock + ' + R:' + this._heightOfRenderBlock + ' + B:' +
                    this._heightBelowRenderBlock + ' = ' + itemBlockHeight + ', FilledViewable: ' + this._isInitialFillComplete);
        }
    }