Understanding blocks and block-level operations in Yoopta Editor
Blocks are the top-level content units in Yoopta Editor. Each block represents a distinct piece of content like a paragraph, heading, image, or code block. Blocks can be nested, reordered, and transformed independently.
Every block in Yoopta Editor follows this structure:
packages/core/editor/src/editor/types.ts
type YooptaBlockData = { id: string; // Unique block identifier type: string; // Block type (PascalCase: "Paragraph", "HeadingOne") value: SlateElement[]; // Array of Slate elements meta: YooptaBlockBaseMeta;};type YooptaBlockBaseMeta = { order: number; // Position in document (0-based) depth: number; // Nesting level (0 = root) align?: 'left' | 'center' | 'right'; // Text alignment};
Block types use PascalCase (“Paragraph”, “HeadingOne”) while element types within blocks use kebab-case (“paragraph”, “heading-one”). This convention helps distinguish between the block API and Slate element API.
The elements option is powerful for creating blocks with pre-filled content. This is commonly used when inserting templates or converting between block types.
// Delete a specific blockeditor.deleteBlock(blockId);// Delete with optionsimport { Blocks } from '@yoopta/editor';Blocks.deleteBlock(editor, blockId, { focus: true // Focus the next block after deletion});
Deleting a block is permanent and cannot be undone programmatically. Users can still undo with Ctrl+Z (Cmd+Z), but your code should handle deletion carefully.
Change a block’s type while preserving its content:
// Convert paragraph to headingeditor.toggleBlock(blockId, { type: 'HeadingOne'});// Toggle preserves text formatting and content
The toggleBlock operation intelligently converts content between block types, preserving text formatting (bold, italic, etc.) while adapting to the new block’s structure.
Block depth is used for nested content like lists, tasks, and hierarchical structures. The depth value starts at 0 (root level) and increases for nested items.
// Focus block and place cursor at starteditor.focusBlock(blockId);// Focus with optionseditor.focusBlock(blockId, { at: 'end' // Place cursor at end of block});
// Get all blocksconst content = editor.getEditorValue();// Iterate over blocksObject.values(content).forEach(block => { console.log(`Block ${block.id} (${block.type}) at position ${block.meta.order}`);});// Sort blocks by orderconst sortedBlocks = Object.values(content) .sort((a, b) => a.meta.order - b.meta.order);// Set new contenteditor.setEditorValue({ [blockId1]: block1, [blockId2]: block2,});
When setting editor value with setEditorValue, make sure all blocks have correct order values starting from 0 with no gaps.
editor.batchOperations(() => { // Multiple operations in a single transaction editor.insertBlock('Paragraph'); editor.insertBlock('HeadingOne'); editor.insertBlock('Paragraph');});// This triggers only one 'change' event instead of three