fix: ignore stale BlockNote side menu blocks

This commit is contained in:
lucaronin
2026-04-30 13:48:07 +02:00
parent 886ded9575
commit 765a16a4e8
3 changed files with 177 additions and 8 deletions

View File

@@ -117,6 +117,61 @@
}
function kn(o, e) {
const t = [
--- a/src/extensions/SideMenu/SideMenu.ts
+++ b/src/extensions/SideMenu/SideMenu.ts
@@ -230,38 +230,49 @@
// Doesn't update if the menu is already open and the mouse cursor is still hovering the same block.
if (
this.state?.show &&
this.hoveredBlock?.hasAttribute("data-id") &&
this.hoveredBlock?.getAttribute("data-id") === block.id
) {
return;
}
this.hoveredBlock = block.node;
+ const hoveredBlockId = this.hoveredBlock.getAttribute("data-id");
+ const hoveredEditorBlock = hoveredBlockId
+ ? this.editor.getBlock(hoveredBlockId)
+ : undefined;
+
+ if (!hoveredEditorBlock) {
+ if (this.state?.show) {
+ this.state.show = false;
+ this.updateState(this.state);
+ }
+
+ return;
+ }
// Shows or updates elements.
if (this.editor.isEditable) {
const blockContentBoundingBox = block.node.getBoundingClientRect();
const column = block.node.closest("[data-node-type=column]");
this.state = {
show: true,
referencePos: new DOMRect(
column
? // We take the first child as column elements have some default
// padding. This is a little weird since this child element will
// be the first block, but since it's always non-nested and we
// only take the x coordinate, it's ok.
column.firstElementChild!.getBoundingClientRect().x
: (
this.pmView.dom.firstChild as HTMLElement
).getBoundingClientRect().x,
blockContentBoundingBox.y,
blockContentBoundingBox.width,
blockContentBoundingBox.height,
),
- block: this.editor.getBlock(
- this.hoveredBlock!.getAttribute("data-id")!,
- )!,
+ block: hoveredEditorBlock,
};
this.updateState(this.state);
}
};
--- a/src/extensions/SuggestionMenu/SuggestionMenu.ts
+++ b/src/extensions/SuggestionMenu/SuggestionMenu.ts
@@ -32,7 +32,7 @@
@@ -202,6 +257,48 @@
if (
--- a/dist/TrailingNode-CxM966vN.js
+++ b/dist/TrailingNode-CxM966vN.js
@@ -1200,25 +1200,31 @@
(r = this.state) != null && r.show && (this.state.show = !1, this.updateState(this.state));
return;
}
- if (!((s = this.state) != null && s.show && ((i = this.hoveredBlock) != null && i.hasAttribute("data-id")) && ((a = this.hoveredBlock) == null ? void 0 : a.getAttribute("data-id")) === t.id) && (this.hoveredBlock = t.node, this.editor.isEditable)) {
- const c = t.node.getBoundingClientRect(), l = t.node.closest("[data-node-type=column]");
+ if ((s = this.state) != null && s.show && ((i = this.hoveredBlock) != null && i.hasAttribute("data-id")) && ((a = this.hoveredBlock) == null ? void 0 : a.getAttribute("data-id")) === t.id)
+ return;
+ this.hoveredBlock = t.node;
+ const c = this.hoveredBlock.getAttribute("data-id"), l = c ? this.editor.getBlock(c) : void 0;
+ if (!l) {
+ (i = this.state) != null && i.show && (this.state.show = !1, this.updateState(this.state));
+ return;
+ }
+ if (this.editor.isEditable) {
+ const u = t.node.getBoundingClientRect(), h = t.node.closest("[data-node-type=column]");
this.state = {
show: !0,
referencePos: new DOMRect(
- l ? (
+ h ? (
// We take the first child as column elements have some default
// padding. This is a little weird since this child element will
// be the first block, but since it's always non-nested and we
// only take the x coordinate, it's ok.
- l.firstElementChild.getBoundingClientRect().x
+ h.firstElementChild.getBoundingClientRect().x
) : this.pmView.dom.firstChild.getBoundingClientRect().x,
- c.y,
- c.width,
- c.height
+ u.y,
+ u.width,
+ u.height
),
- block: this.editor.getBlock(
- this.hoveredBlock.getAttribute("data-id")
- )
+ block: l
}, this.updateState(this.state);
}
});
@@ -1746,10 +1746,10 @@
);
this.state.rowIndex !== void 0 && this.state.colIndex !== void 0 && (this.state.rowIndex >= e && (this.state.rowIndex = e - 1), this.state.colIndex >= t && (this.state.colIndex = t - 1));

