0
# Drag & Drop
1
2
RC Tree provides built-in drag and drop functionality with customizable drop validation, drag constraints, and comprehensive event handling for tree reorganization.
3
4
## Capabilities
5
6
### Drag & Drop Configuration
7
8
Enable and configure drag-and-drop functionality with flexible constraints and validation.
9
10
```typescript { .api }
11
/**
12
* Drag and drop configuration options
13
*/
14
interface DragDropConfig<TreeDataType extends BasicDataNode = DataNode> {
15
/** Enable drag and drop functionality */
16
draggable?: DraggableFn | boolean | DraggableConfig;
17
/** Function to validate drop operations */
18
allowDrop?: AllowDrop<TreeDataType>;
19
/** Custom drop indicator renderer */
20
dropIndicatorRender?: (props: DropIndicatorProps) => React.ReactNode;
21
/** Layout direction for drag logic */
22
direction?: Direction;
23
}
24
25
/**
26
* Function to determine if a node can be dragged
27
*/
28
type DraggableFn = (node: DataNode) => boolean;
29
30
/**
31
* Advanced drag configuration object
32
*/
33
type DraggableConfig = {
34
/** Custom drag icon (false to hide) */
35
icon?: React.ReactNode | false;
36
/** Function to determine per-node draggability */
37
nodeDraggable?: DraggableFn;
38
};
39
40
/**
41
* Function to validate drop operations
42
*/
43
type AllowDrop<TreeDataType extends BasicDataNode = DataNode> = (
44
options: AllowDropOptions<TreeDataType>,
45
) => boolean;
46
47
/**
48
* Information provided to drop validation function
49
*/
50
interface AllowDropOptions<TreeDataType extends BasicDataNode = DataNode> {
51
/** The node being dragged */
52
dragNode: TreeDataType;
53
/** The node being dropped onto */
54
dropNode: TreeDataType;
55
/** Drop position relative to dropNode (-1: before, 0: inside, 1: after) */
56
dropPosition: -1 | 0 | 1;
57
}
58
```
59
60
### Drag & Drop Events
61
62
Comprehensive event handling for all phases of drag and drop operations.
63
64
```typescript { .api }
65
/**
66
* Drag and drop event handlers
67
*/
68
interface DragDropEvents<TreeDataType extends BasicDataNode = DataNode> {
69
/** Fired when drag operation starts */
70
onDragStart?: (info: NodeDragEventParams<TreeDataType>) => void;
71
/** Fired when dragged item enters a drop target */
72
onDragEnter?: (info: NodeDragEventParams<TreeDataType> & { expandedKeys: Key[] }) => void;
73
/** Fired continuously while dragging over a drop target */
74
onDragOver?: (info: NodeDragEventParams<TreeDataType>) => void;
75
/** Fired when dragged item leaves a drop target */
76
onDragLeave?: (info: NodeDragEventParams<TreeDataType>) => void;
77
/** Fired when drag operation ends (regardless of success) */
78
onDragEnd?: (info: NodeDragEventParams<TreeDataType>) => void;
79
/** Fired when drop operation completes successfully */
80
onDrop?: (
81
info: NodeDragEventParams<TreeDataType> & {
82
dragNode: EventDataNode<TreeDataType>;
83
dragNodesKeys: Key[];
84
dropPosition: number;
85
dropToGap: boolean;
86
},
87
) => void;
88
}
89
90
/**
91
* Event parameters for drag operations
92
*/
93
type NodeDragEventParams<
94
TreeDataType extends BasicDataNode = DataNode,
95
T = HTMLDivElement,
96
> = {
97
event: React.DragEvent<T>;
98
node: EventDataNode<TreeDataType>;
99
};
100
101
/**
102
* Properties for custom drop indicator rendering
103
*/
104
interface DropIndicatorProps {
105
dropPosition: -1 | 0 | 1;
106
dropLevelOffset: number;
107
indent: number;
108
prefixCls: string;
109
direction: Direction;
110
}
111
```
112
113
**Usage Examples:**
114
115
### Basic Drag & Drop
116
117
```typescript
118
import React, { useState } from "react";
119
import Tree from "rc-tree";
120
121
const BasicDragDrop = () => {
122
const [treeData, setTreeData] = useState([
123
{
124
key: '0-0',
125
title: 'Parent Node',
126
children: [
127
{ key: '0-0-0', title: 'Child 1' },
128
{ key: '0-0-1', title: 'Child 2' },
129
{ key: '0-0-2', title: 'Child 3' },
130
],
131
},
132
{
133
key: '0-1',
134
title: 'Another Parent',
135
children: [
136
{ key: '0-1-0', title: 'Another Child' },
137
],
138
},
139
]);
140
141
const onDrop = (info: any) => {
142
console.log('Drop info:', info);
143
144
const { dragNode, node, dropPosition, dropToGap } = info;
145
const dragKey = dragNode.key;
146
const dropKey = node.key;
147
148
// Implement your tree reorganization logic here
149
console.log(`Moving ${dragKey} to ${dropKey} at position ${dropPosition}`);
150
151
// This is a simplified example - real implementation would
152
// need to properly update the tree data structure
153
};
154
155
return (
156
<Tree
157
prefixCls="rc-tree"
158
draggable
159
treeData={treeData}
160
onDrop={onDrop}
161
defaultExpandAll
162
/>
163
);
164
};
165
```
166
167
### Advanced Drag & Drop with Validation
168
169
```typescript
170
import React, { useState } from "react";
171
import Tree from "rc-tree";
172
173
interface FileNode {
174
key: string;
175
title: string;
176
type: 'file' | 'folder';
177
children?: FileNode[];
178
}
179
180
const AdvancedDragDrop = () => {
181
const [treeData, setTreeData] = useState<FileNode[]>([
182
{
183
key: 'folder-1',
184
title: 'Documents',
185
type: 'folder',
186
children: [
187
{ key: 'file-1', title: 'document.pdf', type: 'file' },
188
{ key: 'file-2', title: 'image.jpg', type: 'file' },
189
],
190
},
191
{
192
key: 'folder-2',
193
title: 'Projects',
194
type: 'folder',
195
children: [
196
{ key: 'file-3', title: 'project.zip', type: 'file' },
197
],
198
},
199
{ key: 'file-4', title: 'readme.txt', type: 'file' },
200
]);
201
202
// Validate drop operations
203
const allowDrop = ({ dragNode, dropNode, dropPosition }: any) => {
204
// Don't allow dropping files into files
205
if (dropNode.type === 'file' && dropPosition === 0) {
206
return false;
207
}
208
209
// Don't allow dropping a folder into its own descendants
210
if (dragNode.type === 'folder' && dropNode.key.startsWith(dragNode.key)) {
211
return false;
212
}
213
214
return true;
215
};
216
217
// Control which nodes can be dragged
218
const nodeDraggable = (node: FileNode) => {
219
// Example: Don't allow dragging system files
220
return !node.title.startsWith('system');
221
};
222
223
const onDrop = (info: any) => {
224
const { dragNode, node, dropPosition, dropToGap } = info;
225
226
console.log('Valid drop:', {
227
dragNode: dragNode.key,
228
dropNode: node.key,
229
dropPosition,
230
dropToGap,
231
});
232
233
// Implement tree data update logic here
234
// This would involve removing the dragNode from its current position
235
// and inserting it at the new position
236
};
237
238
return (
239
<Tree
240
prefixCls="rc-tree"
241
draggable={{
242
icon: <span>π</span>, // Custom drag icon
243
nodeDraggable,
244
}}
245
allowDrop={allowDrop}
246
treeData={treeData}
247
onDrop={onDrop}
248
titleRender={(node) => (
249
<span>
250
{node.type === 'folder' ? 'π' : 'π'} {node.title}
251
</span>
252
)}
253
defaultExpandAll
254
/>
255
);
256
};
257
```
258
259
### Drag & Drop with State Management
260
261
```typescript
262
import React, { useState } from "react";
263
import Tree from "rc-tree";
264
265
const StatefulDragDrop = () => {
266
const [treeData, setTreeData] = useState([
267
{
268
key: '0-0',
269
title: 'Root',
270
children: [
271
{ key: '0-0-0', title: 'Item 1' },
272
{ key: '0-0-1', title: 'Item 2' },
273
{ key: '0-0-2', title: 'Item 3' },
274
],
275
},
276
]);
277
const [expandedKeys, setExpandedKeys] = useState<string[]>(['0-0']);
278
279
// Utility function to find and remove a node
280
const removeNode = (data: any[], key: string): [any[], any | null] => {
281
for (let i = 0; i < data.length; i++) {
282
const item = data[i];
283
if (item.key === key) {
284
return [data.filter((_, index) => index !== i), item];
285
}
286
if (item.children) {
287
const [updatedChildren, removedNode] = removeNode(item.children, key);
288
if (removedNode) {
289
return [
290
data.map((node, index) =>
291
index === i ? { ...node, children: updatedChildren } : node
292
),
293
removedNode
294
];
295
}
296
}
297
}
298
return [data, null];
299
};
300
301
// Utility function to insert a node at a specific position
302
const insertNode = (data: any[], node: any, dropKey: string, dropPosition: number): any[] => {
303
return data.map(item => {
304
if (item.key === dropKey) {
305
if (dropPosition === 0) {
306
// Insert as child
307
return {
308
...item,
309
children: [...(item.children || []), node],
310
};
311
}
312
}
313
if (item.children) {
314
return {
315
...item,
316
children: insertNode(item.children, node, dropKey, dropPosition),
317
};
318
}
319
return item;
320
});
321
};
322
323
const onDrop = (info: any) => {
324
const { dragNode, node, dropPosition } = info;
325
const dragKey = dragNode.key;
326
const dropKey = node.key;
327
328
// Remove the dragged node
329
const [updatedData, draggedNode] = removeNode([...treeData], dragKey);
330
331
if (draggedNode) {
332
// Insert at new position
333
const finalData = insertNode(updatedData, draggedNode, dropKey, dropPosition);
334
setTreeData(finalData);
335
336
// Auto-expand the drop target if dropping inside
337
if (dropPosition === 0 && !expandedKeys.includes(dropKey)) {
338
setExpandedKeys([...expandedKeys, dropKey]);
339
}
340
}
341
};
342
343
const onDragEnter = (info: any) => {
344
console.log('Drag enter:', info);
345
// Auto-expand folders when dragging over them
346
const { node } = info;
347
if (node.children && !expandedKeys.includes(node.key)) {
348
setExpandedKeys([...expandedKeys, node.key]);
349
}
350
};
351
352
return (
353
<Tree
354
prefixCls="rc-tree"
355
draggable
356
treeData={treeData}
357
expandedKeys={expandedKeys}
358
onExpand={setExpandedKeys}
359
onDrop={onDrop}
360
onDragEnter={onDragEnter}
361
/>
362
);
363
};
364
```
365
366
### Custom Drop Indicator
367
368
```typescript
369
import React, { useState } from "react";
370
import Tree from "rc-tree";
371
372
const CustomDropIndicator = () => {
373
const [treeData, setTreeData] = useState([
374
{
375
key: '0-0',
376
title: 'Folder 1',
377
children: [
378
{ key: '0-0-0', title: 'File 1' },
379
{ key: '0-0-1', title: 'File 2' },
380
],
381
},
382
{
383
key: '0-1',
384
title: 'Folder 2',
385
children: [],
386
},
387
]);
388
389
const dropIndicatorRender = (props: any) => {
390
const { dropPosition, dropLevelOffset, prefixCls } = props;
391
392
const style: React.CSSProperties = {
393
position: 'absolute',
394
right: 0,
395
left: dropLevelOffset,
396
height: 2,
397
backgroundColor: '#1890ff',
398
zIndex: 1,
399
pointerEvents: 'none',
400
};
401
402
let className = `${prefixCls}-drop-indicator`;
403
404
if (dropPosition === -1) {
405
style.top = -1;
406
className += ' drop-before';
407
} else if (dropPosition === 1) {
408
style.bottom = -1;
409
className += ' drop-after';
410
} else {
411
// dropPosition === 0 (inside)
412
style.top = -1;
413
style.backgroundColor = '#52c41a';
414
className += ' drop-inside';
415
}
416
417
return <div className={className} style={style} />;
418
};
419
420
return (
421
<Tree
422
prefixCls="rc-tree"
423
draggable
424
treeData={treeData}
425
dropIndicatorRender={dropIndicatorRender}
426
onDrop={(info) => console.log('Drop with custom indicator:', info)}
427
defaultExpandAll
428
/>
429
);
430
};
431
```
432
433
## Drag & Drop Event Details
434
435
### Event Sequence
436
437
```typescript { .api }
438
/**
439
* Typical drag and drop event sequence:
440
* 1. onDragStart - User begins dragging a node
441
* 2. onDragEnter - Dragged item enters a potential drop target
442
* 3. onDragOver - Continuously fired while over drop target
443
* 4. onDragLeave - Dragged item leaves the drop target
444
* 5. onDrop - Drop operation completes (if valid)
445
* 6. onDragEnd - Drag operation ends (always fired)
446
*/
447
448
/**
449
* Enhanced drop event information
450
*/
451
interface DropEventInfo<TreeDataType extends BasicDataNode = DataNode> {
452
/** Native drag event */
453
event: React.DragEvent<HTMLDivElement>;
454
/** The node being dropped onto */
455
node: EventDataNode<TreeDataType>;
456
/** The node being dragged */
457
dragNode: EventDataNode<TreeDataType>;
458
/** Keys of all nodes being dragged (for multi-select drag) */
459
dragNodesKeys: Key[];
460
/** Drop position (-1: before, 0: inside, 1: after) */
461
dropPosition: number;
462
/** Whether dropping into a gap between nodes */
463
dropToGap: boolean;
464
}
465
```
466
467
### Drag State Management
468
469
```typescript { .api }
470
/**
471
* Internal drag state (available in tree context)
472
*/
473
interface DragState {
474
/** Key of the node currently being dragged */
475
draggingNodeKey?: Key;
476
/** Key of the node being dragged over */
477
dragOverNodeKey: Key | null;
478
/** Current drop container key */
479
dropContainerKey: Key | null;
480
/** Current drop target key */
481
dropTargetKey: Key | null;
482
/** Current drop position */
483
dropPosition: -1 | 0 | 1 | null;
484
/** Drop level offset for visual feedback */
485
dropLevelOffset?: number;
486
}
487
```
488
489
## Advanced Drag & Drop Patterns
490
491
### Multi-Node Drag
492
493
```typescript
494
import React, { useState } from "react";
495
import Tree from "rc-tree";
496
497
const MultiNodeDrag = () => {
498
const [treeData, setTreeData] = useState([
499
{
500
key: '0-0',
501
title: 'Source Folder',
502
children: [
503
{ key: '0-0-0', title: 'File 1' },
504
{ key: '0-0-1', title: 'File 2' },
505
{ key: '0-0-2', title: 'File 3' },
506
],
507
},
508
{
509
key: '0-1',
510
title: 'Target Folder',
511
children: [],
512
},
513
]);
514
const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
515
516
const onDrop = (info: any) => {
517
const { dragNodesKeys, node, dropPosition } = info;
518
519
console.log('Multi-node drop:', {
520
draggedNodes: dragNodesKeys,
521
dropTarget: node.key,
522
position: dropPosition,
523
});
524
525
// Handle multiple node movement
526
// Implementation would move all selected nodes
527
};
528
529
return (
530
<Tree
531
prefixCls="rc-tree"
532
draggable
533
selectable
534
multiple
535
treeData={treeData}
536
selectedKeys={selectedKeys}
537
onSelect={setSelectedKeys}
538
onDrop={onDrop}
539
defaultExpandAll
540
/>
541
);
542
};
543
```
544
545
### Conditional Drag & Drop
546
547
```typescript
548
import React, { useState } from "react";
549
import Tree from "rc-tree";
550
551
const ConditionalDragDrop = () => {
552
const [treeData, setTreeData] = useState([
553
{
554
key: 'readonly-folder',
555
title: 'π Read-only Folder',
556
type: 'readonly',
557
children: [
558
{ key: 'readonly-file', title: 'Protected File', type: 'readonly' },
559
],
560
},
561
{
562
key: 'editable-folder',
563
title: 'π Editable Folder',
564
type: 'editable',
565
children: [
566
{ key: 'editable-file', title: 'Normal File', type: 'editable' },
567
],
568
},
569
]);
570
571
const nodeDraggable = (node: any) => {
572
// Only allow dragging editable items
573
return node.type === 'editable';
574
};
575
576
const allowDrop = ({ dragNode, dropNode, dropPosition }: any) => {
577
// Can't drop into readonly containers
578
if (dropNode.type === 'readonly' && dropPosition === 0) {
579
return false;
580
}
581
582
// Can't drop readonly items
583
if (dragNode.type === 'readonly') {
584
return false;
585
}
586
587
return true;
588
};
589
590
return (
591
<Tree
592
prefixCls="rc-tree"
593
draggable={{ nodeDraggable }}
594
allowDrop={allowDrop}
595
treeData={treeData}
596
onDrop={(info) => console.log('Conditional drop:', info)}
597
defaultExpandAll
598
/>
599
);
600
};
601
```