Skip to content

Commit a88946d

Browse files
authored
feat(TreeView): add ScrollIntoViewOptions parameter (#4349)
* fix: 增强脚本防止报错 * perf: 优化性能 * refactor: 增加父级节点跳转逻辑 * feat: 增加选中节点自动居中功能 * feat: 增加 ScrollIntoViewOptions 配置 * test: 更新单元测试
1 parent 44ca571 commit a88946d

File tree

6 files changed

+204
-7
lines changed

6 files changed

+204
-7
lines changed

src/BootstrapBlazor/Components/Drawer/Drawer.razor.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,11 @@ const initDrag = el => {
6161
}
6262

6363
export function init(id) {
64-
const el = document.getElementById(id)
64+
const el = document.getElementById(id);
65+
if (el === null) {
66+
return;
67+
}
68+
6569
const dw = {
6670
el,
6771
body: document.querySelector('body')

src/BootstrapBlazor/Components/TreeView/TreeView.razor.cs

+29-1
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,12 @@ public partial class TreeView<TItem> : IModelEqualityComparer<TItem>
251251
[Parameter]
252252
public bool EnableKeyboardArrowUpDown { get; set; }
253253

254+
/// <summary>
255+
/// 获得/设置 是否键盘上下键操作当前选中节点与视窗关系配置 默认 null 使用 { behavior: "smooth", block: "center", inline: "nearest" }
256+
/// </summary>
257+
[Parameter]
258+
public ScrollIntoViewOptions? ScrollIntoViewOptions { get; set; }
259+
254260
[CascadingParameter]
255261
private ContextMenuZone? ContextMenuZone { get; set; }
256262

@@ -354,12 +360,30 @@ protected override async Task OnParametersSetAsync()
354360
}
355361
}
356362

363+
/// <summary>
364+
/// <inheritdoc/>
365+
/// </summary>
366+
/// <param name="firstRender"></param>
367+
/// <returns></returns>
368+
protected override async Task OnAfterRenderAsync(bool firstRender)
369+
{
370+
await base.OnAfterRenderAsync(firstRender);
371+
372+
if (_keyboardArrowUpDownTrigger)
373+
{
374+
_keyboardArrowUpDownTrigger = false;
375+
await InvokeVoidAsync("scroll", Id, ScrollIntoViewOptions ?? new() { Behavior = ScrollIntoViewBehavior.Smooth, Block = ScrollIntoViewBlock.Center, Inline = ScrollIntoViewInline.Nearest });
376+
}
377+
}
378+
357379
/// <summary>
358380
/// <inheritdoc/>
359381
/// </summary>
360382
/// <returns></returns>
361383
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, nameof(TriggerKeyDown));
362384

385+
private bool _keyboardArrowUpDownTrigger;
386+
363387
/// <summary>
364388
/// 客户端用户键盘操作处理方法 由 JavaScript 调用
365389
/// </summary>
@@ -372,8 +396,8 @@ public async ValueTask TriggerKeyDown(string key)
372396
// 如果兄弟节点没有时,找到父亲节点
373397
if (ActiveItem != null)
374398
{
399+
_keyboardArrowUpDownTrigger = true;
375400
await ActiveTreeViewItem(key, ActiveItem);
376-
StateHasChanged();
377401
}
378402
}
379403

@@ -437,6 +461,10 @@ private async Task ActiveParentTreeViewItem(TreeViewItem<TItem> item)
437461
{
438462
await OnClick(items[index]);
439463
}
464+
else if (item.Parent != null)
465+
{
466+
await ActiveParentTreeViewItem(item.Parent);
467+
}
440468
}
441469

442470
private async Task<bool> OnBeforeStateChangedCallback(TreeViewItem<TItem> item, CheckboxState state)

src/BootstrapBlazor/Components/TreeView/TreeView.razor.js

+11
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,17 @@ export function init(id, invoke, method) {
4141
});
4242
}
4343

