Skip to content

Add InitialIndex parameter and ScrollToIndexAsync API to Virtualize<TItem>#66753

Merged
ilonatommy merged 20 commits into
mainfrom
scroll-to-index
Jun 1, 2026
Merged

Add InitialIndex parameter and ScrollToIndexAsync API to Virtualize<TItem>#66753
ilonatommy merged 20 commits into
mainfrom
scroll-to-index

Conversation

@ilonatommy

@ilonatommy ilonatommy commented May 20, 2026

Copy link
Copy Markdown
Member

Adds two pieces of public API to Virtualize<TItem> that let consumers control which item is visible at the top of the list:

  • InitialIndex parameter - opens the list at a given item on first interactive render.
  • ScrollToIndexAsync(int itemIndex, CancellationToken cancellationToken = default) - programmatically scrolls to an item at any time after first render.

Out-of-range values are silently clamped to the valid range.

Public API

namespace Microsoft.AspNetCore.Components.Web.Virtualization;

public sealed class Virtualize<TItem>
{
    [Parameter]
    public int InitialIndex { get; set; }

    public Task ScrollToIndexAsync(int itemIndex, CancellationToken cancellationToken = default);
}

Main changes

Framework internal changes, including internal API, that are needed:

  • InitialIndex -> OnParametersSet pre-position + OnAfterRenderAsync 1st render setup + RefreshDataCoreAsync re-clamp. Without pre-positioning _itemsBefore before the 1st render, the initial ItemProvider fetch would ask for [0, capacity) range, not the requested chunk defined by InitialIndex (users would see a flash). Re-clamp is a safety net for out-of-range values.
  • ScrollToIndexAsync -> MoveWindowToContain + EnsureRenderCommittedAsync + AlignToTargetAsync. A correct programmatic scroll must move the window so the right slice is fetched, then wait for the DOM to render it, and align the viewport with the item (important in variable height placeholder to item transformation). Each step needs a cancellation hook because rapid calls are possible and otherwise would race.
  • Spacer IO suppression -> BeginProgrammaticScrollAsync + ShouldSuppressSpacerCallback + _currentScrollCts / _inFlightScrollHasRendered. IntersectionObserver fires on any scrollTop change, including these caused by ScrollToIndexAsync. Without suppression, _itemBefore would get overwritten by spacer callbacks. At the same time, we want to allow user scroll to take precedence over ScrollToIndexAsync so we don't supress when we detect that scroll comes from non-programmatic scroll.

Behavior

ScrollToIndex.mp4
Scenario Result
InitialIndex = 500 set before first render List opens with item 500 at the top, no flash of item 0
InitialIndex < OverscanCount (e.g., 1, 5) List opens at the exact requested index
InitialIndex = 0 (default) No initial-scroll work — component opens at item 0
InitialIndex out of range Clamped to the valid range
InitialIndex changed at runtime Ignored — InitialIndex is a one-shot, not a live binding
ScrollToIndexAsync(itemIndex) Returns a Task that completes when the target is aligned to the start of the viewport
Caller cancels the token Task faults with OperationCanceledException
Second ScrollToIndexAsync call while first is in flight First call's Task completes normally (silently superseded); only the last call's target is honored ("last call wins")
Called before first interactive render Throws InvalidOperationException synchronously (not on the returned Task), with a message pointing at InitialIndex
Item count shrinks between window-move and DOM commit Target is re-clamped post-render and the scroll bails cleanly if the window no longer contains the target
User scrolls during the operation The user's scroll wins (best-effort alignment)

Fixes #26943 ,
Fixes #66328

@ilonatommy ilonatommy self-assigned this May 20, 2026
@ilonatommy ilonatommy requested a review from a team as a code owner May 20, 2026 14:09
Copilot AI review requested due to automatic review settings May 20, 2026 14:09
@ilonatommy ilonatommy added the area-blazor Includes: Blazor, Razor Components label May 20, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the Blazor Virtualize<TItem> component with two new public APIs—InitialIndex and ScrollToIndexAsync—to allow consumers to control the initial and subsequent scroll position of a virtualized list, including updated JS interop support and E2E coverage.

Changes:

  • Adds InitialIndex parameter and ScrollToIndexAsync(int, CancellationToken) API to Virtualize<TItem>, including render-commit coordination and “last call wins” cancellation semantics.
  • Extends Virtualize JS interop with an alignToItem function and corresponding C# interop wrapper.
  • Adds unit tests, BasicTestApp UI hooks, and E2E tests covering initial index and programmatic scrolling scenarios.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/Components/Web/src/Virtualization/Virtualize.cs Implements InitialIndex and ScrollToIndexAsync, including window movement, render rendezvous, and anchor/IO suppression during active scroll.