16
pnpm-lock.yaml generated
View File

@@ -6,7 +6,7 @@ settings:
patchedDependencies:
'@blocknote/core@0.46.2':
hash: 28d8e7bb45cd7d97aff60af3926d65b831605bfe4d582a1da725d214d3fb1f42
hash: 05f1896ec4fac0d7ffd1f823acd036baa82c1d6170a8b5699ddeb6a4dd2b3626
path: patches/@blocknote__core@0.46.2.patch
'@blocknote/react@0.46.2':
hash: e09f7011df33f4ff92c0d3fd8c9060e62f08997d076f3e438e1e82f8c1ab2f76
@@ -27,10 +27,10 @@ importers:
version: 0.78.0(zod@4.3.6)
'@blocknote/code-block':
specifier: ^0.46.2
version: 0.46.2(@blocknote/core@0.46.2(patch_hash=28d8e7bb45cd7d97aff60af3926d65b831605bfe4d582a1da725d214d3fb1f42)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0))
version: 0.46.2(@blocknote/core@0.46.2(patch_hash=05f1896ec4fac0d7ffd1f823acd036baa82c1d6170a8b5699ddeb6a4dd2b3626)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0))
'@blocknote/core':
specifier: ^0.46.2
version: 0.46.2(patch_hash=28d8e7bb45cd7d97aff60af3926d65b831605bfe4d582a1da725d214d3fb1f42)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0)
version: 0.46.2(patch_hash=05f1896ec4fac0d7ffd1f823acd036baa82c1d6170a8b5699ddeb6a4dd2b3626)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0)
'@blocknote/mantine':
specifier: ^0.46.2
version: 0.46.2(@floating-ui/dom@1.7.5)(@mantine/core@8.3.14(@mantine/hooks@8.3.14(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@8.3.14(react@19.2.4))(@mantine/utils@6.0.22(react@19.2.4))(@types/hast@3.0.4)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(highlight.js@11.11.1)(lowlight@3.3.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -5359,9 +5359,9 @@ snapshots:
'@bcoe/v8-coverage@1.0.2': {}
'@blocknote/code-block@0.46.2(@blocknote/core@0.46.2(patch_hash=28d8e7bb45cd7d97aff60af3926d65b831605bfe4d582a1da725d214d3fb1f42)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0))':
'@blocknote/code-block@0.46.2(@blocknote/core@0.46.2(patch_hash=05f1896ec4fac0d7ffd1f823acd036baa82c1d6170a8b5699ddeb6a4dd2b3626)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0))':
dependencies:
'@blocknote/core': 0.46.2(patch_hash=28d8e7bb45cd7d97aff60af3926d65b831605bfe4d582a1da725d214d3fb1f42)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0)
'@blocknote/core': 0.46.2(patch_hash=05f1896ec4fac0d7ffd1f823acd036baa82c1d6170a8b5699ddeb6a4dd2b3626)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0)
'@shikijs/core': 3.23.0
'@shikijs/engine-javascript': 3.23.0
'@shikijs/langs': 3.23.0
@@ -5369,7 +5369,7 @@ snapshots:
'@shikijs/themes': 3.23.0
'@shikijs/types': 3.22.0
'@blocknote/core@0.46.2(patch_hash=28d8e7bb45cd7d97aff60af3926d65b831605bfe4d582a1da725d214d3fb1f42)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0)':
'@blocknote/core@0.46.2(patch_hash=05f1896ec4fac0d7ffd1f823acd036baa82c1d6170a8b5699ddeb6a4dd2b3626)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0)':
dependencies:
'@emoji-mart/data': 1.2.1
'@handlewithcare/prosemirror-inputrules': 0.1.4(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.6)
@@ -5421,7 +5421,7 @@ snapshots:
'@blocknote/mantine@0.46.2(@floating-ui/dom@1.7.5)(@mantine/core@8.3.14(@mantine/hooks@8.3.14(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@8.3.14(react@19.2.4))(@mantine/utils@6.0.22(react@19.2.4))(@types/hast@3.0.4)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(highlight.js@11.11.1)(lowlight@3.3.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
'@blocknote/core': 0.46.2(patch_hash=28d8e7bb45cd7d97aff60af3926d65b831605bfe4d582a1da725d214d3fb1f42)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0)
'@blocknote/core': 0.46.2(patch_hash=05f1896ec4fac0d7ffd1f823acd036baa82c1d6170a8b5699ddeb6a4dd2b3626)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0)
'@blocknote/react': 0.46.2(patch_hash=e09f7011df33f4ff92c0d3fd8c9060e62f08997d076f3e438e1e82f8c1ab2f76)(@floating-ui/dom@1.7.5)(@types/hast@3.0.4)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(highlight.js@11.11.1)(lowlight@3.3.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@mantine/core': 8.3.14(@mantine/hooks@8.3.14(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@mantine/hooks': 8.3.14(react@19.2.4)
@@ -5443,7 +5443,7 @@ snapshots:
'@blocknote/react@0.46.2(patch_hash=e09f7011df33f4ff92c0d3fd8c9060e62f08997d076f3e438e1e82f8c1ab2f76)(@floating-ui/dom@1.7.5)(@types/hast@3.0.4)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(highlight.js@11.11.1)(lowlight@3.3.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
'@blocknote/core': 0.46.2(patch_hash=28d8e7bb45cd7d97aff60af3926d65b831605bfe4d582a1da725d214d3fb1f42)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0)
'@blocknote/core': 0.46.2(patch_hash=05f1896ec4fac0d7ffd1f823acd036baa82c1d6170a8b5699ddeb6a4dd2b3626)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0)
'@emoji-mart/data': 1.2.1
'@floating-ui/react': 0.27.17(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@floating-ui/utils': 0.2.10

View File

@@ -0,0 +1,72 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { SideMenuView } from '../../node_modules/@blocknote/core/src/extensions/SideMenu/SideMenu'
const originalElementsFromPoint = document.elementsFromPoint
function createRect({ left, top, width, height }: { left: number; top: number; width: number; height: number }) {
return {
left,
top,
right: left + width,
bottom: top + height,
width,
height,
x: left,
y: top,
toJSON: () => ({}),
} as DOMRect
}
function createStaleEditorDom() {
const editor = document.createElement('div')
editor.className = 'bn-editor'
editor.getBoundingClientRect = vi.fn(() => createRect({ left: 0, top: 0, width: 480, height: 240 }))
const blockGroup = document.createElement('div')
blockGroup.className = 'bn-block-group'
blockGroup.getBoundingClientRect = vi.fn(() => createRect({ left: 20, top: 20, width: 360, height: 160 }))
const block = document.createElement('div')
block.setAttribute('data-node-type', 'blockContainer')
block.setAttribute('data-id', 'stale-table-block')
block.getBoundingClientRect = vi.fn(() => createRect({ left: 20, top: 40, width: 260, height: 48 }))
blockGroup.appendChild(block)
editor.appendChild(blockGroup)
document.body.appendChild(editor)
return { block, editor }
}
describe('patched BlockNote side menu lifecycle', () => {
afterEach(() => {
document.elementsFromPoint = originalElementsFromPoint
document.body.innerHTML = ''
})
it('does not publish side-menu state for a stale hovered block id', () => {
const { block, editor } = createStaleEditorDom()
document.elementsFromPoint = vi.fn(() => [block, editor])
const updates: unknown[] = []
const view = new SideMenuView(
{
getBlock: vi.fn(() => undefined),
isEditable: true,
} as never,
{
dom: editor,
root: document,
} as never,
(state) => updates.push(state),
)
try {
view.onMouseMove(new MouseEvent('mousemove', { clientX: 40, clientY: 50 }))
expect(updates).toEqual([])
} finally {
view.destroy()
}
})
})