Skip to content

Drag & Drop ​

Blockslides provides a complete drag and drop system for reordering content blocks and handling file uploads. The system consists of three main components: DragHandle for block reordering, Dropcursor for visual feedback, and FileHandler for file operations.

Overview ​

The drag and drop functionality includes:

  • Block reordering - Drag handles that appear next to blocks for intuitive reordering
  • Visual feedback - Drop cursor showing where content will be placed
  • File handling - Drag files from your file system directly into the editor
  • Multi-block selection - Drag multiple selected blocks at once

All drag and drop extensions work together seamlessly to provide a polished editing experience.

Installation ​

Using ExtensionKit ​

The drop cursor is included in ExtensionKit by default:

ts
import { ExtensionKit } from '@blockslides/extension-kit'

const editor = useSlideEditor({
  extensions: [
    ExtensionKit.configure({
      // Drop cursor is enabled by default
      dropcursor: {
        color: '#3b82f6',
        width: 2
      }
    })
  ]
})

Individual Extensions ​

For custom setups, import extensions individually:

ts
import { DragHandle } from '@blockslides/extension-drag-handle'
import { Dropcursor } from '@blockslides/extensions'
import { FileHandler } from '@blockslides/extension-file-handler'

const editor = useSlideEditor({
  extensions: [
    DragHandle.configure({
      render: () => {
        const element = document.createElement('div')
        element.innerHTML = '⋮⋮'
        return element
      }
    }),
    Dropcursor.configure({
      color: '#3b82f6',
      width: 2
    }),
    FileHandler.configure({
      onDrop: (editor, files, pos) => {
        // Handle file drops
      }
    })
  ]
})

Framework-Specific Drag Handles ​

Blockslides provides framework-specific implementations for the drag handle:

React:

ts
import { DragHandle } from '@blockslides/extension-drag-handle-react'

Vue 3:

ts
import { DragHandle } from '@blockslides/extension-drag-handle-vue-3'

Vue 2:

ts
import { DragHandle } from '@blockslides/extension-drag-handle-vue-2'

DragHandle Extension ​

The DragHandle extension adds interactive drag handles that appear next to content blocks when you hover over them. Users can drag these handles to reorder content.

Basic Configuration ​

Configure the drag handle appearance and behavior:

ts
DragHandle.configure({
  // Custom render function
  render: () => {
    const element = document.createElement('div')
    element.classList.add('my-drag-handle')
    element.innerHTML = '⋮⋮'
    return element
  },
  
  // Position configuration using floating-ui
  computePositionConfig: {
    placement: 'left-start',
    strategy: 'absolute'
  },
  
  // Lock the handle in place
  locked: false
})

Options:

  • render (() => HTMLElement) - Function that returns the drag handle element
  • computePositionConfig (ComputePositionConfig) - Floating-ui configuration for handle positioning
  • locked (boolean) - When true, prevents the handle from being dragged
  • onNodeChange ((data: { node: Node | null; editor: Editor; pos: number }) => void) - Callback fired when hovering over different nodes
  • onElementDragStart ((e: DragEvent) => void) - Callback fired when drag starts
  • onElementDragEnd ((e: DragEvent) => void) - Callback fired when drag ends

React Implementation ​

Use the React-specific DragHandle component for better integration:

tsx
import { DragHandle } from '@blockslides/extension-drag-handle-react'
import { useSlideEditor } from '@blockslides/react-prebuilts'

function Editor() {
  const editor = useSlideEditor({
    extensions: [/* your extensions */]
  })

  return (
    <>
      <EditorContent editor={editor} />
      <DragHandle editor={editor}>
        <div className="drag-handle-icon">⋮⋮</div>
      </DragHandle>
    </>
  )
}

Props:

  • editor (required) - The editor instance
  • className (string) - CSS class for the drag handle wrapper
  • pluginKey (PluginKey | string) - Custom plugin key
  • computePositionConfig (ComputePositionConfig) - Positioning configuration
  • onNodeChange - Callback when hovering over different nodes
  • onElementDragStart - Callback when drag starts
  • onElementDragEnd - Callback when drag ends
  • children - React children to render inside the drag handle

Commands ​

The DragHandle extension provides commands to control its behavior:

ts
// Lock the drag handle in place
editor.commands.lockDragHandle()

// Unlock the drag handle
editor.commands.unlockDragHandle()

// Toggle lock state
editor.commands.toggleDragHandle()

These commands are useful when you want to temporarily disable dragging, such as when a popup menu is open.

Tracking Node Changes ​

Use the onNodeChange callback to track which node is being hovered:

