@tiptap/react 3.0.0-next.1 → 3.0.0-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,77 +1,83 @@
1
1
  import { FloatingMenuPlugin, FloatingMenuPluginProps } from '@tiptap/extension-floating-menu'
2
- import React, {
3
- useEffect, useRef,
4
- } from 'react'
2
+ import React, { useEffect, useRef } from 'react'
5
3
  import { createPortal } from 'react-dom'
6
4
 
7
5
  import { useCurrentEditor } from './Context.js'
8
6
 
9
- type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>
7
+ type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
10
8
 
11
- export type FloatingMenuProps = Omit<Optional<FloatingMenuPluginProps, 'pluginKey'>, 'element' | 'editor'> & {
9
+ export type FloatingMenuProps = Omit<
10
+ Optional<FloatingMenuPluginProps, 'pluginKey'>,
11
+ 'element' | 'editor'
12
+ > & {
12
13
  editor: FloatingMenuPluginProps['editor'] | null;
13
- className?: string,
14
- children: React.ReactNode
15
- options?: FloatingMenuPluginProps['options']
16
- }
14
+ options?: FloatingMenuPluginProps['options'];
15
+ } & React.HTMLAttributes<HTMLDivElement>;
17
16
 
18
- export const FloatingMenu = (props: FloatingMenuProps) => {
17
+ export const FloatingMenu = React.forwardRef<HTMLDivElement, FloatingMenuProps>(({
18
+ pluginKey = 'floatingMenu',
19
+ editor,
20
+ shouldShow = null,
21
+ options,
22
+ children,
23
+ ...restProps
24
+ }, ref) => {
19
25
  const menuEl = useRef(document.createElement('div'))
26
+
27
+ if (typeof ref === 'function') {
28
+ ref(menuEl.current)
29
+ } else if (ref) {
30
+ ref.current = menuEl.current
31
+ }
32
+
20
33
  const { editor: currentEditor } = useCurrentEditor()
21
34
 
22
35
  useEffect(() => {
23
- menuEl.current.style.visibility = 'hidden'
24
- menuEl.current.style.position = 'absolute'
36
+ const floatingMenuElement = menuEl.current
37
+
38
+ floatingMenuElement.style.visibility = 'hidden'
39
+ floatingMenuElement.style.position = 'absolute'
25
40
 
26
- if (props.editor?.isDestroyed || currentEditor?.isDestroyed) {
41
+ if (editor?.isDestroyed || currentEditor?.isDestroyed) {
27
42
  return
28
43
  }
29
44
 
30
- const {
31
- pluginKey = 'floatingMenu',
32
- editor,
33
- options,
34
- shouldShow = null,
35
- } = props
36
-
37
- const menuEditor = editor || currentEditor
45
+ const attachToEditor = editor || currentEditor
38
46
 
39
- if (!menuEditor) {
40
- console.warn('FloatingMenu component is not rendered inside of an editor component or does not have editor prop.')
47
+ if (!attachToEditor) {
48
+ console.warn(
49
+ 'FloatingMenu component is not rendered inside of an editor component or does not have editor prop.',
50
+ )
41
51
  return
42
52
  }
43
53
 
44
54
  const plugin = FloatingMenuPlugin({
55
+ editor: attachToEditor,
56
+ element: floatingMenuElement,
45
57
  pluginKey,
46
- editor: menuEditor,
47
- element: menuEl.current,
48
- options,
49
58
  shouldShow,
59
+ options,
50
60
  })
51
61
 
52
- menuEditor.registerPlugin(plugin)
62
+ attachToEditor.registerPlugin(plugin)
63
+
53
64
  return () => {
54
- menuEditor.unregisterPlugin(pluginKey)
65
+ attachToEditor.unregisterPlugin(pluginKey)
55
66
  window.requestAnimationFrame(() => {
56
- if (menuEl.current.parentNode) {
57
- menuEl.current.parentNode.removeChild(menuEl.current)
67
+ if (floatingMenuElement.parentNode) {
68
+ floatingMenuElement.parentNode.removeChild(floatingMenuElement)
58
69
  }
59
70
  })
60
71
  }
61
- }, [
62
- props.editor,
63
- currentEditor,
64
- ])
65
-
66
- const portal = createPortal(
67
- (
68
- <div className={props.className}>
69
- {props.children}
70
- </div>
71
- ), menuEl.current,
72
- )
72
+ // eslint-disable-next-line react-hooks/exhaustive-deps
73
+ }, [editor, currentEditor])
73
74
 
74
- return (
75
- <>{portal}</>
75
+ return createPortal(
76
+ <div
77
+ {...restProps}
78
+ >
79
+ {children}
80
+ </div>,
81
+ menuEl.current,
76
82
  )
77
- }
83
+ })
@@ -1,55 +1,90 @@
1
1
  import {
2
2
  DecorationWithType,
3
3
  Editor,
4
+ getRenderedAttributes,
4
5
  NodeView,
5
6
  NodeViewProps,
6
7
  NodeViewRenderer,
7
8
  NodeViewRendererOptions,
8
- NodeViewRendererProps,
9
9
  } from '@tiptap/core'
10
- import { Node as ProseMirrorNode } from '@tiptap/pm/model'
11
- import { Decoration, NodeView as ProseMirrorNodeView } from '@tiptap/pm/view'
12
- import React from 'react'
10
+ import { Node, Node as ProseMirrorNode } from '@tiptap/pm/model'
11
+ import { Decoration, DecorationSource, NodeView as ProseMirrorNodeView } from '@tiptap/pm/view'
12
+ import React, { ComponentType } from 'react'
13
13
 
14
14
  import { EditorWithContentComponent } from './Editor.js'
15
15
  import { ReactRenderer } from './ReactRenderer.js'
16
16
  import { ReactNodeViewContext, ReactNodeViewContextProps } from './useReactNodeView.js'
17
17
 
18
18
  export interface ReactNodeViewRendererOptions extends NodeViewRendererOptions {
19
+ /**
20
+ * This function is called when the node view is updated.
21
+ * It allows you to compare the old node with the new node and decide if the component should update.
22
+ */
19
23
  update:
20
24
  | ((props: {
21
- oldNode: ProseMirrorNode
22
- oldDecorations: Decoration[]
23
- newNode: ProseMirrorNode
24
- newDecorations: Decoration[]
25
- updateProps: () => void
25
+ oldNode: ProseMirrorNode;
26
+ oldDecorations: readonly Decoration[];
27
+ oldInnerDecorations: DecorationSource;
28
+ newNode: ProseMirrorNode;
29
+ newDecorations: readonly Decoration[];
30
+ innerDecorations: DecorationSource;
31
+ updateProps: () => void;
26
32
  }) => boolean)
27
- | null
28
- as?: string
29
- className?: string
30
- attrs?: Record<string, string>
33
+ | null;
34
+ /**
35
+ * The tag name of the element wrapping the React component.
36
+ */
37
+ as?: string;
38
+ /**
39
+ * The class name of the element wrapping the React component.
40
+ */
41
+ className?: string;
42
+ /**
43
+ * Attributes that should be applied to the element wrapping the React component.
44
+ * If this is a function, it will be called each time the node view is updated.
45
+ * If this is an object, it will be applied once when the node view is mounted.
46
+ */
47
+ attrs?:
48
+ | Record<string, string>
49
+ | ((props: {
50
+ node: ProseMirrorNode;
51
+ HTMLAttributes: Record<string, any>;
52
+ }) => Record<string, string>);
31
53
  }
32
54
 
33
- class ReactNodeView extends NodeView<
34
- React.FunctionComponent,
35
- Editor,
36
- ReactNodeViewRendererOptions
37
- > {
38
- renderer!: ReactRenderer
39
-
55
+ export class ReactNodeView<
56
+ Component extends ComponentType<NodeViewProps> = ComponentType<NodeViewProps>,
57
+ NodeEditor extends Editor = Editor,
58
+ Options extends ReactNodeViewRendererOptions = ReactNodeViewRendererOptions,
59
+ > extends NodeView<Component, NodeEditor, Options> {
60
+ /**
61
+ * The renderer instance.
62
+ */
63
+ renderer!: ReactRenderer<unknown, NodeViewProps>
64
+
65
+ /**
66
+ * The element that holds the rich-text content of the node.
67
+ */
40
68
  contentDOMElement!: HTMLElement | null
41
69
 
70
+ /**
71
+ * Setup the React component.
72
+ * Called on initialization.
73
+ */
42
74
  mount() {
43
- const props: NodeViewProps = {
75
+ const props = {
44
76
  editor: this.editor,
45
77
  node: this.node,
46
- decorations: this.decorations,
78
+ decorations: this.decorations as DecorationWithType[],
79
+ innerDecorations: this.innerDecorations,
80
+ view: this.view,
47
81
  selected: false,
48
82
  extension: this.extension,
83
+ HTMLAttributes: this.HTMLAttributes,
49
84
  getPos: () => this.getPos(),
50
85
  updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
51
86
  deleteNode: () => this.deleteNode(),
52
- }
87
+ } satisfies NodeViewProps
53
88
 
54
89
  if (!(this.component as any).displayName) {
55
90
  const capitalizeFirstChar = (string: string): string => {
@@ -69,13 +104,15 @@ class ReactNodeView extends NodeView<
69
104
  const Component = this.component
70
105
  // For performance reasons, we memoize the provider component
71
106
  // And all of the things it requires are declared outside of the component, so it doesn't need to re-render
72
- const ReactNodeViewProvider: React.FunctionComponent = React.memo(componentProps => {
73
- return (
74
- <ReactNodeViewContext.Provider value={context}>
75
- {React.createElement(Component, componentProps)}
76
- </ReactNodeViewContext.Provider>
77
- )
78
- })
107
+ const ReactNodeViewProvider: React.FunctionComponent<NodeViewProps> = React.memo(
108
+ componentProps => {
109
+ return (
110
+ <ReactNodeViewContext.Provider value={context}>
111
+ {React.createElement(Component, componentProps)}
112
+ </ReactNodeViewContext.Provider>
113
+ )
114
+ },
115
+ )
79
116
 
80
117
  ReactNodeViewProvider.displayName = 'ReactNodeView'
81
118
 
@@ -88,6 +125,7 @@ class ReactNodeView extends NodeView<
88
125
  }
89
126
 
90
127
  if (this.contentDOMElement) {
128
+ this.contentDOMElement.dataset.nodeViewContentReact = ''
91
129
  // For some reason the whiteSpace prop is not inherited properly in Chrome and Safari
92
130
  // With this fix it seems to work fine
93
131
  // See: https://github.com/ueberdosis/tiptap/issues/1197
@@ -110,10 +148,15 @@ class ReactNodeView extends NodeView<
110
148
  props,
111
149
  as,
112
150
  className: `node-${this.node.type.name} ${className}`.trim(),
113
- attrs: this.options.attrs,
114
151
  })
152
+
153
+ this.updateElementAttributes()
115
154
  }
116
155
 
156
+ /**
157
+ * Return the DOM element.
158
+ * This is the element that will be used to display the node view.
159
+ */
117
160
  get dom() {
118
161
  if (
119
162
  this.renderer.element.firstElementChild
@@ -125,6 +168,10 @@ class ReactNodeView extends NodeView<
125
168
  return this.renderer.element as HTMLElement
126
169
  }
127
170
 
171
+ /**
172
+ * Return the content DOM element.
173
+ * This is the element that will be used to display the rich-text content of the node.
174
+ */
128
175
  get contentDOM() {
129
176
  if (this.node.isLeaf) {
130
177
  return null
@@ -133,10 +180,19 @@ class ReactNodeView extends NodeView<
133
180
  return this.contentDOMElement
134
181
  }
135
182
 
183
+ /**
184
+ * On editor selection update, check if the node is selected.
185
+ * If it is, call `selectNode`, otherwise call `deselectNode`.
186
+ */
136
187
  handleSelectionUpdate() {
137
188
  const { from, to } = this.editor.state.selection
189
+ const pos = this.getPos()
190
+
191
+ if (typeof pos !== 'number') {
192
+ return
193
+ }
138
194
 
139
- if (from <= this.getPos() && to >= this.getPos() + this.node.nodeSize) {
195
+ if (from <= pos && to >= pos + this.node.nodeSize) {
140
196
  if (this.renderer.props.selected) {
141
197
  return
142
198
  }
@@ -151,9 +207,20 @@ class ReactNodeView extends NodeView<
151
207
  }
152
208
  }
153
209
 
154
- update(node: ProseMirrorNode, decorations: DecorationWithType[]) {
155
- const updateProps = (props?: Record<string, any>) => {
210
+ /**
211
+ * On update, update the React component.
212
+ * To prevent unnecessary updates, the `update` option can be used.
213
+ */
214
+ update(
215
+ node: Node,
216
+ decorations: readonly Decoration[],
217
+ innerDecorations: DecorationSource,
218
+ ): boolean {
219
+ const rerenderComponent = (props?: Record<string, any>) => {
156
220
  this.renderer.updateProps(props)
221
+ if (typeof this.options.attrs === 'function') {
222
+ this.updateElementAttributes()
223
+ }
157
224
  }
158
225
 
159
226
  if (node.type !== this.node.type) {
@@ -163,31 +230,44 @@ class ReactNodeView extends NodeView<
163
230
  if (typeof this.options.update === 'function') {
164
231
  const oldNode = this.node
165
232
  const oldDecorations = this.decorations
233
+ const oldInnerDecorations = this.innerDecorations
166
234
 
167
235
  this.node = node
168
236
  this.decorations = decorations
237
+ this.innerDecorations = innerDecorations
169
238
 
170
239
  return this.options.update({
171
240
  oldNode,
172
241
  oldDecorations,
173
242
  newNode: node,
174
243
  newDecorations: decorations,
175
- updateProps: () => updateProps({ node, decorations }),
244
+ oldInnerDecorations,
245
+ innerDecorations,
246
+ updateProps: () => rerenderComponent({ node, decorations, innerDecorations }),
176
247
  })
177
248
  }
178
249
 
179
- if (node === this.node && this.decorations === decorations) {
250
+ if (
251
+ node === this.node
252
+ && this.decorations === decorations
253
+ && this.innerDecorations === innerDecorations
254
+ ) {
180
255
  return true
181
256
  }
182
257
 
183
258
  this.node = node
184
259
  this.decorations = decorations
260
+ this.innerDecorations = innerDecorations
185
261
 
186
- updateProps({ node, decorations })
262
+ rerenderComponent({ node, decorations, innerDecorations })
187
263
 
188
264
  return true
189
265
  }
190
266
 
267
+ /**
268
+ * Select the node.
269
+ * Add the `selected` prop and the `ProseMirror-selectednode` class.
270
+ */
191
271
  selectNode() {
192
272
  this.renderer.updateProps({
193
273
  selected: true,
@@ -195,6 +275,10 @@ class ReactNodeView extends NodeView<
195
275
  this.renderer.element.classList.add('ProseMirror-selectednode')
196
276
  }
197
277
 
278
+ /**
279
+ * Deselect the node.
280
+ * Remove the `selected` prop and the `ProseMirror-selectednode` class.
281
+ */
198
282
  deselectNode() {
199
283
  this.renderer.updateProps({
200
284
  selected: false,
@@ -202,25 +286,52 @@ class ReactNodeView extends NodeView<
202
286
  this.renderer.element.classList.remove('ProseMirror-selectednode')
203
287
  }
204
288
 
289
+ /**
290
+ * Destroy the React component instance.
291
+ */
205
292
  destroy() {
206
293
  this.renderer.destroy()
207
294
  this.editor.off('selectionUpdate', this.handleSelectionUpdate)
208
295
  this.contentDOMElement = null
209
296
  }
297
+
298
+ /**
299
+ * Update the attributes of the top-level element that holds the React component.
300
+ * Applying the attributes defined in the `attrs` option.
301
+ */
302
+ updateElementAttributes() {
303
+ if (this.options.attrs) {
304
+ let attrsObj: Record<string, string> = {}
305
+
306
+ if (typeof this.options.attrs === 'function') {
307
+ const extensionAttributes = this.editor.extensionManager.attributes
308
+ const HTMLAttributes = getRenderedAttributes(this.node, extensionAttributes)
309
+
310
+ attrsObj = this.options.attrs({ node: this.node, HTMLAttributes })
311
+ } else {
312
+ attrsObj = this.options.attrs
313
+ }
314
+
315
+ this.renderer.updateAttributes(attrsObj)
316
+ }
317
+ }
210
318
  }
211
319
 
320
+ /**
321
+ * Create a React node view renderer.
322
+ */
212
323
  export function ReactNodeViewRenderer(
213
- component: any,
324
+ component: ComponentType<NodeViewProps>,
214
325
  options?: Partial<ReactNodeViewRendererOptions>,
215
326
  ): NodeViewRenderer {
216
- return (props: NodeViewRendererProps) => {
327
+ return props => {
217
328
  // try to get the parent component
218
329
  // this is important for vue devtools to show the component hierarchy correctly
219
330
  // maybe it’s `undefined` because <editor-content> isn’t rendered yet
220
331
  if (!(props.editor as EditorWithContentComponent).contentComponent) {
221
- return {}
332
+ return {} as unknown as ProseMirrorNodeView
222
333
  }
223
334
 
224
- return new ReactNodeView(component, props, options) as unknown as ProseMirrorNodeView
335
+ return new ReactNodeView(component, props, options)
225
336
  }
226
337
  }
@@ -57,14 +57,6 @@ export interface ReactRendererOptions {
57
57
  * @example 'foo bar'
58
58
  */
59
59
  className?: string,
60
-
61
- /**
62
- * The attributes of the element.
63
- * @type {Record<string, string>}
64
- * @default {}
65
- * @example { 'data-foo': 'bar' }
66
- */
67
- attrs?: Record<string, string>,
68
60
  }
69
61
 
70
62
  type ComponentType<R, P> =
@@ -83,7 +75,7 @@ type ComponentType<R, P> =
83
75
  * as: 'span',
84
76
  * })
85
77
  */
86
- export class ReactRenderer<R = unknown, P = unknown> {
78
+ export class ReactRenderer<R = unknown, P extends Record<string, any> = {}> {
87
79
  id: string
88
80
 
89
81
  editor: Editor
@@ -92,23 +84,25 @@ export class ReactRenderer<R = unknown, P = unknown> {
92
84
 
93
85
  element: Element
94
86
 
95
- props: Record<string, any>
87
+ props: P
96
88
 
97
89
  reactElement: React.ReactNode
98
90
 
99
91
  ref: R | null = null
100
92
 
93
+ /**
94
+ * Immediately creates element and renders the provided React component.
95
+ */
101
96
  constructor(component: ComponentType<R, P>, {
102
97
  editor,
103
98
  props = {},
104
99
  as = 'div',
105
100
  className = '',
106
- attrs,
107
101
  }: ReactRendererOptions) {
108
102
  this.id = Math.floor(Math.random() * 0xFFFFFFFF).toString()
109
103
  this.component = component
110
104
  this.editor = editor as EditorWithContentComponent
111
- this.props = props
105
+ this.props = props as P
112
106
  this.element = document.createElement(as)
113
107
  this.element.classList.add('react-renderer')
114
108
 
@@ -116,12 +110,6 @@ export class ReactRenderer<R = unknown, P = unknown> {
116
110
  this.element.classList.add(...className.split(' '))
117
111
  }
118
112
 
119
- if (attrs) {
120
- Object.keys(attrs).forEach(key => {
121
- this.element.setAttribute(key, attrs[key])
122
- })
123
- }
124
-
125
113
  if (this.editor.isInitialized) {
126
114
  // On first render, we need to flush the render synchronously
127
115
  // Renders afterwards can be async, but this fixes a cursor positioning issue
@@ -133,22 +121,29 @@ export class ReactRenderer<R = unknown, P = unknown> {
133
121
  }
134
122
  }
135
123
 
124
+ /**
125
+ * Render the React component.
126
+ */
136
127
  render(): void {
137
128
  const Component = this.component
138
129
  const props = this.props
139
130
  const editor = this.editor as EditorWithContentComponent
140
131
 
141
132
  if (isClassComponent(Component) || isForwardRefComponent(Component)) {
133
+ // @ts-ignore This is a hack to make the ref work
142
134
  props.ref = (ref: R) => {
143
135
  this.ref = ref
144
136
  }
145
137
  }
146
138
 
147
- this.reactElement = React.createElement(Component, props)
139
+ this.reactElement = <Component {...props} />
148
140
 
149
141
  editor?.contentComponent?.setRenderer(this.id, this)
150
142
  }
151
143
 
144
+ /**
145
+ * Re-renders the React component with new props.
146
+ */
152
147
  updateProps(props: Record<string, any> = {}): void {
153
148
  this.props = {
154
149
  ...this.props,
@@ -158,9 +153,21 @@ export class ReactRenderer<R = unknown, P = unknown> {
158
153
  this.render()
159
154
  }
160
155
 
156
+ /**
157
+ * Destroy the React component.
158
+ */
161
159
  destroy(): void {
162
160
  const editor = this.editor as EditorWithContentComponent
163
161
 
164
162
  editor?.contentComponent?.removeRenderer(this.id)
165
163
  }
164
+
165
+ /**
166
+ * Update the attributes of the element that holds the React component.
167
+ */
168
+ updateAttributes(attributes: Record<string, string>): void {
169
+ Object.keys(attributes).forEach(key => {
170
+ this.element.setAttribute(key, attributes[key])
171
+ })
172
+ }
166
173
  }
package/src/useEditor.ts CHANGED
@@ -29,7 +29,7 @@ export type UseEditorOptions = Partial<EditorOptions> & {
29
29
  /**
30
30
  * Whether to re-render the editor on each transaction.
31
31
  * This is legacy behavior that will be removed in future versions.
32
- * @default true
32
+ * @default false
33
33
  */
34
34
  shouldRerenderOnTransaction?: boolean;
35
35
  };
@@ -78,6 +78,7 @@ class EditorInstanceManager {
78
78
  this.options = options
79
79
  this.subscriptions = new Set<() => void>()
80
80
  this.setEditor(this.getInitialEditor())
81
+ this.scheduleDestroy()
81
82
 
82
83
  this.getEditor = this.getEditor.bind(this)
83
84
  this.getServerSnapshot = this.getServerSnapshot.bind(this)
@@ -147,6 +148,8 @@ class EditorInstanceManager {
147
148
  onTransaction: (...args) => this.options.current.onTransaction?.(...args),
148
149
  onUpdate: (...args) => this.options.current.onUpdate?.(...args),
149
150
  onContentError: (...args) => this.options.current.onContentError?.(...args),
151
+ onDrop: (...args) => this.options.current.onDrop?.(...args),
152
+ onPaste: (...args) => this.options.current.onPaste?.(...args),
150
153
  }
151
154
  const editor = new Editor(optionsToApply)
152
155
 
@@ -195,7 +198,10 @@ class EditorInstanceManager {
195
198
  if (this.editor && !this.editor.isDestroyed && deps.length === 0) {
196
199
  // if the editor does exist & deps are empty, we don't need to re-initialize the editor
197
200
  // we can fast-path to update the editor options on the existing instance
198
- this.editor.setOptions(this.options.current)
201
+ this.editor.setOptions({
202
+ ...this.options.current,
203
+ editable: this.editor.isEditable,
204
+ })
199
205
  } else {
200
206
  // When the editor:
201
207
  // - does not yet exist
@@ -252,10 +258,10 @@ class EditorInstanceManager {
252
258
  const currentInstanceId = this.instanceId
253
259
  const currentEditor = this.editor
254
260
 
255
- // Wait a tick to see if the component is still mounted
261
+ // Wait two ticks to see if the component is still mounted
256
262
  this.scheduledDestructionTimeout = setTimeout(() => {
257
263
  if (this.isComponentMounted && this.instanceId === currentInstanceId) {
258
- // If still mounted on the next tick, with the same instanceId, do not destroy the editor
264
+ // If still mounted on the following tick, with the same instanceId, do not destroy the editor
259
265
  if (currentEditor) {
260
266
  // just re-apply options as they might have changed
261
267
  currentEditor.setOptions(this.options.current)
@@ -268,7 +274,9 @@ class EditorInstanceManager {
268
274
  this.setEditor(null)
269
275
  }
270
276
  }
271
- }, 0)
277
+ // This allows the effect to run again between ticks
278
+ // which may save us from having to re-create the editor
279
+ }, 1)
272
280
  }
273
281
  }
274
282
 
@@ -280,9 +288,9 @@ class EditorInstanceManager {
280
288
  * @example const editor = useEditor({ extensions: [...] })
281
289
  */
282
290
  export function useEditor(
283
- options: UseEditorOptions & { immediatelyRender: true },
291
+ options: UseEditorOptions & { immediatelyRender: false },
284
292
  deps?: DependencyList
285
- ): Editor;
293
+ ): Editor | null;
286
294
 
287
295
  /**
288
296
  * This hook allows you to create an editor instance.
@@ -291,7 +299,7 @@ export function useEditor(
291
299
  * @returns The editor instance
292
300
  * @example const editor = useEditor({ extensions: [...] })
293
301
  */
294
- export function useEditor(options?: UseEditorOptions, deps?: DependencyList): Editor | null;
302
+ export function useEditor(options: UseEditorOptions, deps?: DependencyList): Editor;
295
303
 
296
304
  export function useEditor(
297
305
  options: UseEditorOptions = {},
@@ -320,7 +328,7 @@ export function useEditor(
320
328
  useEditorState({
321
329
  editor,
322
330
  selector: ({ transactionNumber }) => {
323
- if (options.shouldRerenderOnTransaction === false) {
331
+ if (options.shouldRerenderOnTransaction === false || options.shouldRerenderOnTransaction === undefined) {
324
332
  // This will prevent the editor from re-rendering on each transaction
325
333
  return null
326
334
  }