refactor(module: tree): add draggable, fix default value binding (#1517)

* delete demo docs

* fix tree default value and dnd

* fix tree line and keys

* fix switcher icon and line

* fix line demo

* fix switcher

* add DirectoryTree demo

* fix draggable demo

* clean code

* clean code

* clean up the classmapper call

* update docs

* fix the docs

* fix comment

Co-authored-by: James Yeung <shunjiey@hotmail.com>
This commit is contained in:
琢磨先生 2021-07-11 18:43:25 +08:00 committed by GitHub
parent e98a68625a
commit 300123045f
52 changed files with 2204 additions and 962 deletions

View File

@ -51,10 +51,10 @@
}
else
{
<ul class="ant-transfer-list-content ng-star-inserted">
<ul class="ant-transfer-list-content">
@foreach (var item in _leftDataSource)
{
<li @key="item" class="ant-transfer-list-content-item ng-star-inserted @(item.Disabled || Disabled ? DisabledClass : "")">
<li @key="item" class="ant-transfer-list-content-item @(item.Disabled || Disabled ? DisabledClass : "")">
<Checkbox Label="@item.Key" Checked="_sourceSelectedKeys.Contains(item.Key)" CheckedChange="@(checkedState => SelectItem(checkedState, TransferDirection.Left, item.Key))" Disabled="@(item.Disabled || Disabled)">
@if (Render == null)
{

View File

@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Text;
namespace AntDesign
{
public class DirectoryTree<TItem> : Tree<TItem>
{
public DirectoryTree()
{
base.BlockNode = true;
base.Directory = true;
}
}
}

View File

@ -1,21 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace AntDesign
{
public interface IRendered<TItem>
{
/// <summary>
/// 渲染完成后
/// </summary>
Action OnRendered { get; set; }
/// <summary>
/// 新节点数据,用于展开并选择新节点
/// </summary>
TItem NewChildData { get; set; }
}
}

View File

@ -20,4 +20,4 @@
</CascadingValue>
</div>
</div>
</div>
</div>

View File

@ -1,45 +1,106 @@
using System;
using System.Collections;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
namespace AntDesign
{
public partial class Tree<TItem> : AntDomComponentBase
{
#region fields
/// <summary>
/// All of the node
/// </summary>
internal List<TreeNode<TItem>> _allNodes = new List<TreeNode<TItem>>();
/// <summary>
/// All the checked nodes
/// </summary>
private ConcurrentDictionary<long, TreeNode<TItem>> _checkedNodes = new ConcurrentDictionary<long, TreeNode<TItem>>();
#endregion fields
#region Tree
/// <summary>
/// 节点前添加展开图标
/// Shows an expansion icon before the node
/// </summary>
[Parameter]
public bool ShowExpand { get; set; } = true;
/// <summary>
/// 是否展示连接线
/// Shows a connecting line
/// </summary>
[Parameter]
public bool ShowLine { get; set; }
public bool ShowLine
{
get => _showLine;
set
{
_showLine = value;
if (!_hasSetShowLeafIcon)
{
ShowLeafIcon = _showLine;
}
}
}
/// <summary>
/// 是否展示 TreeNode title 前的图标
/// show treeNode icon icon
/// </summary>
[Parameter]
public bool ShowIcon { get; set; }
/// <summary>
/// 是否节点占据一行
/// Whether treeNode fill remaining horizontal space
/// </summary>
[Parameter]
public bool BlockNode { get; set; }
/// <summary>
/// 设置节点可拖拽
/// Whether the node allows drag and drop
/// </summary>
[Parameter]
public bool Draggable { get; set; }
/// <summary>
/// The tree is disabled
/// </summary>
[Parameter]
public bool Disabled { get; set; }
/// <summary>
/// Displays the cotyledon icon
/// </summary>
[Parameter]
public bool ShowLeafIcon
{
get => _showLeafIcon;
set
{
_showLeafIcon = value;
_hasSetShowLeafIcon = true;
}
}
private bool _hasSetShowLeafIcon;
/// <summary>
/// Specific the Icon type of switcher
/// </summary>
[Parameter]
public string SwitcherIcon { get; set; }
public bool Directory { get; set; }
private void SetClassMapper()
{
ClassMapper
@ -47,7 +108,9 @@ namespace AntDesign
.If("ant-tree-show-line", () => ShowLine)
.If("ant-tree-icon-hide", () => ShowIcon)
.If("ant-tree-block-node", () => BlockNode)
.If("ant-tree-directory", () => Directory)
.If("draggable-tree", () => Draggable)
.If("ant-tree-unselectable", () => !Selectable)
.If("ant-tree-rtl", () => RTL);
}
@ -58,16 +121,19 @@ namespace AntDesign
[Parameter]
public RenderFragment Nodes { get; set; }
[Parameter]
public List<TreeNode<TItem>> ChildNodes { get; set; } = new List<TreeNode<TItem>>();
/// <summary>
/// tree childnodes
/// Add values when the node is initialized
/// </summary>
internal List<TreeNode<TItem>> ChildNodes { get; set; } = new List<TreeNode<TItem>>();
/// <summary>
/// 添加节点
/// Add a node
/// </summary>
/// <param name="treeNode"></param>
/// <param name=""></param>
internal void AddNode(TreeNode<TItem> treeNode)
{
treeNode.NodeIndex = ChildNodes.Count;
ChildNodes.Add(treeNode);
}
@ -76,26 +142,48 @@ namespace AntDesign
#region Selected
/// <summary>
/// 支持点选多个节点(节点本身)
/// Whether can be selected
/// </summary>
[Parameter]
public bool Selectable { get; set; } = true;
/// <summary>
/// Allows selecting multiple treeNodes
/// </summary>
[Parameter]
public bool Multiple { get; set; }
[Parameter]
public string[] DefaultSelectedKeys { get; set; }
/// <summary>
/// 选中的树节点
/// The selected tree node
/// </summary>
internal Dictionary<long, TreeNode<TItem>> SelectedNodesDictionary { get; set; } = new Dictionary<long, TreeNode<TItem>>();
public List<string> SelectedTitles => SelectedNodesDictionary.Select(x => x.Value.Title).ToList();
internal List<string> SelectedTitles => SelectedNodesDictionary.Select(x => x.Value.Title).ToList();
/// <summary>
/// Add the selected node
/// </summary>
/// <param name="treeNode"></param>
internal void SelectedNodeAdd(TreeNode<TItem> treeNode)
{
if (SelectedNodesDictionary.ContainsKey(treeNode.NodeId) == false)
SelectedNodesDictionary.Add(treeNode.NodeId, treeNode);
if (OnSelect.HasDelegate)
{
OnSelect.InvokeAsync(new TreeEventArgs<TItem>(this, treeNode));
}
UpdateBindData();
}
/// <summary>
/// remove the selected node
/// </summary>
/// <param name="treeNode"></param>
internal void SelectedNodeRemove(TreeNode<TItem> treeNode)
{
if (SelectedNodesDictionary.ContainsKey(treeNode.NodeId) == true)
@ -104,6 +192,9 @@ namespace AntDesign
UpdateBindData();
}
/// <summary>
/// Deselect all selections
/// </summary>
public void DeselectAll()
{
foreach (var item in SelectedNodesDictionary.Select(x => x.Value).ToList())
@ -113,16 +204,19 @@ namespace AntDesign
}
/// <summary>
/// 选择的Key
/// @bind-SelectedKey
/// </summary>
[Parameter]
public string SelectedKey { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
public EventCallback<string> SelectedKeyChanged { get; set; }
/// <summary>
/// 选择的节点
/// @bind-SelectedNode
/// </summary>
[Parameter]
public TreeNode<TItem> SelectedNode { get; set; }
@ -131,7 +225,7 @@ namespace AntDesign
public EventCallback<TreeNode<TItem>> SelectedNodeChanged { get; set; }
/// <summary>
/// 选择的数据
/// @bing-SelectedData
/// </summary>
[Parameter]
public TItem SelectedData { get; set; }
@ -140,7 +234,7 @@ namespace AntDesign
public EventCallback<TItem> SelectedDataChanged { get; set; }
/// <summary>
/// 选择的Key集合
///
/// </summary>
[Parameter]
public string[] SelectedKeys { get; set; }
@ -149,19 +243,19 @@ namespace AntDesign
public EventCallback<string[]> SelectedKeysChanged { get; set; }
/// <summary>
/// 选择的节点集合
/// The collection of selected nodes
/// </summary>
[Parameter]
public TreeNode<TItem>[] SelectedNodes { get; set; }
/// <summary>
/// 选择的数据集合
/// The selected data set
/// </summary>
[Parameter]
public TItem[] SelectedDatas { get; set; }
/// <summary>
/// 更新绑定数据
/// Update binding data
/// </summary>
private void UpdateBindData()
{
@ -196,28 +290,32 @@ namespace AntDesign
#region Checkable
/// <summary>
/// 节点前添加 Checkbox 复选框
/// Add a Checkbox before the node
/// </summary>
[Parameter]
public bool Checkable { get; set; }
public List<TreeNode<TItem>> CheckedNodes => GetCheckedNodes(ChildNodes);
/// <summary>
/// Check treeNode precisely; parent treeNode and children treeNodes are not associated
/// </summary>
[Parameter]
public bool CheckStrictly { get; set; }
public List<string> CheckedKeys => GetCheckedNodes(ChildNodes).Select(x => x.Key).ToList();
/// <summary>
/// Checked keys
/// </summary>
[Parameter]
public string[] CheckedKeys { get; set; } = Array.Empty<string>();
public List<string> CheckedTitles => GetCheckedNodes(ChildNodes).Select(x => x.Title).ToList();
private List<TreeNode<TItem>> GetCheckedNodes(List<TreeNode<TItem>> childs)
{
List<TreeNode<TItem>> checkeds = new List<TreeNode<TItem>>();
foreach (var item in childs)
{
if (item.Checked) checkeds.Add(item);
checkeds.AddRange(GetCheckedNodes(item.ChildNodes));
}
return checkeds;
}
/// <summary>
/// @bind-CheckedKeys
/// </summary>
[Parameter]
public EventCallback<string[]> CheckedKeysChanged { get; set; }
/// <summary>
/// Dechecked all selected items
/// </summary>
public void CheckedAll()
{
foreach (var item in ChildNodes)
@ -226,7 +324,7 @@ namespace AntDesign
}
}
//取消所有选择项目
// Decheck all of the checked nodes
public void DecheckedAll()
{
foreach (var item in ChildNodes)
@ -235,14 +333,41 @@ namespace AntDesign
}
}
/// <summary>
/// Specifies the keys of the default checked treeNodes
/// </summary>
[Parameter]
public string[] DefaultCheckedKeys { get; set; }
/// <summary>
/// Disable node Checkbox
/// </summary>
public string[] DisableCheckKeys { get; set; }
/// <summary>
/// Adds or removes a checkbox node
/// </summary>
/// <param name="treeNode"></param>
internal void AddOrRemoveCheckNode(TreeNode<TItem> treeNode)
{
if (treeNode.Checked)
_checkedNodes.TryAdd(treeNode.NodeId, treeNode);
else
_checkedNodes.TryRemove(treeNode.NodeId, out TreeNode<TItem> _);
CheckedKeys = _checkedNodes.Select(x => x.Value.Key).ToArray();
if (CheckedKeysChanged.HasDelegate) CheckedKeysChanged.InvokeAsync(CheckedKeys);
}
#endregion Checkable
#region Search
public string _searchValue;
private string _searchValue;
private bool _showLeafIcon;
private bool _showLine;
/// <summary>
/// 按需筛选树,双向绑定
/// search value
/// </summary>
[Parameter]
public string SearchValue
@ -250,189 +375,197 @@ namespace AntDesign
get => _searchValue;
set
{
if (_searchValue == value) return;
_searchValue = value;
if (string.IsNullOrEmpty(value)) return;
foreach (var item in ChildNodes)
var allList = _allNodes.ToList();
List<TreeNode<TItem>> searchDatas = null, exceptList = null;
if (!String.IsNullOrEmpty(value))
searchDatas = allList.Where(x => x.Title.Contains(value)).ToList();
if (searchDatas != null && searchDatas.Any())
exceptList = allList.Except(searchDatas).ToList();
if (exceptList != null || searchDatas != null)
{
SearchNode(item);
exceptList?.ForEach(m => { m.Expand(false); m.Matched = false; });
searchDatas?.ForEach(node => { node.OpenPropagation(); node.Matched = true; });
}
else
{
allList.ForEach(m => { m.Matched = false; });
}
}
}
///// <summary>
/////
///// </summary>
//[Parameter]
//public EventCallback<TreeEventArgs<TItem>> OnSearchValueChanged { get; set; }
/// <summary>
/// 返回一个值是否是页节点
/// Search for matching text styles
/// </summary>
[Parameter]
public Func<TreeNode<TItem>, bool> SearchExpression { get; set; }
/// <summary>
/// 查询节点
/// </summary>
/// <param name="treeNode"></param>
/// <returns></returns>
private bool SearchNode(TreeNode<TItem> treeNode)
{
if (SearchExpression != null)
treeNode.Matched = SearchExpression(treeNode);
else
treeNode.Matched = treeNode.Title.Contains(SearchValue);
var hasChildMatched = treeNode.Matched;
foreach (var item in treeNode.ChildNodes)
{
var itemMatched = SearchNode(item);
hasChildMatched = hasChildMatched || itemMatched;
}
treeNode.HasChildMatched = hasChildMatched;
return hasChildMatched;
}
public string MatchedStyle { get; set; } = "";
#endregion Search
#region DataBind
/// <summary>
///
/// </summary>
[Parameter]
public IList<TItem> DataSource { get; set; }
public IEnumerable<TItem> DataSource { get; set; }
/// <summary>
/// 指定一个方法,该表达式返回节点的文本。
/// Specifies a method that returns the text of the node.
/// </summary>
[Parameter]
public Func<TreeNode<TItem>, string> TitleExpression { get; set; }
/// <summary>
/// 指定一个返回节点名称的方法。
/// Specifies a method that returns the key of the node.
/// </summary>
[Parameter]
public Func<TreeNode<TItem>, string> KeyExpression { get; set; }
/// <summary>
/// 指定一个返回节点名称的方法。
/// Specifies a method to return the node icon.
/// </summary>
[Parameter]
public Func<TreeNode<TItem>, string> IconExpression { get; set; }
/// <summary>
/// 返回一个值是否是页节点
/// Specifies a method that returns whether the expression is a leaf node.
/// </summary>
[Parameter]
public Func<TreeNode<TItem>, bool> IsLeafExpression { get; set; }
/// <summary>
/// 返回子节点的方法
/// Specifies a method to return a child node
/// </summary>
[Parameter]
public Func<TreeNode<TItem>, IList<TItem>> ChildrenExpression { get; set; }
/// <summary>
/// Specifies a method to return a disabled node
/// </summary>
[Parameter]
public Func<TreeNode<TItem>, bool> DisabledExpression { get; set; }
#endregion DataBind
#region Event
/// <summary>
/// 延迟加载
/// Lazy load callbacks
/// </summary>
/// <remarks>必须使用async且返回类型为Task否则可能会出现载入时差导致显示问题</remarks>
/// <remarks>You must use async and the return type is Task, otherwise you may experience load lag and display problems</remarks>
[Parameter]
public EventCallback<TreeEventArgs<TItem>> OnNodeLoadDelayAsync { get; set; }
/// <summary>
/// 点击树节点触发
/// Click the tree node callback
/// </summary>
[Parameter]
public EventCallback<TreeEventArgs<TItem>> OnClick { get; set; }
/// <summary>
/// 双击树节点触发
/// Double-click the node callback
/// </summary>
[Parameter]
public EventCallback<TreeEventArgs<TItem>> OnDblClick { get; set; }
/// <summary>
/// 右键树节点触发
/// Right-click tree node callback
/// </summary>
[Parameter]
public EventCallback<TreeEventArgs<TItem>> OnContextMenu { get; set; }
/// <summary>
/// 点击树节点 Checkbox 触发
/// checked the tree node callback
/// </summary>
[Parameter]
public EventCallback<TreeEventArgs<TItem>> OnCheckBoxChanged { get; set; }
public EventCallback<TreeEventArgs<TItem>> OnCheck { get; set; }
[Parameter]
public EventCallback<TreeEventArgs<TItem>> OnSelect { get; set; }
/// <summary>
/// 点击展开树节点图标触发
/// Click the expansion tree node icon to call back
/// </summary>
[Parameter]
public EventCallback<TreeEventArgs<TItem>> OnExpandChanged { get; set; }
/// <summary>
/// 搜索节点时调用(与SearchValue配合使用)
/// </summary>
[Parameter]
public EventCallback<TreeEventArgs<TItem>> OnSearchValueChanged { get; set; }
///// <summary>
///// 开始拖拽时调用
///// </summary>
//public EventCallback<TreeEventArgs> OnDragStart { get; set; }
///// <summary>
///// dragenter 触发时调用
///// </summary>
//public EventCallback<TreeEventArgs> OnDragEnter { get; set; }
///// <summary>
///// dragover 触发时调用
///// </summary>
//public EventCallback<TreeEventArgs> OnDragOver { get; set; }
///// <summary>
///// dragleave 触发时调用
///// </summary>
//public EventCallback<TreeEventArgs> OnDragLeave { get; set; }
///// <summary>
///// drop 触发时调用
///// </summary>
//public EventCallback<TreeEventArgs> OnDrop { get; set; }
///// <summary>
///// dragend 触发时调用
///// </summary>
//public EventCallback<TreeEventArgs> OnDragEnd { get; set; }
#endregion Event
#region Template
/// <summary>
/// 缩进模板
/// The indentation template
/// </summary>
[Parameter]
public RenderFragment<TreeNode<TItem>> IndentTemplate { get; set; }
/// <summary>
/// 标题模板
/// Customize the header template
/// </summary>
[Parameter]
public RenderFragment<TreeNode<TItem>> TitleTemplate { get; set; }
/// <summary>
/// 图标模板
/// Customize the icon templates
/// </summary>
[Parameter]
public RenderFragment<TreeNode<TItem>> TitleIconTemplate { get; set; }
/// <summary>
/// 切换图标模板
/// Customize toggle icon templates
/// </summary>
[Parameter]
public RenderFragment<TreeNode<TItem>> SwitcherIconTemplate { get; set; }
#endregion Template
#region DragDrop
/// <summary>
/// 当前拖拽项
/// </summary>
internal TreeNode<TItem> DragItem { get; set; }
/// <summary>
/// Called when the drag and drop begins
/// </summary>
[Parameter]
public EventCallback<TreeEventArgs<TItem>> OnDragStart { get; set; }
/// <summary>
/// Called when drag and drop into a releasable target
/// </summary>
[Parameter]
public EventCallback<TreeEventArgs<TItem>> OnDragEnter { get; set; }
/// <summary>
/// Called when drag and drop away from a releasable target
/// </summary>
[Parameter]
public EventCallback<TreeEventArgs<TItem>> OnDragLeave { get; set; }
/// <summary>
/// Triggered when drag-and-drop drops succeed
/// </summary>
[Parameter]
public EventCallback<TreeEventArgs<TItem>> OnDrop { get; set; }
/// <summary>
/// Drag-and-drop end callback
/// </summary>
/// <remarks>this callback method must be set</remarks>
[Parameter]
public EventCallback<TreeEventArgs<TItem>> OnDragEnd { get; set; }
#endregion DragDrop
protected override void OnInitialized()
{
SetClassMapper();
@ -466,26 +599,43 @@ namespace AntDesign
return null;
}
/// <summary>
/// from node expand to root
/// </summary>
/// <param name="node">Node</param>
public void ExpandToNode(TreeNode<TItem> node)
{
if (node == null)
{
throw new ArgumentNullException(nameof(node));
}
var parentNode = node.ParentNode;
while (parentNode != null)
{
parentNode.Expand(true);
parentNode = parentNode.ParentNode;
}
}
#region Expand
/// <summary>
/// 展开全部节点
/// All tree nodes are expanded by default
/// </summary>
[Parameter]
public bool DefaultExpandAll { get; set; }
/// <summary>
/// The parent node is expanded by default
/// </summary>
[Parameter]
public bool DefaultExpandParent { get; set; }
/// <summary>
/// Expand the specified tree node by default
/// </summary>
[Parameter]
public string[] DefaultExpandedKeys { get; set; }
/// <summary>
/// (Controlled) expands the specified tree node
/// </summary>
[Parameter]
public string[] ExpandedKeys { get; set; }
[Parameter]
public EventCallback<string[]> ExpandedKeysChanged { get; set; }
[Parameter]
public EventCallback<(string[] ExpandedKeys, TreeNode<TItem> Node, bool Expanded)> OnExpand { get; set; }
[Parameter]
public bool AutoExpandParent { get; set; }
/// <summary>
/// Expand all nodes
/// </summary>
public void ExpandAll()
{
@ -493,17 +643,55 @@ namespace AntDesign
}
/// <summary>
/// 折叠全部节点
/// Collapse all nodes
/// </summary>
public void CollapseAll()
{
this.ChildNodes.ForEach(node => Switch(node, false));
}
/// <summary>
/// 节点展开关闭
/// </summary>
/// <param name="node"></param>
/// <param name="expanded"></param>
private void Switch(TreeNode<TItem> node, bool expanded)
{
node.Expand(expanded);
node.ChildNodes.ForEach(n => Switch(n, expanded));
}
internal async Task OnNodeExpand(TreeNode<TItem> node, bool expanded, MouseEventArgs args)
{
var expandedKeys = _allNodes.Select(x => x.Key).ToArray();
if (OnNodeLoadDelayAsync.HasDelegate && expanded == true)
{
node.SetLoading(true);
await OnNodeLoadDelayAsync.InvokeAsync(new TreeEventArgs<TItem>(this, node, args));
node.SetLoading(false);
}
if (OnExpandChanged.HasDelegate)
{
await OnExpandChanged.InvokeAsync(new TreeEventArgs<TItem>(this, node, args));
}
if (ExpandedKeysChanged.HasDelegate)
{
await ExpandedKeysChanged.InvokeAsync(expandedKeys);
}
if (OnExpand.HasDelegate)
{
await OnExpand.InvokeAsync((expandedKeys, node, expanded));
}
if (AutoExpandParent && expanded)
{
node.ParentNode?.Expand(true);
}
}
#endregion Expand
}
}

View File

@ -8,12 +8,16 @@ namespace AntDesign
public TreeEventArgs() { }
public TreeEventArgs(Tree<TItem> tree) { Tree = tree; }
public TreeEventArgs(Tree<TItem> tree, TreeNode<TItem> node) { Tree = tree; Node = node; }
public TreeEventArgs(Tree<TItem> tree, TreeNode<TItem> node, MouseEventArgs originalEvent) { Tree = tree; Node = node; OriginalEvent = originalEvent; }
public Tree<TItem> Tree { get; set; }
public TreeNode<TItem> Node { get; set; }
/// <summary>
/// 目标节点
/// </summary>
public TreeNode<TItem> TargetNode { get; set; }
/// <summary>
/// 原生事件
/// </summary>

View File

@ -3,7 +3,7 @@
@typeparam TItem
<span class="ant-tree-indent" aria-hidden="true">
@for (int i = 0; i < TreeLevel; i++)
@for (int i = TreeLevel; i > 0; i--)
{
if (TreeComponent.IndentTemplate != null)
{
@ -11,7 +11,8 @@
}
else
{
<span class="ant-tree-indent-unit ng-star-inserted"></span>
var node = GetParentNode(SelfNode, i);
<span class="ant-tree-indent-unit ant-tree-indent-unit-start @(node.IsLastNode?"ant-tree-indent-unit-end":"")"></span>
}
}
</span>

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Components;
namespace AntDesign
@ -8,18 +9,34 @@ namespace AntDesign
public partial class TreeIndent<TItem> : ComponentBase
{
/// <summary>
/// 树控件本身
/// Root Tree
/// </summary>
[CascadingParameter(Name = "Tree")]
public Tree<TItem> TreeComponent { get; set; }
/// <summary>
/// 当前节点
/// Current Node
/// </summary>
[CascadingParameter(Name = "SelfNode")]
public TreeNode<TItem> SelfNode { get; set; }
[Parameter]
public int TreeLevel { get; set; }
/// <summary>
/// To find specific level parent node
/// </summary>
/// <param name="node"></param>
/// <param name="level"></param>
/// <returns></returns>
private static TreeNode<TItem> GetParentNode(TreeNode<TItem> node, int level)
{
if (level > 0 && node.ParentNode != null)
{
return GetParentNode(node.ParentNode, level - 1);
}
return node;
}
}
}

View File

@ -35,4 +35,4 @@
{
@Nodes
}
</CascadingValue>
</CascadingValue>

View File

@ -1,4 +1,8 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@ -25,26 +29,40 @@ namespace AntDesign
public TreeNode<TItem> ParentNode { get; set; }
/// <summary>
/// 子节点
///
/// </summary>
[Parameter]
public RenderFragment Nodes { get; set; }
public List<TreeNode<TItem>> ChildNodes { get; set; } = new List<TreeNode<TItem>>();
public bool HasChildNodes => ChildNodes?.Count > 0;
internal List<TreeNode<TItem>> ChildNodes { get; set; } = new List<TreeNode<TItem>>();
/// <summary>
/// 当前节点级别
/// Whether child nodes exist
/// </summary>
public int TreeLevel => (ParentNode?.TreeLevel ?? -1) + 1;//因为第一层是0所以默认是-1
internal bool HasChildNodes => ChildNodes?.Count > 0;
/// <summary>
/// 添加节点
/// Current Node Level
/// </summary>
/// <param name=""></param>
public int TreeLevel => (ParentNode?.TreeLevel ?? -1) + 1;
/// <summary>
/// record the index in children nodes list of parent node.
/// </summary>
internal int NodeIndex { get; set; }
/// <summary>
/// Determine if it is the last node in the same level nodes.
/// </summary>
internal bool IsLastNode => NodeIndex == (ParentNode?.ChildNodes.Count ?? TreeComponent?.ChildNodes.Count) - 1;
/// <summary>
/// add node to parent node
/// </summary>
/// <param name="treeNode"></param>
internal void AddNode(TreeNode<TItem> treeNode)
{
treeNode.NodeIndex = ChildNodes.Count;
ChildNodes.Add(treeNode);
IsLeaf = false;
}
@ -76,7 +94,7 @@ namespace AntDesign
}
/// <summary>
/// 获得上级数据集合
/// Obtain the parent data set
/// </summary>
/// <returns></returns>
public List<TreeNode<TItem>> GetParentNodes()
@ -119,7 +137,7 @@ namespace AntDesign
private string _key;
/// <summary>
/// 指定当前节点的唯一标识符名称
/// Specifies the unique identifier name of the current node
/// </summary>
[Parameter]
public string Key
@ -140,19 +158,19 @@ namespace AntDesign
private bool _disabled;
/// <summary>
/// 是否禁用
/// The disabled state is subject to the parent node
/// </summary>
[Parameter]
public bool Disabled
{
get { return _disabled || (ParentNode?.Disabled ?? false); }//禁用状态受制于父节点
get { return _disabled || (ParentNode?.Disabled ?? false); }
set { _disabled = value; }
}
private bool _selected;
/// <summary>
/// 是否已选中
/// Selected or not
/// </summary>
[Parameter]
public bool Selected
@ -165,10 +183,19 @@ namespace AntDesign
}
}
/// <summary>
/// Setting Selection State
/// </summary>
/// <param name="value"></param>
public void SetSelected(bool value)
{
if (Disabled) return;
if (!TreeComponent.Selectable && TreeComponent.Checkable)
{
SetChecked(!Checked);
return;
}
if (_selected == value) return;
if (Disabled == true) return;
_selected = value;
if (value == true)
{
@ -183,21 +210,91 @@ namespace AntDesign
}
/// <summary>
/// 是否异步加载状态(影响展开图标展示)
/// Whether the load state is asynchronous (affects the display of the expansion icon)
/// </summary>
[Parameter]
public bool Loading { get; set; }
private bool _dragTarget;
/// <summary>
/// Whether or not to release the target
/// </summary>
internal bool DragTarget
{
get { return _dragTarget; }
set
{
_dragTarget = value;
StateHasChanged();
}
}
/// <summary>
///
/// </summary>
internal bool DragTargetBottom { get; private set; }
/// <summary>
/// Sets the node to release the target location
/// </summary>
/// <param name="value"></param>
public void SetTargetBottom(bool value = false)
{
if (DragTargetBottom == value) return;
this.DragTargetBottom = value;
StateHasChanged();
}
/// <summary>
///
/// </summary>
private bool TargetContainer { get; set; }
/// <summary>
/// Sets the drag and drop target node container
/// </summary>
internal void SetParentTargetContainer(bool value = false)
{
if (this.ParentNode == null) return;
if (this.ParentNode.TargetContainer == value) return;
this.ParentNode.TargetContainer = value;
this.ParentNode.StateHasChanged();
}
/// <summary>
/// Gets the children of the parent node
/// </summary>
/// <returns></returns>
private List<TreeNode<TItem>> GetParentChildNodes()
{
return this.ParentNode?.ChildNodes ?? TreeComponent.ChildNodes;
}
/// <summary>
/// Remove the current node
/// </summary>
public void RemoveNode()
{
GetParentChildNodes().Remove(this);
}
private void SetTreeNodeClassMapper()
{
ClassMapper.Clear().Add("ant-tree-treenode")
ClassMapper
.Add("ant-tree-treenode")
.If("ant-tree-treenode-disabled", () => Disabled)
.If("ant-tree-treenode-switcher-open", () => SwitcherOpen)
.If("ant-tree-treenode-switcher-close", () => SwitcherClose)
.If("ant-tree-treenode-checkbox-checked", () => Checked)
.If("ant-tree-treenode-checkbox-indeterminate", () => Indeterminate)
.If("ant-tree-treenode-selected", () => Selected)
.If("ant-tree-treenode-loading", () => Loading);
.If("ant-tree-treenode-loading", () => Loading)
.If("drop-target", () => DragTarget)
.If("drag-over-gap-bottom", () => DragTarget && DragTargetBottom)
.If("drag-over", () => DragTarget && !DragTargetBottom)
.If("drop-container", () => TargetContainer)
.If("ant-tree-treenode-leaf-last", () => IsLastNode);
}
#endregion TreeNode
@ -207,7 +304,7 @@ namespace AntDesign
private bool _isLeaf = true;
/// <summary>
/// 是否为叶子节点
/// Whether it is a leaf node
/// </summary>
[Parameter]
public bool IsLeaf
@ -228,121 +325,180 @@ namespace AntDesign
}
/// <summary>
/// 是否已展开
/// Whether it has been expanded
/// </summary>
[Parameter]
public bool Expanded { get; set; }
/// <summary>
/// 折叠节点
/// Expand the node
/// </summary>
/// <param name="expanded"></param>
public void Expand(bool expanded)
{
if (Expanded == expanded) return;
Expanded = expanded;
}
/// <summary>
/// 真实的展开状态,路径上只要存在折叠,那么下面的全部折叠
/// The real expand state, as long as there is a expaneded node on the path, then all the folds below
/// </summary>
internal bool RealDisplay
{
get
{
if (string.IsNullOrEmpty(TreeComponent.SearchValue))
{//普通模式下节点显示规则
if (ParentNode == null) return true;//第一级节点默认显示
if (ParentNode.Expanded == false) return false;//上级节点如果是折叠的,必定折叠
return ParentNode.RealDisplay; //否则查找路径三的级节点显示情况
}
else
{//筛选模式下不考虑节点是否展开,只要节点符合条件,或者存在符合条件的子节点是就展开显示
return Matched || HasChildMatched;
}
if (ParentNode == null) return true;
if (ParentNode.Expanded == false) return false;
return ParentNode.RealDisplay;
}
}
/// <summary>
/// Nodes switch
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private async Task OnSwitcherClick(MouseEventArgs args)
{
this.Expanded = !this.Expanded;
if (TreeComponent.OnNodeLoadDelayAsync.HasDelegate && this.Expanded == true)
{
//自有节点被展开时才需要延迟加载
//如果支持异步载入,那么在展开时是调用异步载入代码
this.Loading = true;
await TreeComponent.OnNodeLoadDelayAsync.InvokeAsync(new TreeEventArgs<TItem>(TreeComponent, this, args));
this.Loading = false;
}
if (TreeComponent.OnExpandChanged.HasDelegate)
await TreeComponent.OnExpandChanged.InvokeAsync(new TreeEventArgs<TItem>(TreeComponent, this, args));
await TreeComponent?.OnNodeExpand(this, this.Expanded, args);
}
internal void SetLoading(bool loading)
{
this.Loading = loading;
}
/// <summary>
/// switcher is opened
/// </summary>
private bool SwitcherOpen => Expanded && !IsLeaf;
/// <summary>
/// switcher is close
/// </summary>
private bool SwitcherClose => !Expanded && !IsLeaf;
/// <summary>
/// expaned parents
/// </summary>
internal void OpenPropagation()
{
this.Expand(true);
if (this.ParentNode != null)
this.ParentNode.OpenPropagation();
}
#endregion Switcher
#region Checkbox
/// <summary>
/// According to check the
/// </summary>
[Parameter]
public bool Checked { get; set; }
[Parameter]
public bool Indeterminate { get; set; }
[Parameter]
public bool DisableCheckbox { get; set; }//是否可以选择不受父节点控制
private bool _disableCheckbox;
/// <summary>
/// 当点击选择框是触发
/// Disable checkbox
/// </summary>
private async void OnCheckBoxClick(MouseEventArgs args)
[Parameter]
public bool DisableCheckbox
{
SetChecked(!Checked);
if (TreeComponent.OnCheckBoxChanged.HasDelegate)
await TreeComponent.OnCheckBoxChanged.InvokeAsync(new TreeEventArgs<TItem>(TreeComponent, this, args));
get
{
return _disableCheckbox || (TreeComponent?.DisableCheckKeys?.Any(k => k == Key) ?? false);
}
set
{
_disableCheckbox = value;
}
}
/// <summary>
/// 设置选中状态
/// Triggered when the selection box is clicked
/// </summary>
private async void OnCheckBoxClick(MouseEventArgs args)
{
if (DisableCheckbox)
return;
SetChecked(!Checked);
if (TreeComponent.OnCheck.HasDelegate)
await TreeComponent.OnCheck.InvokeAsync(new TreeEventArgs<TItem>(TreeComponent, this, args));
}
/// <summary>
/// Set the checkbox state
/// </summary>
/// <param name="check"></param>
public void SetChecked(bool check)
{
if (Disabled) return;
this.Checked = check;
this.Indeterminate = false;
if (HasChildNodes)
if (!Disabled)
{
foreach (var subnode in ChildNodes)
subnode?.SetChecked(check);
if (TreeComponent.CheckStrictly)
{
this.Checked = check;
}
else
{
SetChildChecked(this, check);
if (ParentNode != null)
ParentNode.UpdateCheckState();
}
}
if (ParentNode != null)
ParentNode.UpdateCheckState();
else
TreeComponent.AddOrRemoveCheckNode(this);
StateHasChanged();
}
/// <summary>
/// 更新选中状态
/// Sets the checkbox status of child nodes
/// </summary>
/// <param name="subnode"></param>
/// <param name="check"></param>
private void SetChildChecked(TreeNode<TItem> subnode, bool check)
{
if (Disabled) return;
this.Checked = DisableCheckbox ? false : check;
this.Indeterminate = false;
TreeComponent.AddOrRemoveCheckNode(this);
if (subnode.HasChildNodes)
foreach (var child in subnode.ChildNodes)
child?.SetChildChecked(child, check);
}
/// <summary>
/// Update check status
/// </summary>
/// <param name="halfChecked"></param>
public void UpdateCheckState(bool? halfChecked = null)
private void UpdateCheckState(bool? halfChecked = null)
{
if (halfChecked.HasValue && halfChecked.Value == true)
{//如果子元素存在不确定状态,父元素必定存在不确定状态
if (halfChecked == true)
{
//If the child node is indeterminate, the parent node must is indeterminate.
this.Checked = false;
this.Indeterminate = true;
}
else if (HasChildNodes == true)
{//判断当前节点的选择状态
else if (HasChildNodes == true && !DisableCheckbox)
{
//Determines the selection status of the current node
bool hasChecked = false;
bool hasUnchecked = false;
foreach (var item in ChildNodes)
{
if (item.Indeterminate == true) break;
if (item.Checked == true) hasChecked = true;
if (item.Checked == false) hasUnchecked = true;
if (!item.DisableCheckbox && !item.Disabled)
{
if (item.Indeterminate == true) break;
if (item.Checked == true) hasChecked = true;
if (item.Checked == false) hasUnchecked = true;
}
}
if (hasChecked && !hasUnchecked)
@ -361,11 +517,11 @@ namespace AntDesign
this.Indeterminate = true;
}
}
TreeComponent.AddOrRemoveCheckNode(this);
if (ParentNode != null)
ParentNode.UpdateCheckState(this.Indeterminate);
//当达到最顶级后进行刷新状态,避免每一级刷新的性能问题
if (ParentNode == null)
StateHasChanged();
}
@ -380,7 +536,7 @@ namespace AntDesign
private string _icon;
/// <summary>
/// 节点前的图标,与 `ShowIcon` 组合使用
/// The icon in front of the node
/// </summary>
[Parameter]
public string Icon
@ -398,11 +554,17 @@ namespace AntDesign
}
}
[Parameter]
public RenderFragment<TreeNode<TItem>> IconTemplate { get; set; }
[Parameter]
public string SwitcherIcon { get; set; }
[Parameter]
public RenderFragment<TreeNode<TItem>> SwitcherIconTemplate { get; set; }
private string _title;
/// <summary>
/// 文本
/// </summary>
[Parameter]
public string Title
{
@ -419,6 +581,9 @@ namespace AntDesign
}
}
[Parameter]
public RenderFragment TitleTemplate { get; set; }
/// <summary>
/// title是否包含SearchValue(搜索使用)
/// </summary>
@ -431,7 +596,7 @@ namespace AntDesign
#endregion Title
#region
#region data binding
[Parameter]
public TItem DataItem { get; set; }
@ -456,15 +621,15 @@ namespace AntDesign
if (this.ParentNode != null)
return this.ParentNode.ChildDataItems;
else
return this.TreeComponent.DataSource;
return this.TreeComponent.DataSource.ToList();
}
#endregion
#endregion data binding
#region
#region Node data operation
/// <summary>
/// 添加子节点
/// Add child node
/// </summary>
/// <param name="dataItem"></param>
public void AddChildNode(TItem dataItem)
@ -473,7 +638,7 @@ namespace AntDesign
}
/// <summary>
/// 节点后面添加节点
/// Add a node next the node
/// </summary>
/// <param name="dataItem"></param>
public void AddNextNode(TItem dataItem)
@ -486,7 +651,7 @@ namespace AntDesign
}
/// <summary>
/// 节点前面添加节点
/// Add a node before the node
/// </summary>
/// <param name="dataItem"></param>
public void AddPreviousNode(TItem dataItem)
@ -499,7 +664,7 @@ namespace AntDesign
}
/// <summary>
/// 删除节点
/// remove
/// </summary>
public void Remove()
{
@ -507,6 +672,10 @@ namespace AntDesign
parentChildDataItems.Remove(this.DataItem);
}
/// <summary>
/// The node moves into the child node
/// </summary>
/// <param name="treeNode">target node</param>
public void MoveInto(TreeNode<TItem> treeNode)
{
if (treeNode == this || this.DataItem.Equals(treeNode.DataItem)) return;
@ -516,7 +685,7 @@ namespace AntDesign
}
/// <summary>
/// 上移节点
/// Move up the nodes
/// </summary>
public void MoveUp()
{
@ -528,7 +697,7 @@ namespace AntDesign
}
/// <summary>
/// 下移节点
/// Move down the node
/// </summary>
public void MoveDown()
{
@ -540,7 +709,7 @@ namespace AntDesign
}
/// <summary>
/// 降级节点
///
/// </summary>
public void Downgrade()
{
@ -552,7 +721,7 @@ namespace AntDesign
}
/// <summary>
/// 升级节点
/// Upgrade nodes
/// </summary>
public void Upgrade()
{
@ -563,25 +732,6 @@ namespace AntDesign
parentChildDataItems.Insert(index + 1, this.DataItem);
}
#endregion
protected override void OnInitialized()
{
SetTreeNodeClassMapper();
if (ParentNode != null)
ParentNode.AddNode(this);
else
TreeComponent.AddNode(this);
base.OnInitialized();
}
protected override void OnParametersSet()
{
SetTreeNodeClassMapper();
base.OnParametersSet();
}
private void AddNodeAndSelect(TItem dataItem)
{
var tn = ChildNodes.FirstOrDefault(treeNode => treeNode.DataItem.Equals(dataItem));
@ -591,5 +741,111 @@ namespace AntDesign
tn.SetSelected(true);
}
}
/// <summary>
/// Drag and drop into child nodes
/// </summary>
/// <param name="treeNode">目标</param>
internal void DragMoveInto(TreeNode<TItem> treeNode)
{
if (TreeComponent.DataSource == null || !TreeComponent.DataSource.Any())
return;
if (treeNode == this || this.DataItem.Equals(treeNode.DataItem)) return;
Remove();
treeNode.AddChildNode(this.DataItem);
treeNode.IsLeaf = false;
treeNode.Expand(true);
}
/// <summary>
/// Drag and drop to the bottom of the target
/// </summary>
/// <param name="treeNode">目标</param>
internal void DragMoveDown(TreeNode<TItem> treeNode)
{
if (TreeComponent.DataSource == null || !TreeComponent.DataSource.Any())
return;
if (treeNode == this || this.DataItem.Equals(treeNode.DataItem)) return;
Remove();
treeNode.AddNextNode(this.DataItem);
}
#endregion Node data operation
bool _defaultBinding;
protected override void OnInitialized()
{
SetTreeNodeClassMapper();
if (ParentNode != null)
ParentNode.AddNode(this);
else
{
TreeComponent.AddNode(this);
if (!TreeComponent.DefaultExpandAll && TreeComponent.DefaultExpandParent)
Expand(true);
}
TreeComponent._allNodes.Add(this);
if (TreeComponent.DisabledExpression != null)
Disabled = TreeComponent.DisabledExpression(this);
if (TreeComponent.DefaultExpandAll)
Expand(true);
else if (TreeComponent.ExpandedKeys != null)
{
Expand(TreeComponent.ExpandedKeys.Any(k => k == this.Key));
}
if (TreeComponent.Selectable && TreeComponent.SelectedKeys != null)
{
this.Selected = TreeComponent.SelectedKeys.Any(k => k == this.Key);
}
base.OnInitialized();
}
protected override void OnParametersSet()
{
DefaultBinding();
base.OnParametersSet();
}
private void DefaultBinding()
{
if (!_defaultBinding)
{
_defaultBinding = true;
if (this.Checked)
this.SetChecked(true);
TreeComponent.DefaultCheckedKeys?.ForEach(k =>
{
var node = TreeComponent._allNodes.FirstOrDefault(x => x.Key == k);
if (node != null)
node.SetChecked(true);
});
TreeComponent.DefaultSelectedKeys?.ForEach(k =>
{
var node = TreeComponent._allNodes.FirstOrDefault(x => x.Key == k);
if (node != null)
node.SetSelected(true);
});
if (!TreeComponent.DefaultExpandAll)
{
if (this.Expanded)
this.OpenPropagation();
TreeComponent.DefaultExpandedKeys?.ForEach(k =>
{
var node = TreeComponent._allNodes.FirstOrDefault(x => x.Key == k);
if (node != null)
node.OpenPropagation();
});
}
}
}
}
}

