or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

auto-scrolling.mdcomponent-utilities.mddrag-drop-hooks.mdeditor-transforms.mdindex.mdplugin-configuration.mdutility-functions.md

utility-functions.mddocs/

0

# Utility Functions

1

2

Helper functions for calculating drop directions, handling hover states, and determining valid drop operations. These utilities provide the low-level calculations and state management needed for accurate drag-and-drop behavior.

3

4

## Capabilities

5

6

### Direction Calculation Functions

7

8

Functions for determining and managing drop directions based on mouse position and element geometry.

9

10

### getHoverDirection

11

12

Calculates the hover direction based on mouse position relative to an element's bounds, supporting both vertical and horizontal orientations.

13

14

```typescript { .api }

15

/**

16

* Calculates hover direction from mouse coordinates and element bounds

17

* Determines which edge of an element the mouse is closest to

18

* @param options - Configuration for direction calculation

19

* @returns Direction string ('top', 'bottom', 'left', 'right', or empty)

20

*/

21

export function getHoverDirection(options: GetHoverDirectionOptions): string;

22

23

export interface GetHoverDirectionOptions {

24

/** Current mouse coordinates */

25

clientOffset: { x: number; y: number };

26

/** Element's bounding rectangle */

27

hoveredClientRect: DOMRect;

28

/** Drag orientation */

29

orientation?: 'horizontal' | 'vertical';

30

/** Threshold for edge detection (0-1) */

31

threshold?: number;

32

}

33

```

34

35

**Usage Examples:**

36

37

```typescript

38

import { getHoverDirection } from "@udecode/plate-dnd";

39

40

// In a drag hover handler

41

function handleDragHover(monitor: DropTargetMonitor, elementRef: React.RefObject<HTMLElement>) {

42

const clientOffset = monitor.getClientOffset();

43

const element = elementRef.current;

44

45

if (!clientOffset || !element) return;

46

47

const direction = getHoverDirection({

48

clientOffset,

49

hoveredClientRect: element.getBoundingClientRect(),

50

orientation: 'vertical',

51

threshold: 0.25

52

});

53

54

console.log('Hover direction:', direction);

55

return direction;

56

}

57

58

// Custom threshold for different sensitivity

59

function getSensitiveHoverDirection(

60

clientOffset: { x: number; y: number },

61

element: HTMLElement

62

) {

63

return getHoverDirection({

64

clientOffset,

65

hoveredClientRect: element.getBoundingClientRect(),

66

orientation: 'vertical',

67

threshold: 0.1 // More sensitive (10% of element height)

68

});

69

}

70

71

// Horizontal orientation example

72

function getHorizontalDirection(

73

clientOffset: { x: number; y: number },

74

element: HTMLElement

75

) {

76

return getHoverDirection({

77

clientOffset,

78

hoveredClientRect: element.getBoundingClientRect(),

79

orientation: 'horizontal',

80

threshold: 0.3

81

});

82

}

83

84

// In a custom drop zone component

85

function CustomDropZone({ children }) {

86

const elementRef = useRef<HTMLDivElement>(null);

87

const [hoverDirection, setHoverDirection] = useState<string>('');

88

89

const [{ isOver }, drop] = useDrop({

90

accept: 'block',

91

hover: (item, monitor) => {

92

const clientOffset = monitor.getClientOffset();

93

if (!clientOffset || !elementRef.current) return;

94

95

const direction = getHoverDirection({

96

clientOffset,

97

hoveredClientRect: elementRef.current.getBoundingClientRect(),

98

orientation: 'vertical'

99

});

100

101

setHoverDirection(direction);

102

},

103

collect: (monitor) => ({

104

isOver: monitor.isOver()

105

})

106

});

107

108

const combinedRef = useCallback((el: HTMLDivElement) => {

109

elementRef.current = el;

110

drop(el);

111

}, [drop]);

112

113

return (

114

<div

115

ref={combinedRef}

116

style={{

117

position: 'relative',

118

minHeight: '60px',

119

border: isOver ? '2px dashed #007acc' : '2px solid transparent'

120

}}

121

>

122

{/* Visual indicator based on hover direction */}

123

{isOver && hoverDirection && (

124

<div

125

style={{

126

position: 'absolute',

127

backgroundColor: '#007acc',

128

[hoverDirection]: '-2px',

129

...(hoverDirection === 'top' || hoverDirection === 'bottom'

130

? { left: 0, right: 0, height: '4px' }

131

: { top: 0, bottom: 0, width: '4px' }

132

)

133

}}

134

/>

135

)}

136

{children}

137

</div>

138

);

139

}

140

```

