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
20 changes: 18 additions & 2 deletions src/BootstrapBlazor/Components/TreeView/TreeView.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ protected override async Task OnParametersSetAsync()
if (_init == false)
{
_activeItem ??= Items.FirstOrDefaultActiveItem();
_activeItem?.SetParentExpand<TreeViewItem<TItem>, TItem>(true);
ExpandParentNodes(_activeItem);
_init = true;
_scrollIntoView = true;
}
Expand Down Expand Up @@ -762,11 +762,27 @@ private Task OnClickResetSearch()
public void SetActiveItem(TreeViewItem<TItem>? item)
{
_activeItem = item;
_activeItem?.SetParentExpand<TreeViewItem<TItem>, TItem>(true);
ExpandParentNodes(_activeItem);
_rows = null;
_scrollIntoView = true;
StateHasChanged();
}

/// <summary>
/// <para lang="zh">向上级联展开祖先节点并同步节点缓存状态 防止缓存中的收缩状态覆盖此处的展开状态</para>
/// <para lang="en">Cascade expand ancestor nodes upwards and synchronize the node cache state to prevent the collapsed state in the cache from overriding the expanded state set here</para>
/// </summary>
/// <param name="item"></param>
private void ExpandParentNodes(TreeViewItem<TItem>? item)
{
var parent = item?.Parent;
while (parent is TreeViewItem<TItem> p)
{
_treeNodeStateCache.ExpandNode(p);
parent = p.Parent;
}
}

/// <summary>
/// <para lang="zh">Set the 数据 source method for <see cref="Items"/></para>
/// <para lang="en">Set the data source method for <see cref="Items"/></para>
Expand Down
22 changes: 18 additions & 4 deletions src/BootstrapBlazor/Misc/ExpandableNodeCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,18 @@ public virtual async Task ToggleNodeAsync(TNode node, Func<TNode, Task<IEnumerab
}
}

/// <summary>
/// <para lang="zh">展开指定节点并同步缓存状态</para>
/// <para lang="en">Expand the specified node and synchronize the cache state</para>
/// </summary>
/// <param name="node"></param>
public void ExpandNode(IExpandableNode<TItem> node)
{
node.IsExpand = true;
ExpandedNodeCache.Add(node.Value);
CollapsedNodeCache.Remove(node.Value);
}

/// <summary>
/// <para lang="zh">检查当前节点是否展开</para>
/// <para lang="en">Check whether current node is expanded</para>
Expand All @@ -89,14 +101,16 @@ public async Task CheckExpandAsync(TNode node, Func<TNode, Task<IEnumerable<IExp
{
if (node.IsExpand)
{
// 已收缩
if (CollapsedNodeCache.Contains(node.Value))
{
// 用户手动收缩过此节点 保持收缩状态
node.IsExpand = false;
}

// 状态为 展开
ExpandedNodeCache.Add(node.Value);
else
{
// 状态为 展开
ExpandedNodeCache.Add(node.Value);
}
}
else
{
Expand Down
36 changes: 36 additions & 0 deletions test/UnitTest/Components/TreeViewTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,42 @@ public async Task Items_SetActive()
await cut.InvokeAsync(() => cut.Instance.SetActiveItem(activeItem));
}

[Fact]
public async Task SetActiveItem_ExpandCollapsedParent()
{
// https://github.com/dotnetcore/BootstrapBlazor/issues/8185
// 手动收缩父节点后再设置激活子节点 祖先节点应重新展开
var items = TreeFoo.GetTreeItems();
var cut = Context.Render<TreeView<TreeFoo>>(pb =>
{
pb.Add(a => a.Items, items);
});

// 初始激活节点为 Sub Menu Three 祖先节点 Navigation two 处于展开状态
// 手动收缩 Navigation two 节点
await cut.InvokeAsync(() => cut.FindAll(".node-icon.visible")[0].Click());
Assert.DoesNotContain("Sub menu 1", cut.Markup);

// 设置激活节点为 Navigation two 的子节点 Sub menu 1 祖先节点应重新展开
await cut.InvokeAsync(() => cut.Instance.SetActiveItem(items[1].Items[0].Value));
Assert.Contains("Sub menu 1", cut.Markup);

// 模拟父组件重新渲染 缓存中的收缩状态不应覆盖程序设置的展开状态
cut.Render();
var node = cut.Find(".active .tree-node-text");
Assert.Equal("Sub menu 1", node.TextContent);

// 再次渲染 确认展开状态稳定 不出现展开收缩循环切换
cut.Render();
node = cut.Find(".active .tree-node-text");
Assert.Equal("Sub menu 1", node.TextContent);

// 再次手动收缩激活节点的祖先节点后重新渲染 保持用户手动设置的收缩状态
await cut.InvokeAsync(() => cut.FindAll(".node-icon.visible")[0].Click());
cut.Render();
Assert.DoesNotContain("Sub menu 1", cut.Markup);
}

[Fact]
public void AppendNode_Ok()
{
Expand Down
2 changes: 1 addition & 1 deletion test/UnitTest/Dynamic/ChangeDetectionCleanTaskTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public async Task CleanLoop_Ok()
var removed = false;
for (var i = 0; i < 100 && !removed; i++)
{
await Task.Delay(50);
await Task.Delay(50, Xunit.TestContext.Current.CancellationToken);
removed = !cache.ContainsKey(dynamicType);
}
Assert.True(removed);
Expand Down
Loading