View File

@ -7,13 +7,13 @@ namespace AntDesign
public partial class TreeNodeCheckbox<TItem> : ComponentBase
{
/// <summary>
/// 树控件本身
/// Root Tree
/// </summary>
[CascadingParameter(Name = "Tree")]
public Tree<TItem> TreeComponent { get; set; }
/// <summary>
/// 当前节点
/// Current Node
/// </summary>
[CascadingParameter(Name = "SelfNode")]
public TreeNode<TItem> SelfNode { get; set; }
@ -22,11 +22,11 @@ namespace AntDesign
private void SetClassMap()
{
ClassMapper.Clear().Add("ant-tree-checkbox")
ClassMapper
.Add("ant-tree-checkbox")
.If("ant-tree-checkbox-checked", () => SelfNode.Checked)
.If("ant-tree-checkbox-indeterminate", () => SelfNode.Indeterminate)
.If("ant-tree-checkbox-disabled", () => SelfNode.Disabled || SelfNode.DisableCheckbox)
;
.If("ant-tree-checkbox-disabled", () => SelfNode.Disabled || SelfNode.DisableCheckbox);
}
protected override void OnInitialized()
@ -35,12 +35,6 @@ namespace AntDesign
base.OnInitialized();
}
protected override void OnParametersSet()
{
SetClassMap();
base.OnParametersSet();
}
[Parameter]
public EventCallback<MouseEventArgs> OnCheckBoxClick { get; set; }

