or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

context-provider.mddrag-layer.mddrag-sources.mddrop-targets.mdindex.mdutilities.md

drag-layer.mddocs/

0

# Drag Layer

1

2

The useDragLayer hook enables access to global drag state for creating custom drag previews and drag-aware components that respond to drag operations anywhere in the application.

3

4

## Capabilities

5

6

### useDragLayer Hook

7

8

Hook for accessing global drag state and creating custom drag layer components.

9

10

```typescript { .api }

11

/**

12

* Hook for accessing drag layer state for custom drag previews

13

* @param collect - Function to collect properties from the drag layer monitor

14

* @returns Collected properties from the monitor

15

*/

16

function useDragLayer<CollectedProps, DragObject = any>(

17

collect: (monitor: DragLayerMonitor<DragObject>) => CollectedProps

18

): CollectedProps;

19

```

20

21

**Basic Usage:**

22

23

```typescript

24

import React from "react";

25

import { useDragLayer } from "react-dnd";

26

27

function CustomDragLayer() {

28

const { isDragging, itemType, item, currentOffset } = useDragLayer((monitor) => ({

29

isDragging: monitor.isDragging(),

30

itemType: monitor.getItemType(),

31

item: monitor.getItem(),

32

currentOffset: monitor.getClientOffset(),

33

}));

34

35

if (!isDragging || !currentOffset) {

36

return null;

37

}

38

39

return (

40

<div

41

style={{

42

position: "fixed",

43

pointerEvents: "none",

44

zIndex: 100,

45

left: currentOffset.x,

46

top: currentOffset.y,

47

transform: "translate(-50%, -50%)",

48

}}

49

>

50

<CustomPreview itemType={itemType} item={item} />

51

</div>

52

);

53

}

54

55

function CustomPreview({ itemType, item }) {

56

switch (itemType) {

57

case "card":

58

return <div className="card-preview">{item.name}</div>;

59

case "file":

60

return <div className="file-preview">📄 {item.filename}</div>;

61

default:

62

return <div className="default-preview">Dragging...</div>;

63

}

64

}

65

```

66

67

**Advanced Drag Layer with Animations:**

68

69

```typescript

70

import React, { useState, useEffect } from "react";

71

import { useDragLayer } from "react-dnd";

72

73

function AnimatedDragLayer() {

74

const [trail, setTrail] = useState([]);

75

76

const {

77

isDragging,

78

itemType,

79

item,

80

currentOffset,

81

initialOffset,

82

differenceFromInitialOffset,

83

} = useDragLayer((monitor) => ({

84

isDragging: monitor.isDragging(),

85

itemType: monitor.getItemType(),

86

item: monitor.getItem(),

87

currentOffset: monitor.getClientOffset(),

88

initialOffset: monitor.getInitialClientOffset(),

89

differenceFromInitialOffset: monitor.getDifferenceFromInitialOffset(),

90

}));

91

92

// Create trail effect

93

useEffect(() => {

94

if (isDragging && currentOffset) {

95

setTrail(prev => [

96

...prev.slice(-10), // Keep last 10 positions

97

{ x: currentOffset.x, y: currentOffset.y, timestamp: Date.now() }

98

]);

99

} else {

100

setTrail([]);

101

}

102

}, [isDragging, currentOffset]);

103

104

if (!isDragging) {

105

return null;

106

}

107

108

const layerStyles = {

109

position: "fixed" as const,

110

pointerEvents: "none" as const,

111

zIndex: 100,

112

left: 0,

113

top: 0,

114

width: "100%",

115

height: "100%",

116

};

117

118

return (

119

<div style={layerStyles}>

120

{/* Trail effect */}

121

{trail.map((point, index) => (

122

<div

123

key={point.timestamp}

124

style={{

125

position: "absolute",

126

left: point.x,

127

top: point.y,

128

width: 4,

129

height: 4,

130

backgroundColor: "rgba(0, 100, 255, " + (index / trail.length) + ")",

131

borderRadius: "50%",

132

transform: "translate(-50%, -50%)",

133

}}

134

/>

135

))}

136

137

{/* Main preview */}

138

{currentOffset && (

139

<div

140

style={{

141

position: "absolute",

142

left: currentOffset.x,

143

top: currentOffset.y,

144

transform: "translate(-50%, -50%) rotate(" +

145

(differenceFromInitialOffset ? differenceFromInitialOffset.x * 0.1 : 0) + "deg)",

146

transition: "transform 0.1s ease-out",

147

}}

148

>

149

<DragPreview itemType={itemType} item={item} />

150

</div>

151

)}

152

</div>

153

);

154

}

155

```

