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
```