View File

@ -3,49 +3,66 @@
@typeparam TItem
<span class="@ClassMapper.Class" @onclick="OnClick">
@if (IsShowSwitchIcon)
@if (SelfNode.Loading)
{
@if (!SelfNode.Loading)
<Icon Type="loading" Theme="outline" Class="ant-tree-switcher-loading-icon" />
}
else
{
<!-- node has its own switcher icon -->
@if (SelfNode?.SwitcherIconTemplate != null || SelfNode?.SwitcherIcon != null)
{
if (TreeComponent.SwitcherIconTemplate != null)
if (SelfNode.SwitcherIconTemplate != null)
{
@TreeComponent.SwitcherIconTemplate(SelfNode)
<span class="ant-tree-switcher-icon">
@SelfNode.SwitcherIconTemplate(SelfNode)
</span>
}
else
else if (SelfNode.SwitcherIcon != null)
{
<Icon Type="caret-down" Theme="outline" class="ant-tree-switcher-icon" />
<Icon Type="@SelfNode.SwitcherIcon" Class="ant-tree-switcher-icon" />
}
}
else
{
<Icon Type="loading" Theme="outline" class="ant-tree-switcher-loading-icon" />
}
}
@if (TreeComponent.ShowLine)
{
@if (!SelfNode.Loading)
{
if (TreeComponent.SwitcherIconTemplate != null)
@if (TreeComponent.ShowLine)
{
@TreeComponent.SwitcherIconTemplate(SelfNode)
}
else
{
@if (IsShowLineIcon)
<!-- When the node is not leaf, then show switcher icon -->
if (!SelfNode.IsLeaf)
{
<Icon Type="@(IsSwitcherOpen ? "minus-square" : "plus-square")" Theme="outline" class="ant-tree-switcher-line-icon" />
@if (TreeComponent.SwitcherIconTemplate != null)
{
<span class="ant-tree-switcher-icon">
@TreeComponent.SwitcherIconTemplate(SelfNode)
</span>
}
else if (TreeComponent.SwitcherIcon != null)
{
<Icon Type="TreeComponent.SwitcherIcon" Class="ant-tree-switcher-icon" />
}
else
{
<Icon Type="@(IsSwitcherOpen ? "minus-square" : "plus-square")" Class="ant-tree-switcher-line-icon" />
}
}
else
{
<Icon Type="file" Theme="outline" class="ant-tree-switcher-line-icon" />
<!-- Show the leaf icon (file) or leaf line () -->
if (TreeComponent.ShowLeafIcon)
{
<Icon Type="file" Theme="outline" Class="ant-tree-switcher-line-icon" />
}
else
{
<span class="ant-tree-switcher-leaf-line"></span>
}
}
}
else if (!SelfNode.IsLeaf)
{
<!-- node is not leaf and not show line, then show the default arrow icon -->
<Icon Type="caret-down" Theme="outline" Class="ant-tree-switcher-icon" />
}
}
else
{
<Icon Type="loading" Theme="outline" class="ant-tree-switcher-loading-icon" />
}
}
</span>

