|
1 | 1 | 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"; |
3 | 9 | import { EditorView } from "prosemirror-view";
|
4 | 10 |
|
5 | 11 | import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
|
@@ -198,6 +204,57 @@ export class FormattingToolbarView implements PluginView {
|
198 | 204 | // e.g. the download file button, should still be accessible. Therefore,
|
199 | 205 | // logic for hiding when the editor is non-editable is handled
|
200 | 206 | // 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 | + |
201 | 258 | const nextState = {
|
202 | 259 | show: true,
|
203 | 260 | referencePos: this.getSelectionBoundingBox(),
|
|
0 commit comments