CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-tiptap--pm

Comprehensive wrapper around ProseMirror packages providing unified entry point for rich text editing functionality in Tiptap framework

Pending
Overview
Eval results
Files

state-management.mddocs/

Editor State Management

The state management system provides complete control over editor state, transactions, selections, and the plugin system. It forms the core of ProseMirror's reactive architecture.

Capabilities

Editor State

Represents the complete state of an editor at a given moment.

/**
 * The state of a ProseMirror editor
 */
class EditorState {
  /** The current document */
  readonly doc: Node;
  
  /** The current selection */
  readonly selection: Selection;
  
  /** The stored marks at the current position */
  readonly storedMarks: Mark[] | null;
  
  /** The schema used by this state */
  readonly schema: Schema;
  
  /** The plugins active in this state */
  readonly plugins: Plugin[];
  
  /** The current transaction being built */
  readonly tr: Transaction;
  
  /**
   * Create a new editor state
   */
  static create(config: EditorStateConfig): EditorState;
  
  /**
   * Apply a transaction to create a new state
   */
  apply(tr: Transaction): EditorState;
  
  /**
   * Start a new transaction from this state
   */
  get tr(): Transaction;
  
  /**
   * Reconfigure the state with new plugins or schema
   */
  reconfigure(config: { plugins?: Plugin[]; schema?: Schema }): EditorState;
  
  /**
   * Serialize the state to JSON
   */
  toJSON(pluginFields?: { [propName: string]: any }): any;
  
  /**
   * Create a state from a JSON representation
   */
  static fromJSON(config: EditorStateConfig, json: any, pluginFields?: { [propName: string]: any }): EditorState;
}

interface EditorStateConfig {
  /** The schema to use */
  schema?: Schema;
  
  /** The initial document */
  doc?: Node;
  
  /** The initial selection */
  selection?: Selection;
  
  /** The initial stored marks */
  storedMarks?: Mark[];
  
  /** The plugins to use */
  plugins?: Plugin[];
}

Usage Examples:

import { EditorState } from "@tiptap/pm/state";
import { schema } from "@tiptap/pm/schema-basic";

// Create a new state
const state = EditorState.create({
  schema: schema,
  doc: schema.node("doc", null, [
    schema.node("paragraph", null, [schema.text("Hello world!")])
  ])
});

// Apply a transaction
const tr = state.tr.insertText("New text", 1);
const newState = state.apply(tr);

Transactions

Represent atomic changes to the editor state.

/**
 * A transaction represents a set of changes to a document
 */
class Transaction extends Transform {
  /** The editor state this transaction started from */
  readonly doc: Node;
  
  /** The current selection in this transaction */
  selection: Selection;
  
  /** The stored marks in this transaction */
  storedMarks: Mark[] | null;
  
  /** Time when this transaction was created */
  readonly time: number;
  
  /** Whether this transaction changes the document */
  readonly docChanged: boolean;
  
  /** Whether this transaction changes the selection */
  readonly selectionSet: boolean;
  
  /** Whether this transaction changes stored marks */
  readonly storedMarksSet: boolean;
  
  /** Whether this transaction is generic */
  isGeneric: boolean;
  
  /** Metadata attached to this transaction */
  readonly meta: { [key: string]: any };
  
  /**
   * Replace the selection with the given content
   */
  replaceSelection(slice: Slice): Transaction;
  
  /**
   * Replace the selection with the given node
   */
  replaceSelectionWith(node: Node, inheritMarks?: boolean): Transaction;
  
  /**
   * Delete the current selection
   */
  deleteSelection(): Transaction;
  
  /**
   * Insert text at the current position
   */
  insertText(text: string, from?: number, to?: number): Transaction;
  
  /**
   * Set the selection
   */
  setSelection(selection: Selection): Transaction;
  
  /**
   * Set the stored marks
   */
  setStoredMarks(marks: Mark[] | null): Transaction;
  
  /**
   * Ensure the stored marks match the current selection
   */
  ensureMarks(marks: Mark[]): Transaction;
  
  /**
   * Add a mark to the stored marks
   */
  addStoredMark(mark: Mark): Transaction;
  
  /**
   * Remove a mark from the stored marks
   */
  removeStoredMark(mark: Mark | MarkType): Transaction;
  
  /**
   * Set metadata on this transaction
   */
  setMeta(key: string | Plugin | PluginKey, value: any): Transaction;
  
  /**
   * Get metadata from this transaction
   */
  getMeta(key: string | Plugin | PluginKey): any;
  
  /**
   * Mark this transaction as not mergeable with previous history events
   */
  setTime(time: number): Transaction;
  
  /**
   * Scroll the selection into view after applying this transaction
   */
  scrollIntoView(): Transaction;
}

Usage Examples:

import { EditorState } from "@tiptap/pm/state";

// Start a transaction
const tr = state.tr;

// Chain operations
tr.insertText("Hello ")
  .insertText("world!")
  .setSelection(TextSelection.create(tr.doc, 1, 6));

// Apply the transaction
const newState = state.apply(tr);

Selections

Represent the current selection in the editor.

/**
 * Base class for selections
 */
abstract class Selection {
  /** Start position of the selection */
  readonly $from: ResolvedPos;
  
  /** End position of the selection */
  readonly $to: ResolvedPos;
  
  /** Start position as number */
  readonly from: number;
  
  /** End position as number */
  readonly to: number;
  
  /** Anchor position of the selection */
  readonly $anchor: ResolvedPos;
  
  /** Head position of the selection */
  readonly $head: ResolvedPos;
  
  /** Anchor position as number */
  readonly anchor: number;
  
  /** Head position as number */
  readonly head: number;
  
  /** Whether the selection is empty */
  readonly empty: boolean;
  
  /**
   * Map the selection through a position mapping
   */
  map(doc: Node, mapping: Mappable): Selection;
  
  /**
   * Get the content of this selection as a slice
   */
  content(): Slice;
  
  /**
   * Check if this selection equals another selection
   */
  eq(other: Selection): boolean;
  
  /**
   * Convert the selection to JSON
   */
  toJSON(): any;
  
  /**
   * Create a selection from JSON
   */
  static fromJSON(doc: Node, json: any): Selection;
  
  /**
   * Find a valid selection near the given position
   */
  static near(pos: ResolvedPos, bias?: number): Selection;
  
  /**
   * Find the cursor wrapper around the given position
   */
  static atStart(doc: Node): Selection;
  
  /**
   * Find the cursor wrapper at the end of the given node
   */
  static atEnd(doc: Node): Selection;
}

/**
 * A text selection between two positions
 */
class TextSelection extends Selection {
  /**
   * Create a text selection
   */
  constructor($anchor: ResolvedPos, $head?: ResolvedPos);
  
  /**
   * Create a text selection between two positions
   */
  static create(doc: Node, anchor: number, head?: number): TextSelection;
  
  /**
   * Create a text selection that includes the given range
   */
  static between($anchor: ResolvedPos, $head: ResolvedPos, bias?: number): Selection;
}

/**
 * A selection that selects a specific node
 */
class NodeSelection extends Selection {
  /** The selected node */
  readonly node: Node;
  
  /**
   * Create a node selection
   */
  constructor($pos: ResolvedPos);
  
  /**
   * Create a node selection at the given position
   */
  static create(doc: Node, from: number): NodeSelection;
  
  /**
   * Check if a node can be selected at the given position
   */
  static isSelectable(node: Node): boolean;
}

/**
 * A selection that selects everything in the document
 */
class AllSelection extends Selection {
  /**
   * Create an all selection for the given document
   */
  constructor(doc: Node);
}

Plugins

Extensibility system for adding functionality to the editor.

/**
 * A plugin provides additional functionality to an editor
 */
class Plugin {
  /** The plugin's specification */
  readonly spec: PluginSpec;
  
  /**
   * Create a new plugin
   */
  constructor(spec: PluginSpec);
  
  /**
   * Get the plugin's state from an editor state
   */
  getState(state: EditorState): any;
}

/**
 * A key used to identify and access a plugin
 */
class PluginKey<T = any> {
  /** The key name */
  readonly key: string;
  
  /**
   * Create a new plugin key
   */
  constructor(name?: string);
  
  /**
   * Get the plugin state from an editor state
   */
  getState(state: EditorState): T | undefined;
  
  /**
   * Get the plugin from an editor state
   */
  get(state: EditorState): Plugin | undefined;
}

interface PluginSpec {
  /** Initial state for this plugin */
  state?: StateField<any>;
  
  /** Properties to add to the editor */
  props?: EditorProps;
  
  /** Key to identify this plugin */
  key?: PluginKey;
  
  /** View constructor for this plugin */
  view?: (view: EditorView) => PluginView;
}

interface StateField<T> {
  /** Initialize the plugin state */
  init(config: EditorStateConfig, state: EditorState): T;
  
  /** Update the plugin state when a transaction is applied */
  apply(tr: Transaction, value: T, oldState: EditorState, newState: EditorState): T;
  
  /** Serialize the plugin state to JSON */
  toJSON?(value: T): any;
  
  /** Deserialize the plugin state from JSON */
  fromJSON?(config: EditorStateConfig, value: any, state: EditorState): T;
}

interface PluginView {
  /** Called when the plugin view is created */
  update?(view: EditorView, prevState: EditorState): void;
  
  /** Called when the plugin view is destroyed */
  destroy?(): void;
}