View File

@ -22,10 +22,6 @@ namespace AntDesign
[CascadingParameter(Name = "SelfNode")]
public TreeNode<TItem> SelfNode { get; set; }
private bool IsShowLineIcon => !SelfNode.IsLeaf && TreeComponent.ShowLine;
private bool IsShowSwitchIcon => !SelfNode.IsLeaf && !TreeComponent.ShowLine;
/// <summary>
/// 节点是否处于展开状态
/// </summary>
@ -40,7 +36,8 @@ namespace AntDesign
private void SetClassMap()
{
ClassMapper.Clear().Add("ant-tree-switcher")
ClassMapper
.Add("ant-tree-switcher")
.If("ant-tree-switcher-noop", () => SelfNode.IsLeaf)
.If("ant-tree-switcher_open", () => IsSwitcherOpen)
.If("ant-tree-switcher_close", () => IsSwitcherClose);
@ -52,12 +49,6 @@ namespace AntDesign
base.OnInitialized();
}
protected override void OnParametersSet()
{
SetClassMap();
base.OnParametersSet();
}
[Parameter]
public EventCallback<MouseEventArgs> OnSwitcherClick { get; set; }

View File

@ -1,36 +1,113 @@
@namespace AntDesign
@inherits ComponentBase
@typeparam TItem
<span class="@TitleClassMapper.Class" title="@SelfNode.Title" @onclick="OnClick" @ondblclick="OnDblClick" draggable="@CanDraggable" aria-grabbed="@CanDraggable">
@if (TreeComponent.TitleIconTemplate != null && TreeComponent.ShowIcon)
{
<span class="ant-tree-iconEle @(IsSwitcherOpen?"ant-tree-icon__open":"") @(IsSwitcherClose?"ant-tree-icon__close":"") @(SelfNode.Loading?"ant-tree-icon_loading":"")">
<span class="ant-tree-iconEle ant-tree-icon__customize">
@TreeComponent.TitleIconTemplate(SelfNode)
@if (CanDraggable)
{
<span class="@TitleClassMapper.Class" title="@SelfNode.Title" @onclick="OnClick" @ondblclick="OnDblClick" draggable="true"
@ondragover:preventDefault @ondragover="OnDragOver" @ondragleave="OnDragLeave" @ondragenter="OnDragEnter" @ondrop:preventDefault @ondrop="OnDrop"
@ondragstart="OnDragStart" @ondragend="OnDragEnd" aria-grabbed="true">
@if (TreeComponent.TitleIconTemplate != null && TreeComponent.ShowIcon)
{
<span class="ant-tree-iconEle @(IsSwitcherOpen?"ant-tree-icon__open":"") @(IsSwitcherClose?"ant-tree-icon__close":"") @(SelfNode.Loading?"ant-tree-icon_loading":"")">
<span class="ant-tree-iconEle ant-tree-icon__customize">
@TreeComponent.TitleIconTemplate(SelfNode)
</span>
</span>
</span>
}
else if (string.IsNullOrWhiteSpace(SelfNode.Icon) == false && TreeComponent.ShowIcon)
{
<span class="ant-tree-iconEle @(IsSwitcherOpen?"ant-tree-icon__open":"") @(IsSwitcherClose?"ant-tree-icon__close":"") @(SelfNode.Loading?"ant-tree-icon_loading":"")">
<span class="ant-tree-iconEle ant-tree-icon__customize">
@if (string.IsNullOrWhiteSpace(SelfNode.Icon) == false)
}
else if (string.IsNullOrWhiteSpace(SelfNode.Icon) == false && TreeComponent.ShowIcon)
{
<span class="ant-tree-iconEle @(IsSwitcherOpen?"ant-tree-icon__open":"") @(IsSwitcherClose?"ant-tree-icon__close":"") @(SelfNode.Loading?"ant-tree-icon_loading":"")">
<span class="ant-tree-iconEle ant-tree-icon__customize">
@if (string.IsNullOrWhiteSpace(SelfNode.Icon) == false)
{
<Icon Type="@SelfNode.Icon" Theme="outline" />
}
</span>
</span>
}
@if (TreeComponent.TitleTemplate != null)
{
@TreeComponent.TitleTemplate(SelfNode)
}
else
{
<span class="ant-tree-title" style="pointer-events: none;">
@if (SelfNode.TitleTemplate != null)
{
<Icon Type="@SelfNode.Icon" Theme="outline" />
@SelfNode.TitleTemplate
}
else if (SelfNode.Matched)
{
var value = $"<span Style=\"{TreeComponent.MatchedStyle}\">{TreeComponent.SearchValue}</span>";
<span>
@((MarkupString)SelfNode.Title.Replace(TreeComponent.SearchValue, value))
</span>
}
else
{
@SelfNode.Title
}
</span>
</span>
}
@if (TreeComponent.TitleTemplate != null)
{
@TreeComponent.TitleTemplate(SelfNode)
}
else
{
<span class="ant-tree-title">
@SelfNode.Title
</span>
}
</span>
}
@if (SelfNode.DragTarget)
{
<div class="ant-tree-drop-indicator" style="left:@(SelfNode.DragTargetBottom?"4px":"28px");right:0;bottom:-3px;"></div>
}
</span>
}
else
{
<span class="@TitleClassMapper.Class" title="@SelfNode.Title" @onclick="OnClick" @ondblclick="OnDblClick">
@if (TreeComponent.TitleIconTemplate != null && TreeComponent.ShowIcon)
{
<span class="ant-tree-iconEle @(IsSwitcherOpen?"ant-tree-icon__open":"") @(IsSwitcherClose?"ant-tree-icon__close":"") @(SelfNode.Loading?"ant-tree-icon_loading":"")">
<span class="ant-tree-iconEle ant-tree-icon__customize">
@TreeComponent.TitleIconTemplate(SelfNode)
</span>
</span>
}
else if ((!string.IsNullOrWhiteSpace(SelfNode.Icon) || SelfNode.IconTemplate != null) && TreeComponent.ShowIcon)
{
<span class="ant-tree-iconEle @(IsSwitcherOpen?"ant-tree-icon__open":"") @(IsSwitcherClose?"ant-tree-icon__close":"") @(SelfNode.Loading?"ant-tree-icon_loading":"")">
<span class="ant-tree-iconEle ant-tree-icon__customize">
@if (SelfNode.IconTemplate != null)
{
@SelfNode.IconTemplate(SelfNode)
}
else if (!string.IsNullOrWhiteSpace(SelfNode.Icon))
{
<Icon Type="@SelfNode.Icon" Theme="outline" />
}
</span>
</span>
}
@if (TreeComponent.TitleTemplate != null)
{
@TreeComponent.TitleTemplate(SelfNode)
}
else if (SelfNode.TitleTemplate != null)
{
@SelfNode.TitleTemplate
}
else
{
<span class="ant-tree-title">
@if (SelfNode.Matched)
{
var value = $"<span Style=\"{TreeComponent.MatchedStyle}\">{TreeComponent.SearchValue}</span>";
<span>
@((MarkupString)SelfNode.Title.Replace(TreeComponent.SearchValue, value))
</span>
}
else
{
@SelfNode.Title
}
</span>
}
@if (SelfNode.DragTarget)
{
<div class="ant-tree-drop-indicator" style="left:@(SelfNode.DragTargetBottom?"4px":"28px");right:0;bottom:-3px;"></div>
}
</span>
}

View File