141

142

### getNewDirection

143

144

Determines if a direction change has occurred and returns the new direction if different from the previous state.

145

146

```typescript { .api }

147

/**

148

* Determines new drop direction based on previous state

149

* Returns new direction only if it differs from the previous direction

150

* @param previousDir - The previous direction state

151

* @param dir - The current direction

152

* @returns New direction or undefined if no change

153

*/

154

export function getNewDirection(

155

previousDir: string,

156

dir?: string

157

): DropLineDirection | undefined;

158

159

export type DropLineDirection = '' | 'bottom' | 'left' | 'right' | 'top';

160

```

161

162

**Usage Examples:**

163

164

```typescript

165

import { getNewDirection } from "@udecode/plate-dnd";

166

167

// State management for direction changes

168

function useDirectionState() {

169

const [currentDirection, setCurrentDirection] = useState<string>('');

170

171

const updateDirection = (newDir?: string) => {

172

const changedDirection = getNewDirection(currentDirection, newDir);

173

174

if (changedDirection !== undefined) {

175

setCurrentDirection(changedDirection);

176

console.log('Direction changed to:', changedDirection);

177

return true; // Direction changed

178

}

179

180

return false; // No change

181

};

182

183

return { currentDirection, updateDirection };

184

}

185

186

// In a hover handler with direction tracking

187

function SmartHoverHandler({ onDirectionChange }) {

188

const [lastDirection, setLastDirection] = useState<string>('');

189

190

const handleHover = (monitor: DropTargetMonitor, element: HTMLElement) => {

191

const clientOffset = monitor.getClientOffset();

192

if (!clientOffset) return;

193

194

const currentDir = getHoverDirection({

195

clientOffset,

196

hoveredClientRect: element.getBoundingClientRect(),

197

orientation: 'vertical'

198

});

199

200

const newDirection = getNewDirection(lastDirection, currentDir);

201

202

if (newDirection !== undefined) {

203

setLastDirection(newDirection);

204

onDirectionChange?.(newDirection);

205

}

206

};

207

208

return { handleHover };

209

}

210

211

// Debounced direction changes

212

function useDebouncedDirection(delay: number = 50) {

213

const [direction, setDirection] = useState<string>('');

214

const [debouncedDirection, setDebouncedDirection] = useState<string>('');

215

216

useEffect(() => {

217

const timer = setTimeout(() => {

218

const newDir = getNewDirection(debouncedDirection, direction);

219

if (newDir !== undefined) {

220

setDebouncedDirection(newDir);

221

}

222

}, delay);

223

224

return () => clearTimeout(timer);

225

}, [direction, debouncedDirection, delay]);

226

227

return { direction: debouncedDirection, setDirection };

228

}

229

```

230

231

### Query Functions

232

233

Functions for finding and filtering editor nodes based on specific criteria.

234

235

### getBlocksWithId

236

237

Finds all blocks in the editor that have an ID property, which is essential for drag-and-drop operations.

238

239

```typescript { .api }

240

/**

241

* Get blocks with an id property

242

* Finds all editor blocks that have an ID, which are draggable

243

* @param editor - The editor instance

244

* @param options - Options for node searching

245

* @returns Array of node entries for blocks with IDs

246

*/

247

export function getBlocksWithId<E extends Editor>(

248

editor: E,

249

options: EditorNodesOptions<ValueOf<E>>

250

): NodeEntry<TElement>[];

251

252

export type EditorNodesOptions<T> = {

253

/** Location to search within */

254

at?: Location;

255

/** Function to match specific nodes */

256

match?: (node: T) => boolean;

257

/** Search mode */

258

mode?: 'all' | 'highest' | 'lowest';

259

/** Whether to include universal nodes */

260

universal?: boolean;

261

/** Whether to search in reverse order */

262

reverse?: boolean;

263

/** Whether to include void nodes */

264

voids?: boolean;

265

};

266

```

267

268

**Usage Examples:**

269

270

