@tiptap/react 2.0.0-beta.9 → 2.0.0-beta.91
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.
- package/LICENSE.md +1 -1
- package/README.md +2 -2
- package/package.json +17 -9
- package/src/BubbleMenu.tsx +23 -7
- package/src/EditorContent.tsx +8 -7
- package/src/FloatingMenu.tsx +46 -0
- package/src/NodeViewContent.tsx +9 -6
- package/src/NodeViewWrapper.tsx +11 -9
- package/src/ReactNodeViewRenderer.tsx +68 -28
- package/src/ReactRenderer.tsx +26 -14
- package/src/index.ts +1 -0
- package/src/useEditor.ts +10 -4
- package/src/useReactNodeView.ts +1 -2
- package/CHANGELOG.md +0 -80
- package/dist/packages/react/src/BubbleMenu.d.ts +0 -6
- package/dist/packages/react/src/Editor.d.ts +0 -6
- package/dist/packages/react/src/EditorContent.d.ts +0 -19
- package/dist/packages/react/src/NodeViewContent.d.ts +0 -6
- package/dist/packages/react/src/NodeViewWrapper.d.ts +0 -6
- package/dist/packages/react/src/ReactNodeViewRenderer.d.ts +0 -9
- package/dist/packages/react/src/ReactRenderer.d.ts +0 -21
- package/dist/packages/react/src/index.d.ts +0 -9
- package/dist/packages/react/src/useEditor.d.ts +0 -3
- package/dist/packages/react/src/useReactNodeView.d.ts +0 -7
- package/dist/tiptap-react.bundle.umd.min.js +0 -54
- package/dist/tiptap-react.bundle.umd.min.js.map +0 -1
- package/dist/tiptap-react.cjs.js +0 -298
- package/dist/tiptap-react.cjs.js.map +0 -1
- package/dist/tiptap-react.esm.js +0 -274
- package/dist/tiptap-react.esm.js.map +0 -1
- package/dist/tiptap-react.umd.js +0 -299
- package/dist/tiptap-react.umd.js.map +0 -1
package/LICENSE.md
CHANGED
package/README.md
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
## Introduction
|
|
8
8
|
tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as *New York Times*, *The Guardian* or *Atlassian*.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Official Documentation
|
|
11
11
|
Documentation can be found on the [tiptap website](https://tiptap.dev).
|
|
12
12
|
|
|
13
13
|
## License
|
|
14
|
-
tiptap is open
|
|
14
|
+
tiptap is open sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap/blob/main/LICENSE.md).
|
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.
|
|
4
|
+
"version": "2.0.0-beta.91",
|
|
5
5
|
"homepage": "https://tiptap.dev",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"tiptap",
|
|
@@ -15,23 +15,31 @@
|
|
|
15
15
|
"main": "dist/tiptap-react.cjs.js",
|
|
16
16
|
"umd": "dist/tiptap-react.umd.js",
|
|
17
17
|
"module": "dist/tiptap-react.esm.js",
|
|
18
|
-
"unpkg": "dist/tiptap-react.bundle.umd.min.js",
|
|
19
18
|
"types": "dist/packages/react/src/index.d.ts",
|
|
20
19
|
"files": [
|
|
21
20
|
"src",
|
|
22
21
|
"dist"
|
|
23
22
|
],
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/react": "^17.0.34",
|
|
25
|
+
"@types/react-dom": "^17.0.11",
|
|
26
|
+
"react": "^17.0.0",
|
|
27
|
+
"react-dom": "^17.0.0"
|
|
28
|
+
},
|
|
24
29
|
"peerDependencies": {
|
|
25
30
|
"@tiptap/core": "^2.0.0-beta.1",
|
|
26
|
-
"react": "^17.0.
|
|
27
|
-
"react-dom": "^17.0.
|
|
31
|
+
"react": "^17.0.0",
|
|
32
|
+
"react-dom": "^17.0.0"
|
|
28
33
|
},
|
|
29
34
|
"dependencies": {
|
|
30
|
-
"@tiptap/extension-bubble-menu": "^2.0.0-beta.
|
|
31
|
-
"
|
|
35
|
+
"@tiptap/extension-bubble-menu": "^2.0.0-beta.48",
|
|
36
|
+
"@tiptap/extension-floating-menu": "^2.0.0-beta.42",
|
|
37
|
+
"prosemirror-view": "^1.22.0"
|
|
32
38
|
},
|
|
33
|
-
"
|
|
34
|
-
"
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/ueberdosis/tiptap",
|
|
42
|
+
"directory": "packages/react"
|
|
35
43
|
},
|
|
36
|
-
"gitHead": "
|
|
44
|
+
"gitHead": "fce16e805824972834d5a8ce8d60e3ff41d63c7e"
|
|
37
45
|
}
|
package/src/BubbleMenu.tsx
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React, { useEffect, useRef } from 'react'
|
|
2
|
-
import { BubbleMenuPlugin,
|
|
2
|
+
import { BubbleMenuPlugin, BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu'
|
|
3
3
|
|
|
4
|
-
|
|
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'> & {
|
|
5
7
|
className?: string,
|
|
6
8
|
}
|
|
7
9
|
|
|
@@ -9,21 +11,35 @@ export const BubbleMenu: React.FC<BubbleMenuProps> = props => {
|
|
|
9
11
|
const element = useRef<HTMLDivElement>(null)
|
|
10
12
|
|
|
11
13
|
useEffect(() => {
|
|
12
|
-
|
|
14
|
+
if (!element.current) {
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const {
|
|
19
|
+
pluginKey = 'bubbleMenu',
|
|
20
|
+
editor,
|
|
21
|
+
tippyOptions = {},
|
|
22
|
+
shouldShow = null,
|
|
23
|
+
} = props
|
|
13
24
|
|
|
14
25
|
editor.registerPlugin(BubbleMenuPlugin({
|
|
26
|
+
pluginKey,
|
|
15
27
|
editor,
|
|
16
28
|
element: element.current as HTMLElement,
|
|
17
|
-
|
|
29
|
+
tippyOptions,
|
|
30
|
+
shouldShow,
|
|
18
31
|
}))
|
|
19
32
|
|
|
20
33
|
return () => {
|
|
21
|
-
editor.unregisterPlugin(
|
|
34
|
+
editor.unregisterPlugin(pluginKey)
|
|
22
35
|
}
|
|
23
|
-
}, [
|
|
36
|
+
}, [
|
|
37
|
+
props.editor,
|
|
38
|
+
element.current,
|
|
39
|
+
])
|
|
24
40
|
|
|
25
41
|
return (
|
|
26
|
-
<div ref={element} className={props.className}>
|
|
42
|
+
<div ref={element} className={props.className} style={{ visibility: 'hidden' }}>
|
|
27
43
|
{props.children}
|
|
28
44
|
</div>
|
|
29
45
|
)
|
package/src/EditorContent.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, { HTMLProps } from 'react'
|
|
2
2
|
import ReactDOM from 'react-dom'
|
|
3
3
|
import { Editor } from './Editor'
|
|
4
4
|
import { ReactRenderer } from './ReactRenderer'
|
|
@@ -17,7 +17,7 @@ const Portals: React.FC<{ renderers: Map<string, ReactRenderer> }> = ({ renderer
|
|
|
17
17
|
)
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export interface EditorContentProps {
|
|
20
|
+
export interface EditorContentProps extends HTMLProps<HTMLDivElement> {
|
|
21
21
|
editor: Editor | null,
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -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.
|
|
58
|
+
element.append(...editor.options.element.childNodes)
|
|
59
59
|
|
|
60
60
|
editor.setOptions({
|
|
61
61
|
element,
|
|
@@ -63,8 +63,7 @@ export class PureEditorContent extends React.Component<EditorContentProps, Edito
|
|
|
63
63
|
|
|
64
64
|
editor.contentComponent = this
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
setTimeout(() => editor.createNodeViews(), 0)
|
|
66
|
+
editor.createNodeViews()
|
|
68
67
|
}
|
|
69
68
|
}
|
|
70
69
|
|
|
@@ -89,7 +88,7 @@ export class PureEditorContent extends React.Component<EditorContentProps, Edito
|
|
|
89
88
|
|
|
90
89
|
const newElement = document.createElement('div')
|
|
91
90
|
|
|
92
|
-
newElement.
|
|
91
|
+
newElement.append(...editor.options.element.childNodes)
|
|
93
92
|
|
|
94
93
|
editor.setOptions({
|
|
95
94
|
element: newElement,
|
|
@@ -97,9 +96,11 @@ export class PureEditorContent extends React.Component<EditorContentProps, Edito
|
|
|
97
96
|
}
|
|
98
97
|
|
|
99
98
|
render() {
|
|
99
|
+
const { editor, ...rest } = this.props
|
|
100
|
+
|
|
100
101
|
return (
|
|
101
102
|
<>
|
|
102
|
-
<div ref={this.editorContentRef} />
|
|
103
|
+
<div ref={this.editorContentRef} {...rest} />
|
|
103
104
|
<Portals renderers={this.state.renderers} />
|
|
104
105
|
</>
|
|
105
106
|
)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react'
|
|
2
|
+
import { FloatingMenuPlugin, FloatingMenuPluginProps } from '@tiptap/extension-floating-menu'
|
|
3
|
+
|
|
4
|
+
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>
|
|
5
|
+
|
|
6
|
+
export type FloatingMenuProps = Omit<Optional<FloatingMenuPluginProps, 'pluginKey'>, 'element'> & {
|
|
7
|
+
className?: string,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const FloatingMenu: React.FC<FloatingMenuProps> = props => {
|
|
11
|
+
const element = useRef<HTMLDivElement>(null)
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (!element.current) {
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const {
|
|
19
|
+
pluginKey = 'floatingMenu',
|
|
20
|
+
editor,
|
|
21
|
+
tippyOptions = {},
|
|
22
|
+
shouldShow = null,
|
|
23
|
+
} = props
|
|
24
|
+
|
|
25
|
+
editor.registerPlugin(FloatingMenuPlugin({
|
|
26
|
+
pluginKey,
|
|
27
|
+
editor,
|
|
28
|
+
element: element.current as HTMLElement,
|
|
29
|
+
tippyOptions,
|
|
30
|
+
shouldShow,
|
|
31
|
+
}))
|
|
32
|
+
|
|
33
|
+
return () => {
|
|
34
|
+
editor.unregisterPlugin(pluginKey)
|
|
35
|
+
}
|
|
36
|
+
}, [
|
|
37
|
+
props.editor,
|
|
38
|
+
element.current,
|
|
39
|
+
])
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div ref={element} className={props.className} style={{ visibility: 'hidden' }}>
|
|
43
|
+
{props.children}
|
|
44
|
+
</div>
|
|
45
|
+
)
|
|
46
|
+
}
|
package/src/NodeViewContent.tsx
CHANGED
|
@@ -2,20 +2,23 @@ import React from 'react'
|
|
|
2
2
|
import { useReactNodeView } from './useReactNodeView'
|
|
3
3
|
|
|
4
4
|
export interface NodeViewContentProps {
|
|
5
|
-
|
|
6
|
-
as
|
|
5
|
+
[key: string]: any,
|
|
6
|
+
as?: React.ElementType,
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export const NodeViewContent: React.FC<NodeViewContentProps> = props => {
|
|
10
|
-
const { isEditable } = useReactNodeView()
|
|
11
10
|
const Tag = props.as || 'div'
|
|
11
|
+
const { nodeViewContentRef } = useReactNodeView()
|
|
12
12
|
|
|
13
13
|
return (
|
|
14
14
|
<Tag
|
|
15
|
-
|
|
15
|
+
{...props}
|
|
16
|
+
ref={nodeViewContentRef}
|
|
16
17
|
data-node-view-content=""
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
style={{
|
|
19
|
+
...props.style,
|
|
20
|
+
whiteSpace: 'pre-wrap',
|
|
21
|
+
}}
|
|
19
22
|
/>
|
|
20
23
|
)
|
|
21
24
|
}
|
package/src/NodeViewWrapper.tsx
CHANGED
|
@@ -2,22 +2,24 @@ import React from 'react'
|
|
|
2
2
|
import { useReactNodeView } from './useReactNodeView'
|
|
3
3
|
|
|
4
4
|
export interface NodeViewWrapperProps {
|
|
5
|
-
|
|
6
|
-
as
|
|
5
|
+
[key: string]: any,
|
|
6
|
+
as?: React.ElementType,
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export const NodeViewWrapper: React.FC<NodeViewWrapperProps> = props => {
|
|
9
|
+
export const NodeViewWrapper: React.FC<NodeViewWrapperProps> = React.forwardRef((props, ref) => {
|
|
10
10
|
const { onDragStart } = useReactNodeView()
|
|
11
11
|
const Tag = props.as || 'div'
|
|
12
12
|
|
|
13
13
|
return (
|
|
14
14
|
<Tag
|
|
15
|
-
|
|
15
|
+
{...props}
|
|
16
|
+
ref={ref}
|
|
16
17
|
data-node-view-wrapper=""
|
|
17
18
|
onDragStart={onDragStart}
|
|
18
|
-
style={{
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
style={{
|
|
20
|
+
...props.style,
|
|
21
|
+
whiteSpace: 'normal',
|
|
22
|
+
}}
|
|
23
|
+
/>
|
|
22
24
|
)
|
|
23
|
-
}
|
|
25
|
+
})
|
|
@@ -1,25 +1,33 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react'
|
|
2
2
|
import {
|
|
3
3
|
NodeView,
|
|
4
4
|
NodeViewProps,
|
|
5
5
|
NodeViewRenderer,
|
|
6
6
|
NodeViewRendererProps,
|
|
7
|
+
NodeViewRendererOptions,
|
|
7
8
|
} from '@tiptap/core'
|
|
8
9
|
import { Decoration, NodeView as ProseMirrorNodeView } from 'prosemirror-view'
|
|
9
10
|
import { Node as ProseMirrorNode } from 'prosemirror-model'
|
|
10
11
|
import { Editor } from './Editor'
|
|
11
12
|
import { ReactRenderer } from './ReactRenderer'
|
|
12
|
-
import { ReactNodeViewContext } from './useReactNodeView'
|
|
13
|
-
|
|
14
|
-
interface ReactNodeViewRendererOptions {
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
import { ReactNodeViewContext, ReactNodeViewContextProps } from './useReactNodeView'
|
|
14
|
+
|
|
15
|
+
export interface ReactNodeViewRendererOptions extends NodeViewRendererOptions {
|
|
16
|
+
update: ((props: {
|
|
17
|
+
oldNode: ProseMirrorNode,
|
|
18
|
+
oldDecorations: Decoration[],
|
|
19
|
+
newNode: ProseMirrorNode,
|
|
20
|
+
newDecorations: Decoration[],
|
|
21
|
+
updateProps: () => void,
|
|
22
|
+
}) => boolean) | null,
|
|
17
23
|
}
|
|
18
24
|
|
|
19
|
-
class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
|
|
25
|
+
class ReactNodeView extends NodeView<React.FunctionComponent, Editor, ReactNodeViewRendererOptions> {
|
|
20
26
|
|
|
21
27
|
renderer!: ReactRenderer
|
|
22
28
|
|
|
29
|
+
contentDOMElement!: HTMLElement | null
|
|
30
|
+
|
|
23
31
|
mount() {
|
|
24
32
|
const props: NodeViewProps = {
|
|
25
33
|
editor: this.editor,
|
|
@@ -29,6 +37,7 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
|
|
|
29
37
|
extension: this.extension,
|
|
30
38
|
getPos: () => this.getPos(),
|
|
31
39
|
updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
|
|
40
|
+
deleteNode: () => this.deleteNode(),
|
|
32
41
|
}
|
|
33
42
|
|
|
34
43
|
if (!(this.component as any).displayName) {
|
|
@@ -36,26 +45,24 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
|
|
|
36
45
|
return string.charAt(0).toUpperCase() + string.substring(1)
|
|
37
46
|
}
|
|
38
47
|
|
|
39
|
-
|
|
40
|
-
this.component.displayName = capitalizeFirstChar(this.extension.config.name)
|
|
48
|
+
this.component.displayName = capitalizeFirstChar(this.extension.name)
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
const ReactNodeViewProvider: React.FunctionComponent = componentProps => {
|
|
44
|
-
const [isEditable, setIsEditable] = useState(this.editor.isEditable)
|
|
45
|
-
const onDragStart = this.onDragStart.bind(this)
|
|
46
|
-
const onViewUpdate = () => setIsEditable(this.editor.isEditable)
|
|
47
52
|
const Component = this.component
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
this.
|
|
53
|
+
const onDragStart = this.onDragStart.bind(this)
|
|
54
|
+
const nodeViewContentRef: ReactNodeViewContextProps['nodeViewContentRef'] = element => {
|
|
55
|
+
if (
|
|
56
|
+
element
|
|
57
|
+
&& this.contentDOMElement
|
|
58
|
+
&& element.firstChild !== this.contentDOMElement
|
|
59
|
+
) {
|
|
60
|
+
element.appendChild(this.contentDOMElement)
|
|
54
61
|
}
|
|
55
|
-
}
|
|
62
|
+
}
|
|
56
63
|
|
|
57
64
|
return (
|
|
58
|
-
<ReactNodeViewContext.Provider value={{ onDragStart,
|
|
65
|
+
<ReactNodeViewContext.Provider value={{ onDragStart, nodeViewContentRef }}>
|
|
59
66
|
<Component {...componentProps} />
|
|
60
67
|
</ReactNodeViewContext.Provider>
|
|
61
68
|
)
|
|
@@ -63,15 +70,32 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
|
|
|
63
70
|
|
|
64
71
|
ReactNodeViewProvider.displayName = 'ReactNodeView'
|
|
65
72
|
|
|
73
|
+
this.contentDOMElement = this.node.isLeaf
|
|
74
|
+
? null
|
|
75
|
+
: document.createElement(this.node.isInline ? 'span' : 'div')
|
|
76
|
+
|
|
77
|
+
if (this.contentDOMElement) {
|
|
78
|
+
// For some reason the whiteSpace prop is not inherited properly in Chrome and Safari
|
|
79
|
+
// With this fix it seems to work fine
|
|
80
|
+
// See: https://github.com/ueberdosis/tiptap/issues/1197
|
|
81
|
+
this.contentDOMElement.style.whiteSpace = 'inherit'
|
|
82
|
+
}
|
|
83
|
+
|
|
66
84
|
this.renderer = new ReactRenderer(ReactNodeViewProvider, {
|
|
67
85
|
editor: this.editor,
|
|
68
86
|
props,
|
|
87
|
+
as: this.node.isInline
|
|
88
|
+
? 'span'
|
|
89
|
+
: 'div',
|
|
69
90
|
})
|
|
70
91
|
}
|
|
71
92
|
|
|
72
93
|
get dom() {
|
|
73
|
-
if (
|
|
74
|
-
|
|
94
|
+
if (
|
|
95
|
+
this.renderer.element.firstElementChild
|
|
96
|
+
&& !this.renderer.element.firstElementChild?.hasAttribute('data-node-view-wrapper')
|
|
97
|
+
) {
|
|
98
|
+
throw Error('Please use the NodeViewWrapper component for your node view.')
|
|
75
99
|
}
|
|
76
100
|
|
|
77
101
|
return this.renderer.element
|
|
@@ -82,27 +106,42 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
|
|
|
82
106
|
return null
|
|
83
107
|
}
|
|
84
108
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return contentElement || this.dom
|
|
109
|
+
return this.contentDOMElement
|
|
88
110
|
}
|
|
89
111
|
|
|
90
112
|
update(node: ProseMirrorNode, decorations: Decoration[]) {
|
|
91
|
-
|
|
92
|
-
|
|
113
|
+
const updateProps = (props?: Record<string, any>) => {
|
|
114
|
+
this.renderer.updateProps(props)
|
|
93
115
|
}
|
|
94
116
|
|
|
95
117
|
if (node.type !== this.node.type) {
|
|
96
118
|
return false
|
|
97
119
|
}
|
|
98
120
|
|
|
121
|
+
if (typeof this.options.update === 'function') {
|
|
122
|
+
const oldNode = this.node
|
|
123
|
+
const oldDecorations = this.decorations
|
|
124
|
+
|
|
125
|
+
this.node = node
|
|
126
|
+
this.decorations = decorations
|
|
127
|
+
|
|
128
|
+
return this.options.update({
|
|
129
|
+
oldNode,
|
|
130
|
+
oldDecorations,
|
|
131
|
+
newNode: node,
|
|
132
|
+
newDecorations: decorations,
|
|
133
|
+
updateProps: () => updateProps({ node, decorations }),
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
99
137
|
if (node === this.node && this.decorations === decorations) {
|
|
100
138
|
return true
|
|
101
139
|
}
|
|
102
140
|
|
|
103
141
|
this.node = node
|
|
104
142
|
this.decorations = decorations
|
|
105
|
-
|
|
143
|
+
|
|
144
|
+
updateProps({ node, decorations })
|
|
106
145
|
|
|
107
146
|
return true
|
|
108
147
|
}
|
|
@@ -121,6 +160,7 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
|
|
|
121
160
|
|
|
122
161
|
destroy() {
|
|
123
162
|
this.renderer.destroy()
|
|
163
|
+
this.contentDOMElement = null
|
|
124
164
|
}
|
|
125
165
|
}
|
|
126
166
|
|
package/src/ReactRenderer.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import {
|
|
3
|
-
import { Editor } from './Editor'
|
|
2
|
+
import { Editor } from '@tiptap/core'
|
|
3
|
+
import { Editor as ExtendedEditor } from './Editor'
|
|
4
4
|
|
|
5
5
|
function isClassComponent(Component: any) {
|
|
6
6
|
return !!(
|
|
@@ -10,33 +10,45 @@ function isClassComponent(Component: any) {
|
|
|
10
10
|
)
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
function isForwardRefComponent(Component: any) {
|
|
14
|
+
return !!(
|
|
15
|
+
typeof Component === 'object'
|
|
16
|
+
&& Component.$$typeof?.toString() === 'Symbol(react.forward_ref)'
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
export interface ReactRendererOptions {
|
|
14
|
-
as?: string,
|
|
15
21
|
editor: Editor,
|
|
16
|
-
props?:
|
|
22
|
+
props?: Record<string, any>,
|
|
23
|
+
as?: string,
|
|
17
24
|
}
|
|
18
25
|
|
|
19
|
-
|
|
26
|
+
type ComponentType<R> =
|
|
27
|
+
| React.ComponentClass
|
|
28
|
+
| React.FunctionComponent
|
|
29
|
+
| React.ForwardRefExoticComponent<{ items: any[], command: any } & React.RefAttributes<R>>
|
|
30
|
+
|
|
31
|
+
export class ReactRenderer<R = unknown> {
|
|
20
32
|
id: string
|
|
21
33
|
|
|
22
|
-
editor:
|
|
34
|
+
editor: ExtendedEditor
|
|
23
35
|
|
|
24
36
|
component: any
|
|
25
37
|
|
|
26
38
|
element: Element
|
|
27
39
|
|
|
28
|
-
props:
|
|
40
|
+
props: Record<string, any>
|
|
29
41
|
|
|
30
42
|
reactElement: React.ReactNode
|
|
31
43
|
|
|
32
|
-
ref:
|
|
44
|
+
ref: R | null = null
|
|
33
45
|
|
|
34
|
-
constructor(component:
|
|
46
|
+
constructor(component: ComponentType<R>, { editor, props = {}, as = 'div' }: ReactRendererOptions) {
|
|
35
47
|
this.id = Math.floor(Math.random() * 0xFFFFFFFF).toString()
|
|
36
48
|
this.component = component
|
|
37
|
-
this.editor = editor
|
|
49
|
+
this.editor = editor as ExtendedEditor
|
|
38
50
|
this.props = props
|
|
39
|
-
this.element = document.createElement(
|
|
51
|
+
this.element = document.createElement(as)
|
|
40
52
|
this.element.classList.add('react-renderer')
|
|
41
53
|
this.render()
|
|
42
54
|
}
|
|
@@ -45,8 +57,8 @@ export class ReactRenderer {
|
|
|
45
57
|
const Component = this.component
|
|
46
58
|
const props = this.props
|
|
47
59
|
|
|
48
|
-
if (isClassComponent(Component)) {
|
|
49
|
-
props.ref = (ref:
|
|
60
|
+
if (isClassComponent(Component) || isForwardRefComponent(Component)) {
|
|
61
|
+
props.ref = (ref: R) => {
|
|
50
62
|
this.ref = ref
|
|
51
63
|
}
|
|
52
64
|
}
|
|
@@ -63,7 +75,7 @@ export class ReactRenderer {
|
|
|
63
75
|
}
|
|
64
76
|
}
|
|
65
77
|
|
|
66
|
-
updateProps(props:
|
|
78
|
+
updateProps(props: Record<string, any> = {}): void {
|
|
67
79
|
this.props = {
|
|
68
80
|
...this.props,
|
|
69
81
|
...props,
|
package/src/index.ts
CHANGED
package/src/useEditor.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react'
|
|
1
|
+
import { useState, useEffect, DependencyList } from 'react'
|
|
2
2
|
import { EditorOptions } from '@tiptap/core'
|
|
3
3
|
import { Editor } from './Editor'
|
|
4
4
|
|
|
@@ -8,7 +8,7 @@ function useForceUpdate() {
|
|
|
8
8
|
return () => setValue(value => value + 1)
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export const useEditor = (options: Partial<EditorOptions> = {}) => {
|
|
11
|
+
export const useEditor = (options: Partial<EditorOptions> = {}, deps: DependencyList = []) => {
|
|
12
12
|
const [editor, setEditor] = useState<Editor | null>(null)
|
|
13
13
|
const forceUpdate = useForceUpdate()
|
|
14
14
|
|
|
@@ -17,12 +17,18 @@ export const useEditor = (options: Partial<EditorOptions> = {}) => {
|
|
|
17
17
|
|
|
18
18
|
setEditor(instance)
|
|
19
19
|
|
|
20
|
-
instance.on('transaction',
|
|
20
|
+
instance.on('transaction', () => {
|
|
21
|
+
requestAnimationFrame(() => {
|
|
22
|
+
requestAnimationFrame(() => {
|
|
23
|
+
forceUpdate()
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
})
|
|
21
27
|
|
|
22
28
|
return () => {
|
|
23
29
|
instance.destroy()
|
|
24
30
|
}
|
|
25
|
-
},
|
|
31
|
+
}, deps)
|
|
26
32
|
|
|
27
33
|
return editor
|
|
28
34
|
}
|
package/src/useReactNodeView.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { createContext, useContext } from 'react'
|
|
2
2
|
|
|
3
3
|
export interface ReactNodeViewContextProps {
|
|
4
|
-
isEditable: boolean,
|
|
5
4
|
onDragStart: (event: DragEvent) => void,
|
|
5
|
+
nodeViewContentRef: (element: HTMLElement | null) => void,
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export const ReactNodeViewContext = createContext<Partial<ReactNodeViewContextProps>>({
|
|
9
|
-
isEditable: undefined,
|
|
10
9
|
onDragStart: undefined,
|
|
11
10
|
})
|
|
12
11
|
|