@tiptap/react 2.0.0-beta.21 → 2.0.0-beta.211

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 (37) hide show
  1. package/README.md +2 -2
  2. package/dist/index.cjs +437 -0
  3. package/dist/index.d.ts +101 -0
  4. package/dist/index.js +437 -0
  5. package/package.json +45 -16
  6. package/src/BubbleMenu.tsx +32 -16
  7. package/src/Editor.ts +9 -2
  8. package/src/EditorContent.tsx +55 -12
  9. package/src/FloatingMenu.tsx +36 -14
  10. package/src/NodeViewContent.tsx +10 -3
  11. package/src/NodeViewWrapper.tsx +11 -8
  12. package/src/ReactNodeViewRenderer.tsx +82 -34
  13. package/src/ReactRenderer.tsx +38 -29
  14. package/src/index.ts +6 -6
  15. package/src/useEditor.ts +16 -4
  16. package/src/useReactNodeView.ts +1 -0
  17. package/CHANGELOG.md +0 -190
  18. package/LICENSE.md +0 -21
  19. package/dist/packages/react/src/BubbleMenu.d.ts +0 -6
  20. package/dist/packages/react/src/Editor.d.ts +0 -6
  21. package/dist/packages/react/src/EditorContent.d.ts +0 -19
  22. package/dist/packages/react/src/FloatingMenu.d.ts +0 -6
  23. package/dist/packages/react/src/NodeViewContent.d.ts +0 -6
  24. package/dist/packages/react/src/NodeViewWrapper.d.ts +0 -6
  25. package/dist/packages/react/src/ReactNodeViewRenderer.d.ts +0 -9
  26. package/dist/packages/react/src/ReactRenderer.d.ts +0 -21
  27. package/dist/packages/react/src/index.d.ts +0 -10
  28. package/dist/packages/react/src/useEditor.d.ts +0 -3
  29. package/dist/packages/react/src/useReactNodeView.d.ts +0 -6
  30. package/dist/tiptap-react.bundle.umd.min.js +0 -54
  31. package/dist/tiptap-react.bundle.umd.min.js.map +0 -1
  32. package/dist/tiptap-react.cjs.js +0 -318
  33. package/dist/tiptap-react.cjs.js.map +0 -1
  34. package/dist/tiptap-react.esm.js +0 -293
  35. package/dist/tiptap-react.esm.js.map +0 -1
  36. package/dist/tiptap-react.umd.js +0 -318
  37. package/dist/tiptap-react.umd.js.map +0 -1
