@tiptap/react 2.5.9 → 2.6.1
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/dist/index.cjs +206 -156
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +205 -157
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +206 -156
- package/dist/index.umd.js.map +1 -1
- package/dist/packages/core/src/Editor.d.ts +4 -0
- package/dist/packages/core/src/Extension.d.ts +4 -3
- package/dist/packages/core/src/Mark.d.ts +4 -3
- package/dist/packages/core/src/Node.d.ts +4 -3
- package/dist/packages/core/src/commands/setMeta.d.ts +2 -1
- package/dist/packages/core/src/commands/splitListItem.d.ts +2 -1
- package/dist/packages/core/src/pasteRules/nodePasteRule.d.ts +2 -1
- package/dist/packages/react/src/Context.d.ts +1 -1
- package/dist/packages/react/src/Editor.d.ts +9 -8
- package/dist/packages/react/src/EditorContent.d.ts +5 -9
- package/dist/packages/react/src/ReactRenderer.d.ts +1 -2
- package/dist/packages/react/src/index.d.ts +1 -1
- package/dist/packages/react/src/useEditor.d.ts +1 -2
- package/dist/packages/react/src/useEditorState.d.ts +1 -1
- package/package.json +7 -7
- package/src/Context.tsx +1 -1
- package/src/Editor.ts +7 -8
- package/src/EditorContent.tsx +98 -50
- package/src/NodeViewContent.tsx +1 -0
- package/src/NodeViewWrapper.tsx +1 -0
- package/src/ReactNodeViewRenderer.tsx +17 -18
- package/src/ReactRenderer.tsx +19 -7
- package/src/index.ts +1 -1
- package/src/useEditor.ts +15 -15
- package/src/useEditorState.ts +1 -2
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { Editor
|
|
2
|
-
import
|
|
3
|
-
import { EditorContentProps, EditorContentState } from './EditorContent.js';
|
|
1
|
+
import { Editor } from '@tiptap/core';
|
|
2
|
+
import { ReactPortal } from 'react';
|
|
4
3
|
import { ReactRenderer } from './ReactRenderer.js';
|
|
5
|
-
type
|
|
4
|
+
export type EditorWithContentComponent = Editor & {
|
|
5
|
+
contentComponent: ContentComponent | null;
|
|
6
|
+
};
|
|
7
|
+
export type ContentComponent = {
|
|
6
8
|
setRenderer(id: string, renderer: ReactRenderer): void;
|
|
7
9
|
removeRenderer(id: string): void;
|
|
10
|
+
subscribe: (callback: () => void) => () => void;
|
|
11
|
+
getSnapshot: () => Record<string, ReactPortal>;
|
|
12
|
+
getServerSnapshot: () => Record<string, ReactPortal>;
|
|
8
13
|
};
|
|
9
|
-
export declare class Editor extends CoreEditor {
|
|
10
|
-
contentComponent: ContentComponent | null;
|
|
11
|
-
}
|
|
12
|
-
export {};
|
|
@@ -1,23 +1,19 @@
|
|
|
1
|
+
import { Editor } from '@tiptap/core';
|
|
1
2
|
import React, { ForwardedRef, HTMLProps } from 'react';
|
|
2
|
-
import { Editor } from './Editor.js';
|
|
3
|
-
import { ReactRenderer } from './ReactRenderer.js';
|
|
4
3
|
export interface EditorContentProps extends HTMLProps<HTMLDivElement> {
|
|
5
4
|
editor: Editor | null;
|
|
6
5
|
innerRef?: ForwardedRef<HTMLDivElement | null>;
|
|
7
6
|
}
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
export declare class PureEditorContent extends React.Component<EditorContentProps, EditorContentState> {
|
|
7
|
+
export declare class PureEditorContent extends React.Component<EditorContentProps, {
|
|
8
|
+
hasContentComponentInitialized: boolean;
|
|
9
|
+
}> {
|
|
12
10
|
editorContentRef: React.RefObject<any>;
|
|
13
11
|
initialized: boolean;
|
|
12
|
+
unsubscribeToContentComponent?: () => void;
|
|
14
13
|
constructor(props: EditorContentProps);
|
|
15
14
|
componentDidMount(): void;
|
|
16
15
|
componentDidUpdate(): void;
|
|
17
16
|
init(): void;
|
|
18
|
-
maybeFlushSync(fn: () => void): void;
|
|
19
|
-
setRenderer(id: string, renderer: ReactRenderer): void;
|
|
20
|
-
removeRenderer(id: string): void;
|
|
21
17
|
componentWillUnmount(): void;
|
|
22
18
|
render(): React.JSX.Element;
|
|
23
19
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Editor } from '@tiptap/core';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { Editor as ExtendedEditor } from './Editor.js';
|
|
4
3
|
export interface ReactRendererOptions {
|
|
5
4
|
/**
|
|
6
5
|
* The editor instance.
|
|
@@ -48,7 +47,7 @@ type ComponentType<R, P> = React.ComponentClass<P> | React.FunctionComponent<P>
|
|
|
48
47
|
*/
|
|
49
48
|
export declare class ReactRenderer<R = unknown, P = unknown> {
|
|
50
49
|
id: string;
|
|
51
|
-
editor:
|
|
50
|
+
editor: Editor;
|
|
52
51
|
component: any;
|
|
53
52
|
element: Element;
|
|
54
53
|
props: Record<string, any>;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export * from './BubbleMenu.js';
|
|
2
2
|
export * from './Context.js';
|
|
3
|
-
export { Editor } from './Editor.js';
|
|
4
3
|
export * from './EditorContent.js';
|
|
5
4
|
export * from './FloatingMenu.js';
|
|
6
5
|
export * from './NodeViewContent.js';
|
|
@@ -10,4 +9,5 @@ export * from './ReactRenderer.js';
|
|
|
10
9
|
export * from './useEditor.js';
|
|
11
10
|
export * from './useEditorState.js';
|
|
12
11
|
export * from './useReactNodeView.js';
|
|
12
|
+
export { Editor } from '@tiptap/core';
|
|
13
13
|
export * from '@tiptap/core';
|
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.
|
|
4
|
+
"version": "2.6.1",
|
|
5
5
|
"homepage": "https://tiptap.dev",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"tiptap",
|
|
@@ -29,22 +29,22 @@
|
|
|
29
29
|
"dist"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@tiptap/extension-bubble-menu": "^2.
|
|
33
|
-
"@tiptap/extension-floating-menu": "^2.
|
|
32
|
+
"@tiptap/extension-bubble-menu": "^2.6.1",
|
|
33
|
+
"@tiptap/extension-floating-menu": "^2.6.1",
|
|
34
34
|
"@types/use-sync-external-store": "^0.0.6",
|
|
35
35
|
"use-sync-external-store": "^1.2.2"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@tiptap/core": "^2.
|
|
39
|
-
"@tiptap/pm": "^2.
|
|
38
|
+
"@tiptap/core": "^2.6.1",
|
|
39
|
+
"@tiptap/pm": "^2.6.1",
|
|
40
40
|
"@types/react": "^18.2.14",
|
|
41
41
|
"@types/react-dom": "^18.2.6",
|
|
42
42
|
"react": "^18.0.0",
|
|
43
43
|
"react-dom": "^18.0.0"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"@tiptap/core": "^2.
|
|
47
|
-
"@tiptap/pm": "^2.
|
|
46
|
+
"@tiptap/core": "^2.6.1",
|
|
47
|
+
"@tiptap/pm": "^2.6.1",
|
|
48
48
|
"react": "^17.0.0 || ^18.0.0",
|
|
49
49
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
50
50
|
},
|
package/src/Context.tsx
CHANGED
package/src/Editor.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { Editor
|
|
2
|
-
import
|
|
1
|
+
import { Editor } from '@tiptap/core'
|
|
2
|
+
import { ReactPortal } from 'react'
|
|
3
3
|
|
|
4
|
-
import { EditorContentProps, EditorContentState } from './EditorContent.js'
|
|
5
4
|
import { ReactRenderer } from './ReactRenderer.js'
|
|
6
5
|
|
|
7
|
-
type
|
|
6
|
+
export type EditorWithContentComponent = Editor & { contentComponent: ContentComponent | null }
|
|
7
|
+
export type ContentComponent = {
|
|
8
8
|
setRenderer(id: string, renderer: ReactRenderer): void;
|
|
9
9
|
removeRenderer(id: string): void;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
public contentComponent: ContentComponent | null = null
|
|
10
|
+
subscribe: (callback: () => void) => () => void;
|
|
11
|
+
getSnapshot: () => Record<string, ReactPortal>;
|
|
12
|
+
getServerSnapshot: () => Record<string, ReactPortal>;
|
|
14
13
|
}
|
package/src/EditorContent.tsx
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { Editor } from '@tiptap/core'
|
|
1
2
|
import React, {
|
|
2
3
|
ForwardedRef, forwardRef, HTMLProps, LegacyRef, MutableRefObject,
|
|
3
4
|
} from 'react'
|
|
4
|
-
import ReactDOM
|
|
5
|
+
import ReactDOM from 'react-dom'
|
|
6
|
+
import { useSyncExternalStore } from 'use-sync-external-store/shim'
|
|
5
7
|
|
|
6
|
-
import {
|
|
8
|
+
import { ContentComponent, EditorWithContentComponent } from './Editor.js'
|
|
7
9
|
import { ReactRenderer } from './ReactRenderer.js'
|
|
8
10
|
|
|
9
11
|
const mergeRefs = <T extends HTMLDivElement>(
|
|
@@ -20,12 +22,23 @@ const mergeRefs = <T extends HTMLDivElement>(
|
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
/**
|
|
26
|
+
* This component renders all of the editor's node views.
|
|
27
|
+
*/
|
|
28
|
+
const Portals: React.FC<{ contentComponent: ContentComponent }> = ({
|
|
29
|
+
contentComponent,
|
|
30
|
+
}) => {
|
|
31
|
+
// For performance reasons, we render the node view portals on state changes only
|
|
32
|
+
const renderers = useSyncExternalStore(
|
|
33
|
+
contentComponent.subscribe,
|
|
34
|
+
contentComponent.getSnapshot,
|
|
35
|
+
contentComponent.getServerSnapshot,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
// This allows us to directly render the portals without any additional wrapper
|
|
24
39
|
return (
|
|
25
40
|
<>
|
|
26
|
-
{Object.
|
|
27
|
-
return ReactDOM.createPortal(renderer.reactElement, renderer.element, key)
|
|
28
|
-
})}
|
|
41
|
+
{Object.values(renderers)}
|
|
29
42
|
</>
|
|
30
43
|
)
|
|
31
44
|
}
|
|
@@ -35,22 +48,67 @@ export interface EditorContentProps extends HTMLProps<HTMLDivElement> {
|
|
|
35
48
|
innerRef?: ForwardedRef<HTMLDivElement | null>;
|
|
36
49
|
}
|
|
37
50
|
|
|
38
|
-
|
|
39
|
-
|
|
51
|
+
function getInstance(): ContentComponent {
|
|
52
|
+
const subscribers = new Set<() => void>()
|
|
53
|
+
let renderers: Record<string, React.ReactPortal> = {}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
/**
|
|
57
|
+
* Subscribe to the editor instance's changes.
|
|
58
|
+
*/
|
|
59
|
+
subscribe(callback: () => void) {
|
|
60
|
+
subscribers.add(callback)
|
|
61
|
+
return () => {
|
|
62
|
+
subscribers.delete(callback)
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
getSnapshot() {
|
|
66
|
+
return renderers
|
|
67
|
+
},
|
|
68
|
+
getServerSnapshot() {
|
|
69
|
+
return renderers
|
|
70
|
+
},
|
|
71
|
+
/**
|
|
72
|
+
* Adds a new NodeView Renderer to the editor.
|
|
73
|
+
*/
|
|
74
|
+
setRenderer(id: string, renderer: ReactRenderer) {
|
|
75
|
+
renderers = {
|
|
76
|
+
...renderers,
|
|
77
|
+
[id]: ReactDOM.createPortal(renderer.reactElement, renderer.element, id),
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
subscribers.forEach(subscriber => subscriber())
|
|
81
|
+
},
|
|
82
|
+
/**
|
|
83
|
+
* Removes a NodeView Renderer from the editor.
|
|
84
|
+
*/
|
|
85
|
+
removeRenderer(id: string) {
|
|
86
|
+
const nextRenderers = { ...renderers }
|
|
87
|
+
|
|
88
|
+
delete nextRenderers[id]
|
|
89
|
+
renderers = nextRenderers
|
|
90
|
+
subscribers.forEach(subscriber => subscriber())
|
|
91
|
+
},
|
|
92
|
+
}
|
|
40
93
|
}
|
|
41
94
|
|
|
42
|
-
export class PureEditorContent extends React.Component<
|
|
95
|
+
export class PureEditorContent extends React.Component<
|
|
96
|
+
EditorContentProps,
|
|
97
|
+
{ hasContentComponentInitialized: boolean }
|
|
98
|
+
> {
|
|
43
99
|
editorContentRef: React.RefObject<any>
|
|
44
100
|
|
|
45
101
|
initialized: boolean
|
|
46
102
|
|
|
103
|
+
unsubscribeToContentComponent?: () => void
|
|
104
|
+
|
|
47
105
|
constructor(props: EditorContentProps) {
|
|
48
106
|
super(props)
|
|
49
107
|
this.editorContentRef = React.createRef()
|
|
50
108
|
this.initialized = false
|
|
51
109
|
|
|
52
110
|
this.state = {
|
|
53
|
-
|
|
111
|
+
hasContentComponentInitialized: Boolean((props.editor as EditorWithContentComponent).contentComponent),
|
|
54
112
|
}
|
|
55
113
|
}
|
|
56
114
|
|
|
@@ -63,7 +121,7 @@ export class PureEditorContent extends React.Component<EditorContentProps, Edito
|
|
|
63
121
|
}
|
|
64
122
|
|
|
65
123
|
init() {
|
|
66
|
-
const
|
|
124
|
+
const editor = this.props.editor as EditorWithContentComponent
|
|
67
125
|
|
|
68
126
|
if (editor && !editor.isDestroyed && editor.options.element) {
|
|
69
127
|
if (editor.contentComponent) {
|
|
@@ -78,7 +136,27 @@ export class PureEditorContent extends React.Component<EditorContentProps, Edito
|
|
|
78
136
|
element,
|
|
79
137
|
})
|
|
80
138
|
|
|
81
|
-
editor.contentComponent =
|
|
139
|
+
editor.contentComponent = getInstance()
|
|
140
|
+
|
|
141
|
+
// Has the content component been initialized?
|
|
142
|
+
if (!this.state.hasContentComponentInitialized) {
|
|
143
|
+
// Subscribe to the content component
|
|
144
|
+
this.unsubscribeToContentComponent = editor.contentComponent.subscribe(() => {
|
|
145
|
+
this.setState(prevState => {
|
|
146
|
+
if (!prevState.hasContentComponentInitialized) {
|
|
147
|
+
return {
|
|
148
|
+
hasContentComponentInitialized: true,
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return prevState
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
// Unsubscribe to previous content component
|
|
155
|
+
if (this.unsubscribeToContentComponent) {
|
|
156
|
+
this.unsubscribeToContentComponent()
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
}
|
|
82
160
|
|
|
83
161
|
editor.createNodeViews()
|
|
84
162
|
|
|
@@ -86,43 +164,8 @@ export class PureEditorContent extends React.Component<EditorContentProps, Edito
|
|
|
86
164
|
}
|
|
87
165
|
}
|
|
88
166
|
|
|
89
|
-
maybeFlushSync(fn: () => void) {
|
|
90
|
-
// Avoid calling flushSync until the editor is initialized.
|
|
91
|
-
// Initialization happens during the componentDidMount or componentDidUpdate
|
|
92
|
-
// lifecycle methods, and React doesn't allow calling flushSync from inside
|
|
93
|
-
// a lifecycle method.
|
|
94
|
-
if (this.initialized) {
|
|
95
|
-
flushSync(fn)
|
|
96
|
-
} else {
|
|
97
|
-
fn()
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
setRenderer(id: string, renderer: ReactRenderer) {
|
|
102
|
-
this.maybeFlushSync(() => {
|
|
103
|
-
this.setState(({ renderers }) => ({
|
|
104
|
-
renderers: {
|
|
105
|
-
...renderers,
|
|
106
|
-
[id]: renderer,
|
|
107
|
-
},
|
|
108
|
-
}))
|
|
109
|
-
})
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
removeRenderer(id: string) {
|
|
113
|
-
this.maybeFlushSync(() => {
|
|
114
|
-
this.setState(({ renderers }) => {
|
|
115
|
-
const nextRenderers = { ...renderers }
|
|
116
|
-
|
|
117
|
-
delete nextRenderers[id]
|
|
118
|
-
|
|
119
|
-
return { renderers: nextRenderers }
|
|
120
|
-
})
|
|
121
|
-
})
|
|
122
|
-
}
|
|
123
|
-
|
|
124
167
|
componentWillUnmount() {
|
|
125
|
-
const
|
|
168
|
+
const editor = this.props.editor as EditorWithContentComponent
|
|
126
169
|
|
|
127
170
|
if (!editor) {
|
|
128
171
|
return
|
|
@@ -136,6 +179,10 @@ export class PureEditorContent extends React.Component<EditorContentProps, Edito
|
|
|
136
179
|
})
|
|
137
180
|
}
|
|
138
181
|
|
|
182
|
+
if (this.unsubscribeToContentComponent) {
|
|
183
|
+
this.unsubscribeToContentComponent()
|
|
184
|
+
}
|
|
185
|
+
|
|
139
186
|
editor.contentComponent = null
|
|
140
187
|
|
|
141
188
|
if (!editor.options.element.firstChild) {
|
|
@@ -158,7 +205,7 @@ export class PureEditorContent extends React.Component<EditorContentProps, Edito
|
|
|
158
205
|
<>
|
|
159
206
|
<div ref={mergeRefs(innerRef, this.editorContentRef)} {...rest} />
|
|
160
207
|
{/* @ts-ignore */}
|
|
161
|
-
<Portals
|
|
208
|
+
{editor?.contentComponent && <Portals contentComponent={editor.contentComponent} />}
|
|
162
209
|
</>
|
|
163
210
|
)
|
|
164
211
|
}
|
|
@@ -168,7 +215,8 @@ export class PureEditorContent extends React.Component<EditorContentProps, Edito
|
|
|
168
215
|
const EditorContentWithKey = forwardRef<HTMLDivElement, EditorContentProps>(
|
|
169
216
|
(props: Omit<EditorContentProps, 'innerRef'>, ref) => {
|
|
170
217
|
const key = React.useMemo(() => {
|
|
171
|
-
return Math.floor(Math.random() *
|
|
218
|
+
return Math.floor(Math.random() * 0xffffffff).toString()
|
|
219
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
172
220
|
}, [props.editor])
|
|
173
221
|
|
|
174
222
|
// Can't use JSX here because it conflicts with the type definition of Vue's JSX, so use createElement
|
package/src/NodeViewContent.tsx
CHANGED
package/src/NodeViewWrapper.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DecorationWithType,
|
|
3
|
+
Editor,
|
|
3
4
|
NodeView,
|
|
4
5
|
NodeViewProps,
|
|
5
6
|
NodeViewRenderer,
|
|
@@ -10,7 +11,7 @@ import { Node as ProseMirrorNode } from '@tiptap/pm/model'
|
|
|
10
11
|
import { Decoration, NodeView as ProseMirrorNodeView } from '@tiptap/pm/view'
|
|
11
12
|
import React from 'react'
|
|
12
13
|
|
|
13
|
-
import {
|
|
14
|
+
import { EditorWithContentComponent } from './Editor.js'
|
|
14
15
|
import { ReactRenderer } from './ReactRenderer.js'
|
|
15
16
|
import { ReactNodeViewContext, ReactNodeViewContextProps } from './useReactNodeView.js'
|
|
16
17
|
|
|
@@ -58,25 +59,23 @@ class ReactNodeView extends NodeView<
|
|
|
58
59
|
this.component.displayName = capitalizeFirstChar(this.extension.name)
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (element && this.contentDOMElement && element.firstChild !== this.contentDOMElement) {
|
|
66
|
-
element.appendChild(this.contentDOMElement)
|
|
67
|
-
}
|
|
62
|
+
const onDragStart = this.onDragStart.bind(this)
|
|
63
|
+
const nodeViewContentRef: ReactNodeViewContextProps['nodeViewContentRef'] = element => {
|
|
64
|
+
if (element && this.contentDOMElement && element.firstChild !== this.contentDOMElement) {
|
|
65
|
+
element.appendChild(this.contentDOMElement)
|
|
68
66
|
}
|
|
69
|
-
|
|
67
|
+
}
|
|
68
|
+
const context = { onDragStart, nodeViewContentRef }
|
|
69
|
+
const Component = this.component
|
|
70
|
+
// For performance reasons, we memoize the provider component
|
|
71
|
+
// And all of the things it requires are declared outside of the component, so it doesn't need to re-render
|
|
72
|
+
const ReactNodeViewProvider: React.FunctionComponent = React.memo(componentProps => {
|
|
70
73
|
return (
|
|
71
|
-
|
|
72
|
-
{
|
|
73
|
-
|
|
74
|
-
{/* @ts-ignore */}
|
|
75
|
-
<Component {...componentProps} />
|
|
76
|
-
</ReactNodeViewContext.Provider>
|
|
77
|
-
</>
|
|
74
|
+
<ReactNodeViewContext.Provider value={context}>
|
|
75
|
+
{React.createElement(Component, componentProps)}
|
|
76
|
+
</ReactNodeViewContext.Provider>
|
|
78
77
|
)
|
|
79
|
-
}
|
|
78
|
+
})
|
|
80
79
|
|
|
81
80
|
ReactNodeViewProvider.displayName = 'ReactNodeView'
|
|
82
81
|
|
|
@@ -218,7 +217,7 @@ export function ReactNodeViewRenderer(
|
|
|
218
217
|
// try to get the parent component
|
|
219
218
|
// this is important for vue devtools to show the component hierarchy correctly
|
|
220
219
|
// maybe it’s `undefined` because <editor-content> isn’t rendered yet
|
|
221
|
-
if (!(props.editor as
|
|
220
|
+
if (!(props.editor as EditorWithContentComponent).contentComponent) {
|
|
222
221
|
return {}
|
|
223
222
|
}
|
|
224
223
|
|
package/src/ReactRenderer.tsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Editor } from '@tiptap/core'
|
|
2
2
|
import React from 'react'
|
|
3
|
+
import { flushSync } from 'react-dom'
|
|
3
4
|
|
|
4
|
-
import {
|
|
5
|
+
import { EditorWithContentComponent } from './Editor.js'
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Check if a component is a class component.
|
|
@@ -85,7 +86,7 @@ type ComponentType<R, P> =
|
|
|
85
86
|
export class ReactRenderer<R = unknown, P = unknown> {
|
|
86
87
|
id: string
|
|
87
88
|
|
|
88
|
-
editor:
|
|
89
|
+
editor: Editor
|
|
89
90
|
|
|
90
91
|
component: any
|
|
91
92
|
|
|
@@ -106,7 +107,7 @@ export class ReactRenderer<R = unknown, P = unknown> {
|
|
|
106
107
|
}: ReactRendererOptions) {
|
|
107
108
|
this.id = Math.floor(Math.random() * 0xFFFFFFFF).toString()
|
|
108
109
|
this.component = component
|
|
109
|
-
this.editor = editor as
|
|
110
|
+
this.editor = editor as EditorWithContentComponent
|
|
110
111
|
this.props = props
|
|
111
112
|
this.element = document.createElement(as)
|
|
112
113
|
this.element.classList.add('react-renderer')
|
|
@@ -121,12 +122,21 @@ export class ReactRenderer<R = unknown, P = unknown> {
|
|
|
121
122
|
})
|
|
122
123
|
}
|
|
123
124
|
|
|
124
|
-
this.
|
|
125
|
+
if (this.editor.isInitialized) {
|
|
126
|
+
// On first render, we need to flush the render synchronously
|
|
127
|
+
// Renders afterwards can be async, but this fixes a cursor positioning issue
|
|
128
|
+
flushSync(() => {
|
|
129
|
+
this.render()
|
|
130
|
+
})
|
|
131
|
+
} else {
|
|
132
|
+
this.render()
|
|
133
|
+
}
|
|
125
134
|
}
|
|
126
135
|
|
|
127
136
|
render(): void {
|
|
128
137
|
const Component = this.component
|
|
129
138
|
const props = this.props
|
|
139
|
+
const editor = this.editor as EditorWithContentComponent
|
|
130
140
|
|
|
131
141
|
if (isClassComponent(Component) || isForwardRefComponent(Component)) {
|
|
132
142
|
props.ref = (ref: R) => {
|
|
@@ -134,9 +144,9 @@ export class ReactRenderer<R = unknown, P = unknown> {
|
|
|
134
144
|
}
|
|
135
145
|
}
|
|
136
146
|
|
|
137
|
-
this.reactElement =
|
|
147
|
+
this.reactElement = React.createElement(Component, props)
|
|
138
148
|
|
|
139
|
-
|
|
149
|
+
editor?.contentComponent?.setRenderer(this.id, this)
|
|
140
150
|
}
|
|
141
151
|
|
|
142
152
|
updateProps(props: Record<string, any> = {}): void {
|
|
@@ -149,6 +159,8 @@ export class ReactRenderer<R = unknown, P = unknown> {
|
|
|
149
159
|
}
|
|
150
160
|
|
|
151
161
|
destroy(): void {
|
|
152
|
-
this.editor
|
|
162
|
+
const editor = this.editor as EditorWithContentComponent
|
|
163
|
+
|
|
164
|
+
editor?.contentComponent?.removeRenderer(this.id)
|
|
153
165
|
}
|
|
154
166
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export * from './BubbleMenu.js'
|
|
2
2
|
export * from './Context.js'
|
|
3
|
-
export { Editor } from './Editor.js'
|
|
4
3
|
export * from './EditorContent.js'
|
|
5
4
|
export * from './FloatingMenu.js'
|
|
6
5
|
export * from './NodeViewContent.js'
|
|
@@ -10,4 +9,5 @@ export * from './ReactRenderer.js'
|
|
|
10
9
|
export * from './useEditor.js'
|
|
11
10
|
export * from './useEditorState.js'
|
|
12
11
|
export * from './useReactNodeView.js'
|
|
12
|
+
export { Editor } from '@tiptap/core'
|
|
13
13
|
export * from '@tiptap/core'
|
package/src/useEditor.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EditorOptions } from '@tiptap/core'
|
|
1
|
+
import { type EditorOptions, Editor } from '@tiptap/core'
|
|
2
2
|
import {
|
|
3
3
|
DependencyList,
|
|
4
4
|
MutableRefObject,
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
} from 'react'
|
|
10
10
|
import { useSyncExternalStore } from 'use-sync-external-store/shim'
|
|
11
11
|
|
|
12
|
-
import { Editor } from './Editor.js'
|
|
13
12
|
import { useEditorState } from './useEditorState.js'
|
|
14
13
|
|
|
15
14
|
const isDev = process.env.NODE_ENV !== 'production'
|
|
@@ -137,18 +136,20 @@ class EditorInstanceManager {
|
|
|
137
136
|
* Create a new editor instance. And attach event listeners.
|
|
138
137
|
*/
|
|
139
138
|
private createEditor(): Editor {
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
139
|
+
const optionsToApply: Partial<EditorOptions> = {
|
|
140
|
+
...this.options.current,
|
|
141
|
+
// Always call the most recent version of the callback function by default
|
|
142
|
+
onBeforeCreate: (...args) => this.options.current.onBeforeCreate?.(...args),
|
|
143
|
+
onBlur: (...args) => this.options.current.onBlur?.(...args),
|
|
144
|
+
onCreate: (...args) => this.options.current.onCreate?.(...args),
|
|
145
|
+
onDestroy: (...args) => this.options.current.onDestroy?.(...args),
|
|
146
|
+
onFocus: (...args) => this.options.current.onFocus?.(...args),
|
|
147
|
+
onSelectionUpdate: (...args) => this.options.current.onSelectionUpdate?.(...args),
|
|
148
|
+
onTransaction: (...args) => this.options.current.onTransaction?.(...args),
|
|
149
|
+
onUpdate: (...args) => this.options.current.onUpdate?.(...args),
|
|
150
|
+
onContentError: (...args) => this.options.current.onContentError?.(...args),
|
|
151
|
+
}
|
|
152
|
+
const editor = new Editor(optionsToApply)
|
|
152
153
|
|
|
153
154
|
// no need to keep track of the event listeners, they will be removed when the editor is destroyed
|
|
154
155
|
|
|
@@ -216,7 +217,6 @@ class EditorInstanceManager {
|
|
|
216
217
|
* Recreate the editor instance if the dependencies have changed.
|
|
217
218
|
*/
|
|
218
219
|
private refreshEditorInstance(deps: DependencyList) {
|
|
219
|
-
|
|
220
220
|
if (this.editor && !this.editor.isDestroyed) {
|
|
221
221
|
// Editor instance already exists
|
|
222
222
|
if (this.previousDeps === null) {
|
package/src/useEditorState.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
+
import type { Editor } from '@tiptap/core'
|
|
1
2
|
import { useDebugValue, useEffect, useState } from 'react'
|
|
2
3
|
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector'
|
|
3
4
|
|
|
4
|
-
import type { Editor } from './Editor.js'
|
|
5
|
-
|
|
6
5
|
export type EditorStateSnapshot<TEditor extends Editor | null = Editor | null> = {
|
|
7
6
|
editor: TEditor;
|
|
8
7
|
transactionNumber: number;
|