@tiptap/react 2.0.0-beta.17 → 2.0.0-beta.194
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/README.md +2 -2
- package/dist/packages/react/src/BubbleMenu.d.ts +6 -3
- package/dist/packages/react/src/Editor.d.ts +1 -1
- package/dist/packages/react/src/EditorContent.d.ts +2 -2
- package/dist/packages/react/src/FloatingMenu.d.ts +6 -3
- package/dist/packages/react/src/NodeViewContent.d.ts +1 -1
- package/dist/packages/react/src/NodeViewWrapper.d.ts +1 -1
- package/dist/packages/react/src/ReactNodeViewRenderer.d.ts +12 -6
- package/dist/packages/react/src/ReactRenderer.d.ts +12 -9
- package/dist/packages/react/src/index.d.ts +6 -6
- package/dist/packages/react/src/useEditor.d.ts +2 -1
- package/dist/packages/react/src/useReactNodeView.d.ts +1 -0
- package/dist/tiptap-react.cjs.js +232 -151
- package/dist/tiptap-react.cjs.js.map +1 -1
- package/dist/tiptap-react.esm.js +232 -149
- package/dist/tiptap-react.esm.js.map +1 -1
- package/dist/tiptap-react.umd.js +234 -153
- package/dist/tiptap-react.umd.js.map +1 -1
- package/package.json +18 -12
- package/src/BubbleMenu.tsx +37 -15
- package/src/Editor.ts +2 -1
- package/src/EditorContent.tsx +9 -7
- package/src/FloatingMenu.tsx +37 -14
- package/src/NodeViewContent.tsx +10 -3
- package/src/NodeViewWrapper.tsx +11 -8
- package/src/ReactNodeViewRenderer.tsx +87 -25
- package/src/ReactRenderer.tsx +56 -27
- package/src/index.ts +6 -6
- package/src/useEditor.ts +16 -4
- package/src/useReactNodeView.ts +1 -0
- package/CHANGELOG.md +0 -151
- package/LICENSE.md +0 -21
- package/dist/tiptap-react.bundle.umd.min.js +0 -54
- package/dist/tiptap-react.bundle.umd.min.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.
|
|
4
|
+
"version": "2.0.0-beta.194",
|
|
5
5
|
"homepage": "https://tiptap.dev",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"tiptap",
|
|
@@ -15,25 +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": "^18.0.1",
|
|
25
|
+
"@types/react-dom": "^18.0.0",
|
|
26
|
+
"react": "^18.0.0",
|
|
27
|
+
"react-dom": "^18.0.0"
|
|
28
|
+
},
|
|
24
29
|
"peerDependencies": {
|
|
25
|
-
"@tiptap/core": "^2.0.0-beta.
|
|
26
|
-
"react": "^17.0.
|
|
27
|
-
"react-dom": "^17.0.
|
|
30
|
+
"@tiptap/core": "^2.0.0-beta.193",
|
|
31
|
+
"react": "^17.0.0 || ^18.0.0",
|
|
32
|
+
"react-dom": "^17.0.0 || ^18.0.0"
|
|
28
33
|
},
|
|
29
34
|
"dependencies": {
|
|
30
|
-
"@tiptap/extension-bubble-menu": "^2.0.0-beta.
|
|
31
|
-
"@tiptap/extension-floating-menu": "^2.0.0-beta.
|
|
32
|
-
"prosemirror-view": "
|
|
35
|
+
"@tiptap/extension-bubble-menu": "^2.0.0-beta.194",
|
|
36
|
+
"@tiptap/extension-floating-menu": "^2.0.0-beta.194",
|
|
37
|
+
"prosemirror-view": "1.26.2"
|
|
33
38
|
},
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/ueberdosis/tiptap",
|
|
42
|
+
"directory": "packages/react"
|
|
37
43
|
},
|
|
38
|
-
"
|
|
44
|
+
"sideEffects": false
|
|
39
45
|
}
|
package/src/BubbleMenu.tsx
CHANGED
|
@@ -1,29 +1,51 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { BubbleMenuPlugin, BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu'
|
|
2
|
+
import React, {
|
|
3
|
+
useEffect, useState,
|
|
4
|
+
} from 'react'
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>
|
|
7
|
+
|
|
8
|
+
export type BubbleMenuProps = Omit<Optional<BubbleMenuPluginProps, 'pluginKey'>, 'element'> & {
|
|
5
9
|
className?: string,
|
|
10
|
+
children: React.ReactNode
|
|
6
11
|
}
|
|
7
12
|
|
|
8
|
-
export const BubbleMenu
|
|
9
|
-
const element =
|
|
13
|
+
export const BubbleMenu = (props: BubbleMenuProps) => {
|
|
14
|
+
const [element, setElement] = useState<HTMLDivElement | null>(null)
|
|
10
15
|
|
|
11
16
|
useEffect(() => {
|
|
12
|
-
|
|
17
|
+
if (!element) {
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (props.editor.isDestroyed) {
|
|
22
|
+
return
|
|
23
|
+
}
|
|
13
24
|
|
|
14
|
-
|
|
25
|
+
const {
|
|
26
|
+
pluginKey = 'bubbleMenu',
|
|
15
27
|
editor,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
28
|
+
tippyOptions = {},
|
|
29
|
+
shouldShow = null,
|
|
30
|
+
} = props
|
|
19
31
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
32
|
+
const plugin = BubbleMenuPlugin({
|
|
33
|
+
pluginKey,
|
|
34
|
+
editor,
|
|
35
|
+
element,
|
|
36
|
+
tippyOptions,
|
|
37
|
+
shouldShow,
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
editor.registerPlugin(plugin)
|
|
41
|
+
return () => editor.unregisterPlugin(pluginKey)
|
|
42
|
+
}, [
|
|
43
|
+
props.editor,
|
|
44
|
+
element,
|
|
45
|
+
])
|
|
24
46
|
|
|
25
47
|
return (
|
|
26
|
-
<div ref={
|
|
48
|
+
<div ref={setElement} className={props.className} style={{ visibility: 'hidden' }}>
|
|
27
49
|
{props.children}
|
|
28
50
|
</div>
|
|
29
51
|
)
|
package/src/Editor.ts
CHANGED
package/src/EditorContent.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, { HTMLProps } from 'react'
|
|
2
2
|
import ReactDOM from 'react-dom'
|
|
3
|
+
|
|
3
4
|
import { Editor } from './Editor'
|
|
4
5
|
import { ReactRenderer } from './ReactRenderer'
|
|
5
6
|
|
|
@@ -17,7 +18,7 @@ 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
|
|
|
@@ -55,7 +56,7 @@ export class PureEditorContent extends React.Component<EditorContentProps, Edito
|
|
|
55
56
|
|
|
56
57
|
const element = this.editorContentRef.current
|
|
57
58
|
|
|
58
|
-
element.
|
|
59
|
+
element.append(...editor.options.element.childNodes)
|
|
59
60
|
|
|
60
61
|
editor.setOptions({
|
|
61
62
|
element,
|
|
@@ -63,8 +64,7 @@ export class PureEditorContent extends React.Component<EditorContentProps, Edito
|
|
|
63
64
|
|
|
64
65
|
editor.contentComponent = this
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
setTimeout(() => editor.createNodeViews(), 0)
|
|
67
|
+
editor.createNodeViews()
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
@@ -89,7 +89,7 @@ export class PureEditorContent extends React.Component<EditorContentProps, Edito
|
|
|
89
89
|
|
|
90
90
|
const newElement = document.createElement('div')
|
|
91
91
|
|
|
92
|
-
newElement.
|
|
92
|
+
newElement.append(...editor.options.element.childNodes)
|
|
93
93
|
|
|
94
94
|
editor.setOptions({
|
|
95
95
|
element: newElement,
|
|
@@ -97,9 +97,11 @@ export class PureEditorContent extends React.Component<EditorContentProps, Edito
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
render() {
|
|
100
|
+
const { editor, ...rest } = this.props
|
|
101
|
+
|
|
100
102
|
return (
|
|
101
103
|
<>
|
|
102
|
-
<div ref={this.editorContentRef} />
|
|
104
|
+
<div ref={this.editorContentRef} {...rest} />
|
|
103
105
|
<Portals renderers={this.state.renderers} />
|
|
104
106
|
</>
|
|
105
107
|
)
|
package/src/FloatingMenu.tsx
CHANGED
|
@@ -1,28 +1,51 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { FloatingMenuPlugin, FloatingMenuPluginProps } from '@tiptap/extension-floating-menu'
|
|
2
|
+
import React, {
|
|
3
|
+
useEffect, useState,
|
|
4
|
+
} from 'react'
|
|
3
5
|
|
|
4
|
-
|
|
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
|
|
9
|
-
const element =
|
|
13
|
+
export const FloatingMenu = (props: FloatingMenuProps) => {
|
|
14
|
+
const [element, setElement] = useState<HTMLDivElement | null>(null)
|
|
10
15
|
|
|
11
16
|
useEffect(() => {
|
|
12
|
-
|
|
17
|
+
if (!element) {
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (props.editor.isDestroyed) {
|
|
22
|
+
return
|
|
23
|
+
}
|
|
13
24
|
|
|
14
|
-
|
|
25
|
+
const {
|
|
26
|
+
pluginKey = 'floatingMenu',
|
|
15
27
|
editor,
|
|
16
|
-
|
|
17
|
-
|
|
28
|
+
tippyOptions = {},
|
|
29
|
+
shouldShow = null,
|
|
30
|
+
} = props
|
|
18
31
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
32
|
+
const plugin = FloatingMenuPlugin({
|
|
33
|
+
pluginKey,
|
|
34
|
+
editor,
|
|
35
|
+
element,
|
|
36
|
+
tippyOptions,
|
|
37
|
+
shouldShow,
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
editor.registerPlugin(plugin)
|
|
41
|
+
return () => editor.unregisterPlugin(pluginKey)
|
|
42
|
+
}, [
|
|
43
|
+
props.editor,
|
|
44
|
+
element,
|
|
45
|
+
])
|
|
23
46
|
|
|
24
47
|
return (
|
|
25
|
-
<div ref={
|
|
48
|
+
<div ref={setElement} className={props.className} style={{ visibility: 'hidden' }}>
|
|
26
49
|
{props.children}
|
|
27
50
|
</div>
|
|
28
51
|
)
|
package/src/NodeViewContent.tsx
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
|
|
3
|
+
import { useReactNodeView } from './useReactNodeView'
|
|
4
|
+
|
|
3
5
|
export interface NodeViewContentProps {
|
|
4
|
-
|
|
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
|
-
|
|
16
|
+
{...props}
|
|
17
|
+
ref={nodeViewContentRef}
|
|
14
18
|
data-node-view-content=""
|
|
15
|
-
style={{
|
|
19
|
+
style={{
|
|
20
|
+
whiteSpace: 'pre-wrap',
|
|
21
|
+
...props.style,
|
|
22
|
+
}}
|
|
16
23
|
/>
|
|
17
24
|
)
|
|
18
25
|
}
|
package/src/NodeViewWrapper.tsx
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
16
|
+
{...props}
|
|
17
|
+
ref={ref}
|
|
16
18
|
data-node-view-wrapper=""
|
|
17
19
|
onDragStart={onDragStart}
|
|
18
|
-
style={{
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
style={{
|
|
21
|
+
whiteSpace: 'normal',
|
|
22
|
+
...props.style,
|
|
23
|
+
}}
|
|
24
|
+
/>
|
|
22
25
|
)
|
|
23
|
-
}
|
|
26
|
+
})
|
|
@@ -1,25 +1,41 @@
|
|
|
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
8
|
import { Node as ProseMirrorNode } from 'prosemirror-model'
|
|
9
|
+
import { Decoration, NodeView as ProseMirrorNodeView } from 'prosemirror-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
|
-
|
|
16
|
-
|
|
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<
|
|
20
|
-
|
|
30
|
+
class ReactNodeView extends NodeView<
|
|
31
|
+
React.FunctionComponent,
|
|
32
|
+
Editor,
|
|
33
|
+
ReactNodeViewRendererOptions
|
|
34
|
+
> {
|
|
21
35
|
renderer!: ReactRenderer
|
|
22
36
|
|
|
37
|
+
contentDOMElement!: HTMLElement | null
|
|
38
|
+
|
|
23
39
|
mount() {
|
|
24
40
|
const props: NodeViewProps = {
|
|
25
41
|
editor: this.editor,
|
|
@@ -29,6 +45,7 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
|
|
|
29
45
|
extension: this.extension,
|
|
30
46
|
getPos: () => this.getPos(),
|
|
31
47
|
updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
|
|
48
|
+
deleteNode: () => this.deleteNode(),
|
|
32
49
|
}
|
|
33
50
|
|
|
34
51
|
if (!(this.component as any).displayName) {
|
|
@@ -36,15 +53,20 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
|
|
|
36
53
|
return string.charAt(0).toUpperCase() + string.substring(1)
|
|
37
54
|
}
|
|
38
55
|
|
|
39
|
-
this.component.displayName = capitalizeFirstChar(this.extension.
|
|
56
|
+
this.component.displayName = capitalizeFirstChar(this.extension.name)
|
|
40
57
|
}
|
|
41
58
|
|
|
42
59
|
const ReactNodeViewProvider: React.FunctionComponent = componentProps => {
|
|
43
|
-
const onDragStart = this.onDragStart.bind(this)
|
|
44
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
|
+
}
|
|
45
67
|
|
|
46
68
|
return (
|
|
47
|
-
<ReactNodeViewContext.Provider value={{ onDragStart }}>
|
|
69
|
+
<ReactNodeViewContext.Provider value={{ onDragStart, nodeViewContentRef }}>
|
|
48
70
|
<Component {...componentProps} />
|
|
49
71
|
</ReactNodeViewContext.Provider>
|
|
50
72
|
)
|
|
@@ -52,21 +74,42 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
|
|
|
52
74
|
|
|
53
75
|
ReactNodeViewProvider.displayName = 'ReactNodeView'
|
|
54
76
|
|
|
77
|
+
this.contentDOMElement = this.node.isLeaf
|
|
78
|
+
? null
|
|
79
|
+
: document.createElement(this.node.isInline ? 'span' : 'div')
|
|
80
|
+
|
|
81
|
+
if (this.contentDOMElement) {
|
|
82
|
+
// For some reason the whiteSpace prop is not inherited properly in Chrome and Safari
|
|
83
|
+
// With this fix it seems to work fine
|
|
84
|
+
// See: https://github.com/ueberdosis/tiptap/issues/1197
|
|
85
|
+
this.contentDOMElement.style.whiteSpace = 'inherit'
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let as = this.node.isInline ? 'span' : 'div'
|
|
89
|
+
|
|
90
|
+
if (this.options.as) {
|
|
91
|
+
as = this.options.as
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const { className = '' } = this.options
|
|
95
|
+
|
|
55
96
|
this.renderer = new ReactRenderer(ReactNodeViewProvider, {
|
|
56
97
|
editor: this.editor,
|
|
57
98
|
props,
|
|
58
|
-
as
|
|
59
|
-
|
|
60
|
-
: 'div',
|
|
99
|
+
as,
|
|
100
|
+
className: `node-${this.node.type.name} ${className}`.trim(),
|
|
61
101
|
})
|
|
62
102
|
}
|
|
63
103
|
|
|
64
104
|
get dom() {
|
|
65
|
-
if (
|
|
105
|
+
if (
|
|
106
|
+
this.renderer.element.firstElementChild
|
|
107
|
+
&& !this.renderer.element.firstElementChild?.hasAttribute('data-node-view-wrapper')
|
|
108
|
+
) {
|
|
66
109
|
throw Error('Please use the NodeViewWrapper component for your node view.')
|
|
67
110
|
}
|
|
68
111
|
|
|
69
|
-
return this.renderer.element
|
|
112
|
+
return this.renderer.element as HTMLElement
|
|
70
113
|
}
|
|
71
114
|
|
|
72
115
|
get contentDOM() {
|
|
@@ -74,27 +117,42 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
|
|
|
74
117
|
return null
|
|
75
118
|
}
|
|
76
119
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
return contentElement || this.dom
|
|
120
|
+
return this.contentDOMElement
|
|
80
121
|
}
|
|
81
122
|
|
|
82
123
|
update(node: ProseMirrorNode, decorations: Decoration[]) {
|
|
83
|
-
|
|
84
|
-
|
|
124
|
+
const updateProps = (props?: Record<string, any>) => {
|
|
125
|
+
this.renderer.updateProps(props)
|
|
85
126
|
}
|
|
86
127
|
|
|
87
128
|
if (node.type !== this.node.type) {
|
|
88
129
|
return false
|
|
89
130
|
}
|
|
90
131
|
|
|
132
|
+
if (typeof this.options.update === 'function') {
|
|
133
|
+
const oldNode = this.node
|
|
134
|
+
const oldDecorations = this.decorations
|
|
135
|
+
|
|
136
|
+
this.node = node
|
|
137
|
+
this.decorations = decorations
|
|
138
|
+
|
|
139
|
+
return this.options.update({
|
|
140
|
+
oldNode,
|
|
141
|
+
oldDecorations,
|
|
142
|
+
newNode: node,
|
|
143
|
+
newDecorations: decorations,
|
|
144
|
+
updateProps: () => updateProps({ node, decorations }),
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
|
|
91
148
|
if (node === this.node && this.decorations === decorations) {
|
|
92
149
|
return true
|
|
93
150
|
}
|
|
94
151
|
|
|
95
152
|
this.node = node
|
|
96
153
|
this.decorations = decorations
|
|
97
|
-
|
|
154
|
+
|
|
155
|
+
updateProps({ node, decorations })
|
|
98
156
|
|
|
99
157
|
return true
|
|
100
158
|
}
|
|
@@ -113,10 +171,14 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
|
|
|
113
171
|
|
|
114
172
|
destroy() {
|
|
115
173
|
this.renderer.destroy()
|
|
174
|
+
this.contentDOMElement = null
|
|
116
175
|
}
|
|
117
176
|
}
|
|
118
177
|
|
|
119
|
-
export function ReactNodeViewRenderer(
|
|
178
|
+
export function ReactNodeViewRenderer(
|
|
179
|
+
component: any,
|
|
180
|
+
options?: Partial<ReactNodeViewRendererOptions>,
|
|
181
|
+
): NodeViewRenderer {
|
|
120
182
|
return (props: NodeViewRendererProps) => {
|
|
121
183
|
// try to get the parent component
|
|
122
184
|
// this is important for vue devtools to show the component hierarchy correctly
|
|
@@ -125,6 +187,6 @@ export function ReactNodeViewRenderer(component: any, options?: Partial<ReactNod
|
|
|
125
187
|
return {}
|
|
126
188
|
}
|
|
127
189
|
|
|
128
|
-
return new ReactNodeView(component, props, options) as ProseMirrorNodeView
|
|
190
|
+
return new ReactNodeView(component, props, options) as unknown as ProseMirrorNodeView
|
|
129
191
|
}
|
|
130
192
|
}
|
package/src/ReactRenderer.tsx
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { Editor } from '@tiptap/core'
|
|
1
2
|
import React from 'react'
|
|
2
|
-
import {
|
|
3
|
-
|
|
3
|
+
import { flushSync } from 'react-dom'
|
|
4
|
+
|
|
5
|
+
import { Editor as ExtendedEditor } from './Editor'
|
|
4
6
|
|
|
5
7
|
function isClassComponent(Component: any) {
|
|
6
8
|
return !!(
|
|
@@ -10,34 +12,57 @@ function isClassComponent(Component: any) {
|
|
|
10
12
|
)
|
|
11
13
|
}
|
|
12
14
|
|
|
15
|
+
function isForwardRefComponent(Component: any) {
|
|
16
|
+
return !!(
|
|
17
|
+
typeof Component === 'object'
|
|
18
|
+
&& Component.$$typeof?.toString() === 'Symbol(react.forward_ref)'
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
13
22
|
export interface ReactRendererOptions {
|
|
14
23
|
editor: Editor,
|
|
15
|
-
props?:
|
|
24
|
+
props?: Record<string, any>,
|
|
16
25
|
as?: string,
|
|
26
|
+
className?: string,
|
|
17
27
|
}
|
|
18
28
|
|
|
19
|
-
|
|
29
|
+
type ComponentType<R, P> =
|
|
30
|
+
React.ComponentClass<P> |
|
|
31
|
+
React.FunctionComponent<P> |
|
|
32
|
+
React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<R>>;
|
|
33
|
+
|
|
34
|
+
export class ReactRenderer<R = unknown, P = unknown> {
|
|
20
35
|
id: string
|
|
21
36
|
|
|
22
|
-
editor:
|
|
37
|
+
editor: ExtendedEditor
|
|
23
38
|
|
|
24
39
|
component: any
|
|
25
40
|
|
|
26
41
|
element: Element
|
|
27
42
|
|
|
28
|
-
props:
|
|
43
|
+
props: Record<string, any>
|
|
29
44
|
|
|
30
45
|
reactElement: React.ReactNode
|
|
31
46
|
|
|
32
|
-
ref:
|
|
47
|
+
ref: R | null = null
|
|
33
48
|
|
|
34
|
-
constructor(component:
|
|
49
|
+
constructor(component: ComponentType<R, P>, {
|
|
50
|
+
editor,
|
|
51
|
+
props = {},
|
|
52
|
+
as = 'div',
|
|
53
|
+
className = '',
|
|
54
|
+
}: ReactRendererOptions) {
|
|
35
55
|
this.id = Math.floor(Math.random() * 0xFFFFFFFF).toString()
|
|
36
56
|
this.component = component
|
|
37
|
-
this.editor = editor
|
|
57
|
+
this.editor = editor as ExtendedEditor
|
|
38
58
|
this.props = props
|
|
39
59
|
this.element = document.createElement(as)
|
|
40
60
|
this.element.classList.add('react-renderer')
|
|
61
|
+
|
|
62
|
+
if (className) {
|
|
63
|
+
this.element.classList.add(...className.split(' '))
|
|
64
|
+
}
|
|
65
|
+
|
|
41
66
|
this.render()
|
|
42
67
|
}
|
|
43
68
|
|
|
@@ -45,25 +70,27 @@ export class ReactRenderer {
|
|
|
45
70
|
const Component = this.component
|
|
46
71
|
const props = this.props
|
|
47
72
|
|
|
48
|
-
if (isClassComponent(Component)) {
|
|
49
|
-
props.ref = (ref:
|
|
73
|
+
if (isClassComponent(Component) || isForwardRefComponent(Component)) {
|
|
74
|
+
props.ref = (ref: R) => {
|
|
50
75
|
this.ref = ref
|
|
51
76
|
}
|
|
52
77
|
}
|
|
53
78
|
|
|
54
79
|
this.reactElement = <Component {...props } />
|
|
55
80
|
|
|
56
|
-
|
|
57
|
-
this.editor
|
|
58
|
-
|
|
59
|
-
this.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
81
|
+
flushSync(() => {
|
|
82
|
+
if (this.editor?.contentComponent) {
|
|
83
|
+
this.editor.contentComponent.setState({
|
|
84
|
+
renderers: this.editor.contentComponent.state.renderers.set(
|
|
85
|
+
this.id,
|
|
86
|
+
this,
|
|
87
|
+
),
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
})
|
|
64
91
|
}
|
|
65
92
|
|
|
66
|
-
updateProps(props:
|
|
93
|
+
updateProps(props: Record<string, any> = {}): void {
|
|
67
94
|
this.props = {
|
|
68
95
|
...this.props,
|
|
69
96
|
...props,
|
|
@@ -73,14 +100,16 @@ export class ReactRenderer {
|
|
|
73
100
|
}
|
|
74
101
|
|
|
75
102
|
destroy(): void {
|
|
76
|
-
|
|
77
|
-
|
|
103
|
+
flushSync(() => {
|
|
104
|
+
if (this.editor?.contentComponent) {
|
|
105
|
+
const { renderers } = this.editor.contentComponent.state
|
|
78
106
|
|
|
79
|
-
|
|
107
|
+
renderers.delete(this.id)
|
|
80
108
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
109
|
+
this.editor.contentComponent.setState({
|
|
110
|
+
renderers,
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
})
|
|
85
114
|
}
|
|
86
115
|
}
|
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 './
|
|
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'
|