Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ updates:
- dependency-name: "@tiptap/extension-underline"
# prosemirror packages
- dependency-name: "prosemirror-changeset"
- dependency-name: "prosemirror-dropcursor"
- dependency-name: "prosemirror-highlight"
- dependency-name: "prosemirror-model"
- dependency-name: "prosemirror-state"
Expand Down
6 changes: 6 additions & 0 deletions examples/03-ui-components/18-drag-n-drop/.bnexample.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"playground": true,
"docs": false,
"author": "nperez0111",
"tags": ["Intermediate", "UI Components", "Drag & Drop", "Customization"]
}
47 changes: 47 additions & 0 deletions examples/03-ui-components/18-drag-n-drop/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Drag & Drop Exclusion

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.

## Features

- **Drag Exclusion**: Elements with the `bn-drag-exclude` classname are treated as separate drag & drop operations
- **Independent Drag Areas**: Create custom drag & drop functionality alongside BlockNote's editor
- **No Interference**: Custom drag operations won't trigger BlockNote's block reordering
- **Side-by-side Demo**: Shows the editor and custom drag area working independently

## How It Works

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.

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.

## Code Highlights

### Import the constant:

```tsx
import { DRAG_EXCLUSION_CLASSNAME } from "@blocknote/core";
```

### Apply it to your custom drag area:

```tsx
<div className={"drag-demo-section " + DRAG_EXCLUSION_CLASSNAME}>
{/* Your custom drag & drop UI */}
<div draggable onDragStart={handleDragStart} onDrop={handleDrop}>
Custom draggable items
</div>
</div>
```

## Use Cases

- **Custom UI elements**: Add draggable components within or near the editor
- **File upload areas**: Create drag-and-drop file upload zones
- **Sortable lists**: Implement custom sortable lists alongside the editor
- **External integrations**: Integrate with third-party drag & drop libraries

**Relevant Docs:**

- [Side Menu (Drag Handle)](/docs/react/components/side-menu)
- [Editor Setup](/docs/getting-started/editor-setup)
14 changes: 14 additions & 0 deletions examples/03-ui-components/18-drag-n-drop/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Drag &amp; Drop Exclusion</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
</script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>
11 changes: 11 additions & 0 deletions examples/03-ui-components/18-drag-n-drop/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./src/App.jsx";

const root = createRoot(document.getElementById("root")!);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
31 changes: 31 additions & 0 deletions examples/03-ui-components/18-drag-n-drop/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "@blocknote/example-ui-components-drag-n-drop",
"description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
"type": "module",
"private": true,
"version": "0.12.4",
"scripts": {
"start": "vite",
"dev": "vite",
"build:prod": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"@blocknote/ariakit": "latest",
"@blocknote/core": "latest",
"@blocknote/mantine": "latest",
"@blocknote/react": "latest",
"@blocknote/shadcn": "latest",
"@mantine/core": "^8.3.11",
"@mantine/hooks": "^8.3.11",
"@mantine/utils": "^6.0.22",
"react": "^19.2.3",
"react-dom": "^19.2.3"
},
"devDependencies": {
"@types/react": "^19.2.3",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^4.7.0",
"vite": "^5.4.20"
}
}
133 changes: 133 additions & 0 deletions examples/03-ui-components/18-drag-n-drop/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import "@blocknote/core/fonts/inter.css";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import { useCreateBlockNote } from "@blocknote/react";
import { useState } from "react";
import "./styles.css";

export default function App() {
// Creates a new editor instance.
const editor = useCreateBlockNote({
initialContent: [
{
type: "paragraph",
content: "Welcome to the Drag & Drop Exclusion demo!",
},
{
type: "paragraph",
content:
"Try dragging the blocks in the editor - they will work as normal.",
},
{
type: "paragraph",
content:
"Now try dragging the colored boxes on the right - they won't interfere with the editor's drag & drop!",
},
],
});

const [draggedItems, setDraggedItems] = useState([
{ id: "1", color: "#FF6B6B", label: "Red Item" },
{ id: "2", color: "#4ECDC4", label: "Teal Item" },
{ id: "3", color: "#45B7D1", label: "Blue Item" },
{ id: "4", color: "#FFA07A", label: "Orange Item" },
]);

const [droppedItems, setDroppedItems] = useState<typeof draggedItems>([]);

const handleDragStart = (
e: React.DragEvent,
item: (typeof draggedItems)[0],
) => {
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setData("custom-item", JSON.stringify(item));
};

const handleDragOver = (e: React.DragEvent) => {
e.preventDefault();
e.dataTransfer.dropEffect = "move";
};

const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
const data = e.dataTransfer.getData("custom-item");
if (data) {
const item = JSON.parse(data);
setDroppedItems((prev) => [...prev, item]);
setDraggedItems((prev) => prev.filter((i) => i.id !== item.id));
}
};

const handleReset = () => {
setDraggedItems([
{ id: "1", color: "#FF6B6B", label: "Red Item" },
{ id: "2", color: "#4ECDC4", label: "Teal Item" },
{ id: "3", color: "#45B7D1", label: "Blue Item" },
{ id: "4", color: "#FFA07A", label: "Orange Item" },
]);
setDroppedItems([]);
};

return (
<div className="app-container">
<div className="editor-section">
<h2>BlockNote Editor</h2>
<BlockNoteView editor={editor} />
</div>

<div className={`drag-demo-section bn-drag-exclude`}>
<h2>Separate Drag & Drop Area</h2>
<p className="info-text">
This area uses the <code>bn-drag-exclude</code> classname, so dragging
items here won't interfere with the editor.
</p>

<div className="drag-columns">
<div className="drag-column">
<h3>Draggable Items</h3>
<div className="items-container">
{draggedItems.map((item) => (
<div
key={item.id}
className="draggable-item"
draggable
onDragStart={(e) => handleDragStart(e, item)}
style={{ backgroundColor: item.color }}
>
{item.label}
</div>
))}
</div>
</div>

<div
className="drag-column drop-zone"
onDragOver={handleDragOver}
onDrop={handleDrop}
>
<h3>Drop Zone</h3>
<div className="items-container">
{droppedItems.length === 0 ? (
<p className="placeholder">Drop items here</p>
) : (
droppedItems.map((item) => (
<div
key={item.id}
className="draggable-item"
style={{ backgroundColor: item.color }}
>
{item.label}
</div>
))
)}
</div>
</div>
</div>

<button className="reset-button" onClick={handleReset}>
Reset Items
</button>
</div>
</div>
);
}
Loading
Loading