src/Components/Web/src/Virtualization/VirtualizeJsInterop.cs Adds C# JS interop entry point for aligning to an item.
src/Components/Web.JS/src/Virtualize.ts Adds JS alignToItem implementation and refactors anchor-restore measurement helper.
src/Components/Web/src/PublicAPI.Unshipped.txt Declares the new public API surface for Virtualize<TItem>.
src/Components/Web/test/Virtualization/VirtualizeTest.cs Adds unit tests for pre-init behavior, clamping, and cancellation behavior.
src/Components/test/testassets/BasicTestApp/VirtualizationAnchorMode.razor Adds UI controls and handlers to exercise InitialIndex/ScrollToIndexAsync in the test app.
src/Components/test/testassets/BasicTestApp/VirtualizationAnchorModeWindowScroll.razor Adds UI controls/handlers for window-scroll scenarios.
src/Components/test/E2ETest/Tests/VirtualizationTest.cs Adds E2E coverage for ScrollToIndexAsync and InitialIndex behaviors (fixed/variable height, clamping, rapid calls, cancellation, window scroll).
Comments suppressed due to low confidence (1)

src/Components/Web/test/Virtualization/VirtualizeTest.cs:1000

  • This test only asserts the returned Task is non-null, but it doesn't await it. If ScrollToIndexAsync faults asynchronously, the test would still pass and may leave unobserved exceptions. Consider awaiting the Task (optionally with a timeout) to validate successful completion.
        Task task = null;
        await renderer.Dispatcher.InvokeAsync(() => { task = virtualize.ScrollToIndexAsync(99_999); });

        Assert.NotNull(task);
    }

Comment thread src/Components/Web/src/Virtualization/Virtualize.cs Outdated
Comment thread src/Components/Web/src/Virtualization/Virtualize.cs
Comment thread src/Components/Web/src/Virtualization/VirtualizeJsInterop.cs Outdated
Comment thread src/Components/Web.JS/src/Virtualize.ts
Comment thread src/Components/Web/test/Virtualization/VirtualizeTest.cs Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/Components/Web/src/Virtualization/Virtualize.cs:629

  • Same issue as OnBeforeSpacerVisible: ignoring OnAfterSpacerVisible while _currentScrollCts is non-null blocks virtualization window updates during a long-running ScrollToIndexAsync, which can conflict with the documented behavior that user scroll should win. Prefer canceling the scroll operation or allowing these callbacks through (and treating them as user override) rather than returning early for the whole scroll duration.
    void IVirtualizeJsCallbacks.OnAfterSpacerVisible(float spacerSize, float spacerSeparation, float containerSize)
    {
        if (_pendingAnchorRestore || _currentScrollCts is not null)
        {
            return;
        }

Comment thread src/Components/Web/src/Virtualization/Virtualize.cs
Comment thread src/Components/Web/src/Virtualization/Virtualize.cs
Comment thread src/Components/Web/src/Virtualization/Virtualize.cs Outdated
Comment thread src/Components/Web/src/Virtualization/Virtualize.cs

@dariatiurina dariatiurina left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have only one question, but other than that and Ondrej's notes, everything looks good.

Comment thread src/Components/test/E2ETest/Tests/VirtualizationTest.cs
@ilonatommy

ilonatommy commented May 27, 2026

Copy link
Copy Markdown
Member Author

CI failures of AnchorMode_None_AsyncProvider_ScrollDoesNotFlash with Should have scrolled through some items but only reached index 31 cannot be reproduced locally, neither as E2E nor as manual tests. Adding temporary logging to CI to investigate.

@ilonatommy ilonatommy added this to the 11.0-preview6 milestone May 27, 2026
@ilonatommy

Copy link
Copy Markdown
Member Author

Sub-pixel rounding failure, it might be that 119 is still visible by half of pixel:

Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests.ServerVirtualizationTest.ScrollToItem_AsyncProvider_VariableHeight_WithDelay_ReachesTarget(target: 120) [FAIL]
  Failed Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests.ServerVirtualizationTest.ScrollToItem_AsyncProvider_VariableHeight_WithDelay_ReachesTarget(target: 120) [1 m 5 s]
  Error Message:
   OpenQA.Selenium.BrowserAssertFailedException : Xunit.Sdk.TrueException: Top rendered should be real item 120; got 119

Adding a 1px tolerance.

@oroztocil oroztocil left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

@ilonatommy ilonatommy enabled auto-merge (squash) May 28, 2026 15:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-blazor Includes: Blazor, Razor Components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ScrollToItemAsync(int itemIndex) design for Virtualization Virtualize component Row Index Enhancements (API to scroll to item)

4 participants