@ -9,6 +9,17 @@ namespace AntDesign
{
public partial class TreeNodeTitle<TItem> : ComponentBase
{
#region fields
private const double OffSETX = 25;
/// <summary>
///
/// </summary>
private double _dragTargetClientX = 0;
#endregion
/// <summary>
/// 树控件本身
/// </summary>
@ -31,7 +42,8 @@ namespace AntDesign
private void SetTitleClassMapper()
{
TitleClassMapper.Clear().Add("ant-tree-node-content-wrapper")
TitleClassMapper
.Add("ant-tree-node-content-wrapper")
.If("draggable", () => CanDraggable)
.If("ant-tree-node-content-wrapper-open", () => IsSwitcherOpen)
.If("ant-tree-node-content-wrapper-close", () => IsSwitcherClose)
@ -44,12 +56,11 @@ namespace AntDesign
base.OnInitialized();
}
protected override void OnParametersSet()
{
SetTitleClassMapper();
base.OnParametersSet();
}
/// <summary>
///
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private async Task OnClick(MouseEventArgs args)
{
SelfNode.SetSelected(!SelfNode.Selected);
@ -59,10 +70,103 @@ namespace AntDesign
await TreeComponent.OnContextMenu.InvokeAsync(new TreeEventArgs<TItem>(TreeComponent, SelfNode, args));
}
/// <summary>
///
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private async Task OnDblClick(MouseEventArgs args)
{
if (TreeComponent.OnDblClick.HasDelegate && args.Button == 0)
await TreeComponent.OnDblClick.InvokeAsync(new TreeEventArgs<TItem>(TreeComponent, SelfNode, args));
}
/// <summary>
///
/// </summary>
/// <param name="e"></param>
private void OnDragStart(DragEventArgs e)
{
TreeComponent.DragItem = SelfNode;
SelfNode.Expand(false);
if (TreeComponent.OnDragStart.HasDelegate)
TreeComponent.OnDragStart.InvokeAsync(new TreeEventArgs<TItem>(TreeComponent, SelfNode));
}
/// <summary>
/// Leaving releases the target
/// </summary>
/// <param name="e"></param>
private void OnDragLeave(DragEventArgs e)
{
SelfNode.DragTarget = false;
SelfNode.SetParentTargetContainer();
if (TreeComponent.OnDragLeave.HasDelegate)
TreeComponent.OnDragLeave.InvokeAsync(new TreeEventArgs<TItem>(TreeComponent, SelfNode));
}
/// <summary>
///
/// </summary>
/// <param name="e"></param>
private void OnDragEnter(DragEventArgs e)
{
if (TreeComponent.DragItem == SelfNode) return;
SelfNode.DragTarget = true;
_dragTargetClientX = e.ClientX;
System.Diagnostics.Debug.WriteLine($"OnDragEnter {SelfNode.Title} {System.Text.Json.JsonSerializer.Serialize(e)}");
if (TreeComponent.OnDragEnter.HasDelegate)
TreeComponent.OnDragEnter.InvokeAsync(new TreeEventArgs<TItem>(TreeComponent, SelfNode));
}
/// <summary>
/// Can be treated as a child if the target is moved to the right beyond the OffsetX distance
/// </summary>
/// <param name="e"></param>
private void OnDragOver(DragEventArgs e)
{
if (TreeComponent.DragItem == SelfNode) return;
if (e.ClientX - _dragTargetClientX > OffSETX)
{
SelfNode.SetTargetBottom();
SelfNode.SetParentTargetContainer();
SelfNode.Expand(true);
}
else
{
SelfNode.SetTargetBottom(true);
SelfNode.SetParentTargetContainer(true);
}
}
/// <summary>
///
/// </summary>
/// <param name="e"></param>
private void OnDrop(DragEventArgs e)
{
SelfNode.DragTarget = false;
SelfNode.SetParentTargetContainer();
if (SelfNode.DragTargetBottom)
TreeComponent.DragItem.DragMoveDown(SelfNode);
else
TreeComponent.DragItem.DragMoveInto(SelfNode);
if (TreeComponent.OnDrop.HasDelegate)
TreeComponent.OnDrop.InvokeAsync(new TreeEventArgs<TItem>(TreeComponent, TreeComponent.DragItem) { TargetNode = SelfNode });
}
/// <summary>
/// Drag the end
/// </summary>
/// <param name="e"></param>
private void OnDragEnd(DragEventArgs e)
{
if (TreeComponent.OnDragEnd.HasDelegate)
TreeComponent.OnDragEnd.InvokeAsync(new TreeEventArgs<TItem>(TreeComponent, TreeComponent.DragItem) { TargetNode = SelfNode });
}
}
}

View File

@ -1,4 +1,8 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -49,7 +53,7 @@ namespace AntDesign.Docs.Build.CLI.Command
}
catch (Exception ex)
{
Console.WriteLine(ex.StackTrace);
Console.WriteLine(ex);
return -1;
}

View File

@ -1,30 +1,41 @@
<Tree Checkable BlockNode TItem="string">
<Tree TItem="string" Checkable
DefaultExpandedKeys="@(new[] { "0-0-0", "0-0-1"})"
DefaultSelectedKeys="@(new[] {"0-0-0", "0-0-1" })"
DefaultCheckedKeys="@(new[] {"0-0-0", "0-0-1" })"
OnSelect="OnSelect"
OnCheck="OnCheck">
<Nodes>
<TreeNode Title="A" TItem="string">
<TreeNode Title="parent 1" Key="0-0" TItem="string">
<Nodes>
<TreeNode Title="A1" TItem="string">
<TreeNode Title="parent 1-0" Key="0-0-0" Disabled TItem="string">
<Nodes>
<TreeNode Title="A1-1" TItem="string"></TreeNode>
<TreeNode Title="A1-2" TItem="string"></TreeNode>
<TreeNode Title="leaf" Key="0-0-0-0" DisableCheckbox TItem="string"></TreeNode>
<TreeNode Title="leaf" Key="0-0-0-1" TItem="string"></TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="A2" Disabled TItem="string">
<TreeNode Title="parent 1-1" Key="0-0-1" TItem="string">
<Nodes>
<TreeNode Title="A2-1" TItem="string"></TreeNode>
<TreeNode Title="A2-2" TItem="string"></TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="A3" DisableCheckbox TItem="string">
<Nodes>
<TreeNode Title="A3-1" TItem="string"></TreeNode>
<TreeNode Title="A3-2" DisableCheckbox TItem="string"></TreeNode>
<TreeNode Title="A3-3" Disabled TItem="string"></TreeNode>
<TreeNode Key="0-0-1-0" TItem="string">
<TitleTemplate>
<span style="color: #1890ff; ">sss</span>
</TitleTemplate>
</TreeNode>
</Nodes>
</TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="B" TItem="string"></TreeNode>
</Nodes>
</Tree>
</Tree>
@code{
void OnSelect(TreeEventArgs<string> args)
{
Console.WriteLine("OnSelect");
}
void OnCheck(TreeEventArgs<string> args)
{
Console.WriteLine("onCheck");
}
}

View File

@ -1,34 +1,46 @@
<Tree @ref="tree" Checkable Multiple TItem="string">
<Tree @ref="_tree"
Checkable
TItem="string"
@bind-ExpandedKeys="expandedKeys"
@bind-CheckedKeys="checkedKeys"
@bind-SelectedKeys="selectedKeys"
OnExpand="OnExpand"
OnSelect="OnSelect"
OnCheck="OnCheck"
AutoExpandParent="autoExpandParent">
<Nodes>
<TreeNode Title="A" Key="100" TItem="string">
<TreeNode Title="0-0" Key="0-0" TItem="string">
<Nodes>
<TreeNode Title="A1" Key="110" TItem="string">
<TreeNode Title="0-0-0" Key="0-0-0" TItem="string">
<Nodes>
<TreeNode Title="A1-1" Key="111" TItem="string"></TreeNode>
<TreeNode Title="A2-2" Key="112" TItem="string"></TreeNode>
<TreeNode Title="0-0-0-0" Key="0-0-0-0" TItem="string"></TreeNode>
<TreeNode Title="0-0-0-1" Key="0-0-0-1" TItem="string"></TreeNode>
<TreeNode Title="0-0-0-2" Key="0-0-0-2" TItem="string"></TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="A2" Key="120" Disabled TItem="string">
<TreeNode Title="0-0-1" Key="0-0-1" TItem="string">
<Nodes>
<TreeNode Title="A2-1" Key="121" TItem="string"></TreeNode>
<TreeNode Title="A1-2" Key="122" TItem="string"></TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="A3" Key="130" DisableCheckbox TItem="string">
<Nodes>
<TreeNode Title="A3-1" Key="131" TItem="string"></TreeNode>
<TreeNode Title="A3-2" Key="132" DisableCheckbox TItem="string"></TreeNode>
<TreeNode Title="A3-3" Key="133" Disabled TItem="string"></TreeNode>
<TreeNode Title="0-0-1-0" Key="0-0-1-0" TItem="string"></TreeNode>
<TreeNode Title="0-0-1-1" Key="0-0-1-1" TItem="string"></TreeNode>
<TreeNode Title="0-0-1-2" Key="0-0-1-2" TItem="string"></TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="0-0-2" Key="0-0-2" TItem="string"></TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="B" Key="200" TItem="string"></TreeNode>
<TreeNode Title="0-1" Key="0-1" TItem="string">
<Nodes>
<TreeNode Title="0-1-0-0" Key="0-1-0-0" TItem="string"></TreeNode>
<TreeNode Title="0-1-0-1" Key="0-1-0-1" DisableCheckbox TItem="string"></TreeNode>
<TreeNode Title="0-1-0-2" Key="0-1-0-2" Disabled TItem="string"></TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="0-2" Key="0-2" TItem="string"></TreeNode>
</Nodes>
</Tree>
<br />
<Button OnClick="PrintCheckedNodesTitle">Print Checked Nodes Title</Button>
@*<Button OnClick="PrintCheckedNodesTitle">Print Checked Nodes Title</Button>*@
<Button OnClick="PrintCheckedNodesKey">Print Checked Nodes Key</Button>
@ -36,7 +48,7 @@
<br />
<br />
<Button OnClick="PrintSelectedNodes">Print Selected Nodes</Button>
@*<Button OnClick="PrintSelectedNodes">Print Selected Nodes</Button>*@
<Button OnClick="DeselectAll">Deselect All</Button>
<br />
@ -55,49 +67,70 @@
}
</ul>
}
@code{
@code {
string printType;
List<string> printTexts = new List<string>();
string[] printTexts;
Tree<string> tree;
Tree<string> _tree;
private void PrintCheckedNodesTitle()
{
printType = "CheckedNodesTitle";
printTexts = tree.CheckedTitles;
}
string[] expandedKeys;
string[] checkedKeys;
string[] selectedKeys;
bool autoExpandParent;
//private void PrintCheckedNodesTitle()
//{
// printType = "CheckedNodesTitle";
// printTexts = tree.CheckedTitles;
//}
private void PrintCheckedNodesKey()
{
printType = "CheckedNodesKey";
printTexts = tree.CheckedKeys;
printTexts = _tree.CheckedKeys;
}
private void DecheckedAll()
{
tree.DecheckedAll();
_tree.DecheckedAll();
}
private void PrintSelectedNodes()
{
printType = "SelectedNodes";
printTexts = tree.SelectedTitles;
}
//private void PrintSelectedNodes()
//{
// printType = "SelectedNodes";
// printTexts = tree.SelectedTitles;
//}
private void DeselectAll()
{
tree.DeselectAll();
_tree.DeselectAll();
}
private void ExpandAll()
{
tree.ExpandAll();
_tree.ExpandAll();
}
private void CollapseAll()
{
tree.CollapseAll();
_tree.CollapseAll();
}
}
private void OnCheck(TreeEventArgs<string> e)
{
Console.WriteLine("OnCheck:" + e.Node.Key);
}
private void OnSelect(TreeEventArgs<string> e)
{
Console.WriteLine("OnSelect:" + e.Node.Key);
}
private void OnExpand((string[] ExpandedKeys, TreeNode<string> Node, bool Expanded) e)
{
Console.WriteLine("OnExpand:" + JsonSerializer.Serialize(e.ExpandedKeys));
}
}

View File

@ -0,0 +1,5 @@
<h3>BigData</h3>
@code {
}

View File

@ -1,109 +0,0 @@
<Tree @ref="tree"
ShowIcon DataSource="games"
Multiple="@multiple"
TitleExpression="x => x.DataItem.Text"
ChildrenExpression="x => x.DataItem.Items"
IconExpression="x => x.DataItem.Icon"
IsLeafExpression="x => x.DataItem.Items?.Count == 0"
KeyExpression="x => x.DataItem.Id"
@bind-SelectedKey="@selectedKey"
@bind-SelectedData="@selectedData"
@bind-SelectedNode="@selectedNode"
@bind-SelectedKeys="@selectedKeys"
TItem="GameElement">
</Tree>
<Divider>Single</Divider>
<span>bind-SelectedKey:@selectedKey</span>
<br />
<span>bind-SelectedData:@(System.Text.Json.JsonSerializer.Serialize(selectedData))</span>
<br />
<span>bind-SelectedNode:@(System.Text.Json.JsonSerializer.Serialize(selectedNode?.Id))</span>
<Divider>Multiple</Divider>
<Switch @bind-Value="multiple" />Multiple
<br />
<span>bind-SelectedKeys:@(System.Text.Json.JsonSerializer.Serialize(selectedKeys))</span>
<Divider>Action</Divider>
<Button OnClick="AddSon">Add Son Node</Button>
<Button OnClick="DeleteNode">Delete Node</Button>
@code{
private bool multiple;
Tree<GameElement> tree;
string selectedKey;
GameElement selectedData;
TreeNode<GameElement> selectedNode;
string[] selectedKeys;
void AddSon()
{
if (selectedNode != null)
{
var id = new Random().Next(1000).ToString();
selectedNode.DataItem.Items.Add(new GameElement(id, $"Node {id}")
{
Items = new List<GameElement>()
}); ;
}
}
void DeleteNode()
{
if (selectedNode != null)
{
if (selectedNode.ParentNode != null)
{
selectedNode.ParentNode.DataItem.Items.Remove(selectedNode.DataItem);
}
else
{
tree.DataSource.Remove(selectedNode.DataItem);
}
StateHasChanged();
}
}
List<GameElement> games = new List<GameElement>()
{
new ("100","XBox","windows")
{
Items=new List<GameElement>()
{
new ("101","Halo"),
new ("102","Gears of War"),
new ("103","Forza Motosport"),
}
},
new ("200","PlayStation","desktop")
{
Items=new List<GameElement>()
{
new ("201","Uncharted"),
new ("202","God Of War"),
new ("203","The Order:1886"),
}
},
new ("300","Switch","mobile")
{
Items=new List<GameElement>()
{
new ("301","Super Mario Bros"),
new ("302","The Legend of Zelda"),
}
}
};
private record GameElement(string Id, string Text, string Icon = null)
{
public List<GameElement> Items { get; set; } = new List<GameElement>();
}
}

View File

