insertFromDrop

How insertFromDrop inputType handles drag-and-drop operations and varies across browsers.

Overview

The insertFromDrop inputType is triggered when the user drops content into a contenteditable element via drag-and-drop. The browser inserts the dropped content at the drop position.

Basic Behavior

Scenario: Text dropped at cursor

Before (Cursor position)

HTML:
<p>Hello|world</p>

After dropping "text "

HTML:
<p>Hellotext world</p>
Dropped content inserted at drop position

DataTransfer Access

The beforeinput event provides access to dropped data via e.dataTransfer:

  • e.dataTransfer.getData('text/plain') - Plain text
  • e.dataTransfer.getData('text/html') - HTML content
  • e.dataTransfer.files - File list (for images, etc.)
  • e.dataTransfer.types - Available data types

Browser-Specific Behavior

Chrome/Edge

  • Preserves HTML structure when dropping rich text
  • Supports dropping images as <img> elements
  • May sanitize certain HTML elements for security

Firefox

  • May strip or modify HTML structure more aggressively
  • File handling may differ from Chrome

Safari

  • Drag-and-drop behavior may differ, especially on mobile
  • File handling may require additional permissions

Mobile Considerations

ℹ️ Mobile Drag-and-Drop

On mobile devices, drag-and-drop behavior may differ significantly:

  • Touch-based drag-and-drop may not work the same way as mouse-based drag-and-drop
  • Some mobile browsers may not support insertFromDrop inputType
  • File dropping from external apps may require special handling

Editor-Specific Handling

Different editor frameworks handle drop operations similarly to paste operations. Here's how major editors implement insertFromDrop:

Slate.js

Drop Handling

Slate intercepts drop and converts dropped content to Slate nodes:

    import { Editor, Transforms } from 'slate';

element.addEventListener('beforeinput', (e) => {
  if (e.inputType === 'insertFromDrop' && e.dataTransfer) {
    e.preventDefault();
    
    const htmlString = e.dataTransfer.getData('text/html');
    const plainText = e.dataTransfer.getData('text/plain');
    
    // Parse HTML and convert to Slate nodes (similar to paste)
    const fragment = new DOMParser().parseFromString(htmlString || plainText, 'text/html');
    const nodes = Array.from(fragment.body.childNodes).map(node => {
      // Convert DOM node to Slate node structure
      return jsx('element', { type: 'paragraph' }, [{ text: node.textContent || '' }]);
    });
    
    // Insert nodes at drop position
    Transforms.insertNodes(editor, nodes);
  }
});
  
  • Similar to paste: Uses same HTML parsing and conversion logic as paste operations.
  • Position handling: Drop position is determined by the drop event coordinates.
ProseMirror

Drop Handling

ProseMirror uses DOMParser to parse dropped HTML:

    import { DOMParser } from 'prosemirror-model';
import { schema } from './schema';

view.dom.addEventListener('beforeinput', (e) => {
  if (e.inputType === 'insertFromDrop' && e.dataTransfer) {
    e.preventDefault();
    const { state, dispatch } = view;
    
    const htmlString = e.dataTransfer.getData('text/html');
    const dom = new DOMParser(window).parseFromString(htmlString, 'text/html');
    
    // Parse HTML into ProseMirror nodes
    const fragment = DOMParser.fromSchema(schema).parse(dom.body);
    
    // Insert fragment at drop position
    const tr = state.tr.replaceSelection(fragment);
    dispatch(tr);
  }
});
  
  • Schema-aware: HTML is parsed according to ProseMirror schema rules.
  • Position calculation: Drop position is calculated from event coordinates.
Draft.js

Drop Handling

Draft.js uses convertFromHTML to convert dropped HTML:

    import { EditorState } from 'draft-js';
import { stateFromHTML } from 'draft-js-import-html';

element.addEventListener('beforeinput', (e) => {
  if (e.inputType === 'insertFromDrop' && e.dataTransfer) {
    e.preventDefault();
    
    const htmlString = e.dataTransfer.getData('text/html');
    
    // Convert HTML to ContentState
    const contentState = stateFromHTML(htmlString);
    
    // Get drop position from selection
    const selection = editorState.getSelection();
    
    // Insert content at drop position
    const newContentState = Modifier.replaceWithFragment(
      editorState.getCurrentContent(),
      selection,
      contentState.getBlockMap()
    );
    
    const newState = EditorState.push(
      editorState,
      newContentState,
      'insert-fragment'
    );
    
    setEditorState(newState);
  }
});
  
  • HTML import: Uses stateFromHTML to convert HTML to ContentState.
  • Position handling: Drop position is determined from selection state.

Related resources