From ae51608306c4721d61294b0c23bbdfbc4ce98b31 Mon Sep 17 00:00:00 2001 From: Uyarn Date: Thu, 12 Sep 2024 15:17:54 +0800 Subject: [PATCH] feat(Tree): support allowDrop API --- src/tree/Tree.tsx | 4 +++- src/tree/TreeItem.tsx | 4 ++++ src/tree/_example/draggable.tsx | 10 +++++++++- src/tree/hooks/TreeDraggableContext.tsx | 18 +++++++++++++++++- src/tree/hooks/useDraggable.tsx | 12 +++++++++--- src/tree/tree.en-US.md | 1 + src/tree/tree.md | 1 + src/tree/type.ts | 9 +++++++++ 8 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/tree/Tree.tsx b/src/tree/Tree.tsx index d7eb7b7f9c..3e59babe42 100644 --- a/src/tree/Tree.tsx +++ b/src/tree/Tree.tsx @@ -46,7 +46,6 @@ const Tree = forwardRef, TreeProps>((origi // 可见节点集合 const [visibleNodes, setVisibleNodes] = useState([]); - const { empty, activable, @@ -65,6 +64,7 @@ const Tree = forwardRef, TreeProps>((origi scroll, className, style, + allowDrop, } = props; const { value, onChange, expanded, onExpand, onActive, actived } = useControllable(props); @@ -290,6 +290,7 @@ const Tree = forwardRef, TreeProps>((origi icon={icon} label={label} line={line} + allowDrop={allowDrop} transition={transition} expandOnClickNode={expandOnClickNode} activable={activable} @@ -323,6 +324,7 @@ const Tree = forwardRef, TreeProps>((origi icon={icon} label={label} line={line} + allowDrop={allowDrop} transition={transition} expandOnClickNode={expandOnClickNode} activable={activable} diff --git a/src/tree/TreeItem.tsx b/src/tree/TreeItem.tsx index 2cd97c5628..a4d0c3ebca 100644 --- a/src/tree/TreeItem.tsx +++ b/src/tree/TreeItem.tsx @@ -37,6 +37,7 @@ const TreeItem = forwardRef( onTreeItemMounted?: (rowData: { ref: HTMLElement; data: TreeNode }) => void; isVirtual?: boolean; keys: TdTreeProps['keys']; + allowDrop?: TdTreeProps['allowDrop']; }, ref: React.Ref, ) => { @@ -54,6 +55,7 @@ const TreeItem = forwardRef( onChange, isVirtual, onTreeItemMounted, + allowDrop, } = props; const { CaretRightSmallIcon } = useGlobalIcon({ @@ -308,6 +310,7 @@ const TreeItem = forwardRef( const { setDragStatus, isDragging, dropPosition, isDragOver } = useDraggable({ node, nodeRef, + allowDrop, }); const handleDragStart: DragEventHandler = (evt: DragEvent) => { @@ -344,6 +347,7 @@ const TreeItem = forwardRef( }; const handleDrop: DragEventHandler = (evt: DragEvent) => { const { node } = props; + if (!node.isDraggable()) return; evt.stopPropagation(); evt.preventDefault(); diff --git a/src/tree/_example/draggable.tsx b/src/tree/_example/draggable.tsx index a27a86c7df..49aa31835d 100644 --- a/src/tree/_example/draggable.tsx +++ b/src/tree/_example/draggable.tsx @@ -54,7 +54,7 @@ const items = [ }, { value: '2.2', - label: '2.2', + label: '2.2 不允许拖放为 2.2 的子节点', }, ], }, @@ -77,6 +77,13 @@ export default () => { console.log(dragNode, dropPosition, e); }; + const handleAllowDrop: TreeProps['allowDrop'] = (ctx) => { + const { dropNode, dropPosition } = ctx; + if (dropNode.value === '2.2' && dropPosition === 0) { + return false; + } + }; + return ( { onDragEnd={handleDragEnd} onDragLeave={handleDragLeave} onDragOver={handleDragOver} + allowDrop={handleAllowDrop} /> ); diff --git a/src/tree/hooks/TreeDraggableContext.tsx b/src/tree/hooks/TreeDraggableContext.tsx index 6eac0148c9..2eb77426bd 100644 --- a/src/tree/hooks/TreeDraggableContext.tsx +++ b/src/tree/hooks/TreeDraggableContext.tsx @@ -4,6 +4,8 @@ import TreeNode from '../../_common/js/tree-v1/tree-node'; import { TreeProps } from '../Tree'; import { createHookContext } from '../../_util/createHookContext'; +import type { TdTreeProps } from '../type'; + interface Value { props: TreeProps; store: TreeStore; @@ -44,7 +46,12 @@ export const TreeDraggableContext = createHookContext((value: Value) => { }); }; - const onDrop = (context: { node: TreeNode; dropPosition: number; e: DragEvent }) => { + const onDrop = (context: { + node: TreeNode; + dropPosition: number; + e: DragEvent; + allowDrop?: TdTreeProps['allowDrop']; + }) => { const { node, dropPosition } = context; if ( node.value === dragNode.current?.value || @@ -52,6 +59,15 @@ export const TreeDraggableContext = createHookContext((value: Value) => { ) { return; } + const ctx = { + dropNode: node.getModel(), + dragNode: dragNode.current.getModel(), + dropPosition, + e: context.e, + }; + + if (props.allowDrop?.(ctx) === false) return; + const nodes = store.getNodes() as TreeNode[]; nodes.some((_node) => { if (_node.value === node.value) { diff --git a/src/tree/hooks/useDraggable.tsx b/src/tree/hooks/useDraggable.tsx index 060c074a64..6c69179dbd 100644 --- a/src/tree/hooks/useDraggable.tsx +++ b/src/tree/hooks/useDraggable.tsx @@ -5,8 +5,14 @@ import { useTreeDraggableContext } from './TreeDraggableContext'; import { DropPosition } from '../interface'; import { usePersistFn } from '../../hooks/usePersistFn'; -export default function useDraggable(props: { nodeRef: RefObject; node: TreeNode }) { - const { nodeRef, node } = props; +import type { TdTreeProps } from '../type'; + +export default function useDraggable(props: { + nodeRef: RefObject; + node: TreeNode; + allowDrop?: TdTreeProps['allowDrop']; +}) { + const { nodeRef, node, allowDrop } = props; const { onDragStart, onDragEnd, onDragLeave, onDragOver, onDrop } = useTreeDraggableContext(); const [state, setState] = useState<{ @@ -83,7 +89,7 @@ export default function useDraggable(props: { nodeRef: RefObject` | N +allowDrop | Function | - | Determine whether the node can execute the drop operation。Typescript:`(context: { e: DragEvent; dragNode: TreeNodeModel; dropNode: TreeNodeModel; dropPosition: number; }) => boolean` | N allowFoldNodeOnFilter | Boolean | false | \- | N checkProps | Object | - | Typescript:`CheckboxProps`,[Checkbox API Documents](./checkbox?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/tree/type.ts) | N checkStrictly | Boolean | false | \- | N diff --git a/src/tree/tree.md b/src/tree/tree.md index f781b7b828..d5a3e164fa 100644 --- a/src/tree/tree.md +++ b/src/tree/tree.md @@ -10,6 +10,7 @@ style | Object | - | 样式,TS 类型:`React.CSSProperties` | N activable | Boolean | false | 节点是否可高亮 | N activeMultiple | Boolean | false | 是否允许多个节点同时高亮 | N actived | Array | - | 高亮的节点值。TS 类型:`Array` | N +allowDrop | Function | - | 判断节点是否可以执行 drop 操作,泛型 `T` 表示树节点 TS 类型。TS 类型:`(context: { e: DragEvent; dragNode: TreeNodeModel; dropNode: TreeNodeModel; dropPosition: number; }) => boolean` | N allowFoldNodeOnFilter | Boolean | false | 是否允许在过滤时节点折叠节点 | N checkProps | Object | - | 透传属性到 checkbox 组件。参考 checkbox 组件 API。TS 类型:`CheckboxProps`,[Checkbox API Documents](./checkbox?tab=api)。[详细类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/tree/type.ts) | N checkStrictly | Boolean | false | 父子节点选中状态不再关联,可各自选中或取消 | N diff --git a/src/tree/type.ts b/src/tree/type.ts index d5bc2456a5..deba736d66 100644 --- a/src/tree/type.ts +++ b/src/tree/type.ts @@ -27,6 +27,15 @@ export interface TdTreeProps { * 高亮的节点值,非受控属性 */ defaultActived?: Array; + /** + * 判断节点是否可以执行 drop 操作,泛型 `T` 表示树节点 TS 类型 + */ + allowDrop?: (context: { + e: DragEvent; + dragNode: TreeNodeModel; + dropNode: TreeNodeModel; + dropPosition: number; + }) => boolean; /** * 是否允许在过滤时节点折叠节点 * @default false