@tiptap/react 2.0.0-beta.22 → 2.0.0-beta.220

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.
Files changed (40) hide show
  1. package/README.md +2 -2
  2. package/dist/index.cjs +409 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.js +386 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/index.umd.js +409 -0
  7. package/dist/index.umd.js.map +1 -0
  8. package/dist/packages/react/src/BubbleMenu.d.ts +7 -3
  9. package/dist/packages/react/src/Editor.d.ts +8 -2
  10. package/dist/packages/react/src/EditorContent.d.ts +7 -3
  11. package/dist/packages/react/src/FloatingMenu.d.ts +6 -3
  12. package/dist/packages/react/src/NodeViewContent.d.ts +1 -1
  13. package/dist/packages/react/src/NodeViewWrapper.d.ts +1 -1
  14. package/dist/packages/react/src/ReactNodeViewRenderer.d.ts +13 -7
  15. package/dist/packages/react/src/ReactRenderer.d.ts +9 -5
  16. package/dist/packages/react/src/index.d.ts +6 -6
  17. package/dist/packages/react/src/useEditor.d.ts +2 -1
  18. package/dist/packages/react/src/useReactNodeView.d.ts +1 -0
  19. package/package.json +35 -15
  20. package/src/BubbleMenu.tsx +32 -16
  21. package/src/Editor.ts +9 -2
  22. package/src/EditorContent.tsx +61 -18
  23. package/src/FloatingMenu.tsx +36 -14
  24. package/src/NodeViewContent.tsx +10 -3
  25. package/src/NodeViewWrapper.tsx +11 -8
  26. package/src/ReactNodeViewRenderer.tsx +84 -35
  27. package/src/ReactRenderer.tsx +35 -25
  28. package/src/index.ts +6 -6
  29. package/src/useEditor.ts +16 -4
  30. package/src/useReactNodeView.ts +1 -0
  31. package/CHANGELOG.md +0 -198
  32. package/LICENSE.md +0 -21
  33. package/dist/tiptap-react.bundle.umd.min.js +0 -54
  34. package/dist/tiptap-react.bundle.umd.min.js.map +0 -1
  35. package/dist/tiptap-react.cjs.js +0 -318
  36. package/dist/tiptap-react.cjs.js.map +0 -1
  37. package/dist/tiptap-react.esm.js +0 -293
  38. package/dist/tiptap-react.esm.js.map +0 -1
  39. package/dist/tiptap-react.umd.js +0 -318
  40. package/dist/tiptap-react.umd.js.map +0 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tiptap/react",
3
3
  "description": "React components for tiptap",
4
- "version": "2.0.0-beta.22",
4
+ "version": "2.0.0-beta.220",
5
5
  "homepage": "https://tiptap.dev",
6
6
  "keywords": [
7
7
  "tiptap",
@@ -12,28 +12,48 @@
12
12
  "type": "github",
13
13
  "url": "https://github.com/sponsors/ueberdosis"
14
14
  },
15
- "main": "dist/tiptap-react.cjs.js",
16
- "umd": "dist/tiptap-react.umd.js",
17
- "module": "dist/tiptap-react.esm.js",
18
- "unpkg": "dist/tiptap-react.bundle.umd.min.js",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/packages/react/src/index.d.ts",
18
+ "import": "./dist/index.js",
19
+ "require": "./dist/index.cjs"
20
+ }
21
+ },
22
+ "main": "dist/index.cjs",
23
+ "module": "dist/index.js",
24
+ "umd": "dist/index.umd.js",
19
25
  "types": "dist/packages/react/src/index.d.ts",
26
+ "type": "module",
20
27
  "files": [
21
28
  "src",
22
29
  "dist"
23
30
  ],
31
+ "devDependencies": {
32
+ "@tiptap/core": "^2.0.0-beta.220",
33
+ "@tiptap/pm": "^2.0.0-beta.220",
34
+ "@types/react": "^18.0.1",
35
+ "@types/react-dom": "^18.0.0",
36
+ "react": "^18.0.0",
37
+ "react-dom": "^18.0.0"
38
+ },
24
39
  "peerDependencies": {
25
- "@tiptap/core": "^2.0.0-beta.1",
26
- "react": "^17.0.1",
27
- "react-dom": "^17.0.1"
40
+ "@tiptap/core": "^2.0.0-beta.209",
41
+ "@tiptap/pm": "^2.0.0-beta.209",
42
+ "react": "^17.0.0 || ^18.0.0",
43
+ "react-dom": "^17.0.0 || ^18.0.0"
28
44
  },