156

157

### Drag Layer Monitor

158

159

Monitor interface providing global drag state information.

160

161

```typescript { .api }

162

interface DragLayerMonitor<DragObject = unknown> {

163

/** Returns true if any drag operation is in progress */

164

isDragging(): boolean;

165

/** Returns the type of item being dragged */

166

getItemType(): Identifier | null;

167

/** Returns the dragged item data */

168

getItem<T = DragObject>(): T;

169

/** Returns initial pointer coordinates when drag started */

170

getInitialClientOffset(): XYCoord | null;

171

/** Returns initial drag source coordinates */

172

getInitialSourceClientOffset(): XYCoord | null;

173

/** Returns current pointer coordinates */

174

getClientOffset(): XYCoord | null;

175

/** Returns pointer movement since drag start */

176

getDifferenceFromInitialOffset(): XYCoord | null;

177

/** Returns projected source coordinates */

178

getSourceClientOffset(): XYCoord | null;

179

}

180

```

181

182

## Common Patterns

183

184

### Multi-type Custom Previews

185

186

```typescript

187

function TypeAwareDragLayer() {

188

const { isDragging, itemType, item, currentOffset } = useDragLayer((monitor) => ({

189

isDragging: monitor.isDragging(),

190

itemType: monitor.getItemType(),

191

item: monitor.getItem(),

192

currentOffset: monitor.getClientOffset(),

193

}));

194

195

const renderPreview = () => {

196

switch (itemType) {

197

case "card":

198

return (

199

<div className="card-drag-preview">

200

<h4>{item.title}</h4>

201

<p>{item.content}</p>

202

</div>

203

);

204

205

case "file":

206

return (

207

<div className="file-drag-preview">

208

<span className="file-icon">{getFileIcon(item.type)}</span>

209

<span className="file-name">{item.name}</span>

210

<span className="file-size">{formatSize(item.size)}</span>

211

</div>

212

);

213

214

case "list-item":

215

return (

216

<div className="list-item-preview">

217

<div className="item-count">{item.items?.length || 1} item(s)</div>

218

<div className="item-title">{item.title}</div>

219

</div>

220

);

221

222

default:

223

return <div className="default-preview">Dragging {itemType}</div>;

224

}

225

};

226

227

if (!isDragging || !currentOffset) {

228

return null;

229

}

230

231

return (

232

<div

233

style={{

234

position: "fixed",

235

pointerEvents: "none",

236

zIndex: 1000,

237

left: currentOffset.x,

238

top: currentOffset.y,

239

transform: "translate(-50%, -50%)",

240

}}

241

>

242

{renderPreview()}

243

</div>

244

);

245

}

246

```

247

248

### Responsive Drag Feedback

249

250

```typescript

251

function ResponsiveDragLayer() {

252

const {

253

isDragging,

254

item,

255

currentOffset,

256

differenceFromInitialOffset,

257

} = useDragLayer((monitor) => ({

258

isDragging: monitor.isDragging(),

259

item: monitor.getItem(),

260

currentOffset: monitor.getClientOffset(),

261

differenceFromInitialOffset: monitor.getDifferenceFromInitialOffset(),

262

}));

263

264

if (!isDragging || !currentOffset || !differenceFromInitialOffset) {

265

return null;

266

}

267

268

// Calculate drag velocity and direction

269

const velocity = Math.sqrt(

270

Math.pow(differenceFromInitialOffset.x, 2) +

271

Math.pow(differenceFromInitialOffset.y, 2)

272

);

273

274

const scale = Math.min(1.2, 1 + velocity * 0.001);

275

const rotation = differenceFromInitialOffset.x * 0.05;

276

277

return (

278

<div

279

style={{

280

position: "fixed",

281

pointerEvents: "none",

282

zIndex: 100,

283

left: currentOffset.x,

284

top: currentOffset.y,

285

transform: `translate(-50%, -50%) scale(${scale}) rotate(${rotation}deg)`,

286

transition: "transform 0.1s ease-out",

287

}}

288

>

289

<div className="responsive-preview">

290

{item.name}

291

<div className="velocity-indicator" style={{ opacity: velocity * 0.01 }}>

292

Fast!

293

</div>

294

</div>

295

</div>

296

);

297

}

298

```