@ -0,0 +1,32 @@
<Tree TItem="string"
ShowIcon
DefaultExpandAll
DefaultSelectedKeys="@(new[]{"0-0-0"})"
>
<SwitcherIconTemplate>
@switcherIcon
</SwitcherIconTemplate>
<Nodes>
<TreeNode Title="parent 1" Key="0-0" Icon="smile" TItem="string">
<Nodes>
<TreeNode Title="leaf" Key="0-0-0" Icon="meh" TItem="string" />
</Nodes>
</TreeNode>
<TreeNode Title="leaf" Key="0-0-1" TItem="string">
<IconTemplate Context="node">
@if (node.Selected)
{
<Icon Type="frown" Theme="fill" />
}
else
{
<Icon Type="frown" Theme="outline" />
}
</IconTemplate>
</TreeNode>
</Nodes>
</Tree>
@code {
RenderFragment switcherIcon =@<Icon Type="down" />;
}

View File

@ -0,0 +1,32 @@
<DirectoryTree TItem="string"
Multiple
DefaultExpandAll
OnSelect="OnSelect"
OnExpand="OnExpand">
<Nodes>
<TreeNode Title="parent 0" Key="0-0" TItem="string">
<Nodes>
<TreeNode Title="leaf 0-0" Key="0-0-0" TItem="string" />
<TreeNode Title="leaf 0-1" Key="0-0-1" TItem="string" />
</Nodes>
</TreeNode>
<TreeNode Title="parent 1" Key="0-1" TItem="string">
<Nodes>
<TreeNode Title="leaf 1-0" Key="0-1-0" TItem="string" />
<TreeNode Title="leaf 1-1" Key="0-1-1" TItem="string" />
</Nodes>
</TreeNode>
</Nodes>
</DirectoryTree>
@code {
void OnSelect()
{
}
void OnExpand()
{
}
}

View File

@ -0,0 +1,84 @@
<Tree @ref="tree" DefaultExpandAll Draggable BlockNode
ShowIcon DataSource="games"
TitleExpression="x => x.DataItem.Text"
ChildrenExpression="x => x.DataItem.Items"
IconExpression="x => x.DataItem.Icon"
IsLeafExpression="x => x.DataItem.Items?.Count == 0"
KeyExpression="x => x.DataItem.Id"
TItem="GameElement" OnDragEnd="e=> { }" OnDrop="onDrop">
</Tree>
@code{
Tree<GameElement> tree;
string selectedKey;
GameElement selectedData;
TreeNode<GameElement> selectedNode;
List<GameElement> games = new()
{
new ("0-0","0-0")
{
Items = new List<GameElement>()
{
new ("0-0-0","0-0-0")
{
Items = new List<GameElement>()
{
new ("0-0-0-0","0-0-0-0"),
new ("0-0-0-1","0-0-0-1"),
new ("0-0-0-2","0-0-0-2"),
}
},
new ("0-0-1","0-0-1")
{
Items = new List<GameElement>()
{
new ("0-0-1-0","0-0-1-0"),
new ("0-0-1-1","0-0-1-1"),
new ("0-0-1-2","0-0-1-2"),
}
},
new ("0-0-2","0-0-2")
{
Items = new List<GameElement>()
{
new ("0-0-2-0","0-0-2-0"),
new ("0-0-2-1","0-0-2-1"),
new ("0-0-2-2","0-0-2-2"),
}
},
}
},
new ("0-1","0-1")
{
Items = new List<GameElement>()
{
new ("0-1-0","0-1-0"),
new ("0-1-1","0-1-0"),
new ("0-1-2","0-1-2"),
}
},
new ("0-2","0-2")
{
Items = new List<GameElement>()
{
new ("0-2-0","0-2-0"),
new ("0-2-1","0-2-1"),
}
}
};
private record GameElement(string Id, string Text, string Icon = null)
{
public List<GameElement> Items { get; set; } = new List<GameElement>();
}
void onDrop(TreeEventArgs<GameElement> e)
{
}
}

View File

