Skip to content

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],
});

To configure TrailingNode in StarterKit:

StarterKit.configure({
trailingNode: { notAfter: ['paragraph', 'heading'] },
})

To disable TrailingNode in StarterKit:

StarterKit.configure({ trailingNode: false })
OptionTypeDefaultDescription
nodestring'paragraph'The node type name to insert as trailing node
notAfterstring[]['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'],
}),
],
});

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.

TrailingNode does not register any commands.

TrailingNode does not register any keyboard shortcuts.

TrailingNode does not register any input rules.

TrailingNode does not register any toolbar items.

TrailingNode creates a ProseMirror plugin with two parts:

The plugin tracks whether a trailing node needs to be inserted using plugin state:

  1. 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.
  2. Apply - on every document change, re-checks the last child. Returns true if a trailing node needs to be inserted, false otherwise.

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.

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.

import { TrailingNode } from '@domternal/core';
import type { TrailingNodeOptions } from '@domternal/core';
ExportTypeDescription
TrailingNodeExtensionThe trailing node extension
TrailingNodeOptionsTypeScript typeOptions for TrailingNode.configure()

@domternal/core - TrailingNode.ts