Overview
The insertHTML inputType is triggered when HTML content is inserted programmatically (e.g., from paste operations or programmatic insertion). The browser parses the HTML string and inserts it as DOM nodes at the selection position.
Basic Behavior
Scenario 1: HTML string inserted at cursor
Before (Cursor position)
After insertHTML('<strong>bold</strong>')
Scenario 2: HTML replaces selected text
Before (Text selected)
After insertHTML('<em>italic</em>')
Security Considerations
⚠️ XSS Risk
Warning: Inserting arbitrary HTML can lead to XSS (Cross-Site Scripting) vulnerabilities. Always sanitize HTML content before insertion.
Use libraries like DOMPurify to sanitize HTML before inserting it into contenteditable elements.
Browser-Specific Behavior
- HTML parsing behavior may vary between browsers
- Some browsers may strip or modify certain HTML elements for security reasons
- Invalid HTML may be auto-corrected or ignored depending on the browser
IME Composition + insertHTML
⚠️ Critical Issue
During IME composition, inserting HTML (e.g., from paste operations) may cancel the active composition. The composition text may be lost or the HTML may be inserted in an unexpected location.
When handling paste operations, check for active composition state before inserting HTML to avoid interrupting the user's input.
Editor-Specific Handling
Different editor frameworks handle HTML insertion differently, typically converting HTML to their internal model format. Here's how major editors implement insertHTML:
HTML Insertion
Slate parses HTML and converts it to Slate nodes:
import { Editor, Transforms } from 'slate';
import { jsx } from 'slate-hyperscript';
element.addEventListener('beforeinput', (e) => {
if (e.inputType === 'insertHTML' && e.dataTransfer) {
e.preventDefault();
// Parse HTML string to Slate nodes
const htmlString = e.dataTransfer.getData('text/html');
const fragment = new DOMParser().parseFromString(htmlString, 'text/html');
// Convert DOM nodes to Slate nodes (using slate-hyperscript or custom parser)
const nodes = Array.from(fragment.body.childNodes).map(node => {
// Convert DOM node to Slate node structure
// This is a simplified example - real implementation would be more complex
return jsx('element', { type: 'paragraph' }, [{ text: node.textContent }]);
});
// Insert nodes at selection
Transforms.insertNodes(editor, nodes);
}
});
- HTML parsing: Parses HTML string into DOM, then converts to Slate nodes.
- Model conversion: Converts browser HTML structure to Slate's internal node model.
- Schema enforcement: Ensures inserted nodes conform to Slate schema.
HTML Insertion
ProseMirror uses DOMParser to parse HTML into ProseMirror nodes:
import { DOMParser } from 'prosemirror-model';
import { schema } from './schema';
view.dom.addEventListener('beforeinput', (e) => {
if (e.inputType === 'insertHTML' && 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 using schema
const fragment = DOMParser.fromSchema(schema).parse(dom.body);
// Insert fragment at selection
const tr = state.tr.replaceSelection(fragment);
dispatch(tr);
}
});
- DOMParser: Uses ProseMirror's
DOMParser.fromSchema()to parse HTML. - Schema-aware: HTML is parsed according to ProseMirror schema rules.
- Transaction-based: Creates a transaction for undo/redo support.
HTML Insertion
Draft.js uses convertFromHTML to convert HTML to ContentState:
import { EditorState, ContentState } from 'draft-js';
import { stateFromHTML } from 'draft-js-import-html';
element.addEventListener('beforeinput', (e) => {
if (e.inputType === 'insertHTML' && e.dataTransfer) {
e.preventDefault();
const htmlString = e.dataTransfer.getData('text/html');
// Convert HTML to ContentState
const contentState = stateFromHTML(htmlString);
// Get current selection
const selection = editorState.getSelection();
// Insert content at selection
const newContentState = Modifier.replaceWithFragment(
editorState.getCurrentContent(),
selection,
contentState.getBlockMap()
);
const newState = EditorState.push(
editorState,
newContentState,
'insert-fragment'
);
setEditorState(newState);
}
});
- HTML import: Uses
stateFromHTMLto convert HTML to ContentState. - Block conversion: Converts HTML elements to Draft.js ContentBlocks.
- Modifier API: Uses
Modifier.replaceWithFragmentto insert content.