```typescript

271

import { getBlocksWithId } from "@udecode/plate-dnd";

272

273

// Get all draggable blocks in the editor

274

function getAllDraggableBlocks(editor: Editor) {

275

return getBlocksWithId(editor, { at: [] });

276

}

277

278

// Get draggable blocks in the current selection

279

function getDraggableBlocksInSelection(editor: Editor) {

280

if (!editor.selection) return [];

281

282

return getBlocksWithId(editor, {

283

at: editor.selection

284

});

285

}

286

287

// Get specific types of draggable blocks

288

function getDraggableBlocksByType(editor: Editor, blockType: string) {

289

return getBlocksWithId(editor, {

290

match: (node) => node.type === blockType,

291

at: []

292

});

293

}

294

295

// Find draggable blocks in a specific range

296

function getDraggableBlocksInRange(editor: Editor, from: Path, to: Path) {

297

return getBlocksWithId(editor, {

298

at: { anchor: { path: from, offset: 0 }, focus: { path: to, offset: 0 } }

299

});

300

}

301

302

// Count draggable blocks

303

function countDraggableBlocks(editor: Editor): number {

304

const blocks = getBlocksWithId(editor, { at: [] });

305

return blocks.length;

306

}

307

308

// Get draggable blocks with custom filtering

309

function getFilteredDraggableBlocks(

310

editor: Editor,

311

filter: (element: TElement) => boolean

312

) {

313

const allBlocks = getBlocksWithId(editor, { at: [] });

314

return allBlocks.filter(([element]) => filter(element));

315

}

316

317

// Usage in a component

318

function DraggableBlocksList() {

319

const editor = useEditorRef();

320

const [blocks, setBlocks] = useState<NodeEntry<TElement>[]>([]);

321

322

useEffect(() => {

323

const draggableBlocks = getBlocksWithId(editor, { at: [] });

324

setBlocks(draggableBlocks);

325

}, [editor]);

326

327

return (

328

<div>

329

<h3>Draggable Blocks ({blocks.length})</h3>

330

{blocks.map(([element, path]) => (

331

<div key={element.id as string}>

332

Block ID: {element.id} at path: {path.join('.')}

333

</div>

334

))}

335

</div>

336

);

337

}

338

```

339

340

### Practical Integration Examples

341

342

Combining utilities for complete drag-and-drop behavior:

343

344

```typescript

345

import {

346

getHoverDirection,

347

getNewDirection,

348

getBlocksWithId

349

} from "@udecode/plate-dnd";

350

351

// Complete hover direction management

352

function useSmartHoverDirection(

353

elementRef: React.RefObject<HTMLElement>,

354

orientation: 'horizontal' | 'vertical' = 'vertical'

355

) {

356

const [direction, setDirection] = useState<string>('');

357

358

const updateDirection = useCallback((monitor: DropTargetMonitor) => {

359

const clientOffset = monitor.getClientOffset();

360

const element = elementRef.current;

361

362

if (!clientOffset || !element) return;

363

364

const currentDir = getHoverDirection({

365

clientOffset,

366

hoveredClientRect: element.getBoundingClientRect(),

367

orientation

368

});

369

370

const newDir = getNewDirection(direction, currentDir);

371

if (newDir !== undefined) {

372

setDirection(newDir);

373

}

374

}, [direction, elementRef, orientation]);

375

376

return { direction, updateDirection };

377

}

378

379

// Smart block selection during drag operations

380

function useSmartBlockSelection(editor: Editor) {

381

const selectRelevantBlocks = useCallback((draggedBlockId: string) => {

382

const allBlocks = getBlocksWithId(editor, { at: [] });

383

const draggedBlock = allBlocks.find(([el]) => el.id === draggedBlockId);

384

385

if (!draggedBlock) return;

386

387

// If multiple blocks are selected and dragged block is among them,

388

// keep the selection. Otherwise, select just the dragged block.

389

if (editor.selection) {

390

const selectedBlocks = getBlocksWithId(editor, { at: editor.selection });

391

const isDraggedBlockSelected = selectedBlocks.some(([el]) => el.id === draggedBlockId);

392

393

if (!isDraggedBlockSelected) {

394

// Select just the dragged block

395

const [, path] = draggedBlock;

396

editor.tf.select(editor.api.range(path)!);

397

}

398

}

399

}, [editor]);

400

401

return { selectRelevantBlocks };

402

}

403

```

404

405

## Types

406

407

```typescript { .api }

408

export interface GetHoverDirectionOptions {

409

clientOffset: { x: number; y: number };

410

hoveredClientRect: DOMRect;

411

orientation?: 'horizontal' | 'vertical';

412

threshold?: number;

413

}

414

415

export type DropLineDirection = '' | 'bottom' | 'left' | 'right' | 'top';

416

417

export type EditorNodesOptions<T> = {

418

at?: Location;

419

match?: (node: T) => boolean;

420

mode?: 'all' | 'highest' | 'lowest';

421

universal?: boolean;

422

reverse?: boolean;

423

voids?: boolean;

424

};

425

426

export type NodeEntry<T> = [T, Path];

427

428

export type Location = Path | Point | Range;

429

430

export type Path = number[];

431

```