

An editor document is plain, versioned JSON. It is the only source of truth: the DOM is rendered from it, and browser input is translated into changes of it. You can store it, send it over the network, diff it, and render it anywhere.
const document: NgsHeadlessEditorDocument = {
version: 1,
blocks: [
{
id: 'paragraph-1',
type: 'paragraph',
content: [
{ type: 'text', text: 'Hello ', marks: [] },
{ type: 'text', text: 'world', marks: [{ type: 'bold' }] }
]
},
{
id: 'callout-1',
type: 'callout',
content: null, // atomic block
attrs: { tone: 'info', text: 'Heads up' }
}
]
};
const selection: NgsHeadlessEditorSelection = {
anchor: { blockId: 'paragraph-1', offset: 6 },
focus: { blockId: 'paragraph-1', offset: 11 } // selects "world"
};| Type | Description |
|---|---|
NgsHeadlessEditorDocument | Root object with version: 1 and an ordered, non-empty list of blocks. |
NgsHeadlessEditorBlock | Addressable unit with a stable id, a type registered by a plugin, content, and optional attrs. |
NgsHeadlessEditorText | A text run inside a text block: a string and the marks applied to all of it. |
NgsHeadlessEditorMark | Inline annotation with a type and optional serializable attrs (string, number, boolean, or null values). |
NgsHeadlessEditorPoint | A position: block id plus a text offset inside that block. |
NgsHeadlessEditorSelection | An anchor and a focus point. The focus can be before the anchor for backward selections. |
A block whose content is an array of text runs is a text block: the caret can move through it and the user can type into it. Any other content, commonly null with data in attrs, makes an atomic block such as a divider, an image, or an Angular component. Atomic blocks are selected and deleted as a whole. Each text block is one line of the document; lists, quotes, and code are sequences of blocks of the same type. See Blocks.
Every document that enters the editor is normalized. After normalization:
setDocument() copies its input before normalizing, so later mutations of your object do not leak into the editor.
Documents, blocks, runs, and selections are never mutated. Every change produces a new document that reuses the unchanged blocks. The surface relies on this identity to re-render only changed blocks, and the history stores snapshots that share structure instead of copying the document.
const before = editor.document();
editor.insertText('!'); // edits the block that holds the caret
const after = editor.document();
before === after; // false: a new document
before.blocks[1] === after.blocks[1]; // true: untouched blocks are shared Never mutate a document you got from the editor. Create a new object, or use updateBlock(), insertBlock(), and the other editing methods.
Offsets count UTF-16 code units, like JavaScript string indexes, and are clamped to the block text length. Deleting a character removes a whole user-perceived character, so emoji with skin tones, family sequences, and flags are removed in one step.
import {
cloneNgsHeadlessEditorDocument, // deep copy for external mutation
createNgsHeadlessEditorDocument, // document with one paragraph
createNgsHeadlessEditorId, // unique id with a prefix
createNgsHeadlessEditorParagraph, // paragraph block
createNgsHeadlessEditorText, // normalized text run
getNgsHeadlessEditorBlockText, // text of one block
getNgsHeadlessEditorDocumentText, // blocks joined with newlines
isNgsHeadlessEditorDocumentEmpty, // true when only whitespace
isNgsHeadlessEditorTextContent, // type guard for text blocks
ngsHeadlessEditorBlocksEqual,
ngsHeadlessEditorDocumentsEqual,
ngsHeadlessEditorMarksEqual,
ngsHeadlessEditorValuesEqual, // JSON-like deep equality
normalizeNgsHeadlessEditorDocument,
normalizeNgsHeadlessEditorMarks,
normalizeNgsHeadlessEditorTextContent
} from '@ngstarter-ui/components/headless-editor';
const document = createNgsHeadlessEditorDocument('First line');
const text = getNgsHeadlessEditorDocumentText(document); // 'First line'
const same = ngsHeadlessEditorDocumentsEqual(document, cloneNgsHeadlessEditorDocument(document)); // true Equality helpers compare structure, ignore object key order, and treat keys with undefined values as absent. They short-circuit on shared blocks, which makes comparing two revisions of a large document cheap.