From 07edd3c16f353f877b44b5f8572ad9f382db1a30 Mon Sep 17 00:00:00 2001 From: Steve Sanderson <SteveSandersonMS@users.noreply.github.com> Date: Mon, 27 Jun 2022 16:31:16 +0100 Subject: [PATCH] Support configuring spacer element. Fixes #42206 --- .../Web/src/PublicAPI.Unshipped.txt | 2 ++ .../Web/src/Virtualization/Virtualize.cs | 16 +++++++++++-- .../test/E2ETest/Tests/VirtualizationTest.cs | 24 +++++++++++++++++++ .../test/testassets/BasicTestApp/Index.razor | 1 + .../BasicTestApp/VirtualizationTable.razor | 23 ++++++++++++++++++ src/Shared/E2ETesting/selenium-config.json | 2 +- 6 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 src/Components/test/testassets/BasicTestApp/VirtualizationTable.razor diff --git a/src/Components/Web/src/PublicAPI.Unshipped.txt b/src/Components/Web/src/PublicAPI.Unshipped.txt index 34c8afc7fb6..ecfb9e5a989 100644 --- a/src/Components/Web/src/PublicAPI.Unshipped.txt +++ b/src/Components/Web/src/PublicAPI.Unshipped.txt @@ -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 diff --git a/src/Components/Web/src/Virtualization/Virtualize.cs b/src/Components/Web/src/Virtualization/Virtualize.cs index 2e9779ad9a2..b2418c29d63 100644 --- a/src/Components/Web/src/Virtualization/Virtualize.cs +++ b/src/Components/Web/src/Virtualization/Virtualize.cs @@ -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 @@ -178,7 +190,7 @@ public sealed class Virtualize<TItem> : ComponentBase, IVirtualizeJsCallbacks, I throw oldRefreshException; } - builder.OpenElement(0, "div"); + builder.OpenElement(0, SpacerElement); builder.AddAttribute(1, "style", GetSpacerStyle(_itemsBefore)); builder.AddElementReferenceCapture(2, elementReference => _spacerBefore = elementReference); builder.CloseElement(); @@ -235,7 +247,7 @@ public sealed class Virtualize<TItem> : ComponentBase, IVirtualizeJsCallbacks, I 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); diff --git a/src/Components/test/E2ETest/Tests/VirtualizationTest.cs b/src/Components/test/E2ETest/Tests/VirtualizationTest.cs index 1ba1c0174da..b954498b481 100644 --- a/src/Components/test/E2ETest/Tests/VirtualizationTest.cs +++ b/src/Components/test/E2ETest/Tests/VirtualizationTest.cs @@ -238,6 +238,30 @@ public class VirtualizationTest : ServerTestBase<ToggleExecutionModeServerFixtur 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() { diff --git a/src/Components/test/testassets/BasicTestApp/Index.razor b/src/Components/test/testassets/BasicTestApp/Index.razor index b856c58b612..5cd0688202a 100644 --- a/src/Components/test/testassets/BasicTestApp/Index.razor +++ b/src/Components/test/testassets/BasicTestApp/Index.razor @@ -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> diff --git a/src/Components/test/testassets/BasicTestApp/VirtualizationTable.razor b/src/Components/test/testassets/BasicTestApp/VirtualizationTable.razor new file mode 100644 index 00000000000..8a357e526e6 --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/VirtualizationTable.razor @@ -0,0 +1,23 @@ +<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> + +@code { + List<int> fixedItems = Enumerable.Range(0, 1000).ToList(); +} diff --git a/src/Shared/E2ETesting/selenium-config.json b/src/Shared/E2ETesting/selenium-config.json index 91a5051b6c1..71bb468d49a 100644 --- a/src/Shared/E2ETesting/selenium-config.json +++ b/src/Shared/E2ETesting/selenium-config.json @@ -1,7 +1,7 @@ { "drivers": { "chrome": { - "version" : "100.0.4896.60" + "version" : "103.0.5060.53" } }, "ignoreExtraDrivers": true -- GitLab