Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Components/Web.JS/dist/Release/blazor.server.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/Components/Web.JS/dist/Release/blazor.webview.js

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion src/Components/Web.JS/src/Virtualize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ export const Virtualize = {
const observersByDotNetId = {};

function findClosestScrollContainer(element: HTMLElement | null): HTMLElement | null {
if (!element) {
// If we recurse up as far as body or the document root, return null so that the
// IntersectionObserver observes intersection with the top-level scroll viewport
// instead of the with body/documentElement which can be arbitrarily tall.
// See https://github.com/dotnet/aspnetcore/issues/37659 for more about what this fixes.
if (!element || element === document.body || element === document.documentElement) {
return null;
}

Expand Down
2 changes: 2 additions & 0 deletions src/Components/Web/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#nullable enable
Microsoft.AspNetCore.Components.Forms.InputRadio<TValue>.Element.get -> Microsoft.AspNetCore.Components.ElementReference?
Microsoft.AspNetCore.Components.Forms.InputRadio<TValue>.Element.set -> void
Microsoft.AspNetCore.Components.Web.Virtualization.Virtualize<TItem>.SpacerElement.get -> string!
Microsoft.AspNetCore.Components.Web.Virtualization.Virtualize<TItem>.SpacerElement.set -> void
16 changes: 14 additions & 2 deletions src/Components/Web/src/Virtualization/Virtualize.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,18 @@ public sealed class Virtualize<TItem> : ComponentBase, IVirtualizeJsCallbacks, I
[Parameter]
public int OverscanCount { get; set; } = 3;

/// <summary>
/// Gets or sets the tag name of the HTML element that will be used as the virtualization spacer.
/// One such element will be rendered before the visible items, and one more after them, using
/// an explicit "height" style to control the scroll range.
///
/// The default value is "div". If you are placing the <see cref="Virtualize{TItem}"/> instance inside
/// an element that requires a specific child tag name, consider setting that here. For example when
/// rendering inside a "tbody", consider setting <see cref="SpacerElement"/> to the value "tr".
/// </summary>
[Parameter]
public string SpacerElement { get; set; } = "div";

/// <summary>
/// Instructs the component to re-request data from its <see cref="ItemsProvider"/>.
/// This is useful if external data may have changed. There is no need to call this
Expand Down Expand Up @@ -178,7 +190,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
throw oldRefreshException;
}

builder.OpenElement(0, "div");
builder.OpenElement(0, SpacerElement);
builder.AddAttribute(1, "style", GetSpacerStyle(_itemsBefore));
builder.AddElementReferenceCapture(2, elementReference => _spacerBefore = elementReference);
builder.CloseElement();
Expand Down Expand Up @@ -235,7 +247,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)

var itemsAfter = Math.Max(0, _itemCount - _visibleItemCapacity - _itemsBefore);

builder.OpenElement(6, "div");
builder.OpenElement(6, SpacerElement);
builder.AddAttribute(7, "style", GetSpacerStyle(itemsAfter));
builder.AddElementReferenceCapture(8, elementReference => _spacerAfter = elementReference);

Expand Down
24 changes: 24 additions & 0 deletions src/Components/test/E2ETest/Tests/VirtualizationTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,30 @@ public async Task ToleratesIncorrectItemSize()
int GetItemCount() => Browser.FindElements(By.ClassName("incorrect-size-item")).Count;
}

[Fact]
public void CanRenderHtmlTable()
{
Browser.MountTestComponent<VirtualizationTable>();
var expectedInitialSpacerStyle = "height: 0px; flex-shrink: 0;";
var topSpacer = Browser.Exists(By.CssSelector("#virtualized-table > tbody > :first-child"));
var bottomSpacer = Browser.Exists(By.CssSelector("#virtualized-table > tbody > :last-child"));

// We can override the tag name of the spacer
Assert.Equal("tr", topSpacer.TagName.ToLowerInvariant());
Assert.Equal("tr", bottomSpacer.TagName.ToLowerInvariant());
Assert.Contains(expectedInitialSpacerStyle, topSpacer.GetAttribute("style"));

// Check scrolling document element works
Browser.DoesNotExist(By.Id("row-999"));
Browser.ExecuteJavaScript("window.scrollTo(0, document.body.scrollHeight);");
var lastElement = Browser.Exists(By.Id("row-999"));
Browser.True(() => lastElement.Displayed);

// Validate that the top spacer has expanded, and bottom one has collapsed
Browser.False(() => topSpacer.GetAttribute("style").Contains(expectedInitialSpacerStyle));
Assert.Contains(expectedInitialSpacerStyle, bottomSpacer.GetAttribute("style"));
}

[Fact]
public void CanMutateDataInPlace_Sync()
{
Expand Down
1 change: 1 addition & 0 deletions src/Components/test/testassets/BasicTestApp/Index.razor
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
<option value="BasicTestApp.TouchEventComponent">Touch events</option>
<option value="BasicTestApp.VirtualizationComponent">Virtualization</option>
<option value="BasicTestApp.VirtualizationDataChanges">Virtualization data changes</option>
<option value="BasicTestApp.VirtualizationTable">Virtualization HTML table</option>
<option value="BasicTestApp.HotReload.RenderOnHotReload">Render on hot reload</option>
</select>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<p>This is to show we can use an HTML table with Virtualize, despite it having particular rules about the element hierarchy.</p>
<p>We're also using the document root as the scroll container. Other tests cover having a different scroll container, such as a div with overflow:scroll.</p>

<table id="virtualized-table">
<thead style="position: sticky; top: 0; background-color: silver">
<tr>
<th>Item</th>
<th>Another col</th>
</tr>
</thead>
<tbody>
<Virtualize Items="@fixedItems" ItemSize="30" SpacerElement="tr">
<tr @key="context" style="height: 30px;" id="row-@context">
<td>Item @context</td>
<td>Another value</td>
</tr>
</Virtualize>
</tbody>
</table>

<style>
/* Represents https://github.com/dotnet/aspnetcore/issues/37659 */
html, body { overflow-y: scroll }
</style>

@code {
List<int> fixedItems = Enumerable.Range(0, 1000).ToList();
}
2 changes: 1 addition & 1 deletion src/Shared/E2ETesting/selenium-config.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"drivers": {
"chrome": {
"version" : "100.0.4896.60"
"version" : "103.0.5060.53"
}
},
"ignoreExtraDrivers": true
Expand Down