ts
DragHandle.configure({
  onNodeChange: ({ node, editor, pos }) => {
    if (node) {
      console.log('Hovering over:', node.type.name)
      console.log('Node position:', pos)
      // Update UI to show node-specific actions
    } else {
      console.log('Not hovering over any node')
    }
  }
})

Drag Lifecycle Callbacks ​

Monitor the drag lifecycle with start and end callbacks:

ts
DragHandle.configure({
  onElementDragStart: (e) => {
    // Fired when user starts dragging
    // Hide tooltips, close menus, etc.
  },
  
  onElementDragEnd: (e) => {
    // Fired when drag operation completes
    // Restore UI state
  }
})

Custom Positioning ​

Control where the drag handle appears using floating-ui configuration:

ts
DragHandle.configure({
  computePositionConfig: {
    placement: 'left-start',  // Position on left side
    strategy: 'absolute',
    middleware: [
      offset(5),  // 5px offset from the block
      flip(),     // Flip if not enough space
    ]
  }
})

Dropcursor Extension ​

The Dropcursor extension displays a visual line indicator showing where content will be dropped when dragging between blocks.

Configuration ​

Customize the drop cursor appearance:

ts
Dropcursor.configure({
  // Cursor color
  color: '#3b82f6',
  
  // Cursor width in pixels
  width: 2,
  
  // CSS class for styling
  class: 'custom-drop-cursor'
})

Options:

  • color (string | false) - CSS color value, or false to disable default color
  • width (number) - Width in pixels (default: 1)
  • class (string) - CSS class applied to the drop cursor element

Styling with CSS ​

When using a custom class, style the drop cursor with CSS:

ts
Dropcursor.configure({
  class: 'my-drop-cursor',
  color: false  // Disable default color
})
css
.my-drop-cursor {
  background: linear-gradient(to right, #3b82f6, #8b5cf6);
  height: 2px;
  border-radius: 1px;
  box-shadow: 0 0 8px rgba(59, 130, 246, 0.5);
}

Disabling Drop Cursor ​

Disable the drop cursor in ExtensionKit:

ts
ExtensionKit.configure({
  dropcursor: false
})

FileHandler Extension ​

The FileHandler extension enables drag-and-drop and paste support for files like images and videos.

Full Documentation

For complete FileHandler documentation including setup, callbacks, and examples, see the Media & Embeds guide.

Drop Events ​

Blockslides fires a drop event whenever content is dropped in the editor, whether from dragging blocks or external content.

Listening to Drop Events ​

Use the editor's event system to listen for drops:

ts
const editor = useSlideEditor({
  onDrop: (event, slice, moved) => {
    // event - Native DragEvent
    // slice - ProseMirror Slice of dropped content
    // moved - Boolean indicating if content was moved within editor
    
    console.log('Content dropped:', slice)
    console.log('Was moved internally:', moved)
  }
})

Or subscribe after initialization:

ts
editor.on('drop', ({ event, slice, moved }) => {
  // Handle drop event
})

Drop Event Parameters ​

  • event (DragEvent) - The native browser drag event
  • slice (Slice) - ProseMirror slice containing the dropped content structure
  • moved (boolean) - true if content was moved from within the same editor, false if from external source

Use Cases ​

Drop events are useful for:

  • Analytics - Track how users reorganize content
  • Validation - Prevent drops in certain locations
  • Side effects - Update related data when content moves
ts
editor.on('drop', ({ event, slice, moved }) => {
  if (moved) {
    // Track internal reorganization
    analytics.track('content_reordered')
  } else {
    // Track external content drops
    analytics.track('content_dropped_from_external')
  }
})

Multi-Block Dragging ​

The DragHandle extension supports dragging multiple blocks when they're selected:

  1. Select multiple blocks using Shift+Click or Cmd/Ctrl+A
  2. Drag any of the selected blocks' handles
  3. All selected blocks move together

The drag handle automatically detects if it's within the current selection and drags all selected content.

Styling ​

Drag Handle Styles ​

Style the drag handle element returned by your render function:

css
.my-drag-handle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  cursor: grab;
  opacity: 0.5;
  transition: opacity 0.2s;
}

.my-drag-handle:hover {
  opacity: 1;
}

.my-drag-handle:active {
  cursor: grabbing;
}

Drag State ​

The drag handle includes a data-dragging attribute that changes during drag operations:

css
.drag-handle[data-dragging="true"] {
  opacity: 0.5;
}

Drop Cursor Styles ​

Customize the drop cursor for your theme:

css
.drop-cursor {
  background: linear-gradient(90deg, 
    transparent 0%, 
    #3b82f6 20%, 
    #3b82f6 80%, 
    transparent 100%
  );
  height: 2px;
  pointer-events: none;
}