Comprehensive wrapper around ProseMirror packages providing unified entry point for rich text editing functionality in Tiptap framework
—
The view system handles DOM rendering, user interaction, and visual decorations. It bridges between the abstract document model and the browser's DOM.
The main interface for rendering and interacting with ProseMirror documents.
/**
* The view component of a ProseMirror editor
*/
class EditorView {
/** The view's current state */
state: EditorState;
/** The DOM element containing the editor */
readonly dom: Element;
/** Whether the editor is editable */
editable: boolean;
/** Whether the editor has focus */
readonly hasFocus: boolean;
/** The root DOM element */
readonly root: Document | DocumentFragment;
/**
* Create a new editor view
*/
constructor(place: Element | ((p: Element) => void) | { mount: Element }, props: EditorProps);
/**
* Update the view's state
*/
updateState(state: EditorState): void;
/**
* Dispatch a transaction
*/
dispatch(tr: Transaction): void;
/**
* Focus the editor
*/
focus(): void;
/**
* Get the DOM position corresponding to the given document position
*/
coordsAtPos(pos: number, side?: number): { left: number; right: number; top: number; bottom: number };
/**
* Get the document position at the given DOM coordinates
*/
posAtCoords(coords: { left: number; top: number }): { pos: number; inside: number } | null;
/**
* Get the document position at the given DOM node and offset
*/
posAtDOM(node: Node, offset: number, bias?: number): number;
/**
* Get the DOM node and offset at the given document position
*/
domAtPos(pos: number, side?: number): { node: Node; offset: number };
/**
* Get the node view for the given DOM node
*/
nodeViewAtPos(pos: number): NodeView;
/**
* Get the document position before the given DOM node
*/
posBeforeNode(node: Node): number;
/**
* Get the document position after the given DOM node
*/
posAfterNode(node: Node): number;
/**
* Destroy the view
*/
destroy(): void;
/**
* Set the view's props
*/
setProps(props: EditorProps): void;
/**
* Update the view
*/
update(props: EditorProps): void;
}
interface EditorProps {
/** The editor state */
state: EditorState;
/** Function to dispatch transactions */
dispatchTransaction?: (tr: Transaction) => void;
/** Whether the editor should be editable */
editable?: (state: EditorState) => boolean;
/** Attributes for the editor DOM element */
attributes?: (state: EditorState) => { [name: string]: string } | null;
/** Function to handle DOM events */
handleDOMEvents?: { [name: string]: (view: EditorView, event: Event) => boolean };
/** Function to handle keyboard events */
handleKeyDown?: (view: EditorView, event: KeyboardEvent) => boolean;
handleKeyPress?: (view: EditorView, event: KeyboardEvent) => boolean;
/** Function to handle click events */
handleClick?: (view: EditorView, pos: number, event: MouseEvent) => boolean;
handleClickOn?: (view: EditorView, pos: number, node: Node, nodePos: number, event: MouseEvent, direct: boolean) => boolean;
handleDoubleClick?: (view: EditorView, pos: number, event: MouseEvent) => boolean;
handleDoubleClickOn?: (view: EditorView, pos: number, node: Node, nodePos: number, event: MouseEvent, direct: boolean) => boolean;
handleTripleClick?: (view: EditorView, pos: number, event: MouseEvent) => boolean;
handleTripleClickOn?: (view: EditorView, pos: number, node: Node, nodePos: number, event: MouseEvent, direct: boolean) => boolean;
/** Function to handle paste events */
handlePaste?: (view: EditorView, event: ClipboardEvent, slice: Slice) => boolean;
handleDrop?: (view: EditorView, event: DragEvent, slice: Slice, moved: boolean) => boolean;
/** Function to transform pasted content */
transformPastedHTML?: (html: string, view: EditorView) => string;
transformPastedText?: (text: string, view: EditorView) => string;
transformCopied?: (slice: Slice, view: EditorView) => Slice;
/** Function to create custom node views */
nodeViews?: { [name: string]: NodeViewConstructor };
/** Function to create decorations */
decorations?: (state: EditorState) => DecorationSet | null;
/** Function to handle scroll to selection */
handleScrollToSelection?: (view: EditorView) => boolean;
/** CSS class for the editor */
class?: string;
}Usage Examples:
import { EditorView } from "@tiptap/pm/view";
import { EditorState } from "@tiptap/pm/state";
// Create a view
const view = new EditorView(document.querySelector("#editor"), {
state: EditorState.create({ schema: mySchema }),
dispatchTransaction(tr) {
const newState = view.state.apply(tr);
view.updateState(newState);
}
});
// Handle events
const view2 = new EditorView(document.querySelector("#editor2"), {
state: myState,
handleClick(view, pos, event) {
console.log("Clicked at position", pos);
return false; // Let other handlers run
},
handleKeyDown(view, event) {
if (event.key === "Escape") {
view.dom.blur();
return true; // Prevent other handlers
}
return false;
}
});Visual modifications to the document that don't change its content.
/**
* A decoration represents a visual modification to the document
*/
abstract class Decoration {
/** Start position of the decoration */
readonly from: number;
/** End position of the decoration */
readonly to: number;
/** Type of the decoration */
readonly type: DecorationSpec;
/**
* Create a widget decoration
*/
static widget(pos: number, dom: Element, spec?: WidgetDecorationSpec): Decoration;
/**
* Create an inline decoration
*/
static inline(from: number, to: number, attrs: DecorationAttrs, spec?: InlineDecorationSpec): Decoration;
/**
* Create a node decoration
*/
static node(from: number, to: number, attrs: DecorationAttrs, spec?: NodeDecorationSpec): Decoration;
/**
* Map the decoration through a change
*/
map(mapping: Mappable, node: Node, offset: number): Decoration | null;
/**
* Check if this decoration is equal to another
*/
eq(other: Decoration): boolean;
}
/**
* A set of decorations
*/
class DecorationSet {
/** The decorations in this set */
readonly local: Decoration[];
/** The child decoration sets */
readonly children: DecorationSet[];
/**
* The empty decoration set
*/
static empty: DecorationSet;
/**
* Create a decoration set from an array of decorations
*/
static create(doc: Node, decorations: Decoration[]): DecorationSet;
/**
* Find decorations in this set
*/
find(start?: number, end?: number, predicate?: (spec: any) => boolean): Decoration[];
/**
* Map this decoration set through a set of changes
*/
map(mapping: Mappable, doc: Node, options?: { onRemove?: (decorationSpec: any) => void }): DecorationSet;
/**
* Add decorations to this set
*/
add(doc: Node, decorations: Decoration[]): DecorationSet;
/**
* Remove decorations from this set
*/
remove(decorations: Decoration[]): DecorationSet;
}
interface DecorationSpec {
/** Include decorations from child nodes */
inclusiveStart?: boolean;
inclusiveEnd?: boolean;
/** Arbitrary data associated with the decoration */
spec?: any;
}
interface WidgetDecorationSpec extends DecorationSpec {
/** Side of the position to place the widget */
side?: number;
/** Marks to apply to the widget */
marks?: Mark[];
/** Stop events on this widget */
stopEvent?: (event: Event) => boolean;
/** Ignore mutations in this widget */
ignoreSelection?: boolean;
/** Key for widget deduplication */
key?: string;
}
interface InlineDecorationSpec extends DecorationSpec {
/** Include start boundary */
inclusiveStart?: boolean;
/** Include end boundary */
inclusiveEnd?: boolean;
}
interface NodeDecorationSpec extends DecorationSpec {
/** Remove the node if empty */
destroyEmpty?: boolean;
}
type DecorationAttrs = { [attr: string]: string };Usage Examples:
import { Decoration, DecorationSet } from "@tiptap/pm/view";
// Create decorations
const decorations = [
// Add a widget at position 10
Decoration.widget(10, document.createElement("span")),
// Add inline styling from position 5 to 15
Decoration.inline(5, 15, { class: "highlight" }),
// Add node styling to a node
Decoration.node(20, 25, { class: "special-node" })
];
// Create a decoration set
const decorationSet = DecorationSet.create(doc, decorations);
// Use in a plugin
const highlightPlugin = new Plugin({
props: {
decorations(state) {
// Find positions to highlight
const decorations = [];
state.doc.descendants((node, pos) => {
if (node.isText && node.text.includes("highlight")) {
decorations.push(
Decoration.inline(pos, pos + node.nodeSize, { class: "highlighted" })
);
}
});
return DecorationSet.create(state.doc, decorations);
}
}
});Custom rendering and behavior for specific node types.
/**
* Interface for custom node views
*/
interface NodeView {
/** The DOM element representing this node */
dom: Element;
/** The DOM element holding the node's content */
contentDOM?: Element;
/**
* Update the node view when the node changes
*/
update?(node: Node, decorations: Decoration[], innerDecorations: DecorationSource): boolean;
/**
* Select the node view
*/
selectNode?(): void;
/**
* Deselect the node view
*/
deselectNode?(): void;
/**
* Set the selection inside the node view
*/
setSelection?(anchor: number, head: number, root: Document | ShadowRoot): void;
/**
* Stop an event from bubbling
*/
stopEvent?(event: Event): boolean;
/**
* Ignore mutations in the content
*/
ignoreMutation?(mutation: MutationRecord): boolean;
/**
* Destroy the node view
*/
destroy?(): void;
}
/**
* Constructor for node views
*/
type NodeViewConstructor = (node: Node, view: EditorView, getPos: () => number, decorations: Decoration[], innerDecorations: DecorationSource) => NodeView;
/**
* Source of decorations for a node view
*/
interface DecorationSource {
forChild(node: Node, type: string): DecorationSource;
eq(other: DecorationSource): boolean;
}Usage Examples:
import { EditorView } from "@tiptap/pm/view";
// Create a custom node view
function createImageView(node, view, getPos) {
const dom = document.createElement("div");
dom.className = "image-node";
const img = document.createElement("img");
img.src = node.attrs.src;
img.alt = node.attrs.alt;
dom.appendChild(img);
return {
dom,
update(node) {
if (node.type.name !== "image") return false;
img.src = node.attrs.src;
img.alt = node.attrs.alt;
return true;
},
selectNode() {
dom.classList.add("selected");
},
deselectNode() {
dom.classList.remove("selected");
}
};
}
// Use the node view
const view = new EditorView(element, {
state: myState,
nodeViews: {
image: createImageView
}
});Custom rendering for mark types.
/**
* Interface for custom mark views
*/
interface MarkView {
/** The DOM element representing this mark */
dom: Element;
/**
* Update the mark view when the mark changes
*/
update?(mark: Mark): boolean;
/**
* Destroy the mark view
*/
destroy?(): void;
}
/**
* Constructor for mark views
*/
type MarkViewConstructor = (mark: Mark, view: EditorView, inline: boolean) => MarkView;/**
* Utilities for working with coordinates and positions
*/
/**
* Get the bounding box for a range of content
*/
function coordsAtPos(view: EditorView, pos: number, side?: number): { left: number; right: number; top: number; bottom: number };
/**
* Get the position at the given coordinates
*/
function posAtCoords(view: EditorView, coords: { left: number; top: number }): { pos: number; inside: number } | null;
/**
* Check if a position is at the start of a text block
*/
function atStartOfTextblock(view: EditorView, dir: "up" | "down" | "left" | "right" | "forward" | "backward"): boolean;
/**
* Check if a position is at the end of a text block
*/
function atEndOfTextblock(view: EditorView, dir: "up" | "down" | "left" | "right" | "forward" | "backward"): boolean;interface Mappable {
map(pos: number, assoc?: number): number;
mapResult(pos: number, assoc?: number): MapResult;
}
interface MapResult {
pos: number;
deleted: boolean;
}
type DOMNode = Element | Text | Comment;
interface ClipboardEvent extends Event {
clipboardData: DataTransfer;
}
interface DragEvent extends MouseEvent {
dataTransfer: DataTransfer;
}Install with Tessl CLI
npx tessl i tessl/npm-tiptap--pm