29
45
  "dependencies": {
30
- "@tiptap/extension-bubble-menu": "^2.0.0-beta.7",
31
- "@tiptap/extension-floating-menu": "^2.0.0-beta.4",
32
- "prosemirror-view": "^1.18.2"
46
+ "@tiptap/extension-bubble-menu": "^2.0.0-beta.220",
47
+ "@tiptap/extension-floating-menu": "^2.0.0-beta.220"
33
48
  },
34
- "devDependencies": {
35
- "@types/react": "^17.0.3",
36
- "@types/react-dom": "^17.0.3"
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "https://github.com/ueberdosis/tiptap",
52
+ "directory": "packages/react"
37
53
  },
38
- "gitHead": "8fd618a788f288a9005e8e049d2f8f8df5523b6d"
54
+ "sideEffects": false,
55
+ "scripts": {
56
+ "clean": "rm -rf dist",
57
+ "build": "npm run clean && rollup -c"
58
+ }
39
59
  }
@@ -1,29 +1,45 @@
1
- import React, { useEffect, useRef } from 'react'
2
- import { BubbleMenuPlugin, BubbleMenuPluginKey, BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu'
1
+ import { BubbleMenuPlugin, BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu'
2
+ import React, { useEffect, useState } from 'react'
3
3
 
4
- export type BubbleMenuProps = Omit<BubbleMenuPluginProps, 'element'> & {
5
- className?: string,
6
- }
4
+ type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
5
+
6
+ export type BubbleMenuProps = Omit<Optional<BubbleMenuPluginProps, 'pluginKey'>, 'element'> & {
7
+ className?: string;
8
+ children: React.ReactNode;
9
+ updateDelay?: number;
10
+ };
7
11
 
8
- export const BubbleMenu: React.FC<BubbleMenuProps> = props => {
9
- const element = useRef<HTMLDivElement>(null)
12
+ export const BubbleMenu = (props: BubbleMenuProps) => {
13
+ const [element, setElement] = useState<HTMLDivElement | null>(null)
10
14
 
11
15
  useEffect(() => {
12
- const { editor, tippyOptions } = props
16
+ if (!element) {
17
+ return
18
+ }
13
19
 
14
- editor.registerPlugin(BubbleMenuPlugin({
20
+ if (props.editor.isDestroyed) {
21
+ return
22
+ }
23
+
24
+ const {
25
+ pluginKey = 'bubbleMenu', editor, tippyOptions = {}, updateDelay, shouldShow = null,
26
+ } = props
27
+
28
+ const plugin = BubbleMenuPlugin({
29
+ updateDelay,
15
30
  editor,
16
- element: element.current as HTMLElement,
31
+ element,
32
+ pluginKey,
33
+ shouldShow,
17
34
  tippyOptions,
18
- }))
35
+ })
19
36
 
20
- return () => {
21
- editor.unregisterPlugin(BubbleMenuPluginKey)
22
- }
23
- }, [])
37
+ editor.registerPlugin(plugin)
38
+ return () => editor.unregisterPlugin(pluginKey)
39
+ }, [props.editor, element])
24
40
 
25
41
  return (
26
- <div ref={element} className={props.className} style={{ visibility: 'hidden' }}>
42
+ <div ref={setElement} className={props.className} style={{ visibility: 'hidden' }}>
27
43
  {props.children}
28
44
  </div>
29
45
  )
package/src/Editor.ts CHANGED
@@ -1,7 +1,14 @@
1
- import React from 'react'
2
1
  import { Editor as CoreEditor } from '@tiptap/core'
2
+ import React from 'react'
3
+
3
4
  import { EditorContentProps, EditorContentState } from './EditorContent'
5
+ import { ReactRenderer } from './ReactRenderer'
6
+
7
+ type ContentComponent = React.Component<EditorContentProps, EditorContentState> & {
8
+ setRenderer(id: string, renderer: ReactRenderer): void;
9
+ removeRenderer(id: string): void;
10
+ }
4
11
 
5
12
  export class Editor extends CoreEditor {
6
- public contentComponent: React.Component<EditorContentProps, EditorContentState> | null = null
13
+ public contentComponent: ContentComponent | null = null
7
14
  }
@@ -1,39 +1,39 @@
1
- import React from 'react'
2
- import ReactDOM from 'react-dom'
1
+ import React, { HTMLProps } from 'react'
2
+ import ReactDOM, { flushSync } from 'react-dom'
3
+
3
4
  import { Editor } from './Editor'
4
5
  import { ReactRenderer } from './ReactRenderer'
5
6
 
6
- const Portals: React.FC<{ renderers: Map<string, ReactRenderer> }> = ({ renderers }) => {
7
+ const Portals: React.FC<{ renderers: Record<string, ReactRenderer> }> = ({ renderers }) => {
7
8
  return (
8
9
  <>
9
- {Array.from(renderers).map(([key, renderer]) => {
10
- return ReactDOM.createPortal(
11
- renderer.reactElement,
12
- renderer.element,
13
- key,
14
- )
10
+ {Object.entries(renderers).map(([key, renderer]) => {
11
+ return ReactDOM.createPortal(renderer.reactElement, renderer.element, key)
15
12
  })}
16
13
  </>
17
14
  )
18
15
  }
19
16
 
20
- export interface EditorContentProps {
21
- editor: Editor | null,
17
+ export interface EditorContentProps extends HTMLProps<HTMLDivElement> {
18
+ editor: Editor | null;
22
19
  }
23
20
 
24
21
  export interface EditorContentState {
25
- renderers: Map<string, ReactRenderer>
22
+ renderers: Record<string, ReactRenderer>;
26
23
  }
27
24
 
28
25
  export class PureEditorContent extends React.Component<EditorContentProps, EditorContentState> {
29
26
  editorContentRef: React.RefObject<any>
30
27
 
28
+ initialized: boolean
29
+
31
30
  constructor(props: EditorContentProps) {
32
31
  super(props)
33
32
  this.editorContentRef = React.createRef()
33
+ this.initialized = false
34
34
 
35
35
  this.state = {
36
- renderers: new Map(),
36
+ renderers: {},
37
37
  }
38
38
  }
39
39
 
@@ -55,7 +55,7 @@ export class PureEditorContent extends React.Component<EditorContentProps, Edito
55
55
 
56
56
  const element = this.editorContentRef.current
57
57
 
58
- element.appendChild(editor.options.element.firstChild)
58
+ element.append(...editor.options.element.childNodes)
59
59
 
60
60
  editor.setOptions({
61
61
  element,
@@ -63,11 +63,49 @@ export class PureEditorContent extends React.Component<EditorContentProps, Edito
63
63
 
64
64
  editor.contentComponent = this
65
65
 
66
- // TODO: alternative to setTimeout?
67
- setTimeout(() => editor.createNodeViews(), 0)
66
+ editor.createNodeViews()
67
+
68
+ this.initialized = true
68
69
  }
69
70
  }
70
71
 
72
+ maybeFlushSync(fn: () => void) {
73
+ // Avoid calling flushSync until the editor is initialized.
74
+ // Initialization happens during the componentDidMount or componentDidUpdate
75
+ // lifecycle methods, and React doesn't allow calling flushSync from inside
76
+ // a lifecycle method.
77
+ if (this.initialized) {
78
+ queueMicrotask(() => {
79
+ flushSync(fn)
80
+ })
81
+ } else {
82
+ fn()
83
+ }
84
+ }
85
+
86
+ setRenderer(id: string, renderer: ReactRenderer) {
87
+ this.maybeFlushSync(() => {
88
+ this.setState(({ renderers }) => ({
89
+ renderers: {
90
+ ...renderers,
91
+ [id]: renderer,
92
+ },
93
+ }))
94
+ })
95
+ }
96
+
97
+ removeRenderer(id: string) {
98
+ this.maybeFlushSync(() => {
99
+ this.setState(({ renderers }) => {
100
+ const nextRenderers = { ...renderers }
101
+
102
+ delete nextRenderers[id]
103
+
104
+ return { renderers: nextRenderers }
105
+ })
106
+ })
107
+ }
108
+
71
109
  componentWillUnmount() {
72
110
  const { editor } = this.props
73
111
 
@@ -75,6 +113,8 @@ export class PureEditorContent extends React.Component<EditorContentProps, Edito
75
113
  return
76
114
  }
77
115
 
116
+ this.initialized = false
117
+
78
118
  if (!editor.isDestroyed) {
79
119
  editor.view.setProps({
80
120
  nodeViews: {},
@@ -89,7 +129,7 @@ export class PureEditorContent extends React.Component<EditorContentProps, Edito
89
129
 
90
130
  const newElement = document.createElement('div')
91
131
 
92
- newElement.appendChild(editor.options.element.firstChild)
132
+ newElement.append(...editor.options.element.childNodes)
93
133
 
94
134
  editor.setOptions({
95
135
  element: newElement,
@@ -97,9 +137,12 @@ export class PureEditorContent extends React.Component<EditorContentProps, Edito
97
137
  }
98
138
 
99
139
  render() {
140
+ const { editor, ...rest } = this.props
141
+
100
142
  return (
101
143
  <>
102
- <div ref={this.editorContentRef} />
144
+ <div ref={this.editorContentRef} {...rest} />
145
+ {/* @ts-ignore */}
103
146
  <Portals renderers={this.state.renderers} />
104
147
  </>
105
148
  )
@@ -1,29 +1,51 @@
1
- import React, { useEffect, useRef } from 'react'
2
- import { FloatingMenuPlugin, FloatingMenuPluginKey, FloatingMenuPluginProps } from '@tiptap/extension-floating-menu'
1
+ import { FloatingMenuPlugin, FloatingMenuPluginProps } from '@tiptap/extension-floating-menu'
2
+ import React, {
3
+ useEffect, useState,
4
+ } from 'react'
3
5
 
4
- export type FloatingMenuProps = Omit<FloatingMenuPluginProps, 'element'> & {
6
+ type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>
7
+
8
+ export type FloatingMenuProps = Omit<Optional<FloatingMenuPluginProps, 'pluginKey'>, 'element'> & {
5
9
  className?: string,
10
+ children: React.ReactNode
6
11
  }
7
12
 
8
- export const FloatingMenu: React.FC<FloatingMenuProps> = props => {
9
- const element = useRef<HTMLDivElement>(null)
13
+ export const FloatingMenu = (props: FloatingMenuProps) => {
14
+ const [element, setElement] = useState<HTMLDivElement | null>(null)
10
15
 
11
16
  useEffect(() => {
12
- const { editor, tippyOptions } = props
17
+ if (!element) {
18
+ return
19
+ }
20
+
21
+ if (props.editor.isDestroyed) {
22
+ return
23
+ }
13
24
 
14
- editor.registerPlugin(FloatingMenuPlugin({
25
+ const {
26
+ pluginKey = 'floatingMenu',
15
27
  editor,
16
- element: element.current as HTMLElement,
28
+ tippyOptions = {},
29
+ shouldShow = null,
30
+ } = props
31
+
32
+ const plugin = FloatingMenuPlugin({
33
+ pluginKey,
34
+ editor,
35
+ element,
17
36
  tippyOptions,
18
- }))
37
+ shouldShow,
38
+ })
19
39
 
20
- return () => {
21
- editor.unregisterPlugin(FloatingMenuPluginKey)
22
- }
23
- }, [])
40
+ editor.registerPlugin(plugin)
41
+ return () => editor.unregisterPlugin(pluginKey)
42
+ }, [
43
+ props.editor,
44
+ element,
45
+ ])
24
46
 
25
47
  return (
26
- <div ref={element} className={props.className} style={{ visibility: 'hidden' }}>
48
+ <div ref={setElement} className={props.className} style={{ visibility: 'hidden' }}>
27
49
  {props.children}
28
50
  </div>
29
51
  )
@@ -1,18 +1,25 @@
1
1
  import React from 'react'
2
2
 
3
+ import { useReactNodeView } from './useReactNodeView'
4
+
3
5
  export interface NodeViewContentProps {
4
- className?: string,
6
+ [key: string]: any,
5
7
  as?: React.ElementType,
6
8
  }
7
9
 
8
10
  export const NodeViewContent: React.FC<NodeViewContentProps> = props => {
9
11
  const Tag = props.as || 'div'
12
+ const { nodeViewContentRef } = useReactNodeView()
10
13
 
11
14
  return (
12
15
  <Tag
13
- className={props.className}
16
+ {...props}
17
+ ref={nodeViewContentRef}
14
18
  data-node-view-content=""
15
- style={{ whiteSpace: 'pre-wrap' }}
19
+ style={{
20
+ whiteSpace: 'pre-wrap',
21
+ ...props.style,
22
+ }}
16
23
  />
17
24
  )
18
25
  }
@@ -1,23 +1,26 @@
1
1
  import React from 'react'
2
+
2
3
  import { useReactNodeView } from './useReactNodeView'
3
4
 
4
5
  export interface NodeViewWrapperProps {
5
- className?: string,
6
+ [key: string]: any,
6
7
  as?: React.ElementType,
7
8
  }
8
9
 
9
- export const NodeViewWrapper: React.FC<NodeViewWrapperProps> = props => {
10
+ export const NodeViewWrapper: React.FC<NodeViewWrapperProps> = React.forwardRef((props, ref) => {
10
11
  const { onDragStart } = useReactNodeView()
11
12
  const Tag = props.as || 'div'
12
13
 
13
14
  return (
14
15
  <Tag
15
- className={props.className}
16
+ {...props}
17
+ ref={ref}
16
18
  data-node-view-wrapper=""
17
19
  onDragStart={onDragStart}
18
- style={{ whiteSpace: 'normal' }}
19
- >
20
- {props.children}
21
- </Tag>
20
+ style={{
21
+ whiteSpace: 'normal',
22
+ ...props.style,
23
+ }}
24
+ />
22
25
  )
23
- }
26
+ })
@@ -1,26 +1,41 @@
1
- import React from 'react'
2
1
  import {
2
+ DecorationWithType,
3
3
  NodeView,
4
4
  NodeViewProps,
5
5
  NodeViewRenderer,
6
+ NodeViewRendererOptions,
6
7
  NodeViewRendererProps,
7
8
  } from '@tiptap/core'
8
- import { Decoration, NodeView as ProseMirrorNodeView } from 'prosemirror-view'
9
- import { Node as ProseMirrorNode } from 'prosemirror-model'
9
+ import { Node as ProseMirrorNode } from '@tiptap/pm/model'
10
+ import { Decoration, NodeView as ProseMirrorNodeView } from '@tiptap/pm/view'
11
+ import React from 'react'
12
+
10
13
  import { Editor } from './Editor'
11
14
  import { ReactRenderer } from './ReactRenderer'
12
- import { ReactNodeViewContext } from './useReactNodeView'
13
-
14
- interface ReactNodeViewRendererOptions {
15
- stopEvent: ((event: Event) => boolean) | null,
16
- update: ((node: ProseMirrorNode, decorations: Decoration[]) => boolean) | null,
15
+ import { ReactNodeViewContext, ReactNodeViewContextProps } from './useReactNodeView'
16
+
17
+ export interface ReactNodeViewRendererOptions extends NodeViewRendererOptions {
18
+ update:
19
+ | ((props: {
20
+ oldNode: ProseMirrorNode
21
+ oldDecorations: Decoration[]
22
+ newNode: ProseMirrorNode
23
+ newDecorations: Decoration[]
24
+ updateProps: () => void
25
+ }) => boolean)
26
+ | null
27
+ as?: string
28
+ className?: string
17
29
  }
18
30
 
19
- class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
20
-
31
+ class ReactNodeView extends NodeView<
32
+ React.FunctionComponent,
33
+ Editor,
34
+ ReactNodeViewRendererOptions
35
+ > {
21
36
  renderer!: ReactRenderer
22
37
 
23
- contentDOMElement!: Element | null
38
+ contentDOMElement!: HTMLElement | null
24
39
 
25
40
  mount() {
26
41
  const props: NodeViewProps = {
@@ -31,6 +46,7 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
31
46
  extension: this.extension,
32
47
  getPos: () => this.getPos(),
33
48
  updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
49
+ deleteNode: () => this.deleteNode(),
34
50
  }
35
51
 
36
52
  if (!(this.component as any).displayName) {
@@ -42,13 +58,22 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
42
58
  }
43
59
 
44
60
  const ReactNodeViewProvider: React.FunctionComponent = componentProps => {
45
- const onDragStart = this.onDragStart.bind(this)
46
61
  const Component = this.component
62
+ const onDragStart = this.onDragStart.bind(this)
63
+ const nodeViewContentRef: ReactNodeViewContextProps['nodeViewContentRef'] = element => {
64
+ if (element && this.contentDOMElement && element.firstChild !== this.contentDOMElement) {
65
+ element.appendChild(this.contentDOMElement)
66
+ }
67
+ }
47
68
 
48
69
  return (
49
- <ReactNodeViewContext.Provider value={{ onDragStart }}>
50
- <Component {...componentProps} />
51
- </ReactNodeViewContext.Provider>
70
+ <>
71
+ {/* @ts-ignore */}
72
+ <ReactNodeViewContext.Provider value={{ onDragStart, nodeViewContentRef }}>
73
+ {/* @ts-ignore */}
74
+ <Component {...componentProps} />
75
+ </ReactNodeViewContext.Provider>
76
+ </>
52
77
  )
53
78
  }
54
79
 
@@ -58,12 +83,26 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
58
83
  ? null
59
84
  : document.createElement(this.node.isInline ? 'span' : 'div')
60
85
 
86
+ if (this.contentDOMElement) {
87
+ // For some reason the whiteSpace prop is not inherited properly in Chrome and Safari
88
+ // With this fix it seems to work fine
89
+ // See: https://github.com/ueberdosis/tiptap/issues/1197
90
+ this.contentDOMElement.style.whiteSpace = 'inherit'
91
+ }
92
+
93
+ let as = this.node.isInline ? 'span' : 'div'
94
+
95
+ if (this.options.as) {
96
+ as = this.options.as
97
+ }
98
+
99
+ const { className = '' } = this.options
100
+
61
101
  this.renderer = new ReactRenderer(ReactNodeViewProvider, {
62
102
  editor: this.editor,
63
103
  props,
64
- as: this.node.isInline
65
- ? 'span'
66
- : 'div',
104
+ as,
105
+ className: `node-${this.node.type.name} ${className}`.trim(),
67
106
  })
68
107
  }
69
108
 
@@ -75,7 +114,7 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
75
114
  throw Error('Please use the NodeViewWrapper component for your node view.')
76
115
  }
77
116
 
78
- return this.renderer.element
117
+ return this.renderer.element as HTMLElement
79
118
  }
80
119
 
81
120
  get contentDOM() {
@@ -83,35 +122,42 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
83
122
  return null
84
123
  }
85
124
 
86
- const contentElement = this.dom.querySelector('[data-node-view-content]')
87
-
88
- if (
89
- this.contentDOMElement
90
- && contentElement
91
- && !contentElement.contains(this.contentDOMElement)
92
- ) {
93
- contentElement.appendChild(this.contentDOMElement)
94
- }
95
-
96
125
  return this.contentDOMElement
97
126
  }
98
127
 
99
- update(node: ProseMirrorNode, decorations: Decoration[]) {
100
- if (typeof this.options.update === 'function') {
101
- return this.options.update(node, decorations)
128
+ update(node: ProseMirrorNode, decorations: DecorationWithType[]) {
129
+ const updateProps = (props?: Record<string, any>) => {
130
+ this.renderer.updateProps(props)
102
131
  }
103
132
 
104
133
  if (node.type !== this.node.type) {
105
134
  return false
106
135
  }
107
136
 
137
+ if (typeof this.options.update === 'function') {
138
+ const oldNode = this.node
139
+ const oldDecorations = this.decorations
140
+
141
+ this.node = node
142
+ this.decorations = decorations
143
+
144
+ return this.options.update({
145
+ oldNode,
146
+ oldDecorations,
147
+ newNode: node,
148
+ newDecorations: decorations,
149
+ updateProps: () => updateProps({ node, decorations }),
150
+ })
151
+ }
152
+
108
153
  if (node === this.node && this.decorations === decorations) {
109
154
  return true
110
155
  }
111
156
 
112
157
  this.node = node
113
158
  this.decorations = decorations
114
- this.renderer.updateProps({ node, decorations })
159
+
160
+ updateProps({ node, decorations })
115
161
 
116
162
  return true
117
163
  }
@@ -134,7 +180,10 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
134
180
  }
135
181
  }
136
182
 
137
- export function ReactNodeViewRenderer(component: any, options?: Partial<ReactNodeViewRendererOptions>): NodeViewRenderer {
183
+ export function ReactNodeViewRenderer(
184
+ component: any,
185
+ options?: Partial<ReactNodeViewRendererOptions>,
186
+ ): NodeViewRenderer {
138
187
  return (props: NodeViewRendererProps) => {
139
188
  // try to get the parent component
140
189
  // this is important for vue devtools to show the component hierarchy correctly
@@ -143,6 +192,6 @@ export function ReactNodeViewRenderer(component: any, options?: Partial<ReactNod
143
192
  return {}
144
193
  }
145
194
 
146
- return new ReactNodeView(component, props, options) as ProseMirrorNodeView
195
+ return new ReactNodeView(component, props, options) as unknown as ProseMirrorNodeView
147
196
  }
148
197
  }