From b6e94771f6fd2c1dbda97db8e66540ddfb4b09be Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Mon, 20 Jul 2026 16:55:50 +0200 Subject: [PATCH] fix(react): avoid importing @blocknote/core/y in the default UI BlockNoteDefaultUI and AttributionTooltipController imported the AttributionExtension value from @blocknote/core/y, which pulls in @y/prosemirror even for editors without collaboration. Look up the extension by its string key instead, and inline the two small DOM helpers it needed, so the collaboration entry point is never resolved unless the app actually uses it. Fixes #2901 --- .../AttributionTooltipController.tsx | 33 +++++++++++-------- .../react/src/editor/BlockNoteDefaultUI.tsx | 3 +- packages/react/src/hooks/useExtension.ts | 8 +++-- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/packages/react/src/components/AttributionTooltip/AttributionTooltipController.tsx b/packages/react/src/components/AttributionTooltip/AttributionTooltipController.tsx index 57a691638e..5d070c0c99 100644 --- a/packages/react/src/components/AttributionTooltip/AttributionTooltipController.tsx +++ b/packages/react/src/components/AttributionTooltip/AttributionTooltipController.tsx @@ -1,8 +1,4 @@ -import { - AttributionExtension, - getReferenceClientRects, - getReferenceRect, -} from "@blocknote/core/y"; +import type { AttributionExtension } from "@blocknote/core/y"; import { flip, offset, shift, inline } from "@floating-ui/react"; import { FC, useMemo } from "react"; @@ -39,22 +35,33 @@ export const AttributionTooltipController = (props: { */ portalElement?: HTMLElement | null; }) => { - const state = useExtensionState(AttributionExtension, { + const state = useExtensionState("attribution", { selector: (state) => state, }); - // Position off the hovered mark. Block-level marks are `display: contents`, so - // a live `getBoundingClientRect` (via `getReferenceRect`) is provided rather - // than relying on the wrapper's own (zero-sized) box. const reference = useMemo( () => state ? { element: state.anchor, - getBoundingClientRect: () => getReferenceRect(state.anchor), - // Required by the `inline()` middleware — a virtual reference has no - // default `getClientRects`, and `inline()` reads per-line rects. - getClientRects: () => getReferenceClientRects(state.anchor), + getBoundingClientRect: () => { + const content = state.anchor.firstElementChild ?? state.anchor; + const rect = content.getBoundingClientRect(); + const el = + rect.width || rect.height + ? content + : (content.firstElementChild ?? content); + return el.getBoundingClientRect(); + }, + getClientRects: () => { + const content = state.anchor.firstElementChild ?? state.anchor; + const rect = content.getBoundingClientRect(); + const el = + rect.width || rect.height + ? content + : (content.firstElementChild ?? content); + return el.getClientRects(); + }, } : undefined, [state], diff --git a/packages/react/src/editor/BlockNoteDefaultUI.tsx b/packages/react/src/editor/BlockNoteDefaultUI.tsx index c85eef3973..75d618dc71 100644 --- a/packages/react/src/editor/BlockNoteDefaultUI.tsx +++ b/packages/react/src/editor/BlockNoteDefaultUI.tsx @@ -7,7 +7,6 @@ import { SuggestionMenu, TableHandlesExtension, } from "@blocknote/core/extensions"; -import { AttributionExtension } from "@blocknote/core/y"; import { lazy, Suspense } from "react"; import { FilePanelController } from "../components/FilePanel/FilePanelController.js"; @@ -162,7 +161,7 @@ export function BlockNoteDefaultUI(props: BlockNoteDefaultUIProps) { )} - {editor.getExtension(AttributionExtension) && + {editor.getExtension("attribution") && props.attributionTooltip !== false && ( >, >( - plugin: T, + plugin: T | string, ctx?: { editor?: BlockNoteEditor; selector?: (state: NoInfer>) => TSelected; }, ): TSelected { - const { store } = useExtension(plugin, ctx); + const extension = useExtension( + plugin as ExtensionFactory | Extension | string, + ctx, + ); + const { store } = extension; if (!store) { throw new Error("Store not found on plugin", { cause: { plugin } }); }