or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

async-loading.mddata-management.mddrag-drop.mdindex.mdselection-checking.mdtree-component.mdtree-node.mdvirtual-scrolling.md

drag-drop.mddocs/

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

```