diff --git a/packages/core/src/editor/BlockNoteEditor.test.ts b/packages/core/src/editor/BlockNoteEditor.test.ts
index bb15b66858..55435a083b 100644
--- a/packages/core/src/editor/BlockNoteEditor.test.ts
+++ b/packages/core/src/editor/BlockNoteEditor.test.ts
@@ -6,6 +6,7 @@ import {
getNearestBlockPos,
} from "../api/getBlockInfoFromPos.js";
import { BlockNoteEditor } from "./BlockNoteEditor.js";
+import { BlocksChanged } from "../api/getBlocksChangedByTransaction.js";
/**
* @vitest-environment jsdom
@@ -190,3 +191,62 @@ it("sets an initial block id when using Y.js", async () => {
`"Hello"`,
);
});
+
+it("onBeforeChange", () => {
+ const editor = BlockNoteEditor.create();
+ let beforeChangeCalled = false;
+ let changes: BlocksChanged = [];
+ editor.onBeforeChange(({ getChanges }) => {
+ beforeChangeCalled = true;
+ changes = getChanges();
+ return true;
+ });
+ editor.mount(document.createElement("div"));
+ editor.replaceBlocks(editor.document, [
+ {
+ type: "paragraph",
+ content: [{ text: "Hello", styles: {}, type: "text" }],
+ },
+ ]);
+ expect(beforeChangeCalled).toBe(true);
+ expect(changes).toMatchInlineSnapshot(`
+ [
+ {
+ "block": {
+ "children": [],
+ "content": [],
+ "id": "3",
+ "props": {
+ "backgroundColor": "default",
+ "textAlignment": "left",
+ "textColor": "default",
+ },
+ "type": "paragraph",
+ },
+ "prevBlock": undefined,
+ "source": {
+ "type": "local",
+ },
+ "type": "insert",
+ },
+ {
+ "block": {
+ "children": [],
+ "content": [],
+ "id": "2",
+ "props": {
+ "backgroundColor": "default",
+ "textAlignment": "left",
+ "textColor": "default",
+ },
+ "type": "paragraph",
+ },
+ "prevBlock": undefined,
+ "source": {
+ "type": "local",
+ },
+ "type": "delete",
+ },
+ ]
+ `);
+});
diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts
index 35ecd5265f..545fd3955f 100644
--- a/packages/core/src/editor/BlockNoteEditor.ts
+++ b/packages/core/src/editor/BlockNoteEditor.ts
@@ -52,6 +52,7 @@ import {
} from "./managers/index.js";
import type { Selection } from "./selectionTypes.js";
import { transformPasted } from "./transformPasted.js";
+import { BlockChangeExtension } from "../extensions/index.js";
export type BlockCache<
BSchema extends BlockSchema = any,
@@ -904,6 +905,22 @@ export class BlockNoteEditor<
this._tiptapEditor.on("selectionUpdate", callback);
}
+ /**
+ * Executes a callback before any change is applied to the editor, allowing you to cancel the change.
+ * @param callback The callback to execute.
+ * @returns A function to remove the callback.
+ */
+ public onBeforeChange(
+ callback: (context: {
+ getChanges: () => BlocksChanged;
+ tr: Transaction;
+ }) => boolean | void,
+ ) {
+ return this._extensionManager
+ .getExtension(BlockChangeExtension)
+ ?.subscribe(callback);
+ }
+
/**
* Gets a snapshot of the current text cursor position.
* @returns A snapshot of the current text cursor position.