@@ -1,12 +1,13 @@
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
+ {Object.entries(renderers).map(([key, renderer]) => {
10
11
  return ReactDOM.createPortal(
11
12
  renderer.reactElement,
12
13
  renderer.element,
@@ -17,23 +18,26 @@ const Portals: React.FC<{ renderers: Map<string, ReactRenderer> }> = ({ renderer
17
18
  )
18
19
  }
19
20
 
20
- export interface EditorContentProps {
21
+ export interface EditorContentProps extends HTMLProps<HTMLDivElement> {
21
22
  editor: Editor | null,
22
23
  }
23
24
 
24
25
  export interface EditorContentState {
25
- renderers: Map<string, ReactRenderer>
26
+ renderers: Record<string, ReactRenderer>
26
27
  }
27
28
 
28
29
  export class PureEditorContent extends React.Component<EditorContentProps, EditorContentState> {
29
30
  editorContentRef: React.RefObject<any>
30
31
 
32
+ initialized: boolean
33
+
31
34
  constructor(props: EditorContentProps) {
32
35
  super(props)
33
36
  this.editorContentRef = React.createRef()
37
+ this.initialized = false
34
38
 
35
39
  this.state = {
36
- renderers: new Map(),
40
+ renderers: {},
37
41
  }
38
42
  }
39
43
 
@@ -55,7 +59,7 @@ export class PureEditorContent extends React.Component<EditorContentProps, Edito
55
59
 
56
60
  const element = this.editorContentRef.current
57
61
 
58
- element.appendChild(editor.options.element.firstChild)
62
+ element.append(...editor.options.element.childNodes)
59
63
 
60
64
  editor.setOptions({
61
65
  element,
@@ -63,11 +67,47 @@ export class PureEditorContent extends React.Component<EditorContentProps, Edito
63
67
 
64
68
  editor.contentComponent = this
65
69
 
66
- // TODO: alternative to setTimeout?
67
- setTimeout(() => editor.createNodeViews(), 0)
70
+ editor.createNodeViews()
71
+
72
+ this.initialized = true
73
+ }
74
+ }
75
+
76
+ maybeFlushSync(fn: () => void) {
77
+ // Avoid calling flushSync until the editor is initialized.
78
+ // Initialization happens during the componentDidMount or componentDidUpdate
79
+ // lifecycle methods, and React doesn't allow calling flushSync from inside
80
+ // a lifecycle method.
81
+ if (this.initialized) {
82
+ flushSync(fn)
83
+ } else {
84
+ fn()
68
85
  }
69
86
  }
70
87
 
88
+ setRenderer(id: string, renderer: ReactRenderer) {
89
+ this.maybeFlushSync(() => {
90
+ this.setState(({ renderers }) => ({
91
+ renderers: {
92
+ ...renderers,
93
+ [id]: renderer,
94
+ },
95
+ }))
96
+ })
97
+ }
98
+
99
+ removeRenderer(id: string) {
100
+ this.maybeFlushSync(() => {
101
+ this.setState(({ renderers }) => {
102
+ const nextRenderers = { ...renderers }
103
+
104
+ delete nextRenderers[id]
105
+
106
+ return { renderers: nextRenderers }
107
+ })
108
+ })
109
+ }
110
+
71
111
  componentWillUnmount() {
72
112
  const { editor } = this.props
73
113
 
@@ -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,40 @@
1
- import React from 'react'
2
1
  import {
3
2
  NodeView,
4
3
  NodeViewProps,
5
4
  NodeViewRenderer,
5
+ NodeViewRendererOptions,
6
6
  NodeViewRendererProps,
7
7
  } from '@tiptap/core'
8
- import { Decoration, NodeView as ProseMirrorNodeView } from 'prosemirror-view'
9
- import { Node as ProseMirrorNode } from 'prosemirror-model'
8
+ import { Node as ProseMirrorNode } from '@tiptap/pm/model'
9
+ import { Decoration, NodeView as ProseMirrorNodeView } from '@tiptap/pm/view'
10
+ import React from 'react'
11
+
10
12
  import { Editor } from './Editor'
11
13
  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,
14
+ import { ReactNodeViewContext, ReactNodeViewContextProps } from './useReactNodeView'
15
+
16
+ export interface ReactNodeViewRendererOptions extends NodeViewRendererOptions {
17
+ update:
18
+ | ((props: {
19
+ oldNode: ProseMirrorNode
20
+ oldDecorations: Decoration[]
21
+ newNode: ProseMirrorNode
22
+ newDecorations: Decoration[]
23
+ updateProps: () => void
24
+ }) => boolean)
25
+ | null
26
+ as?: string
27
+ className?: string
17
28
  }
18
29
 
19
- class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
20
-
30
+ class ReactNodeView extends NodeView<
31
+ React.FunctionComponent,
32
+ Editor,
33
+ ReactNodeViewRendererOptions
34
+ > {
21
35
  renderer!: ReactRenderer
22
36
 
23
- contentDOMElement!: Element | null
37
+ contentDOMElement!: HTMLElement | null
24
38
 
25
39
  mount() {
26
40
  const props: NodeViewProps = {
@@ -31,6 +45,7 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
31
45
  extension: this.extension,
32
46
  getPos: () => this.getPos(),
33
47
  updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
48
+ deleteNode: () => this.deleteNode(),
34
49
  }
35
50
 
36
51
  if (!(this.component as any).displayName) {
@@ -42,13 +57,22 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
42
57
  }
43
58
 
44
59
  const ReactNodeViewProvider: React.FunctionComponent = componentProps => {
45
- const onDragStart = this.onDragStart.bind(this)
46
60
  const Component = this.component
61
+ const onDragStart = this.onDragStart.bind(this)
62
+ const nodeViewContentRef: ReactNodeViewContextProps['nodeViewContentRef'] = element => {
63
+ if (element && this.contentDOMElement && element.firstChild !== this.contentDOMElement) {
64
+ element.appendChild(this.contentDOMElement)
65
+ }
66
+ }
47
67
 
48
68
  return (
49
- <ReactNodeViewContext.Provider value={{ onDragStart }}>
50
- <Component {...componentProps} />
51
- </ReactNodeViewContext.Provider>
69
+ <>
70
+ {/* @ts-ignore */}
71
+ <ReactNodeViewContext.Provider value={{ onDragStart, nodeViewContentRef }}>
72
+ {/* @ts-ignore */}
73
+ <Component {...componentProps} />
74
+ </ReactNodeViewContext.Provider>
75
+ </>
52
76
  )
53
77
  }
54
78
 
@@ -58,12 +82,26 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
58
82
  ? null
59
83
  : document.createElement(this.node.isInline ? 'span' : 'div')
60
84
 
85
+ if (this.contentDOMElement) {
86
+ // For some reason the whiteSpace prop is not inherited properly in Chrome and Safari
87
+ // With this fix it seems to work fine
88
+ // See: https://github.com/ueberdosis/tiptap/issues/1197
89
+ this.contentDOMElement.style.whiteSpace = 'inherit'
90
+ }
91
+
92
+ let as = this.node.isInline ? 'span' : 'div'
93
+
94
+ if (this.options.as) {
95
+ as = this.options.as
96
+ }
97
+
98
+ const { className = '' } = this.options
99
+
61
100
  this.renderer = new ReactRenderer(ReactNodeViewProvider, {
62
101
  editor: this.editor,
63
102
  props,
64
- as: this.node.isInline
65
- ? 'span'
66
- : 'div',
103
+ as,
104
+ className: `node-${this.node.type.name} ${className}`.trim(),
67
105
  })
68
106
  }
69
107
 
@@ -75,7 +113,7 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
75
113
  throw Error('Please use the NodeViewWrapper component for your node view.')
76
114
  }
77
115
 
78
- return this.renderer.element
116
+ return this.renderer.element as HTMLElement
79
117
  }
80
118
 
81
119
  get contentDOM() {
@@ -83,35 +121,42 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
83
121
  return null
84
122
  }
85
123
 
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
124
  return this.contentDOMElement
97
125
  }
98
126
 
99
127
  update(node: ProseMirrorNode, decorations: Decoration[]) {
100
- if (typeof this.options.update === 'function') {
101
- return this.options.update(node, decorations)
128
+ const updateProps = (props?: Record<string, any>) => {
129
+ this.renderer.updateProps(props)
102
130
  }
103
131
 
104
132
  if (node.type !== this.node.type) {
105
133
  return false
106
134
  }
107
135
 
136
+ if (typeof this.options.update === 'function') {
137
+ const oldNode = this.node
138
+ const oldDecorations = this.decorations
139
+
140
+ this.node = node
141
+ this.decorations = decorations
142
+
143
+ return this.options.update({
144
+ oldNode,
145
+ oldDecorations,
146
+ newNode: node,
147
+ newDecorations: decorations,
148
+ updateProps: () => updateProps({ node, decorations }),
149
+ })
150
+ }
151
+
108
152
  if (node === this.node && this.decorations === decorations) {
109
153
  return true
110
154
  }
111
155
 
112
156
  this.node = node
113
157
  this.decorations = decorations
114
- this.renderer.updateProps({ node, decorations })
158
+
159
+ updateProps({ node, decorations })
115
160
 
116
161
  return true
117
162
  }
@@ -134,7 +179,10 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
134
179
  }
135
180
  }
136
181
 
137
- export function ReactNodeViewRenderer(component: any, options?: Partial<ReactNodeViewRendererOptions>): NodeViewRenderer {
182
+ export function ReactNodeViewRenderer(
183
+ component: any,
184
+ options?: Partial<ReactNodeViewRendererOptions>,
185
+ ): NodeViewRenderer {
138
186
  return (props: NodeViewRendererProps) => {
139
187
  // try to get the parent component
140
188
  // this is important for vue devtools to show the component hierarchy correctly
@@ -143,6 +191,6 @@ export function ReactNodeViewRenderer(component: any, options?: Partial<ReactNod
143
191
  return {}
144
192
  }
145
193
 
146
- return new ReactNodeView(component, props, options) as ProseMirrorNodeView
194
+ return new ReactNodeView(component, props, options) as unknown as ProseMirrorNodeView
147
195
  }
148
196
  }
@@ -1,6 +1,7 @@
1
+ import { Editor } from '@tiptap/core'
1
2
  import React from 'react'
2
- import { AnyObject } from '@tiptap/core'
3
- import { Editor } from './Editor'
3
+
4
+ import { Editor as ExtendedEditor } from './Editor'
4
5
 
5
6
  function isClassComponent(Component: any) {
6
7
  return !!(
@@ -10,34 +11,57 @@ function isClassComponent(Component: any) {
10
11
  )
11
12
  }
12
13
 
14
+ function isForwardRefComponent(Component: any) {
15
+ return !!(
16
+ typeof Component === 'object'
17
+ && Component.$$typeof?.toString() === 'Symbol(react.forward_ref)'
18
+ )
19
+ }
20
+
13
21
  export interface ReactRendererOptions {
14
22
  editor: Editor,
15
- props?: AnyObject,
23
+ props?: Record<string, any>,
16
24
  as?: string,
25
+ className?: string,
17
26
  }
18
27
 
19
- export class ReactRenderer {
28
+ type ComponentType<R, P> =
29
+ React.ComponentClass<P> |
30
+ React.FunctionComponent<P> |
31
+ React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<R>>;
32
+
33
+ export class ReactRenderer<R = unknown, P = unknown> {
20
34
  id: string
21
35
 
22
- editor: Editor
36
+ editor: ExtendedEditor
23
37
 
24
38
  component: any
25
39
 
26
40
  element: Element
27
41
 
28
- props: AnyObject
42
+ props: Record<string, any>
29
43
 
30
44
  reactElement: React.ReactNode
31
45
 
32
- ref: React.Component | null = null
46
+ ref: R | null = null
33
47
 
34
- constructor(component: React.Component | React.FunctionComponent, { editor, props = {}, as = 'div' }: ReactRendererOptions) {
48
+ constructor(component: ComponentType<R, P>, {
49
+ editor,
50
+ props = {},
51
+ as = 'div',
52
+ className = '',
53
+ }: ReactRendererOptions) {
35
54
  this.id = Math.floor(Math.random() * 0xFFFFFFFF).toString()
36
55
  this.component = component
37
- this.editor = editor
56
+ this.editor = editor as ExtendedEditor
38
57
  this.props = props
39
58
  this.element = document.createElement(as)
40
59
  this.element.classList.add('react-renderer')
60
+
61
+ if (className) {
62
+ this.element.classList.add(...className.split(' '))
63
+ }
64
+
41
65
  this.render()
42
66
  }
43
67
 
@@ -45,25 +69,18 @@ export class ReactRenderer {
45
69
  const Component = this.component
46
70
  const props = this.props
47
71
 
48
- if (isClassComponent(Component)) {
49
- props.ref = (ref: React.Component) => {
72
+ if (isClassComponent(Component) || isForwardRefComponent(Component)) {
73
+ props.ref = (ref: R) => {
50
74
  this.ref = ref
51
75
  }
52
76
  }
53
77
 
54
78
  this.reactElement = <Component {...props } />
55
79
 
56
- if (this.editor?.contentComponent) {
57
- this.editor.contentComponent.setState({
58
- renderers: this.editor.contentComponent.state.renderers.set(
59
- this.id,
60
- this,
61
- ),
62
- })
63
- }
80
+ this.editor?.contentComponent?.setRenderer(this.id, this)
64
81
  }
65
82
 
66
- updateProps(props: AnyObject = {}): void {
83
+ updateProps(props: Record<string, any> = {}): void {
67
84
  this.props = {
68
85
  ...this.props,
69
86
  ...props,
@@ -73,14 +90,6 @@ export class ReactRenderer {
73
90
  }
74
91
 
75
92
  destroy(): void {
76
- if (this.editor?.contentComponent) {
77
- const { renderers } = this.editor.contentComponent.state
78
-
79
- renderers.delete(this.id)
80
-
81
- this.editor.contentComponent.setState({
82
- renderers,
83
- })
84
- }
93
+ this.editor?.contentComponent?.removeRenderer(this.id)
85
94
  }
86
95
  }
package/src/index.ts CHANGED
@@ -1,10 +1,10 @@
1
- export * from '@tiptap/core'
2
1
  export * from './BubbleMenu'
3
2
  export { Editor } from './Editor'
4
- export * from './FloatingMenu'
5
- export * from './useEditor'
6
- export * from './ReactRenderer'
7
- export * from './ReactNodeViewRenderer'
8
3
  export * from './EditorContent'
9
- export * from './NodeViewWrapper'
4
+ export * from './FloatingMenu'
10
5
  export * from './NodeViewContent'
6
+ export * from './NodeViewWrapper'
7
+ export * from './ReactNodeViewRenderer'
8
+ export * from './ReactRenderer'
9
+ export * from './useEditor'
10
+ export * from '@tiptap/core'
package/src/useEditor.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { useState, useEffect } from 'react'
2
1
  import { EditorOptions } from '@tiptap/core'
2
+ import { DependencyList, useEffect, useState } from 'react'
3
+
3
4
  import { Editor } from './Editor'
4
5
 
5
6
  function useForceUpdate() {
@@ -8,21 +9,32 @@ function useForceUpdate() {
8
9
  return () => setValue(value => value + 1)
9
10
  }
10
11
 
11
- export const useEditor = (options: Partial<EditorOptions> = {}) => {
12
+ export const useEditor = (options: Partial<EditorOptions> = {}, deps: DependencyList = []) => {
12
13
  const [editor, setEditor] = useState<Editor | null>(null)
13
14
  const forceUpdate = useForceUpdate()
14
15
 
15
16
  useEffect(() => {
17
+ let isMounted = true
18
+
16
19
  const instance = new Editor(options)
17
20
 
18
21
  setEditor(instance)
19
22
 
20
- instance.on('transaction', forceUpdate)
23
+ instance.on('transaction', () => {
24
+ requestAnimationFrame(() => {
25
+ requestAnimationFrame(() => {
26
+ if (isMounted) {
27
+ forceUpdate()
28
+ }
29
+ })
30
+ })
31
+ })
21
32
 
22
33
  return () => {
23
34
  instance.destroy()
35
+ isMounted = false
24
36
  }
25
- }, [])
37
+ }, deps)
26
38
 
27
39
  return editor
28
40
  }
@@ -2,6 +2,7 @@ import { createContext, useContext } from 'react'
2
2
 
3
3
  export interface ReactNodeViewContextProps {
4
4
  onDragStart: (event: DragEvent) => void,
5
+ nodeViewContentRef: (element: HTMLElement | null) => void,
5
6
  }
6
7
 
7
8
  export const ReactNodeViewContext = createContext<Partial<ReactNodeViewContextProps>>({