Transaction-based state updates that bundle document changes, selection updates, and side effects into atomic operations.
Represents a state transition with document changes, selection updates, and side effects.
/**
* A transaction represents a state change. It is the result of calling EditorState.update.
*/
class Transaction {
/** The state this transaction was created from */
readonly startState: EditorState;
/** The state produced by this transaction (lazily computed) */
readonly state: EditorState;
/** The document changes made by this transaction */
readonly changes: ChangeSet;
/** The new selection produced by this transaction */
readonly selection: EditorSelection;
/** State effects attached to this transaction */
readonly effects: readonly StateEffect<any>[];
/** Annotations attached to this transaction */
readonly annotations: readonly Annotation<any>[];
/** Whether the document changed */
readonly docChanged: boolean;
/** Whether the configuration was reconfigured */
readonly reconfigured: boolean;
/** Whether this transaction should scroll the selection into view */
readonly scrollIntoView: boolean;
/** The new document produced by the transaction (computed on demand) */
readonly newDoc: Text;
/** The new selection produced by the transaction (computed on demand) */
readonly newSelection: EditorSelection;
/** Check whether this transaction has a given annotation */
annotation<T>(type: AnnotationType<T>): T | undefined;
/** Returns true if the transaction has a user event annotation matching the given event */
isUserEvent(event: string): boolean;
}Usage Examples:
import { EditorState, EditorSelection } from "@codemirror/state";
const state = EditorState.create({
doc: "Hello, world!"
});
// Create a transaction with document changes
const transaction = state.update({
changes: { from: 7, to: 12, insert: "CodeMirror" },
selection: EditorSelection.single(18)
});
console.log(transaction.docChanged); // true
console.log(transaction.changes.length); // 13 (original doc length)
console.log(transaction.changes.newLength); // 18 (new doc length)
console.log(transaction.startState.doc.toString()); // "Hello, world!"
console.log(transaction.state.doc.toString()); // "Hello, CodeMirror!"Specification for creating transactions.
/**
* Describes a Transaction when calling the EditorState.update method.
*/
interface TransactionSpec {
/** The changes to the document made by this transaction */
changes?: ChangeSpec;
/** When set, this transaction explicitly updates the selection */
selection?: EditorSelection | {anchor: number, head?: number};
/** Attach state effects to this transaction */
effects?: StateEffect<any> | readonly StateEffect<any>[];
/** Set annotations for this transaction */
annotations?: Annotation<any> | readonly Annotation<any>[];
/** Shorthand for annotations: Transaction.userEvent.of(...) */
userEvent?: string;
/** When set to true, the transaction is marked as needing to scroll the current selection into view */
scrollIntoView?: boolean;
/** By default, transactions can be modified by change filters and transaction filters */
filter?: boolean;
/** When a spec has sequential set to true, its changes are mapped through previous specs */
sequential?: boolean;
}Usage Examples:
// Basic change transaction
const simpleChange: TransactionSpec = {
changes: { from: 0, to: 5, insert: "Hi" }
};
// Transaction with selection update
const changeWithSelection: TransactionSpec = {
changes: { from: 0, to: 5, insert: "Hello" },
selection: EditorSelection.single(5),
scrollIntoView: true
};
// Transaction with effects and annotations
const complexTransaction: TransactionSpec = {
changes: { from: 0, insert: "// " },
effects: [SomeStateEffect.of(someValue)],
annotations: [Transaction.userEvent.of("input.comment")],
userEvent: "input.type"
};
// Apply the transaction
const newState = state.update(simpleChange).state;System for attaching metadata to transactions.
/**
* Annotations are tagged values that are used to add metadata to transactions
*/
class Annotation<T> {
/** Define a new type of annotation */
static define<T>(): AnnotationType<T>;
/** The annotation type */
readonly type: AnnotationType<T>;
/** The value of this annotation */
readonly value: T;
}
/**
* Marker that identifies a type of annotation
*/
class AnnotationType<T> {
/** Create an instance of this annotation */
of(value: T): Annotation<T>;
}Usage Examples:
// Define custom annotation types
const MyAnnotation = Annotation.define<string>();
const CounterAnnotation = Annotation.define<number>();
// Create annotations
const myAnnotation = MyAnnotation.of("custom data");
const counterAnnotation = CounterAnnotation.of(42);
// Use in transaction
const transaction = state.update({
changes: { from: 0, insert: "text" },
annotations: [myAnnotation, counterAnnotation]
});
// Read annotations from transaction
const customData = transaction.annotation(MyAnnotation); // "custom data"
const counter = transaction.annotation(CounterAnnotation); // 42System for representing side effects that should be applied alongside document changes.
/**
* State effects can be used to represent additional effects associated with a transaction
*/
class StateEffect<Value> {
/** Define a new effect type */
static define<Value = null>(spec?: StateEffectSpec<Value>): StateEffectType<Value>;
/** Map an array of effects through a change set */
static mapEffects(effects: readonly StateEffect<any>[], mapping: ChangeDesc): StateEffect<any>[];
/** This effect can be used to reconfigure the root extensions of the editor */
static reconfigure: StateEffectType<Extension>;
/** Append extensions to the top-level configuration of the editor */
static appendConfig: StateEffectType<Extension>;
/** The effect type */
readonly type: StateEffectType<Value>;
/** The value of this effect */
readonly value: Value;
/** Map this effect through a position mapping */
map(mapping: ChangeDesc): StateEffect<Value> | undefined;
/** Tells you whether this effect object is of a given type */
is<T>(type: StateEffectType<T>): this is StateEffect<T>;
}
/**
* Representation of a type of state effect
*/
class StateEffectType<Value> {
/** Create a state effect instance of this type */
of(value: Value): StateEffect<Value>;
}
interface StateEffectSpec<Value> {
/** Provides a way to map an effect through a position mapping */
map?: (value: Value, mapping: ChangeDesc) => Value | undefined;
}Usage Examples:
// Define custom effect types
const HighlightEffect = StateEffect.define<{from: number, to: number}>();
const LogEffect = StateEffect.define<string>();
// Create effects
const highlight = HighlightEffect.of({from: 5, to: 10});
const log = LogEffect.of("User performed action");
// Use effects in transaction
const transaction = state.update({
changes: { from: 0, insert: "New " },
effects: [highlight, log]
});
// Check for specific effects
for (const effect of transaction.effects) {
if (effect.is(HighlightEffect)) {
console.log("Highlight:", effect.value); // {from: 5, to: 10}
}
if (effect.is(LogEffect)) {
console.log("Log:", effect.value); // "User performed action"
}
}
// Built-in reconfiguration effect
const reconfigureEffect = StateEffect.reconfigure.of([someExtension]);
const reconfigureTransaction = state.update({
effects: reconfigureEffect
});Standard annotation types provided by the system.
/**
* Built-in annotation types available on the Transaction class
*/
class Transaction {
/** Annotation that stores a timestamp */
static time: AnnotationType<number>;
/** Annotation that holds a user event string */
static userEvent: AnnotationType<string>;
/** Annotation that indicates whether a transaction should be added to history */
static addToHistory: AnnotationType<boolean>;
/** Annotation that indicates whether a transaction is from a remote source */
static remote: AnnotationType<boolean>;
}Usage Examples:
// Add timestamp to transaction
const timedTransaction = state.update({
changes: { from: 0, insert: "text" },
annotations: Transaction.time.of(Date.now())
});
// Mark user event
const userTransaction = state.update({
changes: { from: 0, to: 4, insert: "" },
annotations: Transaction.userEvent.of("delete.selection")
});
// Control history behavior
const historyTransaction = state.update({
changes: { from: 0, insert: "auto-save: " },
annotations: [
Transaction.addToHistory.of(false), // Don't add to undo history
Transaction.userEvent.of("auto-save")
]
});
// Mark as remote transaction
const remoteTransaction = state.update({
changes: { from: 0, insert: "remote change" },
annotations: Transaction.remote.of(true)
});System for intercepting and modifying transactions before they are applied.
/**
* Function type for change filters
*/
type ChangeFilter = (tr: Transaction) => boolean | readonly number[];
/**
* Function type for transaction filters
*/
type TransactionFilter = (tr: Transaction) => TransactionSpec | readonly TransactionSpec[];
/**
* Function type for transaction extenders
*/
type TransactionExtender = (tr: Transaction) => Pick<TransactionSpec, "effects" | "annotations"> | null;Usage Examples:
// Define a change filter that prevents certain changes
const preventDeleteFilter: ChangeFilter = (tr) => {
// Return false to block the entire transaction
if (tr.annotation(Transaction.userEvent) === "delete.dangerous") {
return false;
}
// Return true to allow all changes
return true;
};
// Define a transaction filter that modifies transactions
const addTimestampFilter: TransactionFilter = (tr) => {
// Return modified transaction spec
return {
...tr,
annotations: [
...tr.annotations,
Transaction.time.of(Date.now())
]
};
};
// Define a transaction extender that adds effects
const logExtender: TransactionExtender = (tr) => {
if (tr.docChanged) {
return {
effects: [LogEffect.of("Document changed")]
};
}
return null;
};
// Register filters via facets (typically done in extensions)
const extension = [
EditorState.changeFilter.of(preventDeleteFilter),
EditorState.transactionFilter.of(addTimestampFilter),
EditorState.transactionExtender.of(logExtender)
];/**
* Options for resolving transactions
*/
interface TransactionOptions {
sequential?: boolean;
}
/**
* Interface for objects that can receive transactions
*/
interface TransactionReceiver {
dispatch(transaction: Transaction): void;
}