Overview
The insertOrderedList inputType is triggered when the user creates an ordered list (typically via toolbar or programmatically). The browser wraps selected paragraphs in an <ol> with <li> items.
Basic Behavior
Scenario: Multiple paragraphs selected
Before (Paragraphs selected)
After insertOrderedList
Browser-Specific Behavior
- If selection is within an existing list, behavior varies: some browsers convert to ordered list, others merge items
- Empty paragraphs may be converted to empty list items or ignored
- Nested lists may be created if selection spans multiple levels
IME Composition + insertOrderedList
⚠️ Critical Issue
During IME composition, creating an ordered list may cancel the composition or commit it partially. The composition text may be lost or inserted incorrectly into list items.
Similar issues may occur with other block-level operations during composition.
Editor-Specific Handling
Different editor frameworks handle list creation differently. Here's how major editors implement insertOrderedList:
Ordered List Creation
Slate uses Transforms.wrapNodes() to wrap blocks in list containers:
import { Editor, Transforms, Element } from 'slate';
element.addEventListener('beforeinput', (e) => {
if (e.inputType === 'insertOrderedList') {
e.preventDefault();
// Check if already in a list
const [match] = Editor.nodes(editor, {
match: n => Element.isElement(n) && n.type === 'ordered-list',
});
if (match) {
// Unwrap list
Transforms.unwrapNodes(editor, {
match: n => Element.isElement(n) && n.type === 'ordered-list',
});
} else {
// Wrap in ordered list
Transforms.wrapNodes(editor, {
type: 'ordered-list',
children: [],
});
// Convert blocks to list items
Transforms.setNodes(editor, {
type: 'list-item',
});
}
}
});
- Transforms.wrapNodes: Wraps selected blocks in a list container.
- Block conversion: Converts paragraphs to list items using
setNodes. - Toggle behavior: Can unwrap lists if already in a list.
Ordered List Creation
ProseMirror uses wrapIn command to create lists:
import { wrapIn } from 'prosemirror-commands';
import { schema } from './schema';
view.dom.addEventListener('beforeinput', (e) => {
if (e.inputType === 'insertOrderedList') {
e.preventDefault();
const { state, dispatch } = view;
// Check if already in ordered list
const { $from } = state.selection;
const listType = schema.nodes.ordered_list;
const inList = $from.node(-1)?.type === listType;
if (inList) {
// Lift out of list
const { lift } = require('prosemirror-transform');
if (lift(state, dispatch)) {
// Handled
}
} else {
// Wrap in ordered list
if (wrapIn(listType)(state, dispatch)) {
// Handled
}
}
}
});
- wrapIn command: Wraps selection in a list node type.
- Schema-based: Uses schema-defined list and list-item node types.
- Lift command: Can lift content out of lists to toggle behavior.
Ordered List Creation
Draft.js uses RichUtils.toggleBlockType() for lists:
import { EditorState, RichUtils } from 'draft-js';
function handleInsertOrderedList(editorState) {
// Draft.js uses block-level styles for lists
return RichUtils.toggleBlockType(editorState, 'ordered-list-item');
}
// In beforeinput handler
element.addEventListener('beforeinput', (e) => {
if (e.inputType === 'insertOrderedList') {
e.preventDefault();
const newState = handleInsertOrderedList(editorState);
setEditorState(newState);
}
});
- RichUtils.toggleBlockType: Toggles block type between paragraph and list-item.
- Block types: Lists are represented as block types (e.g.,
'ordered-list-item'). - Serialization: Block types are converted to HTML list elements during rendering.