From 3a525a6dcb68094461c55c9a80ae07321375f4c2 Mon Sep 17 00:00:00 2001 From: Matthew Lipski Date: Thu, 24 Sep 2026 14:39:13 +0200 Subject: [PATCH 1/2] Fixed WebKit scrolling on block drop --- packages/core/src/extensions/SideMenu/SideMenu.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/core/src/extensions/SideMenu/SideMenu.ts b/packages/core/src/extensions/SideMenu/SideMenu.ts index 112803b95d..3987ffb5d0 100644 --- a/packages/core/src/extensions/SideMenu/SideMenu.ts +++ b/packages/core/src/extensions/SideMenu/SideMenu.ts @@ -752,6 +752,16 @@ export const SideMenuExtension = createExtension(({ editor }) => { prosemirrorPlugins: [ new Plugin({ key: sideMenuPluginKey, + appendTransaction: (transactions, _oldState, newState) => { + if (transactions.some((tr) => tr.getMeta("uiEvent") === "drop")) { + // Forces a `scrollIntoView` immediately after a drop. Fixes WebKit + // specific behavior where focusing scrolls the selection into + // view. This happens on drop before ProseMirror updates the + // document/selection, which is incorrect. + return newState.tr.scrollIntoView(); + } + return null; + }, view: (editorView) => { view = new SideMenuView(editor, editorView, (state) => { // TODO: Without spreading the state, in some cases like toggling From b99ebc2e4372fae0196b5a61c6f6318edf79f0ea Mon Sep 17 00:00:00 2001 From: Matthew Lipski Date: Thu, 24 Sep 2026 14:59:30 +0200 Subject: [PATCH 2/2] Added e2e test --- .../src/end-to-end/dragdrop/dragdrop.test.tsx | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/tests/src/end-to-end/dragdrop/dragdrop.test.tsx b/tests/src/end-to-end/dragdrop/dragdrop.test.tsx index d435842966..b185f8d5c8 100644 --- a/tests/src/end-to-end/dragdrop/dragdrop.test.tsx +++ b/tests/src/end-to-end/dragdrop/dragdrop.test.tsx @@ -1,3 +1,7 @@ +import { BlockNoteEditor } from "@blocknote/core"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { createRef } from "react"; import TestingApp from "@examples/01-basic/testing/src/App"; import PdfFileApp from "@examples/06-custom-schema/04-pdf-file-block/src/App"; import { describe, expect, test } from "vite-plus/test"; @@ -148,4 +152,65 @@ describe("Check Block Dragging Functionality", () => { await compareDocToSnapshot("dragPdf"); }, ); + + test.skipIf(browserName === "firefox")( + "keeps the dropped block visible when the previous selection is offscreen", + async () => { + const editor = BlockNoteEditor.create({ + initialContent: Array.from({ length: 70 }, (_, index) => ({ + id: `paragraph-${index}`, + type: "paragraph", + content: `Paragraph ${index}`, + })), + }); + const scrollRef = createRef(); + await render( +
+ +
, + ); + const scroller = scrollRef.current!; + const selectedBlock = await waitForSelector('[data-id="paragraph-0"]'); + const source = await waitForSelector('[data-id="paragraph-60"]'); + const destination = await waitForSelector('[data-id="paragraph-62"]'); + editor.setTextCursorPosition("paragraph-0", "start"); + editor.focus(); + + // Keep the native selection near the start while scrolling to later blocks. + // Refocusing on drop must not scroll back to that old selection. + scroller.scrollTop += + source.getBoundingClientRect().top - + scroller.getBoundingClientRect().top - + 80; + expect(selectedBlock.getBoundingClientRect().bottom).toBeLessThan( + scroller.getBoundingClientRect().top, + ); + expect(editor.getTextCursorPosition().block.id).toBe("paragraph-0"); + + await dragAndDropBlock(source, destination, false); + + await expect + .poll(() => { + const ids = editor.document.map((block) => block.id); + return ids[ids.indexOf("paragraph-62") + 1]; + }) + .toBe("paragraph-60"); + await expect + .poll(() => { + const moved = scroller.querySelector('[data-id="paragraph-60"]'); + if (!moved) { + return false; + } + const blockRect = moved.getBoundingClientRect(); + const viewport = scroller.getBoundingClientRect(); + return ( + blockRect.top >= viewport.top && blockRect.bottom <= viewport.bottom + ); + }) + .toBe(true); + }, + ); });