299

300

### Drag State Indicator

301

302

```typescript

303

function DragStateIndicator() {

304

const { isDragging, itemType, currentOffset } = useDragLayer((monitor) => ({

305

isDragging: monitor.isDragging(),

306

itemType: monitor.getItemType(),

307

currentOffset: monitor.getClientOffset(),

308

}));

309

310

return (

311

<div className="drag-state-indicator">

312

<div className={`status ${isDragging ? "active" : "inactive"}`}>

313

{isDragging ? `Dragging ${itemType}` : "No active drag"}

314

</div>

315

316

{isDragging && currentOffset && (

317

<div className="coordinates">

318

Position: {Math.round(currentOffset.x)}, {Math.round(currentOffset.y)}

319

</div>

320

)}

321

</div>

322

);

323

}

324

```

325

326

### Drag Constraints Visualization

327

328

```typescript

329

function ConstrainedDragLayer({ bounds }) {

330

const { isDragging, currentOffset, item } = useDragLayer((monitor) => ({

331

isDragging: monitor.isDragging(),

332

currentOffset: monitor.getClientOffset(),

333

item: monitor.getItem(),

334

}));

335

336

if (!isDragging || !currentOffset) {

337

return null;

338

}

339

340

// Check if drag is within allowed bounds

341

const isWithinBounds = bounds &&

342

currentOffset.x >= bounds.left &&

343

currentOffset.x <= bounds.right &&

344

currentOffset.y >= bounds.top &&

345

currentOffset.y <= bounds.bottom;

346

347

return (

348

<div

349

style={{

350

position: "fixed",

351

pointerEvents: "none",

352

zIndex: 100,

353

left: currentOffset.x,

354

top: currentOffset.y,

355

transform: "translate(-50%, -50%)",

356

}}

357

>

358

<div

359

className={`constrained-preview ${isWithinBounds ? "valid" : "invalid"}`}

360

style={{

361

border: isWithinBounds ? "2px solid green" : "2px solid red",

362

backgroundColor: isWithinBounds ? "rgba(0,255,0,0.1)" : "rgba(255,0,0,0.1)",

363

}}

364

>

365

{item.name}

366

{!isWithinBounds && <div className="warning">⚠️ Outside bounds</div>}

367

</div>

368

</div>

369

);

370

}

371

```

372

373

### Global Drag Layer Portal

374

375

```typescript

376

import React from "react";

377

import { createPortal } from "react-dom";

378

import { useDragLayer } from "react-dnd";

379

380

function GlobalDragLayerPortal() {

381

const { isDragging, itemType, item, currentOffset } = useDragLayer((monitor) => ({

382

isDragging: monitor.isDragging(),

383

itemType: monitor.getItemType(),

384

item: monitor.getItem(),

385

currentOffset: monitor.getClientOffset(),

386

}));

387

388

if (!isDragging || !currentOffset) {

389

return null;

390

}

391

392

const dragLayer = (

393

<div

394

style={{

395

position: "fixed",

396

pointerEvents: "none",

397

zIndex: 9999,

398

left: currentOffset.x,

399

top: currentOffset.y,

400

transform: "translate(-50%, -50%)",

401

}}

402

>

403

<GlobalDragPreview itemType={itemType} item={item} />

404

</div>

405

);

406

407

// Render to body to ensure it's always on top

408

return createPortal(dragLayer, document.body);

409

}

410

411

function GlobalDragPreview({ itemType, item }) {

412

return (

413

<div className={`global-drag-preview ${itemType}`}>

414

<div className="preview-content">

415

{item.name || item.title || "Dragging..."}

416

</div>

417

<div className="preview-shadow" />

418

</div>

419

);

420

}

421

```