diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 2a6648f04b..b605ddbb18 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -1259,7 +1259,7 @@ export class BlockNoteEditor< editor: BlockNoteEditor; }) => void, ) { - this._eventManager.onMount(callback); + return this._eventManager.onMount(callback); } /** @@ -1275,7 +1275,7 @@ export class BlockNoteEditor< editor: BlockNoteEditor; }) => void, ) { - this._eventManager.onUnmount(callback); + return this._eventManager.onUnmount(callback); } /** diff --git a/packages/react/src/editor/BlockNoteView.tsx b/packages/react/src/editor/BlockNoteView.tsx index d810fafcbe..66cc4e2733 100644 --- a/packages/react/src/editor/BlockNoteView.tsx +++ b/packages/react/src/editor/BlockNoteView.tsx @@ -10,6 +10,7 @@ import React, { ReactNode, Ref, useCallback, + useEffect, useMemo, useState, } from "react"; @@ -192,6 +193,26 @@ function BlockNoteViewComponent< comments, ]); + // editor.domElement is gated behind TipTap's isInitialized flag, which is + // set in a setTimeout(0) after mount. Force a re-render once it becomes + // available so that all child components (toolbars, menus, etc.) that + // depend on editor.domElement see the real element. + const [, setMounted] = useState(false); + useEffect(() => { + if (editor.domElement) { + return; + } + // We listen to TipTap's "create" event directly because + // editor.onMount() is registered inside the "create" handler in + // EventManager, so it misses the first mount. The "create" event + // fires after isInitialized is set, so editor.domElement is ready. + const handler = () => setMounted(true); + editor._tiptapEditor.on("create", handler); + return () => { + editor._tiptapEditor.off("create", handler); + }; + }, [editor]); + return (