@type-editor/viewdesc
v0.0.3
Published
Type Editor - ViewDesc Module
Maintainers
Readme
@type-editor/viewdesc
A refactored version of ProseMirror's view description system, providing the core rendering engine that maintains a parallel tree structure between the document model and the DOM representation.
Installation
npm install @type-editor/viewdescOverview
The ViewDesc (View Description) system is the internal rendering layer that powers the Type Editor view. It creates and maintains a tree of descriptor objects that:
- Mirror the document structure: Each document node has a corresponding ViewDesc
- Manage DOM nodes: Create, update, and destroy DOM elements efficiently
- Apply decorations: Render visual enhancements without modifying the document
- Map positions: Convert between document positions and DOM coordinates
- Handle updates: Incrementally update the DOM when the document changes
- Support custom rendering: Allow developers to override node rendering
ViewDesc operates as an abstraction layer between the editor's document state and the browser's DOM, enabling efficient updates and sophisticated rendering capabilities.
Core Concepts
The Parallel Tree Structure
Type Editor maintains two parallel trees:
Document Tree (Model) ViewDesc Tree DOM Tree
───────────────────── ───────────── ────────
doc DocViewDesc <div>
│ │ │
├─ paragraph NodeViewDesc <p>
│ └─ "Hello" └─ TextViewDesc └─ "Hello"
│
├─ blockquote NodeViewDesc <blockquote>
│ └─ paragraph └─ NodeViewDesc └─ <p>
│ └─ "World" └─ TextViewDesc └─ "World"Each ViewDesc maintains:
- References to its DOM node(s)
- Knowledge of its document position
- Information about decorations
- Links to parent and child ViewDescs
ViewDesc Types
The system includes several specialized ViewDesc types:
- NodeViewDesc: Represents document nodes (paragraphs, headings, lists, etc.)
- TextViewDesc: Represents text content within nodes
- MarkViewDesc: Represents inline marks (bold, italic, links)
- WidgetViewDesc: Represents decoration widgets (placeholders, UI elements)
- CustomNodeViewDesc: Represents user-defined custom node views
- CompositionViewDesc: Handles IME composition for complex text input
- TrailingHackViewDesc: Browser-specific compatibility fixes
Key Components
ViewDesc (Base Class)
The foundation class providing core functionality:
import { ViewDesc } from '@type-editor/viewdesc';
// ViewDesc properties
viewDesc.dom // The DOM node
viewDesc.contentDOM // Where children are rendered
viewDesc.parent // Parent ViewDesc
viewDesc.children // Child ViewDescs
viewDesc.dirty // Dirty state tracking
viewDesc.node // Associated ProseMirror node
// Position mapping
const pos = viewDesc.posBeforeChild(childDesc);
const domPos = viewDesc.domFromPos(10, 1);
const docPos = viewDesc.localPosFromDOM(domNode, offset, bias);
// Lifecycle
viewDesc.destroy();
viewDesc.markDirty(from, to);NodeViewDesc
Represents actual document nodes with full support for decorations:
import { NodeViewDesc } from '@type-editor/viewdesc';
// NodeViewDesc properties
nodeViewDesc.node // The ProseMirror node
nodeViewDesc.outerDeco // Decorations wrapping the node
nodeViewDesc.innerDeco // Decorations inside the node
nodeViewDesc.nodeDOM // The node's primary DOM element
nodeViewDesc.size // Node size in the document
nodeViewDesc.border // Border size (0 for leaves, 1 for blocks)
// Node matching for efficient updates
const matches = nodeViewDesc.matchesNode(node, outerDeco, innerDeco);
// Apply decorations
const decoratedDOM = NodeViewDesc.applyOuterDeco(dom, decorations, node);TextViewDesc
Represents text nodes with special handling for text content:
import { TextViewDesc } from '@type-editor/viewdesc';
// TextViewDesc wraps text DOM nodes
const textDesc = new TextViewDesc(parent, node, outerDeco, innerDeco, dom);
// Efficiently updates text content
textDesc.node = newTextNode;MarkViewDesc
Represents inline marks that wrap text or other content:
import { MarkViewDesc } from '@type-editor/viewdesc';
// MarkViewDesc wraps content with mark styling
const markDesc = new MarkViewDesc(parent, mark, dom, contentDOM);
// Marks are layered - a bold italic link would have three MarkViewDescsCustomNodeViewDesc
Used when developers provide custom node views:
import { CustomNodeViewDesc } from '@type-editor/viewdesc';
// Created automatically when using custom node views
const customDesc = new CustomNodeViewDesc(
parent,
node,
outerDeco,
innerDeco,
dom,
contentDOM,
nodeDOM,
nodeView, // The custom node view object
view,
getPos
);Working with ViewDescs
Creating the View Tree
ViewDescs are typically created internally by the editor view:
import { docViewDesc } from '@type-editor/viewdesc';
// Creates the root ViewDesc tree
const docView = docViewDesc(
state.doc, // Document
outerDeco, // Outer decorations
innerDeco, // Inner decorations
containerDOM, // Container element
editorView // View instance
);Updating ViewDescs
The ViewDescUpdater handles incremental updates:
import { ViewDescUpdater } from '@type-editor/viewdesc';
// Attempts to update the existing tree
const updated = ViewDescUpdater.update(
viewDesc,
editorView,
newNode,
outerDeco,
innerDeco
);
if (!updated) {
// Full redraw needed
viewDesc.destroy();
viewDesc = createNewViewDesc();
}Position Mapping
Convert between document positions and DOM coordinates:
// Document position → DOM coordinates
const { node, offset } = viewDesc.domFromPos(15, 1);
// Returns the DOM node and offset at document position 15
// DOM coordinates → Document position
const pos = viewDesc.posFromDOM(domNode, domOffset, bias);
// Returns the document position for the given DOM location
// Position before a child
const pos = viewDesc.posBeforeChild(childViewDesc);Utility Functions
The ViewDescUtil provides helper functions:
import { ViewDescUtil } from '@type-editor/viewdesc';
// Find ViewDesc by DOM node
const desc = ViewDescUtil.findByDOM(domNode);
// Find parent block ViewDesc
const blockDesc = ViewDescUtil.findParentBlock(viewDesc);
// Check if ViewDesc needs update
const needsUpdate = ViewDescUtil.needsUpdate(viewDesc, node, deco);Advanced Usage
Custom Node Views
Custom node views automatically get wrapped in CustomNodeViewDesc:
import { EditorView } from '@type-editor/view';
const view = new EditorView({
nodeViews: {
// Custom rendering for images
image(node, view, getPos) {
const dom = document.createElement('figure');
const img = document.createElement('img');
img.src = node.attrs.src;
dom.appendChild(img);
// CustomNodeViewDesc wraps this automatically
return { dom };
},
// Custom rendering with content
callout(node, view, getPos) {
const dom = document.createElement('div');
dom.className = 'callout';
const contentDOM = document.createElement('div');
contentDOM.className = 'callout-content';
dom.appendChild(contentDOM);
return { dom, contentDOM };
}
}
});Decoration Rendering
ViewDescs apply decorations during rendering:
// Inline decorations add attributes to text
const inlineDeco = Decoration.inline(5, 15, {
class: 'highlight',
style: 'background: yellow'
});
// MarkViewDesc or TextViewDesc applies these attributes
// Node decorations wrap entire nodes
const nodeDeco = Decoration.node(20, 45, {
class: 'selected-node'
});
// NodeViewDesc wraps the node with decoration elements
// Widget decorations insert DOM elements
const widgetDeco = Decoration.widget(10, () => {
const el = document.createElement('span');
el.textContent = '📍';
return el;
});
// WidgetViewDesc manages the widget DOMDirty State Tracking
ViewDescs track what needs updating:
import { ViewDirtyState } from '@type-editor/viewdesc';
// Dirty states
ViewDirtyState.NOT_DIRTY // No update needed
ViewDirtyState.CHILD_DIRTY // Children need update
ViewDirtyState.CONTENT_DIRTY // Content needs update
ViewDirtyState.NODE_DIRTY // Entire node needs update
// Mark a range as dirty
viewDesc.markDirty(from, to);
// Check if ViewDesc is dirty
if (viewDesc.dirty !== ViewDirtyState.NOT_DIRTY) {
// Needs update
}Position and Offset Calculations
Working with positions in the ViewDesc tree:
// Get position before a specific child
const posBefore = parent.posBeforeChild(child);
// Get position after a child
const posAfter = posBefore + child.size;
// Find child at a position
const child = viewDesc.childAfter(pos);
// Local position from DOM
const localPos = viewDesc.localPosFromDOM(domNode, offset, bias);Performance Considerations
- Incremental updates: ViewDesc enables efficient partial DOM updates instead of full rerenders
- Dirty tracking: Only updates parts marked as dirty
- Node matching: Reuses existing ViewDesc nodes when possible
- DOM node reuse: Keeps DOM nodes stable across updates when content hasn't changed
- Position caching: Maintains position information for fast lookups
- Minimal DOM operations: Batches and minimizes DOM changes
Internal Architecture
The ViewDesc system consists of several interconnected components:
- ViewDesc: Base class with core functionality
- ViewDescFactory: Creates appropriate ViewDesc instances
- ViewDescUpdater: Handles incremental updates
- ViewDescUtil: Utility functions for working with ViewDescs
- ViewTreeUpdater: Manages tree-level updates
- OuterDecoLevel: Manages decoration wrapper levels
These components work together to provide efficient, incremental DOM rendering while maintaining the connection between the document model and the visual representation.
API Reference
ViewDesc
Base class methods and properties:
Properties:
dom: Node- The DOM nodecontentDOM: HTMLElement | null- Container for child contentparent: ViewDesc | undefined- Parent ViewDescchildren: ViewDesc[]- Child ViewDescsdirty: ViewDirtyState- Dirty statenode: Node | null- Associated ProseMirror node
Methods:
destroy(): void- Clean up resourcesmarkDirty(from: number, to: number): void- Mark range as dirtydomFromPos(pos: number, side: number): DOMPosition- Map position to DOMlocalPosFromDOM(node: Node, offset: number, bias: number): number- Map DOM to positionposBeforeChild(child: ViewDesc): number- Get position before child
NodeViewDesc
Extends ViewDesc with node-specific functionality:
Additional Properties:
outerDeco: Decoration[] | null- Outer decorationsinnerDeco: DecorationSource | null- Inner decorationsnodeDOM: Node- The node's DOM elementsize: number- Node sizeborder: number- Border sizedomAtom: boolean- Whether node is a DOM atom
Static Methods:
applyOuterDeco(dom: Node, deco: Decoration[], node: Node): Node- Apply decorations to DOM
ViewDescUpdater
Static methods for updating ViewDescs:
update(viewDesc: ViewDesc, view: EditorView, node: Node, outerDeco, innerDeco): boolean- Update ViewDescupdateChildren(viewDesc: ViewDesc, view: EditorView, pos: number): void- Update children
ViewDescUtil
Utility functions:
findByDOM(dom: Node): ViewDesc | undefined- Find ViewDesc by DOM node- Various helper functions for ViewDesc manipulation
Compatibility
This module is a refactored version of ProseMirror's internal view description system. While the core concepts remain the same, the API is restructured and improved with TypeScript types. For full ProseMirror compatibility, use the compatibility layer packages.
Related Modules
@type-editor/view: The editor view that uses ViewDesc for rendering@type-editor/decoration: Decoration system that ViewDesc renders@type-editor/model: Document model that ViewDesc represents@type-editor/dom-util: DOM utility functions used by ViewDesc@type-editor/transform: Position mapping used during updates
License
MIT
