feat(ui): add selection support to TreeView (R5)
This commit is contained in:
@@ -26,17 +26,18 @@ else
|
|||||||
var isExpanded = _expandedKeys.Contains(key);
|
var isExpanded = _expandedKeys.Contains(key);
|
||||||
|
|
||||||
<li role="treeitem" @key="key"
|
<li role="treeitem" @key="key"
|
||||||
aria-expanded="@(isBranch ? (isExpanded ? "true" : "false") : null)">
|
aria-expanded="@(isBranch ? (isExpanded ? "true" : "false") : null)"
|
||||||
<div class="tv-row" style="padding-left: @(depth * IndentPx)px">
|
aria-selected="@(Selectable && SelectedKey != null && SelectedKey.Equals(key) ? "true" : null)">
|
||||||
|
<div class="tv-row @(Selectable && SelectedKey != null && SelectedKey.Equals(key) ? SelectedCssClass : "")" style="padding-left: @(depth * IndentPx)px">
|
||||||
@if (isBranch)
|
@if (isBranch)
|
||||||
{
|
{
|
||||||
<span class="tv-toggle" @onclick="() => ToggleExpand(key)">@(isExpanded ? "\u2212" : "+")</span>
|
<span class="tv-toggle" @onclick="() => ToggleExpand(key)" @onclick:stopPropagation>@(isExpanded ? "\u2212" : "+")</span>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<span class="tv-spacer"></span>
|
<span class="tv-spacer"></span>
|
||||||
}
|
}
|
||||||
<span class="tv-content">
|
<span class="tv-content" @onclick="() => OnContentClick(key)" @onclick:stopPropagation>
|
||||||
@NodeContent(item)
|
@NodeContent(item)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -67,6 +68,10 @@ else
|
|||||||
[Parameter] public int IndentPx { get; set; } = 24;
|
[Parameter] public int IndentPx { get; set; } = 24;
|
||||||
[Parameter] public bool ShowGuideLines { get; set; } = true;
|
[Parameter] public bool ShowGuideLines { get; set; } = true;
|
||||||
[Parameter] public Func<TItem, bool>? InitiallyExpanded { get; set; }
|
[Parameter] public Func<TItem, bool>? InitiallyExpanded { get; set; }
|
||||||
|
[Parameter] public bool Selectable { get; set; }
|
||||||
|
[Parameter] public object? SelectedKey { get; set; }
|
||||||
|
[Parameter] public EventCallback<object?> SelectedKeyChanged { get; set; }
|
||||||
|
[Parameter] public string SelectedCssClass { get; set; } = "bg-primary bg-opacity-10";
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
@@ -103,4 +108,12 @@ else
|
|||||||
_expandedKeys.Add(key);
|
_expandedKeys.Add(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task OnContentClick(object key)
|
||||||
|
{
|
||||||
|
if (Selectable)
|
||||||
|
{
|
||||||
|
await SelectedKeyChanged.InvokeAsync(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,20 +29,39 @@ public class TreeViewTests : BunitContext
|
|||||||
List<TestNode>? items = null,
|
List<TestNode>? items = null,
|
||||||
RenderFragment? emptyContent = null,
|
RenderFragment? emptyContent = null,
|
||||||
int indentPx = 24,
|
int indentPx = 24,
|
||||||
Func<TestNode, bool>? initiallyExpanded = null)
|
Func<TestNode, bool>? initiallyExpanded = null,
|
||||||
|
bool selectable = false,
|
||||||
|
object? selectedKey = null,
|
||||||
|
Action<object?>? onSelectedKeyChanged = null,
|
||||||
|
string? selectedCssClass = null)
|
||||||
{
|
{
|
||||||
return Render<TreeView<TestNode>>(parameters => parameters
|
return Render<TreeView<TestNode>>(parameters =>
|
||||||
.Add(p => p.Items, items ?? SimpleRoots())
|
{
|
||||||
.Add(p => p.ChildrenSelector, n => n.Children)
|
parameters
|
||||||
.Add(p => p.HasChildrenSelector, n => n.Children.Count > 0)
|
.Add(p => p.Items, items ?? SimpleRoots())
|
||||||
.Add(p => p.KeySelector, n => n.Key)
|
.Add(p => p.ChildrenSelector, n => n.Children)
|
||||||
.Add(p => p.NodeContent, node => builder =>
|
.Add(p => p.HasChildrenSelector, n => n.Children.Count > 0)
|
||||||
|
.Add(p => p.KeySelector, n => n.Key)
|
||||||
|
.Add(p => p.NodeContent, node => builder =>
|
||||||
|
{
|
||||||
|
builder.AddMarkupContent(0, $"<span class=\"node-label\">{node.Label}</span>");
|
||||||
|
})
|
||||||
|
.Add(p => p.IndentPx, indentPx)
|
||||||
|
.Add(p => p.EmptyContent, emptyContent)
|
||||||
|
.Add(p => p.InitiallyExpanded, initiallyExpanded)
|
||||||
|
.Add(p => p.Selectable, selectable)
|
||||||
|
.Add(p => p.SelectedKey, selectedKey);
|
||||||
|
|
||||||
|
if (onSelectedKeyChanged != null)
|
||||||
{
|
{
|
||||||
builder.AddMarkupContent(0, $"<span class=\"node-label\">{node.Label}</span>");
|
parameters.Add(p => p.SelectedKeyChanged, onSelectedKeyChanged);
|
||||||
})
|
}
|
||||||
.Add(p => p.IndentPx, indentPx)
|
|
||||||
.Add(p => p.EmptyContent, emptyContent)
|
if (selectedCssClass != null)
|
||||||
.Add(p => p.InitiallyExpanded, initiallyExpanded));
|
{
|
||||||
|
parameters.Add(p => p.SelectedCssClass, selectedCssClass);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -238,4 +257,64 @@ public class TreeViewTests : BunitContext
|
|||||||
var alpha2xRow = rows[3];
|
var alpha2xRow = rows[3];
|
||||||
Assert.Contains("padding-left: 60px", alpha2xRow.GetAttribute("style"));
|
Assert.Contains("padding-left: 60px", alpha2xRow.GetAttribute("style"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Selection_Disabled_ClickDoesNotFireCallback()
|
||||||
|
{
|
||||||
|
object? selected = null;
|
||||||
|
var cut = RenderTreeView(selectable: false, onSelectedKeyChanged: k => selected = k);
|
||||||
|
|
||||||
|
cut.Find(".tv-content").Click();
|
||||||
|
|
||||||
|
Assert.Null(selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Selection_Enabled_ClickContentFiresCallback()
|
||||||
|
{
|
||||||
|
object? selected = null;
|
||||||
|
var cut = RenderTreeView(selectable: true, onSelectedKeyChanged: k => selected = k);
|
||||||
|
|
||||||
|
cut.Find(".tv-content").Click();
|
||||||
|
|
||||||
|
Assert.Equal("a", selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Selection_ClickToggle_DoesNotChangeSelection()
|
||||||
|
{
|
||||||
|
object? selected = null;
|
||||||
|
var cut = RenderTreeView(selectable: true, onSelectedKeyChanged: k => selected = k);
|
||||||
|
|
||||||
|
cut.Find(".tv-toggle").Click();
|
||||||
|
|
||||||
|
Assert.Null(selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Selection_SelectedNode_HasCssClass()
|
||||||
|
{
|
||||||
|
var cut = RenderTreeView(selectable: true, selectedKey: "a");
|
||||||
|
|
||||||
|
var alphaRow = cut.FindAll(".tv-row")[0];
|
||||||
|
Assert.Contains("bg-primary", alphaRow.GetAttribute("class"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Selection_CustomCssClass_Applied()
|
||||||
|
{
|
||||||
|
var cut = RenderTreeView(selectable: true, selectedKey: "a", selectedCssClass: "my-highlight");
|
||||||
|
|
||||||
|
var alphaRow = cut.FindAll(".tv-row")[0];
|
||||||
|
Assert.Contains("my-highlight", alphaRow.GetAttribute("class"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Selection_AriaSelected_SetOnSelectedNode()
|
||||||
|
{
|
||||||
|
var cut = RenderTreeView(selectable: true, selectedKey: "a");
|
||||||
|
|
||||||
|
var alphaLi = cut.FindAll("li[role='treeitem']")[0];
|
||||||
|
Assert.Equal("true", alphaLi.GetAttribute("aria-selected"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user