@ -17,7 +17,7 @@
public async Task OnNodeLoadDelayAsync(TreeEventArgs<Data> args)
{
await Task.Delay(2000);//模拟异步执行
await Task.Delay(1500);
var dataItem = ((Data)args.Node.DataItem);
dataItem.Childs.Clear();

View File

@ -1,53 +0,0 @@
<Tree Checkable BlockNode
OnClick='e=>EventRecord(e,"OnClick")'
OnDblClick='e=>EventRecord(e,"OnDblClick")'
OnContextMenu='e=>EventRecord(e,"OnContextMenu")'
OnCheckBoxChanged='e=>EventRecord(e,"OnCheckBoxChange")'
OnExpandChanged='e=>EventRecord(e,"OnExpandChange")' TItem="string">
<Nodes>
<TreeNode Title="A" TItem="string">
<Nodes>
<TreeNode Title="A1" TItem="string">
<Nodes>
<TreeNode Title="A1-1" TItem="string"></TreeNode>
<TreeNode Title="A1-2" TItem="string"></TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="A2" Disabled TItem="string">
<Nodes>
<TreeNode Title="A2-1" TItem="string"></TreeNode>
<TreeNode Title="A2-2" TItem="string"></TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="A3" DisableCheckbox TItem="string">
<Nodes>
<TreeNode Title="A3-1" TItem="string"></TreeNode>
<TreeNode Title="A3-2" DisableCheckbox TItem="string"></TreeNode>
<TreeNode Title="A3-3" Disabled TItem="string"></TreeNode>
</Nodes>
</TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="B" TItem="string"></TreeNode>
</Nodes>
</Tree>
<br />
<span>Events</span>
<div style="width:100%;height:150px;overflow-y:scroll">
<ul>
@foreach (var item in events)
{
<li>@item</li>
}
</ul>
</div>
@code{
public List<string> events { get; set; } = new List<string>();
public void EventRecord(TreeEventArgs<string> args, string eventName)
{
events.Insert(0, $"{eventName}:{args.Node.Title}");
}
}

View File

@ -1,38 +1,74 @@
<Switch @bind-Value="@_showLine"/>
<br />
<div>
<div style="margin-bottom: 16px">
showLine: <Switch @bind-Checked="_showLine" />
<br />
<br />
showIcon: <Switch @bind-Checked="_showIcon" />
<br />
<br />
showLeafIcon: <Switch @bind-Checked="_showLeafIcon" />
</div>
<Tree ShowLine="@_showLine" Checkable TItem="string">
<Nodes>
<TreeNode Title="A" TItem="string">
<Nodes>
<TreeNode Title="A1" TItem="string">
<Nodes>
<TreeNode Title="A1-1" TItem="string"></TreeNode>
<TreeNode Title="A1-2" TItem="string"></TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="A2" Disabled TItem="string">
<Nodes>
<TreeNode Title="A2-1" TItem="string"></TreeNode>
<TreeNode Title="A2-2" TItem="string"></TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="A3" DisableCheckbox TItem="string">
<Nodes>
<TreeNode Title="A3-1" TItem="string"></TreeNode>
<TreeNode Title="A3-2" DisableCheckbox TItem="string"></TreeNode>
<TreeNode Title="A3-3" Disabled TItem="string"></TreeNode>
<Tree
ShowLine="@_showLine"
ShowIcon="@_showIcon"
ShowLeafIcon="@_showLeafIcon"
DefaultExpandedKeys="@(new[]{"0-0-0"})"
OnSelect="OnSelect"
TItem="string">
<Nodes>
<TreeNode Title="parent 1" Key="0-0" Icon="carry-out" TItem="string">
<Nodes>
<TreeNode Title="parent 1-0" Key="0-0-0" Icon="carry-out" TItem="string">
<Nodes>
<TreeNode Title="leaf" Key="0-0-0-0" Icon="carry-out" TItem="string" />
<TreeNode Key="0-0-0-1" Icon="carry-out" TItem="string">
<TitleTemplate>
<div>
<div>multiple line title</div>
<div>multiple line title</div>
</div>
</TitleTemplate>
</TreeNode>
<TreeNode Title="leaf" Key="0-0-0-2" Icon="carry-out" TItem="string" />
</Nodes>
</TreeNode>
<TreeNode Title="parent 1-1" Key="0-0-1" Icon="carry-out" TItem="string">
<Nodes>
<TreeNode Title="left" Key="0-0-1-0" Icon="carry-out" TItem="string" />
</Nodes>
</TreeNode>
<TreeNode Title="parent 1-2" Key="0-0-2" Icon="carry-out" TItem="string">
<Nodes>
<TreeNode Title="leaf" Key="0-0-2-0" Icon="carry-out" TItem="string" />
<TreeNode Title="leaf" Key="0-0-2-1" Icon="carry-out" TItem="string" SwitcherIcon="form" />
</Nodes>
</TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="parent 2" Key="0-1" Icon="carry-out" TItem="string">
<Nodes>
<TreeNode Title="parent 2-0" Key="0-1-0" Icon="carry-out" TItem="string">
<Nodes>
<TreeNode Title="leaf" Key="0-1-0-0" Icon="carry-out" TItem="string" />
<TreeNode Title="leaf" Key="0-1-0-1" Icon="carry-out" TItem="string" />
</Nodes>
</TreeNode>
</Nodes>
</TreeNode>
</Nodes>
</Tree>
</div>
</Nodes>
</TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="B" TItem="string"></TreeNode>
</Nodes>
</Tree>
@code{
@code {
bool _showLine = true;
bool _showIcon = false;
bool _showLeafIcon = true;
void OnSelect(TreeEventArgs<string> e)
{
Console.WriteLine(JsonSerializer.Serialize(e));
}
}

View File

@ -1,14 +0,0 @@
---
order: 7
title:
zh-CN: 修改
en-US: Modify
---
## zh-CN
使用Tree提供的方法修改内容
## en-US
Use the methods provided by Tree to modify the content

View File

@ -1,121 +0,0 @@
<Tree @ref="tree"
SelectedKey="100"
ShowIcon DataSource="games"
TitleExpression="x => x.DataItem.Text"
ChildrenExpression="x => x.DataItem.Items"
IconExpression="x => x.DataItem.Icon"
IsLeafExpression="x => x.DataItem.Items?.Count == 0"
KeyExpression="x => x.DataItem.Id"
@bind-SelectedNode="@selectedNode"
TItem="GameElement">
</Tree>
<Divider>Node Modify</Divider>
<Button OnClick="AddSon">Add Son</Button>
<Button OnClick="AddNext">Add Next</Button>
<Button OnClick="AddPrevious">Add Previous</Button>
<Button OnClick="RemoveSelf">Remove Self</Button>
<Divider>Node Move</Divider>
<Button OnClick="NodeMove">Move To First Node Son</Button>
<Button OnClick="NodeMoveUp">Move Up</Button>
<Button OnClick="NodeMoveDown">Move Down</Button>
<Button OnClick="NodeDowngrade">Downgrade</Button>
<Button OnClick="NodeUpgrade">Upgrade</Button>
@code{
Tree<GameElement> tree;
TreeNode<GameElement> selectedNode;
int i;
GameElement GenerateDataItem()
{
return new GameElement(i++.ToString(), $"Node {i}")
{
Items = new List<GameElement>()
};
}
void AddSon()
{
selectedNode?.AddChildNode(GenerateDataItem());
}
void AddNext()
{
selectedNode?.AddNextNode(GenerateDataItem());
}
void AddPrevious()
{
selectedNode?.AddPreviousNode(GenerateDataItem());
}
void RemoveSelf()
{
selectedNode?.Remove();
}
void NodeMove()
{
var firstNode = tree.ChildNodes.First();
selectedNode?.MoveInto(firstNode);
}
void NodeMoveUp()
{
selectedNode?.MoveUp();
}
void NodeMoveDown()
{
selectedNode?.MoveDown();
}
void NodeDowngrade()
{
selectedNode?.Downgrade();
}
void NodeUpgrade()
{
selectedNode?.Upgrade();
}
List<GameElement> games = new List<GameElement>()
{
new ("100","XBox","windows")
{
Items=new List<GameElement>()
{
new ("101","Halo"),
new ("102","Gears of War"),
new ("103","Forza Motosport"),
}
},
new ("200","PlayStation","desktop")
{
Items=new List<GameElement>()
{
new ("201","Uncharted"),
new ("202","God Of War"),
new ("203","The Order:1886"),
}
},
new ("300","Switch","mobile")
{
Items=new List<GameElement>()
{
new ("301","Super Mario Bros"),
new ("302","The Legend of Zelda"),
}
}
};
private record GameElement(string Id, string Text, string Icon = null)
{
public List<GameElement> Items { get; set; } = new List<GameElement>();
}
}

View File

@ -1,54 +0,0 @@
<Switch @bind-Value="@checkable" />Checkable
<br />
<Switch @bind-Value="@blockNode" />BlockNode
<br />
<Switch @bind-Value="@showExpand" />ShowExpand
<br />
<Switch @bind-Value="@showIcon" />ShowIcon
<br />
<Switch @bind-Value="@multiple" />Multiple
<br />
<br />
<Tree Checkable="@checkable" BlockNode="@blockNode" ShowExpand="@showExpand" ShowIcon="@showIcon" Multiple="@multiple" TItem="string">
<Nodes>
<TreeNode Title="A" Icon="folder-open" TItem="string">
<Nodes>
<TreeNode Title="A1" Icon="folder-open" TItem="string">
<Nodes>
<TreeNode Title="A1-1" Icon="file" TItem="string"></TreeNode>
<TreeNode Title="A1-2" Icon="file" TItem="string"></TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="A2" Icon="folder-open" Disabled TItem="string">
<Nodes>
<TreeNode Title="A2-1" Icon="file" TItem="string"></TreeNode>
<TreeNode Title="A2-2" Icon="file" TItem="string"></TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="A3" Icon="folder-open" DisableCheckbox TItem="string">
<Nodes>
<TreeNode Title="A3-1" Icon="file" TItem="string"></TreeNode>
<TreeNode Title="A3-2" Icon="file" DisableCheckbox TItem="string"></TreeNode>
<TreeNode Title="A3-3" Icon="file" Disabled TItem="string"></TreeNode>
</Nodes>
</TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="B" Icon="folder-open" TItem="string"></TreeNode>
</Nodes>
</Tree>
@code
{
private bool checkable = true;
private bool blockNode = true;
private bool showExpand = true;
private bool showIcon = true;
private bool multiple;
}

View File

@ -1,17 +1,10 @@
<Search @bind-Value="@searchKey" Placeholder="Search.." />
<Divider Text="Default search (case sensitive)"></Divider>
<Tree ShowIcon SearchValue="@searchKey"
<Tree ShowIcon SearchValue="@searchKey" MatchedStyle="color: #f50;"
DataSource="games"
TitleExpression="x => x.DataItem.Text"
ChildrenExpression="x => x.DataItem.Items"
IconExpression="x => x.DataItem.Icon"></Tree>
<Divider Text="Custom search (Ignore case)"></Divider>
<Tree ShowIcon SearchValue="@searchKey"
DataSource="games"
TitleExpression="x => x.DataItem.Text"
ChildrenExpression="x => x.DataItem.Items"
IconExpression="x => x.DataItem.Icon"
SearchExpression="x=> x.DataItem.Text.ToLower().Contains(searchKey.ToLower())"></Tree>
@code{
string searchKey;

View File

@ -0,0 +1,41 @@
<Tree TItem="string"
ShowLine
SwitcherIcon="down"
DefaultExpandedKeys="@(new[]{"0-0-0"})"
OnSelect="OnSelect">
<SwitcherIconTemplate>
<Icon Type="down" />
</SwitcherIconTemplate>
<Nodes>
<TreeNode Title="parent 1" Key="0-0" TItem="string">
<Nodes>
<TreeNode Title="parent 1-0" Key="0-0-0" TItem="string">
<Nodes>
<TreeNode Title="leaf" Key="0-0-0-0" TItem="string" />
<TreeNode Title="leaf" Key="0-0-0-1" TItem="string" />
<TreeNode Title="leaf" Key="0-0-0-2" TItem="string" />
</Nodes>
</TreeNode>
<TreeNode Title="parent 1-1" Key="0-0-1" TItem="string">
<Nodes>
<TreeNode Title="leaf" Key="0-0-1-0" TItem="string" />
</Nodes>
</TreeNode>
<TreeNode Title="parent 1-2" Key="0-0-2" TItem="string">
<Nodes>
<TreeNode Title="leaf" Key="0-0-2-0" TItem="string" />
<TreeNode Title="leaf" Key="0-0-2-1" TItem="string" />
</Nodes>
</TreeNode>
</Nodes>
</TreeNode>
</Nodes>
</Tree>
@code {
void OnSelect()
{
}
}

View File

@ -1,59 +0,0 @@
<Tree Checkable ShowIcon TItem="string">
<Nodes>
<TreeNode Title="A" TItem="string">
<Nodes>
<TreeNode Title="A1" TItem="string">
<Nodes>
<TreeNode Title="A1-1" TItem="string"></TreeNode>
<TreeNode Title="A2-2" TItem="string"></TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="A2" Disabled TItem="string">
<Nodes>
<TreeNode Title="A2-1" TItem="string"></TreeNode>
<TreeNode Title="A2-2" TItem="string"></TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="A3" DisableCheckbox TItem="string">
<Nodes>
<TreeNode Title="A3-1" TItem="string"></TreeNode>
<TreeNode Title="A3-2" DisableCheckbox TItem="string"></TreeNode>
<TreeNode Title="A3-3" Disabled TItem="string"></TreeNode>
</Nodes>
</TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="B" TItem="string">
<Nodes>
<TreeNode Title="B1" TItem="string"></TreeNode>
<TreeNode Title="B2" TItem="string"></TreeNode>
</Nodes>
</TreeNode>
</Nodes>
<IndentTemplate Context="node" >
<span>--></span>
</IndentTemplate>
<TitleTemplate Context="node">
<span style="color:@(node.Title?.FirstOrDefault() switch {'A'=>"red",'B'=>"green",_=>"blue" })">@node.Title</span>
</TitleTemplate>
<TitleIconTemplate Context="node">
@if (node.IsLeaf)
{
<Icon Type="@(node.Title?.FirstOrDefault() switch {'A'=>"user-delete",'B'=>"tag",_=>"" })" Theme="outline" />
}
else
{
<Icon Type="@(node.Title?.FirstOrDefault() switch {'A'=>"usergroup-delete",'B'=>"tags",_=>"" })" Theme="outline" />
}
</TitleIconTemplate>
<SwitcherIconTemplate Context="node">
@if (node.Expanded)
{
<Icon Type="down-circle" Theme="outline" />
}
else
{
<Icon Type="right-circle" Theme="outline" />
}
</SwitcherIconTemplate>
</Tree>

View File

@ -0,0 +1,5 @@
<h3>VirtualScroll</h3>
@code {
}

View File

@ -0,0 +1,15 @@
---
order: 1
title:
zh-CN: 受控操作示例
en-US: Controlled Tree
---
## zh-CN
受控操作示例
## en-US
Controlled mode lets parent nodes reflect the status of child nodes more intelligently.

View File

@ -11,4 +11,4 @@ title:
## en-US
The most basic usage, tell you how to use checkable, selectable, disabled, defaultExpandKeys, and etc.
The most basic usage, tell you how to use checkable, selectable, disabled, defaultExpandKeys, and etc.

View File

@ -1,14 +0,0 @@
---
order: 3
title:
zh-CN: 受控操作示例
en-US: basic controlled example
---
## zh-CN
受控操作示例(默认)。
## en-US
basic controlled example.

View File

@ -0,0 +1,42 @@
---
order: 99
title:
zh-CN: 大数据
en-US: Big data
debug: true
---
## zh-CN
大数据展示。
## en-US
Plenty of tree nodes.
```jsx
import { Tree } from 'antd';
const treeData = [];
for (let i = 0; i < 100; i += 1) {
const children = [];
for (let j = 0; j < 100; j += 1) {
children.push({
title: `child ${i}-${j}`,
key: `l-${i}-${j}`,
});
}
treeData.push({
title: `parent ${i}`,
key: `l-${i}`,
children,
});
}
const Demo = () => <Tree defaultExpandAll height={400} treeData={treeData} />;
ReactDOM.render(<Demo />, mountNode);
```

View File

@ -1,14 +0,0 @@
---
order: 1
title:
zh-CN: 绑定
en-US: Bind
---
## zh-CN
绑定数据创建树
## en-US
Bind data to create a tree

View File

@ -0,0 +1,15 @@
---
order: 6
title:
zh-CN: 自定义图标
en-US: Customize Icon
---
## zh-CN
可以针对不同的节点定制图标。
## en-US
You can customize icons for different nodes.

View File

@ -0,0 +1,15 @@
---
order: 7
title:
zh-CN: 目录
en-US: directory
---
## zh-CN
内置的目录树,`multiple` 模式支持 `ctrl(Windows)` / `command(Mac)` 复选。
## en-US
Built-in directory tree. `multiple` support `ctrl(Windows)` / `command(Mac)` selection.

View File

@ -0,0 +1,141 @@
---
order: 2
title:
zh-CN: 拖动示例
en-US: draggable
---
## zh-CN
将节点拖拽到其他节点内部或前后。
## en-US
Drag treeNode to insert after the other treeNode or insert into the other parent TreeNode.
```jsx
import { Tree } from 'antd';
const x = 3;
const y = 2;
const z = 1;
const gData = [];
const generateData = (_level, _preKey, _tns) => {
const preKey = _preKey || '0';
const tns = _tns || gData;
const children = [];
for (let i = 0; i < x; i++) {
const key = `${preKey}-${i}`;
tns.push({ title: key, key });
if (i < y) {
children.push(key);
}
}
if (_level < 0) {
return tns;
}
const level = _level - 1;
children.forEach((key, index) => {
tns[index].children = [];
return generateData(level, key, tns[index].children);
});
};
generateData(z);
class Demo extends React.Component {
state = {
gData,
expandedKeys: ['0-0', '0-0-0', '0-0-0-0'],
};
onDragEnter = info => {
console.log(info);
// expandedKeys 需要受控时设置
// this.setState({
// expandedKeys: info.expandedKeys,
// });
};
onDrop = info => {
console.log(info);
const dropKey = info.node.key;
const dragKey = info.dragNode.key;
const dropPos = info.node.pos.split('-');
const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]);
const loop = (data, key, callback) => {
for (let i = 0; i < data.length; i++) {
if (data[i].key === key) {
return callback(data[i], i, data);
}
if (data[i].children) {
loop(data[i].children, key, callback);
}
}
};
const data = [...this.state.gData];
// Find dragObject
let dragObj;
loop(data, dragKey, (item, index, arr) => {
arr.splice(index, 1);
dragObj = item;
});
if (!info.dropToGap) {
// Drop on the content
loop(data, dropKey, item => {
item.children = item.children || [];
// where to insert 示例添加到头部,可以是随意位置
item.children.unshift(dragObj);
});
} else if (
(info.node.props.children || []).length > 0 && // Has children
info.node.props.expanded && // Is expanded
dropPosition === 1 // On the bottom gap
) {
loop(data, dropKey, item => {
item.children = item.children || [];
// where to insert 示例添加到头部,可以是随意位置
item.children.unshift(dragObj);
// in previous version, we use item.children.push(dragObj) to insert the
// item to the tail of the children
});
} else {
let ar;
let i;
loop(data, dropKey, (item, index, arr) => {
ar = arr;
i = index;
});
if (dropPosition === -1) {
ar.splice(i, 0, dragObj);
} else {
ar.splice(i + 1, 0, dragObj);
}
}
this.setState({
gData: data,
});
};
render() {
return (
<Tree
className="draggable-tree"
defaultExpandedKeys={this.state.expandedKeys}
draggable
blockNode
onDragEnter={this.onDragEnter}
onDrop={this.onDrop}
treeData={this.state.gData}
/>
);
}
}
ReactDOM.render(<Demo />, mountNode);
```

View File

@ -1,5 +1,5 @@
---
order: 2
order: 3
title:
zh-CN: 异步数据加载
en-US: load data asynchronously
@ -11,4 +11,5 @@ title:
## en-US
To load data asynchronously when click to expand a treeNode.
To load data asynchronously when click to expand a treeNode.

View File

@ -1,14 +0,0 @@
---
order: 4
title:
zh-CN: 事件
en-US: Event
---
## zh-CN
Tree的事件
## en-US
Tree events

View File

@ -1,5 +1,5 @@
---
order: 6
order: 5
title:
zh-CN: 连接线
en-US: Tree with line
@ -11,4 +11,117 @@ title:
## en-US
Tree with connected line between nodes, turn on by `showLine`, customize the preseted icon by `switcherIcon`.
Tree with connected line between nodes, turn on by `showLine`, customize the preseted icon by `switcherIcon`.
```tsx
import React, { useState } from 'react';
import { Tree, Switch } from 'antd';
import { CarryOutOutlined, FormOutlined } from '@ant-design/icons';
const treeData = [
{
title: 'parent 1',
key: '0-0',
icon: <CarryOutOutlined />,
children: [
{
title: 'parent 1-0',
key: '0-0-0',
icon: <CarryOutOutlined />,
children: [
{ title: 'leaf', key: '0-0-0-0', icon: <CarryOutOutlined /> },
{
title: (
<>
<div>multiple line title</div>
<div>multiple line title</div>
</>
),
key: '0-0-0-1',
icon: <CarryOutOutlined />,
},
{ title: 'leaf', key: '0-0-0-2', icon: <CarryOutOutlined /> },
],
},
{
title: 'parent 1-1',
key: '0-0-1',
icon: <CarryOutOutlined />,
children: [{ title: 'leaf', key: '0-0-1-0', icon: <CarryOutOutlined /> }],
},
{
title: 'parent 1-2',
key: '0-0-2',
icon: <CarryOutOutlined />,
children: [
{ title: 'leaf', key: '0-0-2-0', icon: <CarryOutOutlined /> },
{
title: 'leaf',
key: '0-0-2-1',
icon: <CarryOutOutlined />,
switcherIcon: <FormOutlined />,
},
],
},
],
},
{
title: 'parent 2',
key: '0-1',
icon: <CarryOutOutlined />,
children: [
{
title: 'parent 2-0',
key: '0-1-0',
icon: <CarryOutOutlined />,
children: [
{ title: 'leaf', key: '0-1-0-0', icon: <CarryOutOutlined /> },
{ title: 'leaf', key: '0-1-0-1', icon: <CarryOutOutlined /> },
],
},
],
},
];
const Demo: React.FC<{}> = () => {
const [showLine, setShowLine] = useState<boolean | { showLeafIcon: boolean }>(true);
const [showIcon, setShowIcon] = useState<boolean>(false);
const [showLeafIcon, setShowLeafIcon] = useState<boolean>(true);
const onSelect = (selectedKeys: React.Key[], info: any) => {
console.log('selected', selectedKeys, info);
};
const onSetLeafIcon = (checked: boolean) => {
setShowLeafIcon(checked);
setShowLine({ showLeafIcon: checked });
};
const onSetShowLine = (checked: boolean) => {
setShowLine(checked ? { showLeafIcon } : false);
};
return (
<div>
<div style={{ marginBottom: 16 }}>
showLine: <Switch checked={!!showLine} onChange={onSetShowLine} />
<br />
<br />
showIcon: <Switch checked={showIcon} onChange={setShowIcon} />
<br />
<br />
showLeafIcon: <Switch checked={showLeafIcon} onChange={onSetLeafIcon} />
</div>
<Tree
showLine={showLine}
showIcon={showIcon}
defaultExpandedKeys={['0-0-0']}
onSelect={onSelect}
treeData={treeData}
/>
</div>
);
};
ReactDOM.render(<Demo />, mountNode);
```

View File

@ -1,13 +0,0 @@
---
order: 9
title:
zh-CN: 属性
en-US: Property
---
## zh-CN
## en-US

View File

@ -0,0 +1,161 @@
---
order: 4
title:
zh-CN: 可搜索
en-US: Searchable
---
## zh-CN
可搜索的树。
## en-US
Searchable Tree.
```jsx
import { Tree, Input } from 'antd';
const { Search } = Input;
const x = 3;
const y = 2;
const z = 1;
const gData = [];
const generateData = (_level, _preKey, _tns) => {
const preKey = _preKey || '0';
const tns = _tns || gData;
const children = [];
for (let i = 0; i < x; i++) {
const key = `${preKey}-${i}`;
tns.push({ title: key, key });
if (i < y) {
children.push(key);
}
}
if (_level < 0) {
return tns;
}
const level = _level - 1;
children.forEach((key, index) => {
tns[index].children = [];
return generateData(level, key, tns[index].children);
});
};
generateData(z);
const dataList = [];
const generateList = data => {
for (let i = 0; i < data.length; i++) {
const node = data[i];
const { key } = node;
dataList.push({ key, title: key });
if (node.children) {
generateList(node.children);
}
}
};
generateList(gData);
const getParentKey = (key, tree) => {
let parentKey;
for (let i = 0; i < tree.length; i++) {
const node = tree[i];
if (node.children) {
if (node.children.some(item => item.key === key)) {
parentKey = node.key;
} else if (getParentKey(key, node.children)) {
parentKey = getParentKey(key, node.children);
}
}
}
return parentKey;
};
class SearchTree extends React.Component {
state = {
expandedKeys: [],
searchValue: '',
autoExpandParent: true,
};
onExpand = expandedKeys => {
this.setState({
expandedKeys,
autoExpandParent: false,
});
};
onChange = e => {
const { value } = e.target;
const expandedKeys = dataList
.map(item => {
if (item.title.indexOf(value) > -1) {
return getParentKey(item.key, gData);
}
return null;
})
.filter((item, i, self) => item && self.indexOf(item) === i);
this.setState({
expandedKeys,
searchValue: value,
autoExpandParent: true,
});
};
render() {
const { searchValue, expandedKeys, autoExpandParent } = this.state;
const loop = data =>
data.map(item => {
const index = item.title.indexOf(searchValue);
const beforeStr = item.title.substr(0, index);
const afterStr = item.title.substr(index + searchValue.length);
const title =
index > -1 ? (
<span>
{beforeStr}
<span className="site-tree-search-value">{searchValue}</span>
{afterStr}
</span>
) : (
<span>{item.title}</span>
);
if (item.children) {
return { title, key: item.key, children: loop(item.children) };
}
return {
title,
key: item.key,
};
});
return (
<div>
<Search style={{ marginBottom: 8 }} placeholder="Search" onChange={this.onChange} />
<Tree
onExpand={this.onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
treeData={loop(gData)}
/>
</div>
);
}
}
ReactDOM.render(<SearchTree />, mountNode);
```
```css
.site-tree-search-value {
color: #f50;
}
```
<style>
[data-theme="dark"] .site-tree-search-value {
color: #d84a1b;
}
</style>

View File

@ -1,14 +0,0 @@
---
order: 5
title:
zh-CN: 可搜索
en-US: Searchable
---
## zh-CN
可搜索的树。
## en-US
Searchable Tree.

View File

@ -0,0 +1,88 @@
---
order: 8
title:
zh-CN: 自定义展开/折叠图标
en-US: Customize collapse/expand icon
---
## zh-CN
自定义展开/折叠图标。
## en-US
customize collapse/expand icon of tree node
```jsx
import { Tree } from 'antd';
import { DownOutlined } from '@ant-design/icons';
class Demo extends React.Component {
onSelect = (selectedKeys, info) => {
console.log('selected', selectedKeys, info);
};
render() {
return (
<Tree
showLine
switcherIcon={<DownOutlined />}
defaultExpandedKeys={['0-0-0']}
onSelect={this.onSelect}
treeData={[
{
title: 'parent 1',
key: '0-0',
children: [
{
title: 'parent 1-0',
key: '0-0-0',
children: [
{
title: 'leaf',
key: '0-0-0-0',
},
{
title: 'leaf',
key: '0-0-0-1',
},
{
title: 'leaf',
key: '0-0-0-2',
},
],
},
{
title: 'parent 1-1',
key: '0-0-1',
children: [
{
title: 'leaf',
key: '0-0-1-0',
},
],
},
{
title: 'parent 1-2',
key: '0-0-2',
children: [
{
title: 'leaf',
key: '0-0-2-0',
},
{
title: 'leaf',
key: '0-0-2-1',
},
],
},
],
},
]}
/>
);
}
}
ReactDOM.render(<Demo />, mountNode);
```

View File

@ -1,14 +0,0 @@
---
order: 8
title:
zh-CN: 模板
en-US: Template
---
## zh-CN
使用模板重新定义树中的各元素显示内容
## en-US
Use templates to redefine the display content of each element in the tree

View File

@ -0,0 +1,40 @@
---
order: 9
title:
zh-CN: 虚拟滚动
en-US: Virtual scroll
---
## zh-CN
使用 `height` 属性则切换为虚拟滚动。
## en-US
Use virtual list through `height` prop.
```jsx
import { Tree } from 'antd';
function dig(path = '0', level = 3) {
const list = [];
for (let i = 0; i < 10; i += 1) {
const key = `${path}-${i}`;
const treeNode = {
title: key,
key,
};
if (level > 0) {
treeNode.children = dig(key, level - 1);
}
list.push(treeNode);
}
return list;
}
const treeData = dig();
ReactDOM.render(<Tree treeData={treeData} height={233} defaultExpandAll />, mountNode);
```

View File

@ -16,4 +16,94 @@ Almost anything can be represented in a tree structure. Examples include directo
### Tree props
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| --- | --- | --- | --- | --- |
| ShowExpand | Shows an expansion icon before the node | boolean | true | |
| ShowLine | Shows a connecting line | boolean | false | |
| Disabled | The tree is disabled | boolean | false | |
| ShowIcon | show treeNode icon icon | boolean | false | |
| Draggable | Whether the node allows drag and drop | boolean | false | |
| BlockNode | Whether treeNode fill remaining horizontal space | boolean | false | |
| ShowLeafIcon | Displays the cotyledon icon | boolean | false | |
| SwitcherIcon | Customize toggle icon£¬the value is Icon types | string | null | |
| Selectable | Whether can be selected | boolean | true | |
| DefaultSelectedKeys | Specifies the keys of the default selected treeNodes | string[] | null | |
| Multiple | Allows selecting multiple treeNodes | boolean | false | |
| Checkable | Add a Checkbox before the node | boolean | false | |
| CheckStrictly | Check treeNode precisely; parent treeNode and children treeNodes are not associated | boolean | false | |
| DefaultCheckedKeys | Specifies the keys of the default checked treeNodes | string[] | null | |
| DisableCheckKeys | Disable node Checkbox | string[] | null | |
| SearchValue | search value | string | null | |
| MatchedStyle | Search for matching text styles | string | null | |
| DataSource | bing datasource | List | null | |
| TitleExpression | Specifies a method that returns the text of the node. | Func | | |
| KeyExpression | Specifies a method that returns the key of the node. | Func | | |
| IconExpression | Specifies a method to return the node icon. | Func | | |
| IsLeafExpression | Specifies a method that returns whether the expression is a leaf node. | Func | | |
| ChildrenExpression | Specifies a method to return a child node | Func | | |
| DisabledExpression | Specifies a method to return a disabled node | Func | | |
| DefaultExpandAll | All tree nodes are expanded by default | boolean | false | |
| DefaultExpandParent | The parent node is expanded by default | boolean | false | |
| DefaultExpandedKeys | Expand the specified tree node by default | string[] | null | |
| ExpandedKeys | (Controlled) expands the specified tree node | string[] | null | |
| AutoExpandParent | Whether to automatically expand a parent treeNode | bool | false | |
### Bind °ó¶¨Öµ
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| @bind-SelectedKey | SelectedKey | string | | |
| @bind-SelectedNode | SelectedNode | TreeNode | | |
| @bind-SelectedData | SelectedData | string | | |
| @bind-SelectedKeys | SelectedKeys | string[] | | |
| @bind-SelectedDatas | SelectedDatas | TItem[] | | |
| @bind-CheckedKeys | CheckedKeys | string[] | | |
### Tree EventCallback
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| OnNodeLoadDelayAsync | Lazy load callbacks ¡£You must use async and the return type is Task, otherwise you may experience load lag and display problems | EventCallback | | |
| OnClick | Click the tree node callback | EventCallback | | |
| OnDblClick | Double-click the node callback | EventCallback | | |
| OnContextMenu | Right-click tree node callback | EventCallback | | |
| OnCheckBoxChanged | checked the tree node callback | EventCallback | | |
| OnExpandChanged | Click the expansion tree node icon to call back | EventCallback | | |
| OnDragStart | Called when the drag and drop begins | EventCallback | | |
| OnDragEnter | Called when drag and drop into a releasable target | EventCallback | | |
| OnDragLeave | Called when drag and drop away from a releasable target | EventCallback | | |
| OnDrop | Triggered when drag-and-drop drops succeed | EventCallback | | |
| OnDragEnd | Drag-and-drop end callback ¡£this callback method must be set | EventCallback | | |
### Tree Functions
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| ExpandAll | All tree nodes are expanded by default | void | | |
| CollapseAll | The parent node is expanded by default | void | | |
### Tree RenderFragment
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| IndentTemplate | The indentation template | RenderFragment | | |
| TitleTemplate | Customize the header template | RenderFragment | | |
| TitleIconTemplate | Customize the icon templates | RenderFragment | | |
| SwitcherIconTemplate | Customize toggle icon templates | RenderFragment | | |
### TreeNode props
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| Key | key | string | | |
| Disabled | disabled | string | | |
| Checked | checked | boolean | false | |
| DisableCheckbox | | boolean | false | |
| Title | title | string | false | |
| TitleTemplate | title template | RenderFragment | null | |
| Icon | icon | string | false | |
| IconTemplate | icon template | RenderFragment | null | |
| DataItem | dataitem | Type | | |
| SwitcherIcon | Customize node toggle icon £¬the value is Icon types | string | null | |
| SwitcherIconTemplate | SwitcherIcon template | RenderFragment | null | |

View File

@ -17,4 +17,95 @@ cover: https://gw.alipayobjects.com/zos/alicdn/Xh-oWqg9k/Tree.svg
### Tree props
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| --- | --- | --- | --- | --- |
| ShowExpand | 显示展开图标 | boolean | true | |
| ShowLine | 显示连接线 | boolean | false | |
| Disabled | 禁用 | boolean | false | |
| ShowIcon | 显示节点图标 | boolean | false | |
| Draggable | 是否允许拖拽 | boolean | false | |
| BlockNode | 是否节点占据一行 | boolean | false | |
| ShowLeafIcon | 显示子叶图标(如果 ShowLeafIcon 未赋值, 会等于 `ShowLine` 的值) | boolean | false | |
| SwitcherIcon | 设置所有节点的展开图标,值 Icon 的 Type | string | null | |
| Selectable |是否可以选择 | boolean | true | |
| Multiple | 允许选择多个树节点 | boolean | false | |
| Checkable | 节点前添加 Checkbox 复选框 | boolean | false | |
| CheckStrictly | checkable 状态下节点选择完全受控(父子节点选中状态不再关联) | boolean | false | |
| DefaultSelectedKeys | 默认选中的节点key | string[] | null | |
| DefaultCheckedKeys | 默认勾选的节点key | string[] | null | |
| DisableCheckKeys | 默认禁用的勾选节点 | string[] | null | |
| SearchValue | 搜索节点关键字 | string | null | |
| MatchedStyle | 搜索匹配关键字高亮样式 | string | null | |
| DataSource | 数据源 | List | null | |
| TitleExpression | 指定一个方法,该表达式返回节点的文本。 | Func | | |
| KeyExpression | 指定一个返回节点Key的方法。 | Func | | |
| IconExpression | 指定一个返回节点名称的方法。 | Func | | |
| IsLeafExpression | 返回一个值是否是页节点 | Func | | |
| ChildrenExpression | 返回子节点的方法 | Func | | |
| DisabledExpression | 指定一个返回禁用节点的方法 | Func | | |
| DefaultExpandAll | 默认展开所有节点 | boolean | false | |
| DefaultExpandParent | 默认展开顶级父节点 | boolean | false | |
| DefaultExpandedKeys | 默认展开的节点数 | string[] | null | |
| ExpandedKeys | (受控)展开指定的树节点 | string[] | null | |
| AutoExpandParent | 是否自动展开父节点 | bool | false | |
### Bind 绑定值
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| @bind-SelectedKey | 绑定选择的节点Key | string | | |
| @bind-SelectedNode | 绑定选择的节点 | TreeNode | | |
| @bind-SelectedData | 绑定选择的节点数据项 | string | | |
| @bind-SelectedKeys | 绑定选择的节点Key集合 | string[] | | |
| @bind-SelectedDatas | 绑定选择的节点数据项 | TItem[] | | |
| @bind-CheckedKeys | 绑定勾选的数据 keys | string[] | | |
### Tree EventCallback
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| OnNodeLoadDelayAsync | 异步加载书回调,方法异步使用 async | EventCallback | | |
| OnClick | 点击节点回调 | EventCallback | | |
| OnDblClick | 双击节点回调 | EventCallback | | |
| OnContextMenu | 右键回调 | EventCallback | | |
| OnCheckBoxChanged | 节点勾选回调 | EventCallback | | |
| OnExpandChanged | 展开节点回调 | EventCallback | | |
| OnDragStart | 拖拽开始回调 | EventCallback | | |
| OnDragEnter | 拖拽开始进入目标节点回调 | EventCallback | | |
| OnDragLeave | 拖拽离开目标节点回调 | EventCallback | | |
| OnDrop | 拖拽录入目标节点回调 | EventCallback | | |
| OnDragEnd | 拖拽结束回调(此回调方法必须设置) | EventCallback | | |
### Tree Functions
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| ExpandAll | 展开所有节点 | void | | |
| CollapseAll | 关闭所有节点 | void | | |
### Tree RenderFragment
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| IndentTemplate | 自定义缩进模板 | RenderFragment | | |
| TitleTemplate | 自定义标题模板 | RenderFragment | | |
| TitleIconTemplate | 自定义标题Icon | RenderFragment | | |
| SwitcherIconTemplate | 自定义展开图标 | RenderFragment | | |
### TreeNode props
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| Key | 节点key | string | | |
| Disabled | 是否禁用 | string | | |
| Checked | 勾选 | boolean | false | |
| DisableCheckbox | 禁用复选框 | boolean | false | |
| Checked | 勾选 | boolean | false | |
| Title | 标题 | string | false | |
| TitleTemplate | 标题模板 | RenderFragment | null | |
| Icon | 标题前图标 | string | false | |
| IconTemplate | 标题前图标模板 | RenderFragment | null | |
| DataItem | 数据项 | Type | | |
| SwitcherIcon | 该节点的展开图标 | string | null | |
| SwitcherIconTemplate | 该节点的展开图标 | RenderFragment | null | |