Trailing Node
TrailingNode ensures there is always an editable node (by default a paragraph) at the end of the document. Without it, if the document ends with a table, code block, image, or other block element, the user has no place to click and start typing below it.
Included in StarterKit by default.
TrailingNode is included in StarterKit, so it works out of the box. To use it standalone:
import { Editor, Document, Paragraph, Text, TrailingNode } from '@domternal/core';import '@domternal/theme';
const editor = new Editor({ element: document.getElementById('editor')!, extensions: [Document, Paragraph, Text, TrailingNode],});import { Component, signal } from '@angular/core';import { DomternalEditorComponent } from '@domternal/angular';import { Editor, Document, Paragraph, Text, TrailingNode } from '@domternal/core';
@Component({ selector: 'app-editor', imports: [DomternalEditorComponent], templateUrl: './editor.html',})export class EditorComponent { editor = signal<Editor | null>(null); extensions = [Document, Paragraph, Text, TrailingNode];}<domternal-editor [extensions]="extensions" (editorCreated)="editor.set($event)"/>import { Domternal } from '@domternal/react';import { Document, Paragraph, Text, TrailingNode } from '@domternal/core';
export default function Editor() { return ( <Domternal extensions={[Document, Paragraph, Text, TrailingNode]} > <Domternal.Content /> </Domternal> );}import { Editor, Document, Paragraph, Text, TrailingNode } from '@domternal/core';
const editor = new Editor({ element: document.getElementById('editor')!, extensions: [Document, Paragraph, Text, TrailingNode],});To configure TrailingNode in StarterKit:
StarterKit.configure({ trailingNode: { notAfter: ['paragraph', 'heading'] },})To disable TrailingNode in StarterKit:
StarterKit.configure({ trailingNode: false })Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
node | string | 'paragraph' | The node type name to insert as trailing node |
notAfter | string[] | ['paragraph'] | Node types after which a trailing node is not needed |
import { TrailingNode } from '@domternal/core';
const editor = new Editor({ extensions: [ TrailingNode.configure({ node: 'paragraph', notAfter: ['paragraph', 'heading'], }), ],});How notAfter works
Section titled “How notAfter works”The notAfter list tells the plugin which node types are already safe to end the document with. If the last node in the document is one of these types, no trailing node is added.
The trailing node type itself (from the node option) is always included in this list automatically. So with the default configuration (node: 'paragraph', notAfter: ['paragraph']), the plugin only inserts a trailing paragraph when the document ends with something other than a paragraph.
Commands
Section titled “Commands”TrailingNode does not register any commands.
Keyboard shortcuts
Section titled “Keyboard shortcuts”TrailingNode does not register any keyboard shortcuts.
Input rules
Section titled “Input rules”TrailingNode does not register any input rules.
Toolbar items
Section titled “Toolbar items”TrailingNode does not register any toolbar items.
How it works
Section titled “How it works”TrailingNode creates a ProseMirror plugin with two parts:
State tracking
Section titled “State tracking”The plugin tracks whether a trailing node needs to be inserted using plugin state:
- Init - on editor creation, checks the schema for the configured node type (throws an error if not found), builds a list of “trigger” node types (all types except the ignored ones), and checks if the document’s last child is a trigger type.
- Apply - on every document change, re-checks the last child. Returns
trueif a trailing node needs to be inserted,falseotherwise.
The “ignored” set is the union of notAfter and the node type itself. Any node type not in this set is a “trigger” type - if the document ends with a trigger type, a trailing node is added.
Automatic insertion
Section titled “Automatic insertion”The plugin uses appendTransaction to insert the trailing node. When the plugin state is true (document ends with a trigger type), it creates a transaction that inserts a new node of the configured type at the end of the document (state.doc.content.size).
This runs automatically after every transaction that changes the document, so the trailing node is always present when needed.
Exports
Section titled “Exports”import { TrailingNode } from '@domternal/core';import type { TrailingNodeOptions } from '@domternal/core';| Export | Type | Description |
|---|---|---|
TrailingNode | Extension | The trailing node extension |
TrailingNodeOptions | TypeScript type | Options for TrailingNode.configure() |
Source
Section titled “Source”@domternal/core - TrailingNode.ts