44+
export function scroll(id, options) {
45+
const tree = Data.get(id)
46+
if (tree) {
47+
const { el } = tree;
48+
const item = el.querySelector(".active .tree-node");
49+
if (item) {
50+
item.scrollIntoView(options);
51+
}
52+
}
53+
}
54+
4455
export function dispose(id) {
4556
const tree = Data.get(id)
4657
Data.remove(id);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
// Website: https://www.blazor.zone or https://argozhang.github.io/
4+
5+
using BootstrapBlazor.Core.Converter;
6+
7+
namespace BootstrapBlazor.Components;
8+
9+
/// <summary>
10+
/// ScrollIntoViewOptions 配置类
11+
/// </summary>
12+
public class ScrollIntoViewOptions
13+
{
14+
/// <summary>
15+
/// 获得/设置 滚动条宽度 默认 5px
16+
/// </summary>
17+
public ScrollIntoViewBehavior Behavior { get; set; }
18+
19+
/// <summary>
20+
/// 获得/设置 滚动条鼠标悬浮宽度 默认 5px
21+
/// </summary>
22+
public ScrollIntoViewBlock Block { get; set; }
23+
24+
/// <summary>
25+
/// 获得/设置 滚动条鼠标悬浮宽度 默认 5px
26+
/// </summary>
27+
public ScrollIntoViewInline Inline { get; set; }
28+
}
29+
30+
/// <summary>
31+
/// Determines whether scrolling is instant or animates smoothly. This option is a string which must take one of the following values
32+
/// </summary>
33+
[JsonEnumConverter(true)]
34+
public enum ScrollIntoViewBehavior
35+
{
36+
/// <summary>
37+
/// scrolling should animate smoothly
38+
/// </summary>
39+
Smooth,
40+
41+
/// <summary>
42+
/// scrolling should happen instantly in a single jump
43+
/// </summary>
44+
Instant,
45+
46+
/// <summary>
47+
/// scroll behavior is determined by the computed value of scroll-behavior
48+
/// </summary>
49+
Auto
50+
}
51+
52+
/// <summary>
53+
/// Defines vertical alignment
54+
/// </summary>
55+
[JsonEnumConverter(true)]
56+
public enum ScrollIntoViewBlock
57+
{
58+
/// <summary>
59+
/// Start
60+
/// </summary>
61+
Start,
62+
63+
/// <summary>
64+
/// Center
65+
/// </summary>
66+
Center,
67+
68+
/// <summary>
69+
/// End
70+
/// </summary>
71+
End,
72+
73+
/// <summary>
74+
/// Nearest
75+
/// </summary>
76+
Nearest
77+
}
78+
79+
/// <summary>
80+
/// Defines horizontal alignment
81+
/// </summary>
82+
[JsonEnumConverter(true)]
83+
public enum ScrollIntoViewInline
84+
{
85+
/// <summary>
86+
/// Start
87+
/// </summary>
88+
Start,
89+
90+
/// <summary>
91+
/// Center
92+
/// </summary>
93+
Center,
94+
95+
/// <summary>
96+
/// End
97+
/// </summary>
98+
End,
99+
100+
/// <summary>
101+
/// Nearest
102+
/// </summary>
103+
Nearest
104+
}

test/UnitTest/Components/TreeViewTest.cs

+32-5
Original file line numberDiff line numberDiff line change
@@ -876,43 +876,70 @@ public async Task Esc_Ok()
876876
[Fact]
877877
public async Task KeyBoard_Ok()
878878
{
879-
var items = TreeFoo.GetTreeItems();
879+
List<TreeFoo> data =
880+
[
881+
new() { Text = "1010", Id = "1010" },
882+
new() { Text = "1020", Id = "1020" },
883+
new() { Text = "1030", Id = "1030" },
884+
885+
new() { Text = "1020-01", Id = "1020-01", ParentId = "1020" },
886+
new() { Text = "1020-02", Id = "1020-02", ParentId = "1020" },
887+
888+
new() { Text = "1020-02-01", Id = "1020-02-01", ParentId = "1020-02" },
889+
new() { Text = "1020-02-02", Id = "1020-02-02", ParentId = "1020-02" },
890+
891+
new() { Text = "1020-02-02-01", Id = "1020-02-02-01", ParentId = "1020-02-02" },
892+
new() { Text = "1020-02-02-02", Id = "1020-02-02-02", ParentId = "1020-02-02" }
893+
];
894+
895+
var items = TreeFoo.CascadingTree(data);
880896
items[0].IsActive = true;
881897
items[1].IsExpand = true;
882898
items[1].Items[1].IsExpand = true;
883899
items[1].Items[1].Items[1].IsExpand = true;
900+
901+
var activeItemText = "1010";
884902
var cut = Context.RenderComponent<TreeView<TreeFoo>>(pb =>
885903
{
886904
pb.Add(a => a.EnableKeyboardArrowUpDown, true);
887905
pb.Add(a => a.Items, items);
906+
pb.Add(a => a.OnTreeItemClick, new Func<TreeViewItem<TreeFoo>, Task>(treeViewItem =>
907+
{
908+
activeItemText = treeViewItem.Text;
909+
return Task.CompletedTask;
910+
}));
888911
});
889912
await cut.InvokeAsync(() => cut.Instance.TriggerKeyDown("ArrowDown"));
890913
await cut.InvokeAsync(() => cut.Instance.TriggerKeyDown("ArrowDown"));
891914
await cut.InvokeAsync(() => cut.Instance.TriggerKeyDown("ArrowDown"));
915+
892916
await cut.InvokeAsync(() => cut.Instance.TriggerKeyDown("ArrowDown"));
893917
await cut.InvokeAsync(() => cut.Instance.TriggerKeyDown("ArrowDown"));
894918
await cut.InvokeAsync(() => cut.Instance.TriggerKeyDown("ArrowDown"));
919+
895920
await cut.InvokeAsync(() => cut.Instance.TriggerKeyDown("ArrowDown"));
896921
await cut.InvokeAsync(() => cut.Instance.TriggerKeyDown("ArrowDown"));
897922
await cut.InvokeAsync(() => cut.Instance.TriggerKeyDown("ArrowDown"));
923+
898924
await cut.InvokeAsync(() => cut.Instance.TriggerKeyDown("ArrowDown"));
899925
await cut.InvokeAsync(() => cut.Instance.TriggerKeyDown("ArrowDown"));
900-
await cut.InvokeAsync(() => cut.Instance.TriggerKeyDown("ArrowDown"));
901-
926+
Assert.Equal("1030", activeItemText);
902927

903-
904-
await cut.InvokeAsync(() => cut.Instance.TriggerKeyDown("ArrowUp"));
905928
await cut.InvokeAsync(() => cut.Instance.TriggerKeyDown("ArrowUp"));
906929
await cut.InvokeAsync(() => cut.Instance.TriggerKeyDown("ArrowUp"));
907930
await cut.InvokeAsync(() => cut.Instance.TriggerKeyDown("ArrowUp"));
931+
908932
await cut.InvokeAsync(() => cut.Instance.TriggerKeyDown("ArrowUp"));
909933
await cut.InvokeAsync(() => cut.Instance.TriggerKeyDown("ArrowUp"));
910934
await cut.InvokeAsync(() => cut.Instance.TriggerKeyDown("ArrowUp"));
935+
911936
await cut.InvokeAsync(() => cut.Instance.TriggerKeyDown("ArrowUp"));
912937
await cut.InvokeAsync(() => cut.Instance.TriggerKeyDown("ArrowUp"));
913938
await cut.InvokeAsync(() => cut.Instance.TriggerKeyDown("ArrowUp"));
939+
914940
await cut.InvokeAsync(() => cut.Instance.TriggerKeyDown("ArrowUp"));
915941
await cut.InvokeAsync(() => cut.Instance.TriggerKeyDown("ArrowUp"));
942+
Assert.Equal("1010", activeItemText);
916943
}
917944

918945
class MockTree<TItem> : TreeView<TItem> where TItem : class
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
// Website: https://www.blazor.zone or https://argozhang.github.io/
4+
5+
using System.Text.Json;
6+
7+
namespace UnitTest.Options;
8+
9+
public class ScrollIntoViewOptionsTest
10+
{
11+
[Fact]
12+
public void Options_Ok()
13+
{
14+
var options = new ScrollIntoViewOptions()
15+
{
16+
Behavior = ScrollIntoViewBehavior.Auto,
17+
Block = ScrollIntoViewBlock.Center,
18+
Inline = ScrollIntoViewInline.End
19+
};
20+
var json = JsonSerializer.Serialize(options, new JsonSerializerOptions(JsonSerializerDefaults.Web));
21+
Assert.Equal("{\"behavior\":\"auto\",\"block\":\"center\",\"inline\":\"end\"}", json);
22+
}
23+
}

0 commit comments

Comments
 (0)