5
5
StyleSchema ,
6
6
mergeCSSClasses ,
7
7
} from "@blocknote/core" ;
8
-
9
8
import React , {
10
9
HTMLAttributes ,
11
10
ReactNode ,
@@ -15,14 +14,23 @@ import React, {
15
14
useMemo ,
16
15
useState ,
17
16
} from "react" ;
17
+ import { useBlockNoteEditor } from "../hooks/useBlockNoteEditor.js" ;
18
18
import { useEditorChange } from "../hooks/useEditorChange.js" ;
19
19
import { useEditorSelectionChange } from "../hooks/useEditorSelectionChange.js" ;
20
20
import { usePrefersColorScheme } from "../hooks/usePrefersColorScheme.js" ;
21
- import { BlockNoteContext , useBlockNoteContext } from "./BlockNoteContext.js" ;
21
+ import {
22
+ BlockNoteContext ,
23
+ BlockNoteContextValue ,
24
+ useBlockNoteContext ,
25
+ } from "./BlockNoteContext.js" ;
22
26
import {
23
27
BlockNoteDefaultUI ,
24
28
BlockNoteDefaultUIProps ,
25
29
} from "./BlockNoteDefaultUI.js" ;
30
+ import {
31
+ BlockNoteViewContext ,
32
+ useBlockNoteViewContext ,
33
+ } from "./BlockNoteViewContext.js" ;
26
34
import { Portals , getContentComponent } from "./EditorContent.js" ;
27
35
import { ElementRenderer } from "./ElementRenderer.js" ;
28
36
import "./styles.css" ;
@@ -40,8 +48,18 @@ export type BlockNoteViewProps<
40
48
41
49
theme ?: "light" | "dark" ;
42
50
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
+
43
59
/**
44
60
* Locks the editor from being editable by the user if set to `false`.
61
+ *
62
+ * @default true
45
63
*/
46
64
editable ?: boolean ;
47
65
/**
@@ -86,6 +104,8 @@ function BlockNoteViewComponent<
86
104
sideMenu,
87
105
filePanel,
88
106
tableHandles,
107
+ autoFocus,
108
+ renderEditor = ! editor . headless ,
89
109
...rest
90
110
} = props ;
91
111
@@ -109,83 +129,58 @@ function BlockNoteViewComponent<
109
129
editor . isEditable = editable !== false ;
110
130
} , [ editable , editor ] ) ;
111
131
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
+ ) ;
137
138
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 ( ( ) => {
139
142
return {
140
143
...existingContext ,
141
144
editor,
142
145
setContentEditableProps,
143
146
} ;
144
147
} , [ existingContext , editor ] ) ;
145
148
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
+ } ;
156
160
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
+ } ;
163
169
164
170
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 >
189
184
</ BlockNoteContext . Provider >
190
185
) ;
191
186
}
@@ -200,3 +195,76 @@ export const BlockNoteViewRaw = React.forwardRef(BlockNoteViewComponent) as <
200
195
ref ?: React . ForwardedRef < HTMLDivElement > ;
201
196
}
202
197
) => 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