Skip to content

Commit 28d808b

Browse files
fix: Formatting toolbar getting wrong bounding box when updating React inline content (#1908)
1 parent 6a7922f commit 28d808b

File tree

1 file changed

+58
-1
lines changed

1 file changed

+58
-1
lines changed

packages/core/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { isNodeSelection, isTextSelection, posToDOMRect } from "@tiptap/core";
2-
import { EditorState, Plugin, PluginKey, PluginView } from "prosemirror-state";
2+
import {
3+
EditorState,
4+
Plugin,
5+
PluginKey,
6+
PluginView,
7+
TextSelection,
8+
} from "prosemirror-state";
39
import { EditorView } from "prosemirror-view";
410

511
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
@@ -198,6 +204,57 @@ export class FormattingToolbarView implements PluginView {
198204
// e.g. the download file button, should still be accessible. Therefore,
199205
// logic for hiding when the editor is non-editable is handled
200206
// individually in each button.
207+
const newReferencePos = this.getSelectionBoundingBox();
208+
209+
// Workaround to ensure the correct reference position when rendering
210+
// React components. Without this, e.g. updating styles on React inline
211+
// content causes the formatting toolbar to be in the wrong place. We
212+
// know the component has not yet rendered if the reference position has
213+
// zero dimensions.
214+
if (
215+
newReferencePos.x === 0 ||
216+
newReferencePos.y === 0 ||
217+
newReferencePos.width === 0 ||
218+
newReferencePos.height === 0
219+
) {
220+
// Updates the reference position again following the render.
221+
queueMicrotask(() => {
222+
const nextState = {
223+
show: true,
224+
referencePos: this.getSelectionBoundingBox(),
225+
};
226+
227+
this.state = nextState;
228+
this.emitUpdate();
229+
230+
// For some reason, while the selection doesn't actually change and
231+
// remains correct, it visually appears to be collapsed. This forces
232+
// a ProseMirror view update, which fixes the issue.
233+
view.dispatch(
234+
view.state.tr.setSelection(
235+
TextSelection.create(
236+
view.state.doc,
237+
view.state.selection.from + 1,
238+
view.state.selection.to,
239+
),
240+
),
241+
);
242+
// 2 separate `dispatch` calls are needed, else ProseMirror realizes
243+
// that the transaction is a no-op and doesn't update the view.
244+
view.dispatch(
245+
view.state.tr.setSelection(
246+
TextSelection.create(
247+
view.state.doc,
248+
view.state.selection.from - 1,
249+
view.state.selection.to,
250+
),
251+
),
252+
);
253+
});
254+
255+
return;
256+
}
257+
201258
const nextState = {
202259
show: true,
203260
referencePos: this.getSelectionBoundingBox(),

0 commit comments

Comments
 (0)