Skip to content

Expose experimental AnchorMode and ItemComparer on QuickGrid - #67783

Merged
ilonatommy merged 17 commits into
mainfrom
quickgrid-virtualize-intrinsic-tests
Jul 20, 2026
Merged

Expose experimental AnchorMode and ItemComparer on QuickGrid#67783
ilonatommy merged 17 commits into
mainfrom
quickgrid-virtualize-intrinsic-tests

Conversation

@ilonatommy

@ilonatommy ilonatommy commented Jul 14, 2026

Copy link
Copy Markdown
Member

Adds two experimental parameters to QuickGrid<TGridItem> that let virtualized grids control viewport anchoring when data changes at the edges of the list:

  • AnchorMode (VirtualizeAnchorMode, default Start) - controls how the viewport behaves when new items arrive during virtualization (mirrors the existing Virtualize.AnchorMode).
  • ItemComparer (IEqualityComparer<TGridItem>?) - useful for reference types, lets the grid detect whether items were prepended or appended between data loads. Without it, anchoring across data changes might not work properly.

Both are marked [Experimental("ASP0030")], so consumers must explicitly opt in.

Virtualize already supports edge anchoring via AnchorMode/ItemComparer, but QuickGrid (which wraps Virtualize internally) only exposed the default Start behavior and provided no way to enable anchoring for ItemsProvider-backed grids. This closes that gap for scenarios like chat/log tails and infinite-scroll grids.

API surface

namespace Microsoft.AspNetCore.Components.QuickGrid;

public partial class QuickGrid<TGridItem> : ComponentBase
{
    [Parameter]
    [Experimental("ASP0030", UrlFormat = "https://aka.ms/aspnet/analyzer/{0}")]
    public VirtualizeAnchorMode AnchorMode { get; set; } = VirtualizeAnchorMode.Start;

    [Parameter]
    [Experimental("ASP0030", UrlFormat = "https://aka.ms/aspnet/analyzer/{0}")]
    public IEqualityComparer<TGridItem>? ItemComparer { get; set; }
}

Usage example

@* Chat-style log: pin to the bottom so newly appended rows auto-scroll into view,
   but disengage once the user scrolls up to read history. *@
<QuickGrid Items="@_messages" Virtualize="true" ItemSize="50"
           AnchorMode="VirtualizeAnchorMode.End"
           ItemComparer="_byId">
    <PropertyColumn Property="@(m => m.Timestamp)" />
    <PropertyColumn Property="@(m => m.Text)" />
</QuickGrid>

@code {
    IQueryable<Message> _messages = default!;

    // Provider returns fresh instances each load, so compare by a stable key
    // to correctly tell prepends from appends.
    static readonly IEqualityComparer<Message> _byId =
        EqualityComparer<Message>.Create((a, b) => a!.Id == b!.Id, m => m.Id.GetHashCode());
}
Test coverage summary

where 🆕 is a added by this PR, ❌ is failing currently. Tests on 5 runs, result is failures/total:

Op · Position · Source None Start End
⬆️ · Exact top · Items V ✅ · QG 🆕 ✅ 0/5 V ✅ · QG 🆕 ✅ 0/5 V ✅ · QG 🆕 ✅ 0/5
⬆️ · Exact top · Prov V ✅ · QG 🆕 ❌ 1/5 V ✅ · QG 🆕 ✅ 0/5 V ✅ · QG 🆕 ❌ 1/5
⬆️ · Exact top · Prov+delay V n/a · QG 🆕 ❌ 4/5 V n/a · QG 🆕 ✅ 0/5 V n/a · QG 🆕 ❌ 4/5
⬆️ · Near-top ~200px · Items V ✅ · QG 🆕 ✅ 0/5 V ✅ · QG 🆕 ✅ 0/5 V ✅ · QG 🆕 ✅ 0/5
⬆️ · Near-top ~200px · Prov V ✅ · QG 🆕 ❌ 1/5 V ✅ · QG 🆕 ❌ 2/5 V ✅ · QG 🆕 ❌ 1/5
⬆️ · Near-top ~200px · Prov+delay V n/a · QG 🆕 ❌ 5/5 V n/a · QG 🆕 ❌ 3/5 V n/a · QG 🆕 ❌ 4/5
⬆️ · Mid-list · Items V ✅ · QG 🆕 ✅ 0/5 V ✅ · QG 🆕 ✅ 0/5 V ✅ · QG 🆕 ✅ 0/5
⬆️ · Mid-list · Prov V ✅ · QG 🆕 ❌ 2/5 V ✅ · QG 🆕 ❌ 2/5 V ✅ · QG 🆕 ❌ 4/5
⬆️ · Mid-list · Prov+delay V ✅ · QG 🆕 ❌ 5/5 (None_AsyncProvider [Fact])
⬆️ · Bottom · Items V ✅ · QG 🆕 ✅ 0/5 V ✅ · QG 🆕 ✅ 0/5 V ✅ · QG 🆕 ✅ 0/5
⬆️ · Bottom · Prov V ✅ · QG 🆕 ❌ 2/5 V ✅ · QG 🆕 ❌ 4/5 V ✅ · QG 🆕 ❌ 1/5
⬆️ · After leaving top · Items V ✅ · QG 🆕 ✅ 0/5
⬆️ · After leaving top · Prov V ✅ · QG 🆕 ✅ 0/5
⬇️ · Near-top · Items V ✅ · QG 🆕 ✅ 0/5 V ✅ · QG 🆕 ✅ 0/5 V ✅ · QG 🆕 ✅ 0/5
⬇️ · Near-top · Prov V ✅ · QG 🆕 ✅ 0/5 V ✅ · QG 🆕 ✅ 0/5 V ✅ · QG 🆕 ✅ 0/5
⬇️ · Top · Items V ✅ · QG 🆕 ✅ 0/5 V ✅ · QG 🆕 ✅ 0/5 V ✅ · QG 🆕 ✅ 0/5
⬇️ · Top · Prov V ✅ · QG 🆕 ✅ 0/5 V ✅ · QG 🆕 ✅ 0/5 V ✅ · QG 🆕 ❌ 1/6
⬇️ · Mid-list · Items V ✅ · QG 🆕 ✅ 0/5 V ✅ · QG 🆕 ✅ 0/5 V ✅ · QG 🆕 ✅ 0/5
⬇️ · Mid-list · Prov V ✅ · QG 🆕 ✅ 0/5 V ✅ · QG 🆕 ✅ 0/5 V ✅ · QG 🆕 ✅ 0/5
⬇️ · Bottom · Items V ✅ · QG 🆕 ✅ 0/5 V ✅ · QG 🆕 ✅ 0/5 V ✅ · QG 🆕 ✅ 0/5
⬇️ · Bottom · Prov V ✅ · QG 🆕 ✅ 0/5 V ✅ · QG 🆕 ✅ 0/5 V ✅ · QG 🆕 ✅ 0/5
⬇️ · After leaving bottom · Items V ✅ · QG 🆕 ✅ 0/5
⬇️ · After leaving bottom · Prov V ✅ · QG 🆕 ✅ 0/5
⬇️ LARGE (100) · Bottom · Items V ✅ · QG 🆕 ❌ WASM V ✅ · QG 🆕 ✅ 0/5
⬇️ LARGE (100) · Bottom · Prov V ✅ · QG 🆕 ✅ 0/5 V ✅ · QG 🆕 ✅ 0/5
🔄 (fast, continuous) · no backward flash · Prov+delay V ✅ · QG 🆕 ✅ 0/5
⌨️ Home key → top · Items/Prov — · QG 🆕 ✅ 0/5 — · QG 🆕 ✅ 0/5 — · QG 🆕 ✅ 0/5
⌨️ End key → bottom · Items/Prov — · QG 🆕 ✅ 0/5 — · QG 🆕 ✅ 0/5 — · QG 🆕 ❌ 1/6 (CI/Mono)
Behavioral changes in comparison to net10 - no regression of the default `Start` mode without `ItemComparer`
Test family Source net11 (Start) net10 (default) net10 fail type Evidence
NearTop_Prepend Items SHIFT AnchorMode 1 near top: viewport should stay stable after prepend (index before: 3, after: -7, relTop before: -44.65625, after: -44.65625, scrollTop: 240, tolerance: 2)
NearTop_Prepend Provider ❌ (2/5) SHIFT AnchorMode 1 near top: viewport should stay stable after prepend (index before: 3, after: -7, relTop before: -44.65625, after: -44.65625, scrollTop: 240, tolerance: 2)
NearTop_Prepend (delay) Provider ❌ (3/5) SHIFT AnchorMode 1 near top: viewport should stay stable after prepend (index before: 3, after: -7, relTop before: -44.65625, after: -44.65625, scrollTop: 240, tolerance: 2)
NearTop_Append Items
NearTop_Append Provider
Top_Append Items
Top_Append Provider
Bottom_Prepend Items STUCK Visible items should be rendered after scrolling to bottom
Bottom_Prepend Provider ❌ (4/5) STUCK Visible items should be rendered after scrolling to bottom
Start_PrependAtTop_NewItemsVisible Items
Start_PrependAtTop_NewItemsVisible Provider
Start_PrependAtTop_NewItemsVisible (delay) Provider
Start_PrependAfterLeavingTop_DoesNotReengage Items
Start_PrependAfterLeavingTop_DoesNotReengage Provider
Start_MidList_Prepend Items SHIFT Start mode mid-list: viewport should stay stable after prepend (index before: 73, after: 63, relTop before: -21.53125, after: -21.53125, scrollTop: 4080, tolerance: 0)
Start_MidList_Prepend Provider ❌ (2/5) SHIFT Start mode mid-list: viewport should stay stable after prepend (index before: 73, after: 63, relTop before: -21.53125, after: -21.53125, scrollTop: 4080, tolerance: 0)
Start_LargeAppendAtBottom_DoesNotFollow Items STUCK Visible items should be rendered after scrolling to bottom
Start_LargeAppendAtBottom_DoesNotFollow Provider STUCK Visible items should be rendered after scrolling to bottom
MidList_Append Items
MidList_Append Provider
Bottom_Append Items STUCK Visible items should be rendered after scrolling to bottom
Bottom_Append Provider STUCK Visible items should be rendered after scrolling to bottom
HomeKeyJumpsToTop Items
HomeKeyJumpsToTop Provider
EndKeyJumpsToBottom Items
EndKeyJumpsToBottom Provider OTHER AnchorMode 1: End key should jump to the bottom

