55 StyleSchema ,
66 mergeCSSClasses ,
77} from "@blocknote/core" ;
8-
98import React , {
109 HTMLAttributes ,
1110 ReactNode ,
@@ -15,14 +14,23 @@ import React, {
1514 useMemo ,
1615 useState ,
1716} from "react" ;
17+ import { useBlockNoteEditor } from "../hooks/useBlockNoteEditor.js" ;
1818import { useEditorChange } from "../hooks/useEditorChange.js" ;
1919import { useEditorSelectionChange } from "../hooks/useEditorSelectionChange.js" ;
2020import { usePrefersColorScheme } from "../hooks/usePrefersColorScheme.js" ;
21- import { BlockNoteContext , useBlockNoteContext } from "./BlockNoteContext.js" ;
21+ import {
22+ BlockNoteContext ,
23+ BlockNoteContextValue ,
24+ useBlockNoteContext ,
25+ } from "./BlockNoteContext.js" ;
2226import {
2327 BlockNoteDefaultUI ,
2428 BlockNoteDefaultUIProps ,
2529} from "./BlockNoteDefaultUI.js" ;
30+ import {
31+ BlockNoteViewContext ,
32+ useBlockNoteViewContext ,
33+ } from "./BlockNoteViewContext.js" ;
2634import { Portals , getContentComponent } from "./EditorContent.js" ;
2735import { ElementRenderer } from "./ElementRenderer.js" ;
2836import "./styles.css" ;
@@ -40,8 +48,18 @@ export type BlockNoteViewProps<
4048
4149 theme ?: "light" | "dark" ;
4250
51+ /**
52+ * Whether to render the editor element itself.
53+ * When `false`, you're responsible for rendering the editor yourself using the `BlockNoteViewEditor` component.
54+ *
55+ * @default true
56+ */
57+ renderEditor ?: boolean ;
58+
4359 /**
4460 * Locks the editor from being editable by the user if set to `false`.
61+ *
62+ * @default true
4563 */
4664 editable ?: boolean ;
4765 /**
@@ -86,6 +104,8 @@ function BlockNoteViewComponent<
86104 sideMenu,
87105 filePanel,
88106 tableHandles,
107+ autoFocus,
108+ renderEditor = ! editor . headless ,
89109 ...rest
90110 } = props ;
91111
@@ -109,83 +129,58 @@ function BlockNoteViewComponent<
109129 editor . isEditable = editable !== false ;
110130 } , [ editable , editor ] ) ;
111131
112- const renderChildren = useMemo ( ( ) => {
113- return (
114- < >
115- { children }
116- < BlockNoteDefaultUI
117- formattingToolbar = { formattingToolbar }
118- linkToolbar = { linkToolbar }
119- slashMenu = { slashMenu }
120- emojiPicker = { emojiPicker }
121- sideMenu = { sideMenu }
122- filePanel = { filePanel }
123- tableHandles = { tableHandles }
124- />
125- </ >
126- ) ;
127- } , [
128- children ,
129- formattingToolbar ,
130- linkToolbar ,
131- slashMenu ,
132- emojiPicker ,
133- sideMenu ,
134- filePanel ,
135- tableHandles ,
136- ] ) ;
132+ const setElementRenderer = useCallback (
133+ ( ref : ( typeof editor ) [ "elementRenderer" ] ) => {
134+ editor . elementRenderer = ref ;
135+ } ,
136+ [ editor ]
137+ ) ;
137138
138- const context = useMemo ( ( ) => {
139+ // The BlockNoteContext makes sure the editor and some helper methods
140+ // are always available to nesteed compoenents
141+ const blockNoteContext : BlockNoteContextValue < any , any , any > = useMemo ( ( ) => {
139142 return {
140143 ...existingContext ,
141144 editor,
142145 setContentEditableProps,
143146 } ;
144147 } , [ existingContext , editor ] ) ;
145148
146- const setElementRenderer = useCallback (
147- ( ref : ( typeof editor ) [ "elementRenderer" ] ) => {
148- editor . elementRenderer = ref ;
149- } ,
150- [ editor ]
151- ) ;
152-
153- const portalManager = useMemo ( ( ) => {
154- return getContentComponent ( ) ;
155- } , [ ] ) ;
149+ // We set defaultUIProps and editorProps on a different context, the BlockNoteViewContext.
150+ // This BlockNoteViewContext is used to render the editor and the default UI.
151+ const defaultUIProps = {
152+ formattingToolbar,
153+ linkToolbar,
154+ slashMenu,
155+ emojiPicker,
156+ sideMenu,
157+ filePanel,
158+ tableHandles,
159+ } ;
156160
157- const mount = useCallback (
158- ( element : HTMLElement | null ) => {
159- editor . mount ( element , portalManager ) ;
160- } ,
161- [ editor , portalManager ]
162- ) ;
161+ const editorProps = {
162+ autoFocus,
163+ className,
164+ editorColorScheme,
165+ contentEditableProps,
166+ ref,
167+ ...rest ,
168+ } ;
163169
164170 return (
165- < BlockNoteContext . Provider value = { context as any } >
166- < ElementRenderer ref = { setElementRenderer } />
167- { ! editor . headless && (
168- < >
169- < Portals contentComponent = { portalManager } />
170- < div
171- className = { mergeCSSClasses (
172- "bn-container" ,
173- editorColorScheme || "" ,
174- className || ""
175- ) }
176- data-color-scheme = { editorColorScheme }
177- { ...rest }
178- ref = { ref } >
179- < div
180- aria-autocomplete = "list"
181- aria-haspopup = "listbox"
182- ref = { mount }
183- { ...contentEditableProps }
184- />
185- { renderChildren }
186- </ div >
187- </ >
188- ) }
171+ < BlockNoteContext . Provider value = { blockNoteContext } >
172+ < BlockNoteViewContext . Provider
173+ value = { {
174+ editorProps,
175+ defaultUIProps,
176+ } } >
177+ < ElementRenderer ref = { setElementRenderer } />
178+ { renderEditor ? (
179+ < BlockNoteViewEditor > { children } </ BlockNoteViewEditor >
180+ ) : (
181+ children
182+ ) }
183+ </ BlockNoteViewContext . Provider >
189184 </ BlockNoteContext . Provider >
190185 ) ;
191186}
@@ -200,3 +195,76 @@ export const BlockNoteViewRaw = React.forwardRef(BlockNoteViewComponent) as <
200195 ref ?: React . ForwardedRef < HTMLDivElement > ;
201196 }
202197) => ReturnType < typeof BlockNoteViewComponent < BSchema , ISchema , SSchema > > ;
198+
199+ /**
200+ * Renders the editor itself and the default UI elements
201+ */
202+ export const BlockNoteViewEditor = ( props : { children : ReactNode } ) => {
203+ const ctx = useBlockNoteViewContext ( ) ! ;
204+ const editor = useBlockNoteEditor ( ) ;
205+
206+ const portalManager = useMemo ( ( ) => {
207+ return getContentComponent ( ) ;
208+ } , [ ] ) ;
209+
210+ const mount = useCallback (
211+ ( element : HTMLElement | null ) => {
212+ editor . mount ( element , portalManager ) ;
213+ } ,
214+ [ editor , portalManager ]
215+ ) ;
216+
217+ return (
218+ < >
219+ < Portals contentComponent = { portalManager } />
220+ < EditorElement { ...ctx . editorProps } { ...props } mount = { mount } >
221+ { /* Renders the UI elements such as formatting toolbar, etc, unless they have been explicitly disabled in defaultUIProps */ }
222+ < BlockNoteDefaultUI { ...ctx . defaultUIProps } />
223+ { /* Manually passed in children, such as customized UI elements / controllers */ }
224+ { props . children }
225+ </ EditorElement >
226+ </ >
227+ ) ;
228+ } ;
229+
230+ /**
231+ * Renders the container div + contentEditable div.
232+ */
233+ const EditorElement = (
234+ props : {
235+ className ?: string ;
236+ editorColorScheme ?: string ;
237+ autoFocus ?: boolean ;
238+ mount : ( element : HTMLElement | null ) => void ;
239+ contentEditableProps ?: Record < string , any > ;
240+ children : ReactNode ;
241+ } & HTMLAttributes < HTMLDivElement >
242+ ) => {
243+ const {
244+ className,
245+ editorColorScheme,
246+ autoFocus,
247+ mount,
248+ children,
249+ contentEditableProps,
250+ ...rest
251+ } = props ;
252+ return (
253+ // The container wraps the contentEditable div and UI Elements such as sidebar, formatting toolbar, etc.
254+ < div
255+ className = { mergeCSSClasses ( "bn-container" , editorColorScheme , className ) }
256+ data-color-scheme = { editorColorScheme }
257+ { ...rest } >
258+ { /* The actual contentEditable that Prosemirror mounts to */ }
259+ < div
260+ aria-autocomplete = "list"
261+ aria-haspopup = "listbox"
262+ data-bn-autofocus = { autoFocus }
263+ ref = { mount }
264+ { ...contentEditableProps }
265+ />
266+ { /* The UI elements such as sidebar, formatting toolbar, etc. */ }
267+ { children }
268+ </ div >
269+ ) ;
270+ } ;
0 commit comments