Usage Examples:

import { Plugin, PluginKey, EditorState } from "@tiptap/pm/state";

// Create a simple plugin
const myPlugin = new Plugin({
  state: {
    init() {
      return { clickCount: 0 };
    },
    apply(tr, value) {
      if (tr.getMeta("click")) {
        return { clickCount: value.clickCount + 1 };
      }
      return value;
    }
  }
});

// Create a plugin with a key
const myKey = new PluginKey("myPlugin");
const keyedPlugin = new Plugin({
  key: myKey,
  state: {
    init() {
      return { data: [] };
    },
    apply(tr, value) {
      return value;
    }
  }
});

// Use in state creation
const state = EditorState.create({
  schema: mySchema,
  plugins: [myPlugin, keyedPlugin]
});

// Access plugin state
const pluginState = myKey.getState(state);

Selection Bookmarks

Lightweight representations of selections for storage and restoration.

/**
 * A bookmark represents a selection that can be stored and restored
 */
interface SelectionBookmark {
  /**
   * Map the bookmark through a change set
   */
  map(mapping: Mappable): SelectionBookmark;
  
  /**
   * Resolve the bookmark to a selection
   */
  resolve(doc: Node): Selection;
}

Selection Utilities

/**
 * Utility functions for working with selections
 */

/**
 * Check if the given selection can be joined with the one before it
 */
function canJoin(doc: Node, pos: number): boolean;

/**
 * Find the node before the given position
 */
function findWrapping(range: { $from: ResolvedPos; $to: ResolvedPos }, nodeType: NodeType, attrs?: Attrs): { type: NodeType; attrs: Attrs }[] | null;

/**
 * Check if the given range can be lifted out of its parent
 */
function canLift(state: EditorState, range: { $from: ResolvedPos; $to: ResolvedPos }): boolean;

/**
 * Check if the given range can be wrapped in the given node type
 */
function canWrap(range: { $from: ResolvedPos; $to: ResolvedPos }, nodeType: NodeType, attrs?: Attrs): boolean;

Types

interface EditorProps {
  /** Called when a transaction is dispatched */
  dispatchTransaction?: (tr: Transaction) => void;
  
  /** Called to handle DOM events */
  handleDOMEvents?: { [event: string]: (view: EditorView, event: Event) => boolean };
  
  /** Called to handle key events */
  handleKeyDown?: (view: EditorView, event: KeyboardEvent) => boolean;
  handleKeyPress?: (view: EditorView, event: KeyboardEvent) => boolean;
  
  /** Called to handle click events */
  handleClickOn?: (view: EditorView, pos: number, node: Node, nodePos: number, event: MouseEvent, direct: boolean) => boolean;
  handleClick?: (view: EditorView, pos: number, event: MouseEvent) => boolean;
  handleDoubleClickOn?: (view: EditorView, pos: number, node: Node, nodePos: number, event: MouseEvent, direct: boolean) => boolean;
  handleDoubleClick?: (view: EditorView, pos: number, event: MouseEvent) => boolean;
  handleTripleClickOn?: (view: EditorView, pos: number, node: Node, nodePos: number, event: MouseEvent, direct: boolean) => boolean;
  handleTripleClick?: (view: EditorView, pos: number, event: MouseEvent) => boolean;
  
  /** Called to transform pasted content */
  transformPastedHTML?: (html: string) => string;
  transformPastedText?: (text: string) => string;
  
  /** Called when content is pasted */
  handlePaste?: (view: EditorView, event: ClipboardEvent, slice: Slice) => boolean;
  
  /** Called when content is dragged */
  handleDrop?: (view: EditorView, event: DragEvent, slice: Slice, moved: boolean) => boolean;
  
  /** Called to determine if scrolling should happen */
  handleScrollToSelection?: (view: EditorView) => boolean;
  
  /** Function to create node views */
  nodeViews?: { [nodeName: string]: NodeViewConstructor };
  
  /** Function to add decorations */
  decorations?: (state: EditorState) => DecorationSet;
  
  /** Attributes to add to the editor DOM element */
  attributes?: (state: EditorState) => { [attr: string]: string };
  
  /** CSS class to add to the editor */
  class?: string;
}

interface Mappable {
  map(pos: number, assoc?: number): number;
  mapResult(pos: number, assoc?: number): MapResult;
}

interface MapResult {
  pos: number;
  deleted: boolean;
}

Install with Tessl CLI

npx tessl i tessl/npm-tiptap--pm

docs

collaboration.md

commands-and-editing.md

cursors-and-enhancements.md

history.md

index.md

input-and-keymaps.md

markdown.md

menus-and-ui.md

model-and-schema.md

schema-definitions.md

state-management.md

tables.md

transformations.md

view-and-rendering.md

tile.json