@ilonatommy ilonatommy self-assigned this Jul 14, 2026
@ilonatommy ilonatommy added area-blazor Includes: Blazor, Razor Components feature-blazor-virtualization This issue is related to the Blazor Virtualize component feature-blazor-quickgrid labels Jul 14, 2026
@ilonatommy
ilonatommy force-pushed the quickgrid-virtualize-intrinsic-tests branch from 9b5a462 to 9904dd0 Compare July 14, 2026 17:25
@ilonatommy ilonatommy added this to the 11.0-preview7 milestone Jul 15, 2026
Poll the initial "starts at top" scrollTop check via AssertScrollTop
instead of asserting a single instant right after mount, and treat a
blank viewport (no item intersecting) as an unsettled transient state in
WaitForRenderToSettle so it waits for the anchor restore to complete
before the test reads the viewport.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ilonatommy
ilonatommy force-pushed the quickgrid-virtualize-intrinsic-tests branch from 0710611 to 09ec126 Compare July 17, 2026 07:13
@ilonatommy
ilonatommy marked this pull request as ready for review July 20, 2026 10:09
@ilonatommy
ilonatommy requested a review from a team as a code owner July 20, 2026 10:09
Copilot AI review requested due to automatic review settings July 20, 2026 10:09

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 exposes QuickGrid’s virtualization anchoring controls by adding two experimental parameters—AnchorMode and ItemComparer—and forwarding them to the internal Virtualize usage, enabling stable viewport behavior when items are prepended/appended during virtualized data loads.

Changes:

  • Add experimental AnchorMode (VirtualizeAnchorMode) and ItemComparer (IEqualityComparer<TGridItem>?) parameters to QuickGrid<TGridItem>.
  • Forward these parameters to the underlying Virtualize instance via an attribute-splat dictionary.
  • Add a new BasicTestApp test component plus expanded E2E coverage for QuickGrid anchoring behaviors; document new analyzer diagnostic ASP0030.

Reviewed changes

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

Show a summary per file
File Description
src/Components/test/testassets/BasicTestApp/QuickGridTest/QuickGridAnchorModeComponent.razor New test component exercising QuickGrid anchoring modes with Items and ItemsProvider.
src/Components/test/testassets/BasicTestApp/Index.razor Adds navigation entry for the new anchoring test component.
src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj Suppresses ASP0030 for the test asset that intentionally uses experimental APIs.
src/Components/test/E2ETest/Tests/VirtualizationTest.cs Adds E2E tests validating QuickGrid anchoring/viewport stability across modes and mutations.
src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor.cs Introduces AnchorMode/ItemComparer parameters and builds Virtualize attribute-splat values.
src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor Applies the new Virtualize attributes via @attributes.
src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/PublicAPI.Unshipped.txt Declares new public API surface for the added parameters.
docs/list-of-diagnostics.md Updates analyzer diagnostic range and adds ASP0030 description.

Comment thread docs/list-of-diagnostics.md Outdated
Comment thread src/Components/test/testassets/BasicTestApp/Index.razor Outdated

@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 will take a look at tests later, but for now here are the notes:

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 feature-blazor-quickgrid feature-blazor-virtualization This issue is related to the Blazor Virtualize component

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants