feat: reusable React API for source blocks with previews#2883
Conversation
Extracts the math block's React popup UI into reusable components and hooks in @blocknote/react: the PreviewWithSourcePopup shell, the SourceBlockWithPreview/SourceInlineContentWithPreview wrappers, and the useSourceBlockPreviewPopup/useSourceInlineContentPreviewPopup hooks. Moves the generic SourceInlineContentWithPreviewExtension to core after splitting its math-specific input rules into a new MathInlineInputRulesExtension. Also fixes exported inline math HTML rendering as selected with an open popup, and reads getPos() fresh in popup actions instead of capturing it at render time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
@blocknote/ariakit
@blocknote/code-block
@blocknote/core
@blocknote/mantine
@blocknote/math-block
@blocknote/react
@blocknote/server-util
@blocknote/shadcn
@blocknote/xl-ai
@blocknote/xl-docx-exporter
@blocknote/xl-email-exporter
@blocknote/xl-multi-column
@blocknote/xl-odt-exporter
@blocknote/xl-pdf-exporter
commit: |
|
matthewlipski
left a comment
There was a problem hiding this comment.
Looks good, few comments
| * Ref for the element holding the editable source content. | ||
| */ | ||
| contentRef: (node: HTMLElement | null) => void; | ||
| onPreviewMouseDown?: MouseEventHandler; |
There was a problem hiding this comment.
I'm not a fan of passing individual event handlers like this. I would rather pass a setSelected and setPopupOpen then have PreviewWithSourcePopup create the mouse down handlers. Alternatively, if we really want to make PreviewWithSourcePopup as "dumb" as possible, I would still rather make it possible to pass all props rather than picking out just the mouse down handlers (previewContainerProps: HTMLAttributes<HTMLElement>; okButtonProps: HTMLAttributes<HTMLButtonElement>;).
| /** | ||
| * Whether the source popup is shown. | ||
| */ | ||
| popupOpen: boolean; |
There was a problem hiding this comment.
We should standardize the naming of popupOpen & selected to isOpen & isSelected.
| import { useSourceInlineContentPreviewPopup } from "./useSourceInlineContentPreviewPopup.js"; | ||
|
|
||
| export type SourceInlineContentWithPreviewProps = { | ||
| editor: BlockNoteEditor<any, any, any>; |
There was a problem hiding this comment.
Can be picked out from InlineContentImplementation instead of manually re-typed. Same for the block equivalent.
| SourceInlineContentWithPreviewExtension({ | ||
| key: INLINE_MATH_PREVIEW_KEY, | ||
| inlineContentType: "inlineMath", | ||
| inlineContentType: mathInlineContentConfig.type, |
There was a problem hiding this comment.
If we're changing this here we should probably do the same for the math block
TLDR: Separates some of the react components and introduces reusable
<PreviewWithSourcePopup>component. (Is not a decision yet on what to expose or vanilla vs. react)Summary
Builds on #2857: extracts the math block's React popup UI into a reusable API in
@blocknote/reactfor any block or inline content that renders a preview of its source (math today; mermaid, graphviz, etc. tomorrow), and fixes a few bugs found along the way.Rationale
In #2857 the React math block and inline math each hand-rolled the full preview/popup DOM, handlers, and store wiring. That made "build your own preview block" (or "customize the math block") mean copying ~100+ lines of internals. This PR makes the consumer story: config + preview + optionally a popup hook — the math renders themselves are now ~25-line compositions and serve as the copy-me template.
Changes
@blocknote/react— newblocks/SourceWithPreview/PreviewWithSourcePopup— presentational shell (block + inline via aninlineprop). Theerrorprop accepts arbitrary elements, so consumers can render actions (e.g. a "fix with AI" button) alongside the error message.block/SourceBlockWithPreview&inlineContent/SourceInlineContentWithPreview— wired wrappers; callers only supplysource,preview, and optionalerror.block/useSourceBlockPreviewPopup&inlineContent/useSourceInlineContentPreviewPopup— the public popup-state API. Both return the sharedSourcePreviewPopupshape ({ isOpen, isSelected, open, close }), so consumers never touchuseExtensionor raw extension stores. Split per kind deliberately: the block popup is an explicit toggle while the inline popup is selection-driven (isOpen === isSelected), and a unified hook meant type-erasing casts and interleaved branches.AddSourceButtonmoved here from@blocknote/math-block(it was generic).@blocknote/coreSourceInlineContentWithPreviewExtensionmoved from math-block to core, next to its block counterpart, after extracting the math-specific input rules. The two extensions intentionally stay separate — the block/inline UX models differ enough that merging them only hid the differences behind indirection.@blocknote/math-blockMathInlineInputRulesExtension($...$and\(...\)), mirroringMathBlockInputRulesExtension.Bug fixes
data-open="true"andProseMirror-selectednode: outside the editorgetPos()returnsundefined, which matched the store's initialundefinedstate. The hooks now guard against this; theinlineMath/basicsnapshot reflects the corrected output.getPos()once at render time; positions shift without node views re-rendering, so actions could target stale positions. Position is now read fresh at action time.blocks.json,inlinecontent.json) that were already failing on the base branch.Impact
No behavior changes to the math block or code block beyond the export fix. Spec-facing APIs are unchanged; the math package no longer exports
AddSourceButtonor the inline preview extension (now in core).Testing
Additional Notes
#2857TODO about copy/paste while a block popup is closed remains open; the suggested direction is content-layer protection (a ProseMirror plugin viahandleTextInput/handlePaste) instead of the current capture-phase keydown suppression, which also misses drop/IME/autocorrect.getExtension(factory)returns only the last-registered instance per factory, so block + inline (or code block + math) preview extensions can't be looked up by factory — the new hooks avoid this, but a keyed lookup (e.g.getExtension(factory, key)) is the eventual fix.🤖 Generated with Claude Code