

Blocks are the top-level units of a document. Plugins register block types; the document stores only the type name and data, and the definition decides how the block is rendered and edited.
const documentBlocksPlugin = defineNgsHeadlessEditorPlugin({
id: 'document-blocks',
blocks: [
{
type: 'heading',
tagName: 'h3',
create: () => createNgsHeadlessEditorParagraph() // Enter leaves the heading
},
{
type: 'bulletItem',
tagName: 'ul',
contentTagName: 'li',
create: () => ({ ...createNgsHeadlessEditorParagraph(), type: 'bulletItem' })
},
{
type: 'divider',
tagName: 'div',
editable: false,
create: createDivider,
render: element => element.append(element.ownerDocument.createElement('hr')),
isEmpty: () => false
}
]
});| Member | Description |
|---|---|
type | Unique block type stored in the document. |
tagName | Element that represents the block. |
contentTagName | Optional nested element that holds the text, for example ul with li or pre with code. |
editable | Set to false for atomic blocks the browser must not edit. |
create() | Creates an empty block of this type. Enter in a non-empty block uses it for the block that follows: return a paragraph to leave the type after one Enter (headings), or the same type to continue it (quotes, list items, code lines). |
exitOnEmptyEnter | Enter in an empty block of this type replaces the empty line with a block of exitType, so pressing Enter twice leaves a quote, list, or code block. Defaults to true for text blocks; set false to keep creating empty blocks. |
exitType | Block type used when leaving. Defaults to paragraph. |
render(element, block) | Optional custom rendering into the block element, for atomic or non-text blocks. |
read(element, previous) | Optional parser used when browser-owned DOM of this block is read back. |
isEmpty(block) | Optional. Atomic blocks usually return false so a document that contains them is not considered empty. |
editorComponent, rendererComponent | Angular components that render the block. See Component Blocks. |
The paragraph type comes from basicTextEditorPlugin(). Blocks without a registered definition are rendered as div elements.
Every text block holds one line. Lists, quotes, and code are sequences of blocks of the same type, which keeps the model flat and the operations simple. Style consecutive blocks so they read as one list or one code listing, for example by removing margins between adjacent ul elements.
The first Enter at the end of a quote, list item, or code line continues the block with the type returned by create(). A second Enter on the resulting empty line removes it and puts the caret in a new paragraph below the block. The exit is one undo step. Headings leave after a single Enter because their create() returns a paragraph.
{
type: 'blockquote',
tagName: 'blockquote',
create: () => ({ ...createNgsHeadlessEditorParagraph(), type: 'blockquote' }),
// exitOnEmptyEnter: true, default: Enter on an empty quote line leaves the quote
// exitType: 'paragraph' default: what the empty line becomes
}editor.toggleBlock('heading');
editor.isBlockActive('bulletItem');
editor.insertBlock({ id: createNgsHeadlessEditorId('divider'), type: 'divider', content: null });
editor.updateBlock(blockId, { attrs: { level: 2 } });
editor.removeBlock(blockId);| Method | Description |
|---|---|
toggleBlock(type, fallback?) | Switches the selected blocks to a type, or back to the fallback (paragraph) when all of them already have it. Text and marks are kept. |
isBlockActive(type) | True when every selected block has the type. |
insertBlock(block, select?) | Inserts a block after the block that holds the caret. |
updateBlock(id, patch, origin?) | Replaces type, content, or attrs of a block by id. Recorded in history. |
removeBlock(id) | Removes a block; an empty paragraph is created if it was the last one. |
splitBlock() | Splits the text block at the caret, like Enter. |
Atomic blocks have non-text content and are handled as a unit: Backspace at the start of the next text block or Delete at the end of the previous one removes them, and a selection that crosses them removes them when it is replaced. A selection that only ends at an atomic block keeps it.
function createDivider(): NgsHeadlessEditorBlock<null> {
return { id: createNgsHeadlessEditorId('divider'), type: 'divider', content: null };
}
const image: NgsHeadlessEditorBlock<null> = {
id: createNgsHeadlessEditorId('image'),
type: 'image',
content: null,
attrs: { src: '/assets/chart.png', alt: 'Revenue chart' }
};