Skip to content

Commit d8691dd

Browse files
authored
refactor: unified drop cursor extension (#2550)
1 parent 0e94795 commit d8691dd

File tree

24 files changed

+1295
-555
lines changed

24 files changed

+1295
-555
lines changed

.github/dependabot.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ updates:
2828
- dependency-name: "@tiptap/extension-underline"
2929
# prosemirror packages
3030
- dependency-name: "prosemirror-changeset"
31-
- dependency-name: "prosemirror-dropcursor"
3231
- dependency-name: "prosemirror-highlight"
3332
- dependency-name: "prosemirror-model"
3433
- dependency-name: "prosemirror-state"
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"playground": true,
3+
"docs": false,
4+
"author": "nperez0111",
5+
"tags": ["Intermediate", "UI Components", "Drag & Drop", "Customization"]
6+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Drag & Drop Exclusion
2+
3+
This example demonstrates how to use the `DRAG_EXCLUSION_CLASSNAME` to create separate drag & drop areas that don't interfere with BlockNote's built-in block drag & drop functionality.
4+
5+
## Features
6+
7+
- **Drag Exclusion**: Elements with the `bn-drag-exclude` classname are treated as separate drag & drop operations
8+
- **Independent Drag Areas**: Create custom drag & drop functionality alongside BlockNote's editor
9+
- **No Interference**: Custom drag operations won't trigger BlockNote's block reordering
10+
- **Side-by-side Demo**: Shows the editor and custom drag area working independently
11+
12+
## How It Works
13+
14+
By adding the `DRAG_EXCLUSION_CLASSNAME` (`bn-drag-exclude`) to an element, you tell BlockNote's drag & drop handlers to ignore all drag events within that element and its children. This allows you to implement your own custom drag & drop logic without conflicts.
15+
16+
The exclusion check works by traversing up the DOM tree from the drag event target, checking if any ancestor has the exclusion classname. If found, BlockNote's handlers return early, leaving your custom handlers in full control.
17+
18+
## Code Highlights
19+
20+
### Import the constant:
21+
22+
```tsx
23+
import { DRAG_EXCLUSION_CLASSNAME } from "@blocknote/core";
24+
```
25+
26+
### Apply it to your custom drag area:
27+
28+
```tsx
29+
<div className={"drag-demo-section " + DRAG_EXCLUSION_CLASSNAME}>
30+
{/* Your custom drag & drop UI */}
31+
<div draggable onDragStart={handleDragStart} onDrop={handleDrop}>
32+
Custom draggable items
33+
</div>
34+
</div>
35+
```
36+
37+
## Use Cases
38+
39+
- **Custom UI elements**: Add draggable components within or near the editor
40+
- **File upload areas**: Create drag-and-drop file upload zones
41+
- **Sortable lists**: Implement custom sortable lists alongside the editor
42+
- **External integrations**: Integrate with third-party drag & drop libraries
43+
44+
**Relevant Docs:**
45+
46+
- [Side Menu (Drag Handle)](/docs/react/components/side-menu)
47+
- [Editor Setup](/docs/getting-started/editor-setup)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<html lang="en">
2+
<head>
3+
<meta charset="UTF-8" />
4+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
5+
<title>Drag &amp; Drop Exclusion</title>
6+
<script>
7+
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
8+
</script>
9+
</head>
10+
<body>
11+
<div id="root"></div>
12+
<script type="module" src="./main.tsx"></script>
13+
</body>
14+
</html>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
2+
import React from "react";
3+
import { createRoot } from "react-dom/client";
4+
import App from "./src/App.jsx";
5+
6+
const root = createRoot(document.getElementById("root")!);
7+
root.render(
8+
<React.StrictMode>
9+
<App />
10+
</React.StrictMode>
11+
);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "@blocknote/example-ui-components-drag-n-drop",
3+
"description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
4+
"type": "module",
5+
"private": true,
6+
"version": "0.12.4",
7+
"scripts": {
8+
"start": "vite",
9+
"dev": "vite",
10+
"build:prod": "tsc && vite build",
11+
"preview": "vite preview"
12+
},
13+
"dependencies": {
14+
"@blocknote/ariakit": "latest",
15+
"@blocknote/core": "latest",
16+
"@blocknote/mantine": "latest",
17+
"@blocknote/react": "latest",
18+
"@blocknote/shadcn": "latest",
19+
"@mantine/core": "^8.3.11",
20+
"@mantine/hooks": "^8.3.11",
21+
"@mantine/utils": "^6.0.22",
22+
"react": "^19.2.3",
23+
"react-dom": "^19.2.3"
24+
},
25+
"devDependencies": {
26+
"@types/react": "^19.2.3",
27+
"@types/react-dom": "^19.2.3",
28+
"@vitejs/plugin-react": "^4.7.0",
29+
"vite": "^5.4.20"
30+
}
31+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import "@blocknote/core/fonts/inter.css";
2+
import { BlockNoteView } from "@blocknote/mantine";
3+
import "@blocknote/mantine/style.css";
4+
import { useCreateBlockNote } from "@blocknote/react";
5+
import { useState } from "react";
6+
import "./styles.css";
7+
8+
export default function App() {
9+
// Creates a new editor instance.
10+
const editor = useCreateBlockNote({
11+
initialContent: [
12+
{
13+
type: "paragraph",
14+
content: "Welcome to the Drag & Drop Exclusion demo!",
15+
},
16+
{
17+
type: "paragraph",
18+
content:
19+
"Try dragging the blocks in the editor - they will work as normal.",
20+
},
21+
{
22+
type: "paragraph",
23+
content:
24+
"Now try dragging the colored boxes on the right - they won't interfere with the editor's drag & drop!",
25+
},
26+
],
27+
});
28+
29+
const [draggedItems, setDraggedItems] = useState([
30+
{ id: "1", color: "#FF6B6B", label: "Red Item" },
31+
{ id: "2", color: "#4ECDC4", label: "Teal Item" },
32+
{ id: "3", color: "#45B7D1", label: "Blue Item" },
33+
{ id: "4", color: "#FFA07A", label: "Orange Item" },
34+
]);
35+
36+
const [droppedItems, setDroppedItems] = useState<typeof draggedItems>([]);
37+
38+
const handleDragStart = (
39+
e: React.DragEvent,
40+
item: (typeof draggedItems)[0],
41+
) => {
42+
e.dataTransfer.effectAllowed = "move";
43+
e.dataTransfer.setData("custom-item", JSON.stringify(item));
44+
};
45+
46+
const handleDragOver = (e: React.DragEvent) => {
47+
e.preventDefault();
48+
e.dataTransfer.dropEffect = "move";
49+
};
50+
51+
const handleDrop = (e: React.DragEvent) => {
52+
e.preventDefault();
53+
const data = e.dataTransfer.getData("custom-item");
54+
if (data) {
55+
const item = JSON.parse(data);
56+
setDroppedItems((prev) => [...prev, item]);
57+
setDraggedItems((prev) => prev.filter((i) => i.id !== item.id));
58+
}
59+
};
60+
61+
const handleReset = () => {
62+
setDraggedItems([
63+
{ id: "1", color: "#FF6B6B", label: "Red Item" },
64+
{ id: "2", color: "#4ECDC4", label: "Teal Item" },
65+
{ id: "3", color: "#45B7D1", label: "Blue Item" },
66+
{ id: "4", color: "#FFA07A", label: "Orange Item" },
67+
]);
68+
setDroppedItems([]);
69+
};
70+
71+
return (
72+
<div className="app-container">
73+
<div className="editor-section">
74+
<h2>BlockNote Editor</h2>
75+
<BlockNoteView editor={editor} />
76+
</div>
77+
78+
<div className={`drag-demo-section bn-drag-exclude`}>
79+
<h2>Separate Drag & Drop Area</h2>
80+
<p className="info-text">
81+
This area uses the <code>bn-drag-exclude</code> classname, so dragging
82+
items here won't interfere with the editor.
83+
</p>
84+
85+
<div className="drag-columns">
86+
<div className="drag-column">
87+
<h3>Draggable Items</h3>
88+
<div className="items-container">
89+
{draggedItems.map((item) => (
90+
<div
91+
key={item.id}
92+
className="draggable-item"
93+
draggable
94+
onDragStart={(e) => handleDragStart(e, item)}
95+
style={{ backgroundColor: item.color }}
96+
>
97+
{item.label}
98+
</div>
99+
))}
100+
</div>
101+
</div>
102+
103+
<div
104+
className="drag-column drop-zone"
105+
onDragOver={handleDragOver}
106+
onDrop={handleDrop}
107+
>
108+
<h3>Drop Zone</h3>
109+
<div className="items-container">
110+
{droppedItems.length === 0 ? (
111+
<p className="placeholder">Drop items here</p>
112+
) : (
113+
droppedItems.map((item) => (
114+
<div
115+
key={item.id}
116+
className="draggable-item"
117+
style={{ backgroundColor: item.color }}
118+
>
119+
{item.label}
120+
</div>
121+
))
122+
)}
123+
</div>
124+
</div>
125+
</div>
126+
127+
<button className="reset-button" onClick={handleReset}>
128+
Reset Items
129+
</button>
130+
</div>
131+
</div>
132+
);
133+
}

0 commit comments

Comments
 (0)