@tiptap/react 2.0.0-beta.11 → 2.0.0-beta.112
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/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 +2 -2
- package/dist/packages/react/src/NodeViewWrapper.d.ts +2 -2
- package/dist/packages/react/src/ReactNodeViewRenderer.d.ts +12 -6
- package/dist/packages/react/src/ReactRenderer.d.ts +13 -10
- 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 -1
- package/dist/tiptap-react.cjs.js +214 -147
- package/dist/tiptap-react.cjs.js.map +1 -1
- package/dist/tiptap-react.esm.js +213 -144
- package/dist/tiptap-react.esm.js.map +1 -1
- package/dist/tiptap-react.umd.js +216 -149
- package/dist/tiptap-react.umd.js.map +1 -1
- package/package.json +18 -10
- 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 -6
- package/src/NodeViewWrapper.tsx +12 -9
- package/src/ReactNodeViewRenderer.tsx +79 -29
- package/src/ReactRenderer.tsx +38 -14
- package/src/index.ts +6 -6
- package/src/useEditor.ts +11 -4
- package/src/useReactNodeView.ts +1 -2
- package/CHANGELOG.md +0 -96
- 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.112",
|
|
5
5
|
"homepage": "https://tiptap.dev",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"tiptap",
|
|
@@ -15,24 +15,32 @@
|
|
|
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
30
|
"@tiptap/core": "^2.0.0-beta.1",
|
|
26
|
-
"react": "^17.0.
|
|
27
|
-
"react-dom": "^17.0.
|
|
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": "^1.
|
|
35
|
+
"@tiptap/extension-bubble-menu": "^2.0.0-beta.59",
|
|
36
|
+
"@tiptap/extension-floating-menu": "^2.0.0-beta.54",
|
|
37
|
+
"prosemirror-view": "^1.23.6"
|
|
33
38
|
},
|
|
34
|
-
"
|
|
35
|
-
"
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/ueberdosis/tiptap",
|
|
42
|
+
"directory": "packages/react"
|
|
36
43
|
},
|
|
37
|
-
"
|
|
44
|
+
"sideEffects": false,
|
|
45
|
+
"gitHead": "591c0807a2ab5c34b4b7fe12c12511fe4f493ebd"
|
|
38
46
|
}
|
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,21 +1,25 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
+
|
|
2
3
|
import { useReactNodeView } from './useReactNodeView'
|
|
3
4
|
|
|
4
5
|
export interface NodeViewContentProps {
|
|
5
|
-
|
|
6
|
-
as
|
|
6
|
+
[key: string]: any,
|
|
7
|
+
as?: React.ElementType,
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export const NodeViewContent: React.FC<NodeViewContentProps> = props => {
|
|
10
|
-
const { isEditable } = useReactNodeView()
|
|
11
11
|
const Tag = props.as || 'div'
|
|
12
|
+
const { nodeViewContentRef } = useReactNodeView()
|
|
12
13
|
|
|
13
14
|
return (
|
|
14
15
|
<Tag
|
|
15
|
-
|
|
16
|
+
{...props}
|
|
17
|
+
ref={nodeViewContentRef}
|
|
16
18
|
data-node-view-content=""
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
style={{
|
|
20
|
+
...props.style,
|
|
21
|
+
whiteSpace: 'pre-wrap',
|
|
22
|
+
}}
|
|
19
23
|
/>
|
|
20
24
|
)
|
|
21
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
|
-
as
|
|
6
|
+
[key: string]: any,
|
|
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
|
+
...props.style,
|
|
22
|
+
whiteSpace: 'normal',
|
|
23
|
+
}}
|
|
24
|
+
/>
|
|
22
25
|
)
|
|
23
|
-
}
|
|
26
|
+
})
|
|
@@ -1,25 +1,36 @@
|
|
|
1
|
-
import React, { useState, useEffect } 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: ((props: {
|
|
18
|
+
oldNode: ProseMirrorNode,
|
|
19
|
+
oldDecorations: Decoration[],
|
|
20
|
+
newNode: ProseMirrorNode,
|
|
21
|
+
newDecorations: Decoration[],
|
|
22
|
+
updateProps: () => void,
|
|
23
|
+
}) => boolean) | null,
|
|
24
|
+
as?: string,
|
|
25
|
+
className?: string,
|
|
17
26
|
}
|
|
18
27
|
|
|
19
|
-
class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
|
|
28
|
+
class ReactNodeView extends NodeView<React.FunctionComponent, Editor, ReactNodeViewRendererOptions> {
|
|
20
29
|
|
|
21
30
|
renderer!: ReactRenderer
|
|
22
31
|
|
|
32
|
+
contentDOMElement!: HTMLElement | null
|
|
33
|
+
|
|
23
34
|
mount() {
|
|
24
35
|
const props: NodeViewProps = {
|
|
25
36
|
editor: this.editor,
|
|
@@ -29,6 +40,7 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
|
|
|
29
40
|
extension: this.extension,
|
|
30
41
|
getPos: () => this.getPos(),
|
|
31
42
|
updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
|
|
43
|
+
deleteNode: () => this.deleteNode(),
|
|
32
44
|
}
|
|
33
45
|
|
|
34
46
|
if (!(this.component as any).displayName) {
|
|
@@ -36,26 +48,24 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
|
|
|
36
48
|
return string.charAt(0).toUpperCase() + string.substring(1)
|
|
37
49
|
}
|
|
38
50
|
|
|
39
|
-
|
|
40
|
-
this.component.displayName = capitalizeFirstChar(this.extension.config.name)
|
|
51
|
+
this.component.displayName = capitalizeFirstChar(this.extension.name)
|
|
41
52
|
}
|
|
42
53
|
|
|
43
54
|
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
55
|
const Component = this.component
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
this.
|
|
56
|
+
const onDragStart = this.onDragStart.bind(this)
|
|
57
|
+
const nodeViewContentRef: ReactNodeViewContextProps['nodeViewContentRef'] = element => {
|
|
58
|
+
if (
|
|
59
|
+
element
|
|
60
|
+
&& this.contentDOMElement
|
|
61
|
+
&& element.firstChild !== this.contentDOMElement
|
|
62
|
+
) {
|
|
63
|
+
element.appendChild(this.contentDOMElement)
|
|
54
64
|
}
|
|
55
|
-
}
|
|
65
|
+
}
|
|
56
66
|
|
|
57
67
|
return (
|
|
58
|
-
<ReactNodeViewContext.Provider value={{ onDragStart,
|
|
68
|
+
<ReactNodeViewContext.Provider value={{ onDragStart, nodeViewContentRef }}>
|
|
59
69
|
<Component {...componentProps} />
|
|
60
70
|
</ReactNodeViewContext.Provider>
|
|
61
71
|
)
|
|
@@ -63,15 +73,39 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
|
|
|
63
73
|
|
|
64
74
|
ReactNodeViewProvider.displayName = 'ReactNodeView'
|
|
65
75
|
|
|
76
|
+
this.contentDOMElement = this.node.isLeaf
|
|
77
|
+
? null
|
|
78
|
+
: document.createElement(this.node.isInline ? 'span' : 'div')
|
|
79
|
+
|
|
80
|
+
if (this.contentDOMElement) {
|
|
81
|
+
// For some reason the whiteSpace prop is not inherited properly in Chrome and Safari
|
|
82
|
+
// With this fix it seems to work fine
|
|
83
|
+
// See: https://github.com/ueberdosis/tiptap/issues/1197
|
|
84
|
+
this.contentDOMElement.style.whiteSpace = 'inherit'
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let as = this.node.isInline ? 'span' : 'div'
|
|
88
|
+
|
|
89
|
+
if (this.options.as) {
|
|
90
|
+
as = this.options.as
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const { className = '' } = this.options
|
|
94
|
+
|
|
66
95
|
this.renderer = new ReactRenderer(ReactNodeViewProvider, {
|
|
67
96
|
editor: this.editor,
|
|
68
97
|
props,
|
|
98
|
+
as,
|
|
99
|
+
className: `node-${this.node.type.name} ${className}`.trim(),
|
|
69
100
|
})
|
|
70
101
|
}
|
|
71
102
|
|
|
72
103
|
get dom() {
|
|
73
|
-
if (
|
|
74
|
-
|
|
104
|
+
if (
|
|
105
|
+
this.renderer.element.firstElementChild
|
|
106
|
+
&& !this.renderer.element.firstElementChild?.hasAttribute('data-node-view-wrapper')
|
|
107
|
+
) {
|
|
108
|
+
throw Error('Please use the NodeViewWrapper component for your node view.')
|
|
75
109
|
}
|
|
76
110
|
|
|
77
111
|
return this.renderer.element
|
|
@@ -82,27 +116,42 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
|
|
|
82
116
|
return null
|
|
83
117
|
}
|
|
84
118
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return contentElement || this.dom
|
|
119
|
+
return this.contentDOMElement
|
|
88
120
|
}
|
|
89
121
|
|
|
90
122
|
update(node: ProseMirrorNode, decorations: Decoration[]) {
|
|
91
|
-
|
|
92
|
-
|
|
123
|
+
const updateProps = (props?: Record<string, any>) => {
|
|
124
|
+
this.renderer.updateProps(props)
|
|
93
125
|
}
|
|
94
126
|
|
|
95
127
|
if (node.type !== this.node.type) {
|
|
96
128
|
return false
|
|
97
129
|
}
|
|
98
130
|
|
|
131
|
+
if (typeof this.options.update === 'function') {
|
|
132
|
+
const oldNode = this.node
|
|
133
|
+
const oldDecorations = this.decorations
|
|
134
|
+
|
|
135
|
+
this.node = node
|
|
136
|
+
this.decorations = decorations
|
|
137
|
+
|
|
138
|
+
return this.options.update({
|
|
139
|
+
oldNode,
|
|
140
|
+
oldDecorations,
|
|
141
|
+
newNode: node,
|
|
142
|
+
newDecorations: decorations,
|
|
143
|
+
updateProps: () => updateProps({ node, decorations }),
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
99
147
|
if (node === this.node && this.decorations === decorations) {
|
|
100
148
|
return true
|
|
101
149
|
}
|
|
102
150
|
|
|
103
151
|
this.node = node
|
|
104
152
|
this.decorations = decorations
|
|
105
|
-
|
|
153
|
+
|
|
154
|
+
updateProps({ node, decorations })
|
|
106
155
|
|
|
107
156
|
return true
|
|
108
157
|
}
|
|
@@ -121,6 +170,7 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
|
|
|
121
170
|
|
|
122
171
|
destroy() {
|
|
123
172
|
this.renderer.destroy()
|
|
173
|
+
this.contentDOMElement = null
|
|
124
174
|
}
|
|
125
175
|
}
|
|
126
176
|
|
package/src/ReactRenderer.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { Editor } from '@tiptap/core'
|
|
1
2
|
import React from 'react'
|
|
2
|
-
|
|
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
|
-
as?: string,
|
|
15
22
|
editor: Editor,
|
|
16
|
-
props?:
|
|
23
|
+
props?: Record<string, any>,
|
|
24
|
+
as?: string,
|
|
25
|
+
className?: string,
|
|
17
26
|
}
|
|
18
27
|
|
|
19
|
-
|
|
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:
|
|
36
|
+
editor: ExtendedEditor
|
|
23
37
|
|
|
24
38
|
component: any
|
|
25
39
|
|
|
26
40
|
element: Element
|
|
27
41
|
|
|
28
|
-
props:
|
|
42
|
+
props: Record<string, any>
|
|
29
43
|
|
|
30
44
|
reactElement: React.ReactNode
|
|
31
45
|
|
|
32
|
-
ref:
|
|
46
|
+
ref: R | null = null
|
|
33
47
|
|
|
34
|
-
constructor(component:
|
|
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
|
-
this.element = document.createElement(
|
|
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,8 +69,8 @@ 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:
|
|
72
|
+
if (isClassComponent(Component) || isForwardRefComponent(Component)) {
|
|
73
|
+
props.ref = (ref: R) => {
|
|
50
74
|
this.ref = ref
|
|
51
75
|
}
|
|
52
76
|
}
|
|
@@ -63,7 +87,7 @@ export class ReactRenderer {
|
|
|
63
87
|
}
|
|
64
88
|
}
|
|
65
89
|
|
|
66
|
-
updateProps(props:
|
|
90
|
+
updateProps(props: Record<string, any> = {}): void {
|
|
67
91
|
this.props = {
|
|
68
92
|
...this.props,
|
|
69
93
|
...props,
|
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'
|
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,7 +9,7 @@ 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
|
|
|
@@ -17,12 +18,18 @@ export const useEditor = (options: Partial<EditorOptions> = {}) => {
|
|
|
17
18
|
|
|
18
19
|
setEditor(instance)
|
|
19
20
|
|
|
20
|
-
instance.on('transaction',
|
|
21
|
+
instance.on('transaction', () => {
|
|
22
|
+
requestAnimationFrame(() => {
|
|
23
|
+
requestAnimationFrame(() => {
|
|
24
|
+
forceUpdate()
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
})
|
|
21
28
|
|
|
22
29
|
return () => {
|
|
23
30
|
instance.destroy()
|
|
24
31
|
}
|
|
25
|
-
},
|
|
32
|
+
}, deps)
|
|
26
33
|
|
|
27
34
|
return editor
|
|
28
35
|
}
|
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
|
|