@ai-group/chat-sdk 3.5.7 → 3.5.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -43,6 +43,8 @@ var useStyles = (0, import_common.withBasicStyles)(() => ({
43
43
  }
44
44
  `,
45
45
  directoryPanel: import_css.css`
46
+ display: flex;
47
+ flex-direction: column;
46
48
  height: 100%;
47
49
  overflow: auto;
48
50
  border-right: 1px solid #e8e8e8;
@@ -57,9 +59,11 @@ var useStyles = (0, import_common.withBasicStyles)(() => ({
57
59
  `,
58
60
  directoryTreeContent: import_css.css`
59
61
  padding: 4px 8px;
62
+ flex: 1;
60
63
 
61
64
  .ant-tree {
62
65
  background: transparent;
66
+ height: 100%;
63
67
  }
64
68
  .ant-tree-switcher {
65
69
  margin-right: 0;
@@ -70,32 +74,72 @@ var useStyles = (0, import_common.withBasicStyles)(() => ({
70
74
  flex: 1;
71
75
  min-width: 0;
72
76
  padding-left: 0;
77
+ overflow: hidden;
73
78
  }
74
79
  .ant-tree .ant-tree-title {
75
80
  flex: 1;
76
81
  min-width: 0;
82
+ overflow: hidden;
77
83
  }
78
84
  .ant-tree-directory .ant-tree-treenode {
85
+ position: relative;
79
86
  &::before {
80
87
  border-radius: 4px;
81
88
  }
82
89
  }
83
- /* 拖拽指示线:蓝色虚线 */
84
- .ant-tree .ant-tree-drop-indicator {
85
- background: transparent !important;
86
- border: 1px dashed #1677ff;
90
+ /* 自定义拖拽样式 — 基于 ant-tree-treenode 定位,覆盖整行 */
91
+ .ant-tree-treenode.folder-tree-drop-before::after {
92
+ content: "";
93
+ position: absolute;
94
+ top: -2px;
95
+ left: 0;
96
+ right: 0;
87
97
  height: 2px;
98
+ background: transparent;
99
+ border-top: 2px dashed #1677ff;
100
+ z-index: 10;
101
+ pointer-events: none;
88
102
  }
89
- /* 拖拽悬停目标:蓝色虚线边框 */
90
- .ant-tree .ant-tree-treenode.drag-over > .ant-tree-node-content-wrapper {
91
- outline: 1px dashed #1677ff;
92
- outline-offset: -1px;
103
+ .ant-tree-treenode.folder-tree-drop-inside::after {
104
+ content: "";
105
+ position: absolute;
106
+ top: 0;
107
+ left: 0;
108
+ right: 0;
109
+ bottom: 0;
110
+ border: 2px dashed #1677ff;
93
111
  border-radius: 4px;
112
+ background: rgba(22, 119, 255, 0.04);
113
+ z-index: 10;
114
+ pointer-events: none;
94
115
  }
95
- /* 拖拽中的节点半透明 */
96
- .ant-tree .ant-tree-treenode-dragging {
97
- opacity: 0.4;
116
+ .ant-tree-treenode.folder-tree-drop-after::after {
117
+ content: "";
118
+ position: absolute;
119
+ bottom: -2px;
120
+ left: 0;
121
+ right: 0;
122
+ height: 2px;
123
+ background: transparent;
124
+ border-bottom: 2px dashed #1677ff;
125
+ z-index: 10;
126
+ pointer-events: none;
98
127
  }
128
+ `,
129
+ /** 自定义拖拽 - 节点可拖拽样式 */
130
+ dragNodeHandle: import_css.css`
131
+ cursor: grab;
132
+ user-select: none;
133
+
134
+ &:active {
135
+ cursor: grabbing;
136
+ }
137
+ `,
138
+ /** 树节点容器,用于相对定位 drop indicator */
139
+ treeNodeWrapper: import_css.css`
140
+ position: relative;
141
+ min-width: 0;
142
+ overflow: hidden;
99
143
  `,
100
144
  previewPanel: import_css.css`
101
145
  flex: 1;
@@ -138,7 +182,15 @@ var useStyles = (0, import_common.withBasicStyles)(() => ({
138
182
  display: flex;
139
183
  align-items: center;
140
184
  justify-content: space-between;
185
+ min-width: 0;
186
+ overflow: hidden;
141
187
  padding-right: 4px;
188
+
189
+ > span:first-child {
190
+ overflow: hidden;
191
+ text-overflow: ellipsis;
192
+ white-space: nowrap;
193
+ }
142
194
  `,
143
195
  moreIcon: import_css.css`
144
196
  opacity: 0;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/components/FolderTree/styles.tsx"],
4
- "sourcesContent": ["import { css } from \"@emotion/css\";\nimport { withBasicStyles } from \"@/styles/common\";\n\nexport const useStyles = withBasicStyles(() => ({\n container: css`\n display: flex;\n width: 100%;\n height: 500px;\n border: 1px solid #e8e8e8;\n border-radius: 8px;\n overflow: hidden;\n background: #fff;\n `,\n containerNoPreview: css`\n height: auto;\n border: none;\n border-radius: 0;\n & > *:first-child {\n border-right: none;\n }\n `,\n directoryPanel: css`\n height: 100%;\n overflow: auto;\n border-right: 1px solid #e8e8e8;\n background: var(--folder-tree-directory-bg, #fafafa);\n `,\n directoryTitle: css`\n padding: 12px 16px;\n font-weight: 600;\n font-size: 14px;\n border-bottom: 1px solid #e8e8e8;\n color: #333;\n `,\n directoryTreeContent: css`\n padding: 4px 8px;\n\n .ant-tree {\n background: transparent;\n }\n .ant-tree-switcher {\n margin-right: 0;\n }\n .ant-tree .ant-tree-node-content-wrapper {\n display: inline-flex;\n align-items: center;\n flex: 1;\n min-width: 0;\n padding-left: 0;\n }\n .ant-tree .ant-tree-title {\n flex: 1;\n min-width: 0;\n }\n .ant-tree-directory .ant-tree-treenode {\n &::before {\n border-radius: 4px;\n }\n }\n /* 拖拽指示线:蓝色虚线 */\n .ant-tree .ant-tree-drop-indicator {\n background: transparent !important;\n border: 1px dashed #1677ff;\n height: 2px;\n }\n /* 拖拽悬停目标:蓝色虚线边框 */\n .ant-tree .ant-tree-treenode.drag-over > .ant-tree-node-content-wrapper {\n outline: 1px dashed #1677ff;\n outline-offset: -1px;\n border-radius: 4px;\n }\n /* 拖拽中的节点半透明 */\n .ant-tree .ant-tree-treenode-dragging {\n opacity: 0.4;\n }\n `,\n previewPanel: css`\n flex: 1;\n height: 100%;\n overflow: auto;\n display: flex;\n flex-direction: column;\n `,\n previewTitle: css`\n padding: 10px 16px;\n font-size: 13px;\n font-weight: 500;\n border-bottom: 1px solid #e8e8e8;\n display: flex;\n align-items: center;\n justify-content: space-between;\n color: #333;\n background: #fff;\n `,\n previewContent: css`\n flex: 1;\n overflow: auto;\n padding: 16px;\n font-size: 13px;\n line-height: 1.6;\n `,\n emptyContainer: css`\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n `,\n loadingContainer: css`\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n `,\n treeNodeTitle: css`\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding-right: 4px;\n `,\n moreIcon: css`\n opacity: 0;\n cursor: pointer;\n padding: 0 4px;\n margin: 2px;\n color: #999;\n font-size: 12px;\n transition: all 0.2s;\n\n &:hover {\n border-radius: 50%;\n color: #333;\n background: rgba(0, 0, 0, 0.04);\n }\n `,\n treeNodeTitleHover: css`\n &:hover .folder-tree-more-icon {\n opacity: 1;\n }\n `,\n inlineInput: css`\n width: 100%;\n .ant-input {\n height: 22px;\n font-size: 13px;\n padding: 0 6px;\n border-radius: 4px;\n }\n `,\n copyBtn: css`\n cursor: pointer;\n color: #999;\n font-size: 14px;\n &:hover {\n color: #1677ff;\n }\n `,\n}));\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAAoB;AACpB,oBAAgC;AAEzB,IAAM,gBAAY,+BAAgB,OAAO;AAAA,EAC9C,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASX,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQpB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMhB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOhB,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA0CtB,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOd,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWd,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOhB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMhB,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMlB,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMf,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeV,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKpB,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASb,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQX,EAAE;",
4
+ "sourcesContent": ["import { css } from \"@emotion/css\";\nimport { withBasicStyles } from \"@/styles/common\";\n\nexport const useStyles = withBasicStyles(() => ({\n container: css`\n display: flex;\n width: 100%;\n height: 500px;\n border: 1px solid #e8e8e8;\n border-radius: 8px;\n overflow: hidden;\n background: #fff;\n `,\n containerNoPreview: css`\n height: auto;\n border: none;\n border-radius: 0;\n & > *:first-child {\n border-right: none;\n }\n `,\n directoryPanel: css`\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: auto;\n border-right: 1px solid #e8e8e8;\n background: var(--folder-tree-directory-bg, #fafafa);\n `,\n directoryTitle: css`\n padding: 12px 16px;\n font-weight: 600;\n font-size: 14px;\n border-bottom: 1px solid #e8e8e8;\n color: #333;\n `,\n directoryTreeContent: css`\n padding: 4px 8px;\n flex: 1;\n\n .ant-tree {\n background: transparent;\n height: 100%;\n }\n .ant-tree-switcher {\n margin-right: 0;\n }\n .ant-tree .ant-tree-node-content-wrapper {\n display: inline-flex;\n align-items: center;\n flex: 1;\n min-width: 0;\n padding-left: 0;\n overflow: hidden;\n }\n .ant-tree .ant-tree-title {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n }\n .ant-tree-directory .ant-tree-treenode {\n position: relative;\n &::before {\n border-radius: 4px;\n }\n }\n /* 自定义拖拽样式 — 基于 ant-tree-treenode 定位,覆盖整行 */\n .ant-tree-treenode.folder-tree-drop-before::after {\n content: \"\";\n position: absolute;\n top: -2px;\n left: 0;\n right: 0;\n height: 2px;\n background: transparent;\n border-top: 2px dashed #1677ff;\n z-index: 10;\n pointer-events: none;\n }\n .ant-tree-treenode.folder-tree-drop-inside::after {\n content: \"\";\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n border: 2px dashed #1677ff;\n border-radius: 4px;\n background: rgba(22, 119, 255, 0.04);\n z-index: 10;\n pointer-events: none;\n }\n .ant-tree-treenode.folder-tree-drop-after::after {\n content: \"\";\n position: absolute;\n bottom: -2px;\n left: 0;\n right: 0;\n height: 2px;\n background: transparent;\n border-bottom: 2px dashed #1677ff;\n z-index: 10;\n pointer-events: none;\n }\n `,\n\n /** 自定义拖拽 - 节点可拖拽样式 */\n dragNodeHandle: css`\n cursor: grab;\n user-select: none;\n\n &:active {\n cursor: grabbing;\n }\n `,\n /** 树节点容器,用于相对定位 drop indicator */\n treeNodeWrapper: css`\n position: relative;\n min-width: 0;\n overflow: hidden;\n `,\n previewPanel: css`\n flex: 1;\n height: 100%;\n overflow: auto;\n display: flex;\n flex-direction: column;\n `,\n previewTitle: css`\n padding: 10px 16px;\n font-size: 13px;\n font-weight: 500;\n border-bottom: 1px solid #e8e8e8;\n display: flex;\n align-items: center;\n justify-content: space-between;\n color: #333;\n background: #fff;\n `,\n previewContent: css`\n flex: 1;\n overflow: auto;\n padding: 16px;\n font-size: 13px;\n line-height: 1.6;\n `,\n emptyContainer: css`\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n `,\n loadingContainer: css`\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n `,\n treeNodeTitle: css`\n display: flex;\n align-items: center;\n justify-content: space-between;\n min-width: 0;\n overflow: hidden;\n padding-right: 4px;\n\n > span:first-child {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n `,\n moreIcon: css`\n opacity: 0;\n cursor: pointer;\n padding: 0 4px;\n margin: 2px;\n color: #999;\n font-size: 12px;\n transition: all 0.2s;\n\n &:hover {\n border-radius: 50%;\n color: #333;\n background: rgba(0, 0, 0, 0.04);\n }\n `,\n treeNodeTitleHover: css`\n &:hover .folder-tree-more-icon {\n opacity: 1;\n }\n `,\n inlineInput: css`\n width: 100%;\n .ant-input {\n height: 22px;\n font-size: 13px;\n padding: 0 6px;\n border-radius: 4px;\n }\n `,\n copyBtn: css`\n cursor: pointer;\n color: #999;\n font-size: 14px;\n &:hover {\n color: #1677ff;\n }\n `,\n}));\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAAoB;AACpB,oBAAgC;AAEzB,IAAM,gBAAY,+BAAgB,OAAO;AAAA,EAC9C,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASX,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQpB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQhB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOhB,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuEtB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAShB,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKjB,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOd,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWd,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOhB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMhB,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMlB,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcf,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeV,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKpB,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASb,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQX,EAAE;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,53 @@
1
+ /// <reference types="react" />
2
+ import type { DataNode } from "antd/es/tree";
3
+ /**
4
+ * Drop position:
5
+ * -1 = before the node (gap above)
6
+ * 0 = inside the node (as child, for folders only)
7
+ * 1 = after the node (gap below)
8
+ */
9
+ export type DropPosition = -1 | 0 | 1;
10
+ export interface DragState {
11
+ /** 拖拽中的节点 key */
12
+ dragNodeKey: string | null;
13
+ /** 当前悬停的目标节点 key */
14
+ dropNodeKey: string | null;
15
+ /** drop 位置 */
16
+ dropPosition: DropPosition;
17
+ }
18
+ export interface UseFolderDragOptions {
19
+ /** 是否启用拖拽('file' | 'folder' | 'all') */
20
+ dragMode: "file" | "folder" | "all" | false;
21
+ /** 拖拽完成回调 */
22
+ onDrop?: (dragNode: DataNode, dropNode: DataNode, dropPosition: DropPosition) => void;
23
+ /** 拖拽进入节点时回调(用于自动展开) */
24
+ onDragEnterExpand?: (nodeKey: string) => void;
25
+ }
26
+ export interface UseFolderDragReturn {
27
+ dragState: DragState;
28
+ /** 绑定到 draggable 元素的 onDragStart */
29
+ onDragStart: (e: React.DragEvent, node: DataNode) => void;
30
+ /** 绑定到 draggable 元素的 onDragEnd */
31
+ onDragEnd: (e: React.DragEvent) => void;
32
+ /** 绑定到每个树节点容器的 onDragEnter */
33
+ onItemDragEnter: (e: React.DragEvent, node: DataNode) => void;
34
+ /** 绑定到每个树节点容器的 onDragOver */
35
+ onItemDragOver: (e: React.DragEvent, node: DataNode) => void;
36
+ /** 绑定到每个树节点容器的 onDragLeave */
37
+ onItemDragLeave: (e: React.DragEvent) => void;
38
+ /** 绑定到每个树节点容器的 onDrop */
39
+ onItemDrop: (e: React.DragEvent, node: DataNode) => void;
40
+ /** 绑定到树内容容器(directoryTreeContent)的 onDragOver,处理拖到空白区域 */
41
+ onContainerDragOver: (e: React.DragEvent) => void;
42
+ /** 绑定到树内容容器(directoryTreeContent)的 onDrop,处理拖到空白区域 */
43
+ onContainerDrop: (e: React.DragEvent) => void;
44
+ /** 清除拖拽状态 */
45
+ clearDragState: () => void;
46
+ /** 是否正在拖拽中 */
47
+ isDragging: boolean;
48
+ /** 判断节点是否可拖拽 */
49
+ canDragNode: (node: DataNode & {
50
+ _isTempNode?: boolean;
51
+ }) => boolean;
52
+ }
53
+ export declare function useFolderDrag(options: UseFolderDragOptions): UseFolderDragReturn;
@@ -0,0 +1,309 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+
19
+ // src/components/FolderTree/useFolderDrag.ts
20
+ var useFolderDrag_exports = {};
21
+ __export(useFolderDrag_exports, {
22
+ useFolderDrag: () => useFolderDrag
23
+ });
24
+ module.exports = __toCommonJS(useFolderDrag_exports);
25
+ var import_react = require("react");
26
+ function calcDropPositionByRect(clientY, rect, isFolder) {
27
+ const top = rect.top;
28
+ const height = rect.height;
29
+ const relativeY = clientY - top;
30
+ if (relativeY < height / 3) {
31
+ return { dropPosition: -1, insideThreshold: false };
32
+ }
33
+ if (relativeY > height * 2 / 3) {
34
+ return { dropPosition: 1, insideThreshold: false };
35
+ }
36
+ if (isFolder) {
37
+ return { dropPosition: 0, insideThreshold: true };
38
+ }
39
+ return { dropPosition: 1, insideThreshold: false };
40
+ }
41
+ var INDICATOR_BEFORE_CLASS = "folder-tree-drop-before";
42
+ var INDICATOR_INSIDE_CLASS = "folder-tree-drop-inside";
43
+ var INDICATOR_AFTER_CLASS = "folder-tree-drop-after";
44
+ var INDICATOR_CLASSES = [INDICATOR_BEFORE_CLASS, INDICATOR_INSIDE_CLASS, INDICATOR_AFTER_CLASS];
45
+ function getTreeNodeEl(dataNodeKeyEl) {
46
+ return dataNodeKeyEl.closest(".ant-tree-treenode");
47
+ }
48
+ function clearAllTreenodeIndicators() {
49
+ document.querySelectorAll(".ant-tree-treenode").forEach((el) => {
50
+ el.classList.remove(...INDICATOR_CLASSES);
51
+ });
52
+ }
53
+ function useFolderDrag(options) {
54
+ const { dragMode, onDrop, onDragEnterExpand } = options;
55
+ const [dragState, setDragState] = (0, import_react.useState)({
56
+ dragNodeKey: null,
57
+ dropNodeKey: null,
58
+ dropPosition: 0
59
+ });
60
+ const [isDragging, setIsDragging] = (0, import_react.useState)(false);
61
+ const dragNodeRef = (0, import_react.useRef)(null);
62
+ const expandTimerRef = (0, import_react.useRef)(null);
63
+ const prevDropTargetRef = (0, import_react.useRef)(null);
64
+ const canDragNode = (0, import_react.useCallback)(
65
+ (node) => {
66
+ if (node._isTempNode)
67
+ return false;
68
+ if (dragMode === "file")
69
+ return !!node.isLeaf;
70
+ if (dragMode === "folder")
71
+ return !node.isLeaf;
72
+ return true;
73
+ },
74
+ [dragMode]
75
+ );
76
+ const clearAllIndicators = (0, import_react.useCallback)(() => {
77
+ clearAllTreenodeIndicators();
78
+ }, []);
79
+ const clearDragState = (0, import_react.useCallback)(() => {
80
+ clearAllIndicators();
81
+ document.querySelectorAll(".ant-tree-treenode").forEach((el) => {
82
+ el.style.opacity = "";
83
+ });
84
+ setDragState({
85
+ dragNodeKey: null,
86
+ dropNodeKey: null,
87
+ dropPosition: 0
88
+ });
89
+ setIsDragging(false);
90
+ dragNodeRef.current = null;
91
+ prevDropTargetRef.current = null;
92
+ if (expandTimerRef.current) {
93
+ clearTimeout(expandTimerRef.current);
94
+ expandTimerRef.current = null;
95
+ }
96
+ }, [clearAllIndicators]);
97
+ const onDragStart = (0, import_react.useCallback)(
98
+ (e, node) => {
99
+ if (!dragMode)
100
+ return;
101
+ const nd = node;
102
+ if (!canDragNode(nd)) {
103
+ e.preventDefault();
104
+ return;
105
+ }
106
+ dragNodeRef.current = node;
107
+ setIsDragging(true);
108
+ setDragState((prev) => ({
109
+ ...prev,
110
+ dragNodeKey: String(node.key)
111
+ }));
112
+ const treeNodeEl = getTreeNodeEl(e.currentTarget);
113
+ if (treeNodeEl) {
114
+ treeNodeEl.style.opacity = "0.4";
115
+ }
116
+ e.dataTransfer.effectAllowed = "move";
117
+ e.dataTransfer.setData("text/plain", String(node.key));
118
+ },
119
+ [dragMode, canDragNode]
120
+ );
121
+ const onDragEnd = (0, import_react.useCallback)(
122
+ (_e) => {
123
+ clearDragState();
124
+ },
125
+ [clearDragState]
126
+ );
127
+ const onItemDragEnter = (0, import_react.useCallback)(
128
+ (e, node) => {
129
+ e.preventDefault();
130
+ e.stopPropagation();
131
+ if (!dragNodeRef.current)
132
+ return;
133
+ const nodeKey = String(node.key);
134
+ const dragKey = String(dragNodeRef.current.key);
135
+ if (nodeKey === dragKey)
136
+ return;
137
+ if (!node.isLeaf) {
138
+ if (expandTimerRef.current) {
139
+ clearTimeout(expandTimerRef.current);
140
+ }
141
+ expandTimerRef.current = setTimeout(() => {
142
+ onDragEnterExpand == null ? void 0 : onDragEnterExpand(nodeKey);
143
+ }, 800);
144
+ }
145
+ },
146
+ [onDragEnterExpand]
147
+ );
148
+ const onItemDragOver = (0, import_react.useCallback)(
149
+ (e, node) => {
150
+ var _a;
151
+ e.preventDefault();
152
+ e.stopPropagation();
153
+ if (!dragNodeRef.current)
154
+ return;
155
+ const nodeKey = String(node.key);
156
+ const dragKey = String(dragNodeRef.current.key);
157
+ if (nodeKey === dragKey)
158
+ return;
159
+ const rect = e.currentTarget.getBoundingClientRect();
160
+ const isFolder = !node.isLeaf;
161
+ const { dropPosition } = calcDropPositionByRect(e.clientY, rect, isFolder);
162
+ const finalDropPosition = !isFolder && dropPosition === 0 ? 1 : dropPosition;
163
+ const currentTreeNode = getTreeNodeEl(e.currentTarget);
164
+ if (!currentTreeNode)
165
+ return;
166
+ if (prevDropTargetRef.current && prevDropTargetRef.current !== nodeKey) {
167
+ const prevTreeNode = (_a = document.querySelector(
168
+ `.ant-tree-treenode [data-node-key="${prevDropTargetRef.current}"]`
169
+ )) == null ? void 0 : _a.closest(".ant-tree-treenode");
170
+ if (prevTreeNode) {
171
+ prevTreeNode.classList.remove(...INDICATOR_CLASSES);
172
+ }
173
+ }
174
+ prevDropTargetRef.current = nodeKey;
175
+ currentTreeNode.classList.remove(...INDICATOR_CLASSES);
176
+ const cls = finalDropPosition === -1 ? INDICATOR_BEFORE_CLASS : finalDropPosition === 0 ? INDICATOR_INSIDE_CLASS : INDICATOR_AFTER_CLASS;
177
+ currentTreeNode.classList.add(cls);
178
+ setDragState({
179
+ dragNodeKey: dragKey,
180
+ dropNodeKey: nodeKey,
181
+ dropPosition: finalDropPosition
182
+ });
183
+ e.dataTransfer.dropEffect = "move";
184
+ },
185
+ []
186
+ );
187
+ const onItemDragLeave = (0, import_react.useCallback)((_e) => {
188
+ }, []);
189
+ const onItemDrop = (0, import_react.useCallback)(
190
+ (e, node) => {
191
+ e.preventDefault();
192
+ e.stopPropagation();
193
+ if (!dragNodeRef.current)
194
+ return;
195
+ const nodeKey = String(node.key);
196
+ const dragKey = String(dragNodeRef.current.key);
197
+ if (nodeKey === dragKey) {
198
+ clearDragState();
199
+ return;
200
+ }
201
+ const rect = e.currentTarget.getBoundingClientRect();
202
+ const isFolder = !node.isLeaf;
203
+ const { dropPosition } = calcDropPositionByRect(e.clientY, rect, isFolder);
204
+ const finalDropPosition = !isFolder && dropPosition === 0 ? 1 : dropPosition;
205
+ onDrop == null ? void 0 : onDrop(dragNodeRef.current, node, finalDropPosition);
206
+ clearDragState();
207
+ },
208
+ [onDrop, clearDragState]
209
+ );
210
+ const getLastNodeRect = (0, import_react.useCallback)(
211
+ (containerEl) => {
212
+ const nodeEls = containerEl.querySelectorAll("[data-node-key]");
213
+ if (nodeEls.length === 0)
214
+ return null;
215
+ const lastEl = nodeEls[nodeEls.length - 1];
216
+ return lastEl.getBoundingClientRect();
217
+ },
218
+ []
219
+ );
220
+ const onContainerDragOver = (0, import_react.useCallback)(
221
+ (e) => {
222
+ var _a;
223
+ if (!dragNodeRef.current)
224
+ return;
225
+ const containerEl = e.currentTarget;
226
+ const targetEl = e.target;
227
+ const isOnNode = targetEl.closest("[data-node-key]");
228
+ if (isOnNode) {
229
+ if (prevDropTargetRef.current === "__bottom_placeholder__") {
230
+ clearAllIndicators();
231
+ prevDropTargetRef.current = null;
232
+ }
233
+ return;
234
+ }
235
+ const lastRect = getLastNodeRect(containerEl);
236
+ if (lastRect && e.clientY > lastRect.bottom) {
237
+ e.preventDefault();
238
+ e.stopPropagation();
239
+ if (prevDropTargetRef.current && prevDropTargetRef.current !== "__bottom_placeholder__") {
240
+ const prevTreeNode = (_a = document.querySelector(
241
+ `.ant-tree-treenode [data-node-key="${prevDropTargetRef.current}"]`
242
+ )) == null ? void 0 : _a.closest(".ant-tree-treenode");
243
+ if (prevTreeNode) {
244
+ prevTreeNode.classList.remove(...INDICATOR_CLASSES);
245
+ }
246
+ }
247
+ prevDropTargetRef.current = "__bottom_placeholder__";
248
+ const lastNodeEls = containerEl.querySelectorAll(".ant-tree-treenode");
249
+ const lastTreenode = lastNodeEls[lastNodeEls.length - 1];
250
+ if (lastTreenode) {
251
+ lastTreenode.classList.remove(...INDICATOR_CLASSES);
252
+ lastTreenode.classList.add(INDICATOR_AFTER_CLASS);
253
+ }
254
+ setDragState({
255
+ dragNodeKey: String(dragNodeRef.current.key),
256
+ dropNodeKey: "__bottom_placeholder__",
257
+ dropPosition: 1
258
+ });
259
+ e.dataTransfer.dropEffect = "move";
260
+ }
261
+ },
262
+ [clearAllIndicators, getLastNodeRect]
263
+ );
264
+ const onContainerDrop = (0, import_react.useCallback)(
265
+ (e) => {
266
+ if (!dragNodeRef.current) {
267
+ return;
268
+ }
269
+ const containerEl = e.currentTarget;
270
+ const targetEl = e.target;
271
+ const isOnNode = targetEl.closest("[data-node-key]");
272
+ if (isOnNode) {
273
+ return;
274
+ }
275
+ const lastRect = getLastNodeRect(containerEl);
276
+ if (lastRect && e.clientY > lastRect.bottom) {
277
+ e.preventDefault();
278
+ e.stopPropagation();
279
+ const placeholderNode = {
280
+ key: "__bottom_placeholder__",
281
+ title: "",
282
+ isLeaf: true
283
+ };
284
+ onDrop == null ? void 0 : onDrop(dragNodeRef.current, placeholderNode, 1);
285
+ clearDragState();
286
+ }
287
+ },
288
+ [onDrop, clearDragState, getLastNodeRect]
289
+ );
290
+ return {
291
+ dragState,
292
+ onDragStart,
293
+ onDragEnd,
294
+ onItemDragEnter,
295
+ onItemDragOver,
296
+ onItemDragLeave,
297
+ onItemDrop,
298
+ onContainerDragOver,
299
+ onContainerDrop,
300
+ clearDragState,
301
+ isDragging,
302
+ canDragNode
303
+ };
304
+ }
305
+ // Annotate the CommonJS export names for ESM import in node:
306
+ 0 && (module.exports = {
307
+ useFolderDrag
308
+ });
309
+ //# sourceMappingURL=useFolderDrag.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/components/FolderTree/useFolderDrag.ts"],
4
+ "sourcesContent": ["import { useCallback, useRef, useState } from \"react\";\nimport type { DataNode } from \"antd/es/tree\";\n\n/**\n * Drop position:\n * -1 = before the node (gap above)\n * 0 = inside the node (as child, for folders only)\n * 1 = after the node (gap below)\n */\nexport type DropPosition = -1 | 0 | 1;\n\nexport interface DragState {\n /** 拖拽中的节点 key */\n dragNodeKey: string | null;\n /** 当前悬停的目标节点 key */\n dropNodeKey: string | null;\n /** drop 位置 */\n dropPosition: DropPosition;\n}\n\nexport interface UseFolderDragOptions {\n /** 是否启用拖拽('file' | 'folder' | 'all') */\n dragMode: \"file\" | \"folder\" | \"all\" | false;\n /** 拖拽完成回调 */\n onDrop?: (dragNode: DataNode, dropNode: DataNode, dropPosition: DropPosition) => void;\n /** 拖拽进入节点时回调(用于自动展开) */\n onDragEnterExpand?: (nodeKey: string) => void;\n}\n\nexport interface UseFolderDragReturn {\n dragState: DragState;\n /** 绑定到 draggable 元素的 onDragStart */\n onDragStart: (e: React.DragEvent, node: DataNode) => void;\n /** 绑定到 draggable 元素的 onDragEnd */\n onDragEnd: (e: React.DragEvent) => void;\n /** 绑定到每个树节点容器的 onDragEnter */\n onItemDragEnter: (e: React.DragEvent, node: DataNode) => void;\n /** 绑定到每个树节点容器的 onDragOver */\n onItemDragOver: (e: React.DragEvent, node: DataNode) => void;\n /** 绑定到每个树节点容器的 onDragLeave */\n onItemDragLeave: (e: React.DragEvent) => void;\n /** 绑定到每个树节点容器的 onDrop */\n onItemDrop: (e: React.DragEvent, node: DataNode) => void;\n /** 绑定到树内容容器(directoryTreeContent)的 onDragOver,处理拖到空白区域 */\n onContainerDragOver: (e: React.DragEvent) => void;\n /** 绑定到树内容容器(directoryTreeContent)的 onDrop,处理拖到空白区域 */\n onContainerDrop: (e: React.DragEvent) => void;\n /** 清除拖拽状态 */\n clearDragState: () => void;\n /** 是否正在拖拽中 */\n isDragging: boolean;\n /** 判断节点是否可拖拽 */\n canDragNode: (node: DataNode & { _isTempNode?: boolean }) => boolean;\n}\n\n/**\n * 根据鼠标 Y 坐标和节点 DOM rect 计算 dropPosition\n */\nfunction calcDropPositionByRect(\n clientY: number,\n rect: DOMRect,\n isFolder: boolean,\n): { dropPosition: DropPosition; insideThreshold: boolean } {\n const top = rect.top;\n const height = rect.height;\n const relativeY = clientY - top;\n\n // 上半部分 1/3:before\n if (relativeY < height / 3) {\n return { dropPosition: -1, insideThreshold: false };\n }\n // 下半部分 1/3:after\n if (relativeY > (height * 2) / 3) {\n return { dropPosition: 1, insideThreshold: false };\n }\n // 中间 1/3:如果是文件夹,drop inside;如果是文件,drop after\n if (isFolder) {\n return { dropPosition: 0, insideThreshold: true };\n }\n return { dropPosition: 1, insideThreshold: false };\n}\n\nconst INDICATOR_BEFORE_CLASS = \"folder-tree-drop-before\";\nconst INDICATOR_INSIDE_CLASS = \"folder-tree-drop-inside\";\nconst INDICATOR_AFTER_CLASS = \"folder-tree-drop-after\";\n\nconst INDICATOR_CLASSES = [INDICATOR_BEFORE_CLASS, INDICATOR_INSIDE_CLASS, INDICATOR_AFTER_CLASS];\n\n/** 从 data-node-key 元素找到对应的 ant-tree-treenode */\nfunction getTreeNodeEl(dataNodeKeyEl: HTMLElement): HTMLElement | null {\n return dataNodeKeyEl.closest(\".ant-tree-treenode\") as HTMLElement | null;\n}\n\n/** 清除所有 treenode 上的 indicator class */\nfunction clearAllTreenodeIndicators() {\n document.querySelectorAll(\".ant-tree-treenode\").forEach((el) => {\n el.classList.remove(...INDICATOR_CLASSES);\n });\n}\n\nexport function useFolderDrag(options: UseFolderDragOptions): UseFolderDragReturn {\n const { dragMode, onDrop, onDragEnterExpand } = options;\n\n const [dragState, setDragState] = useState<DragState>({\n dragNodeKey: null,\n dropNodeKey: null,\n dropPosition: 0,\n });\n const [isDragging, setIsDragging] = useState(false);\n\n // 用 ref 追踪拖拽中数据,避免闭包问题\n const dragNodeRef = useRef<DataNode | null>(null);\n const expandTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const prevDropTargetRef = useRef<string | null>(null);\n\n const canDragNode = useCallback(\n (node: DataNode & { _isTempNode?: boolean }) => {\n if (node._isTempNode) return false;\n if (dragMode === \"file\") return !!node.isLeaf;\n if (dragMode === \"folder\") return !node.isLeaf;\n return true; // 'all'\n },\n [dragMode],\n );\n\n const clearAllIndicators = useCallback(() => {\n clearAllTreenodeIndicators();\n }, []);\n\n const clearDragState = useCallback(() => {\n clearAllIndicators();\n // 恢复所有 treenode 的透明度\n document.querySelectorAll(\".ant-tree-treenode\").forEach((el) => {\n (el as HTMLElement).style.opacity = \"\";\n });\n\n setDragState({\n dragNodeKey: null,\n dropNodeKey: null,\n dropPosition: 0,\n });\n setIsDragging(false);\n dragNodeRef.current = null;\n prevDropTargetRef.current = null;\n if (expandTimerRef.current) {\n clearTimeout(expandTimerRef.current);\n expandTimerRef.current = null;\n }\n }, [clearAllIndicators]);\n\n const onDragStart = useCallback(\n (e: React.DragEvent, node: DataNode) => {\n if (!dragMode) return;\n const nd = node as DataNode & { _isTempNode?: boolean };\n if (!canDragNode(nd)) {\n e.preventDefault();\n return;\n }\n\n dragNodeRef.current = node;\n setIsDragging(true);\n setDragState((prev) => ({\n ...prev,\n dragNodeKey: String(node.key),\n }));\n\n // 标记被拖拽的节点(在 treenode 上加 translucent)\n const treeNodeEl = getTreeNodeEl(e.currentTarget as HTMLElement);\n if (treeNodeEl) {\n treeNodeEl.style.opacity = \"0.4\";\n }\n\n e.dataTransfer.effectAllowed = \"move\";\n e.dataTransfer.setData(\"text/plain\", String(node.key));\n },\n [dragMode, canDragNode],\n );\n\n const onDragEnd = useCallback(\n (_e: React.DragEvent) => {\n clearDragState();\n },\n [clearDragState],\n );\n\n const onItemDragEnter = useCallback(\n (e: React.DragEvent, node: DataNode) => {\n e.preventDefault();\n e.stopPropagation();\n\n if (!dragNodeRef.current) return;\n\n const nodeKey = String(node.key);\n const dragKey = String(dragNodeRef.current.key);\n\n // 不能拖到自己身上\n if (nodeKey === dragKey) return;\n\n // 自动展开收起状态的文件夹\n if (!node.isLeaf) {\n if (expandTimerRef.current) {\n clearTimeout(expandTimerRef.current);\n }\n expandTimerRef.current = setTimeout(() => {\n onDragEnterExpand?.(nodeKey);\n }, 800);\n }\n },\n [onDragEnterExpand],\n );\n\n const onItemDragOver = useCallback(\n (e: React.DragEvent, node: DataNode) => {\n e.preventDefault();\n e.stopPropagation();\n\n if (!dragNodeRef.current) return;\n\n const nodeKey = String(node.key);\n const dragKey = String(dragNodeRef.current.key);\n if (nodeKey === dragKey) return;\n\n const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();\n const isFolder = !node.isLeaf;\n const { dropPosition } = calcDropPositionByRect(e.clientY, rect, isFolder);\n\n // 如果节点是文件且计算结果是 inside(0),改为 after(1)\n const finalDropPosition = (!isFolder && dropPosition === 0 ? 1 : dropPosition) as DropPosition;\n\n const currentTreeNode = getTreeNodeEl(e.currentTarget as HTMLElement);\n if (!currentTreeNode) return;\n\n // 清除上一个节点的 indicator\n if (prevDropTargetRef.current && prevDropTargetRef.current !== nodeKey) {\n const prevTreeNode = document.querySelector(\n `.ant-tree-treenode [data-node-key=\"${prevDropTargetRef.current}\"]`,\n )?.closest(\".ant-tree-treenode\");\n if (prevTreeNode) {\n prevTreeNode.classList.remove(...INDICATOR_CLASSES);\n }\n }\n prevDropTargetRef.current = nodeKey;\n\n // 在当前 treenode 上设置 indicator class\n currentTreeNode.classList.remove(...INDICATOR_CLASSES);\n const cls =\n finalDropPosition === -1\n ? INDICATOR_BEFORE_CLASS\n : finalDropPosition === 0\n ? INDICATOR_INSIDE_CLASS\n : INDICATOR_AFTER_CLASS;\n currentTreeNode.classList.add(cls);\n\n setDragState({\n dragNodeKey: dragKey,\n dropNodeKey: nodeKey,\n dropPosition: finalDropPosition,\n });\n\n e.dataTransfer.dropEffect = \"move\";\n },\n [],\n );\n\n const onItemDragLeave = useCallback((_e: React.DragEvent) => {\n // 不清除 dragState,因为可能只是进入子元素触发的 leave\n }, []);\n\n const onItemDrop = useCallback(\n (e: React.DragEvent, node: DataNode) => {\n e.preventDefault();\n e.stopPropagation();\n\n if (!dragNodeRef.current) return;\n\n const nodeKey = String(node.key);\n const dragKey = String(dragNodeRef.current.key);\n if (nodeKey === dragKey) {\n clearDragState();\n return;\n }\n\n const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();\n const isFolder = !node.isLeaf;\n const { dropPosition } = calcDropPositionByRect(e.clientY, rect, isFolder);\n const finalDropPosition = (!isFolder && dropPosition === 0 ? 1 : dropPosition) as DropPosition;\n\n onDrop?.(dragNodeRef.current, node, finalDropPosition);\n clearDragState();\n },\n [onDrop, clearDragState],\n );\n\n // ====== 容器级别的事件处理(用于拖到空白区域/底部) ======\n\n /**\n * 获取容器内所有可见的 data-node-key 元素的 rect 列表,\n * 用于判断鼠标是否在最后一个节点下方\n */\n const getLastNodeRect = useCallback(\n (containerEl: HTMLElement): DOMRect | null => {\n const nodeEls = containerEl.querySelectorAll(\"[data-node-key]\");\n if (nodeEls.length === 0) return null;\n const lastEl = nodeEls[nodeEls.length - 1];\n return lastEl.getBoundingClientRect();\n },\n [],\n );\n\n const onContainerDragOver = useCallback(\n (e: React.DragEvent) => {\n if (!dragNodeRef.current) return;\n\n const containerEl = e.currentTarget as HTMLElement;\n\n // 检查鼠标是否在某个节点上(通过判断 event target 是否在 data-node-key 元素内)\n const targetEl = e.target as HTMLElement;\n const isOnNode = targetEl.closest(\"[data-node-key]\");\n\n if (isOnNode) {\n // 鼠标在节点上,由 onItemDragOver 处理,这里清除底部相关的 indicator\n if (prevDropTargetRef.current === \"__bottom_placeholder__\") {\n clearAllIndicators();\n prevDropTargetRef.current = null;\n }\n return;\n }\n\n // 鼠标在空白区域,检查是否在所有节点下方\n const lastRect = getLastNodeRect(containerEl);\n if (lastRect && e.clientY > lastRect.bottom) {\n e.preventDefault();\n e.stopPropagation();\n\n // 清除其他节点的 indicator\n if (prevDropTargetRef.current && prevDropTargetRef.current !== \"__bottom_placeholder__\") {\n const prevTreeNode = document.querySelector(\n `.ant-tree-treenode [data-node-key=\"${prevDropTargetRef.current}\"]`,\n )?.closest(\".ant-tree-treenode\");\n if (prevTreeNode) {\n prevTreeNode.classList.remove(...INDICATOR_CLASSES);\n }\n }\n prevDropTargetRef.current = \"__bottom_placeholder__\";\n\n // 在最后一个 treenode 上显示 after 样式\n const lastNodeEls = containerEl.querySelectorAll(\".ant-tree-treenode\");\n const lastTreenode = lastNodeEls[lastNodeEls.length - 1] as HTMLElement | undefined;\n if (lastTreenode) {\n lastTreenode.classList.remove(...INDICATOR_CLASSES);\n lastTreenode.classList.add(INDICATOR_AFTER_CLASS);\n }\n\n setDragState({\n dragNodeKey: String(dragNodeRef.current.key),\n dropNodeKey: \"__bottom_placeholder__\",\n dropPosition: 1 as DropPosition,\n });\n\n e.dataTransfer.dropEffect = \"move\";\n }\n },\n [clearAllIndicators, getLastNodeRect],\n );\n\n const onContainerDrop = useCallback(\n (e: React.DragEvent) => {\n if (!dragNodeRef.current) {\n return;\n }\n\n const containerEl = e.currentTarget as HTMLElement;\n const targetEl = e.target as HTMLElement;\n const isOnNode = targetEl.closest(\"[data-node-key]\");\n\n if (isOnNode) {\n // 鼠标在节点上,由 onItemDrop 处理\n return;\n }\n\n // 检查是否在最后一个节点下方\n const lastRect = getLastNodeRect(containerEl);\n if (lastRect && e.clientY > lastRect.bottom) {\n e.preventDefault();\n e.stopPropagation();\n\n // 拖到最底部:视为 insert after 最后一个根节点\n const placeholderNode: DataNode = {\n key: \"__bottom_placeholder__\",\n title: \"\",\n isLeaf: true,\n };\n onDrop?.(dragNodeRef.current, placeholderNode, 1 as DropPosition);\n clearDragState();\n }\n },\n [onDrop, clearDragState, getLastNodeRect],\n );\n\n return {\n dragState,\n onDragStart,\n onDragEnd,\n onItemDragEnter,\n onItemDragOver,\n onItemDragLeave,\n onItemDrop,\n onContainerDragOver,\n onContainerDrop,\n clearDragState,\n isDragging,\n canDragNode,\n };\n}"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }