@tiptap/react 2.5.6 → 2.5.8
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 +46 -109
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +46 -109
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +46 -109
- package/dist/index.umd.js.map +1 -1
- package/dist/packages/core/src/helpers/isNodeEmpty.d.ts +7 -1
- package/package.json +7 -7
- package/src/useEditor.ts +56 -140
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.5.
|
|
4
|
+
"version": "2.5.8",
|
|
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.5.
|
|
33
|
-
"@tiptap/extension-floating-menu": "^2.5.
|
|
32
|
+
"@tiptap/extension-bubble-menu": "^2.5.8",
|
|
33
|
+
"@tiptap/extension-floating-menu": "^2.5.8",
|
|
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.5.
|
|
39
|
-
"@tiptap/pm": "^2.5.
|
|
38
|
+
"@tiptap/core": "^2.5.8",
|
|
39
|
+
"@tiptap/pm": "^2.5.8",
|
|
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.5.
|
|
47
|
-
"@tiptap/pm": "^2.5.
|
|
46
|
+
"@tiptap/core": "^2.5.8",
|
|
47
|
+
"@tiptap/pm": "^2.5.8",
|
|
48
48
|
"react": "^17.0.0 || ^18.0.0",
|
|
49
49
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
50
50
|
},
|
package/src/useEditor.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EditorOptions } from '@tiptap/core'
|
|
2
2
|
import {
|
|
3
|
-
DependencyList,
|
|
3
|
+
DependencyList, MutableRefObject,
|
|
4
|
+
useDebugValue, useEffect, useRef, useState,
|
|
4
5
|
} from 'react'
|
|
5
6
|
|
|
6
7
|
import { Editor } from './Editor.js'
|
|
@@ -29,6 +30,25 @@ export type UseEditorOptions = Partial<EditorOptions> & {
|
|
|
29
30
|
shouldRerenderOnTransaction?: boolean;
|
|
30
31
|
};
|
|
31
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Create a new editor instance. And attach event listeners.
|
|
35
|
+
*/
|
|
36
|
+
function createEditor(options: MutableRefObject<UseEditorOptions>): Editor {
|
|
37
|
+
const editor = new Editor(options.current)
|
|
38
|
+
|
|
39
|
+
editor.on('beforeCreate', (...args) => options.current.onBeforeCreate?.(...args))
|
|
40
|
+
editor.on('blur', (...args) => options.current.onBlur?.(...args))
|
|
41
|
+
editor.on('create', (...args) => options.current.onCreate?.(...args))
|
|
42
|
+
editor.on('destroy', (...args) => options.current.onDestroy?.(...args))
|
|
43
|
+
editor.on('focus', (...args) => options.current.onFocus?.(...args))
|
|
44
|
+
editor.on('selectionUpdate', (...args) => options.current.onSelectionUpdate?.(...args))
|
|
45
|
+
editor.on('transaction', (...args) => options.current.onTransaction?.(...args))
|
|
46
|
+
editor.on('update', (...args) => options.current.onUpdate?.(...args))
|
|
47
|
+
editor.on('contentError', (...args) => options.current.onContentError?.(...args))
|
|
48
|
+
|
|
49
|
+
return editor
|
|
50
|
+
}
|
|
51
|
+
|
|
32
52
|
/**
|
|
33
53
|
* This hook allows you to create an editor instance.
|
|
34
54
|
* @param options The editor options
|
|
@@ -57,7 +77,7 @@ export function useEditor(
|
|
|
57
77
|
options: UseEditorOptions = {},
|
|
58
78
|
deps: DependencyList = [],
|
|
59
79
|
): Editor | null {
|
|
60
|
-
const
|
|
80
|
+
const mostRecentOptions = useRef(options)
|
|
61
81
|
const [editor, setEditor] = useState(() => {
|
|
62
82
|
if (options.immediatelyRender === undefined) {
|
|
63
83
|
if (isSSR || isNext) {
|
|
@@ -77,7 +97,7 @@ export function useEditor(
|
|
|
77
97
|
}
|
|
78
98
|
|
|
79
99
|
// Default to immediately rendering when client-side rendering
|
|
80
|
-
return
|
|
100
|
+
return createEditor(mostRecentOptions)
|
|
81
101
|
}
|
|
82
102
|
|
|
83
103
|
if (options.immediatelyRender && isSSR && isDev) {
|
|
@@ -88,163 +108,59 @@ export function useEditor(
|
|
|
88
108
|
}
|
|
89
109
|
|
|
90
110
|
if (options.immediatelyRender) {
|
|
91
|
-
return
|
|
111
|
+
return createEditor(mostRecentOptions)
|
|
92
112
|
}
|
|
93
113
|
|
|
94
114
|
return null
|
|
95
115
|
})
|
|
116
|
+
const mostRecentEditor = useRef<Editor | null>(editor)
|
|
117
|
+
|
|
118
|
+
mostRecentEditor.current = editor
|
|
96
119
|
|
|
97
120
|
useDebugValue(editor)
|
|
98
121
|
|
|
99
122
|
// This effect will handle creating/updating the editor instance
|
|
100
123
|
useEffect(() => {
|
|
101
|
-
|
|
124
|
+
const destroyUnusedEditor = (editorInstance: Editor | null) => {
|
|
125
|
+
if (editorInstance) {
|
|
126
|
+
// We need to destroy the editor asynchronously to avoid memory leaks
|
|
127
|
+
// because the editor instance is still being used in the component.
|
|
128
|
+
|
|
129
|
+
setTimeout(() => {
|
|
130
|
+
// re-use the editor instance if it hasn't been replaced yet
|
|
131
|
+
// otherwise, asynchronously destroy the old editor instance
|
|
132
|
+
if (editorInstance !== mostRecentEditor.current && !editorInstance.isDestroyed) {
|
|
133
|
+
editorInstance.destroy()
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let editorInstance = mostRecentEditor.current
|
|
102
140
|
|
|
103
141
|
if (!editorInstance) {
|
|
104
|
-
editorInstance =
|
|
105
|
-
// instantiate the editor if it doesn't exist
|
|
106
|
-
// for ssr, this is the first time the editor is created
|
|
142
|
+
editorInstance = createEditor(mostRecentOptions)
|
|
107
143
|
setEditor(editorInstance)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
editorInstance = new Editor(options)
|
|
144
|
+
return () => destroyUnusedEditor(editorInstance)
|
|
145
|
+
}
|
|
111
146
|
|
|
112
|
-
|
|
113
|
-
} else {
|
|
147
|
+
if (!Array.isArray(deps) || deps.length === 0) {
|
|
114
148
|
// if the editor does exist & deps are empty, we don't need to re-initialize the editor
|
|
115
149
|
// we can fast-path to update the editor options on the existing instance
|
|
116
150
|
editorInstance.setOptions(options)
|
|
117
|
-
}
|
|
118
|
-
}, deps)
|
|
119
151
|
|
|
120
|
-
|
|
121
|
-
onBeforeCreate,
|
|
122
|
-
onBlur,
|
|
123
|
-
onCreate,
|
|
124
|
-
onDestroy,
|
|
125
|
-
onFocus,
|
|
126
|
-
onSelectionUpdate,
|
|
127
|
-
onTransaction,
|
|
128
|
-
onUpdate,
|
|
129
|
-
onContentError,
|
|
130
|
-
} = options
|
|
131
|
-
|
|
132
|
-
const onBeforeCreateRef = useRef(onBeforeCreate)
|
|
133
|
-
const onBlurRef = useRef(onBlur)
|
|
134
|
-
const onCreateRef = useRef(onCreate)
|
|
135
|
-
const onDestroyRef = useRef(onDestroy)
|
|
136
|
-
const onFocusRef = useRef(onFocus)
|
|
137
|
-
const onSelectionUpdateRef = useRef(onSelectionUpdate)
|
|
138
|
-
const onTransactionRef = useRef(onTransaction)
|
|
139
|
-
const onUpdateRef = useRef(onUpdate)
|
|
140
|
-
const onContentErrorRef = useRef(onContentError)
|
|
141
|
-
|
|
142
|
-
// This effect will handle updating the editor instance
|
|
143
|
-
// when the event handlers change.
|
|
144
|
-
useEffect(() => {
|
|
145
|
-
if (!editor) {
|
|
146
|
-
return
|
|
152
|
+
return () => destroyUnusedEditor(editorInstance)
|
|
147
153
|
}
|
|
148
154
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
onBeforeCreateRef.current = onBeforeCreate
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (onBlur) {
|
|
157
|
-
editor.off('blur', onBlurRef.current)
|
|
158
|
-
editor.on('blur', onBlur)
|
|
159
|
-
|
|
160
|
-
onBlurRef.current = onBlur
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (onCreate) {
|
|
164
|
-
editor.off('create', onCreateRef.current)
|
|
165
|
-
editor.on('create', onCreate)
|
|
166
|
-
|
|
167
|
-
onCreateRef.current = onCreate
|
|
168
|
-
}
|
|
155
|
+
// We need to destroy the editor instance and re-initialize it
|
|
156
|
+
// when the deps array changes
|
|
157
|
+
editorInstance.destroy()
|
|
169
158
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (onFocus) {
|
|
178
|
-
editor.off('focus', onFocusRef.current)
|
|
179
|
-
editor.on('focus', onFocus)
|
|
180
|
-
|
|
181
|
-
onFocusRef.current = onFocus
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (onSelectionUpdate) {
|
|
185
|
-
editor.off('selectionUpdate', onSelectionUpdateRef.current)
|
|
186
|
-
editor.on('selectionUpdate', onSelectionUpdate)
|
|
187
|
-
|
|
188
|
-
onSelectionUpdateRef.current = onSelectionUpdate
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (onTransaction) {
|
|
192
|
-
editor.off('transaction', onTransactionRef.current)
|
|
193
|
-
editor.on('transaction', onTransaction)
|
|
194
|
-
|
|
195
|
-
onTransactionRef.current = onTransaction
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (onUpdate) {
|
|
199
|
-
editor.off('update', onUpdateRef.current)
|
|
200
|
-
editor.on('update', onUpdate)
|
|
201
|
-
|
|
202
|
-
onUpdateRef.current = onUpdate
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if (onContentError) {
|
|
206
|
-
editor.off('contentError', onContentErrorRef.current)
|
|
207
|
-
editor.on('contentError', onContentError)
|
|
208
|
-
|
|
209
|
-
onContentErrorRef.current = onContentError
|
|
210
|
-
}
|
|
211
|
-
}, [
|
|
212
|
-
onBeforeCreate,
|
|
213
|
-
onBlur,
|
|
214
|
-
onCreate,
|
|
215
|
-
onDestroy,
|
|
216
|
-
onFocus,
|
|
217
|
-
onSelectionUpdate,
|
|
218
|
-
onTransaction,
|
|
219
|
-
onUpdate,
|
|
220
|
-
onContentError,
|
|
221
|
-
editor,
|
|
222
|
-
])
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Destroy the editor instance when the component completely unmounts
|
|
226
|
-
* As opposed to the cleanup function in the effect above, this will
|
|
227
|
-
* only be called when the component is removed from the DOM, since it has no deps.
|
|
228
|
-
* */
|
|
229
|
-
useEffect(() => {
|
|
230
|
-
isMounted.current = true
|
|
231
|
-
return () => {
|
|
232
|
-
isMounted.current = false
|
|
233
|
-
if (editor) {
|
|
234
|
-
// We need to destroy the editor asynchronously to avoid memory leaks
|
|
235
|
-
// because the editor instance is still being used in the component.
|
|
236
|
-
|
|
237
|
-
setTimeout(() => {
|
|
238
|
-
// re-use the editor instance if it hasn't been destroyed yet
|
|
239
|
-
// and the component is still mounted
|
|
240
|
-
// otherwise, asynchronously destroy the editor instance
|
|
241
|
-
if (!isMounted.current && !editor.isDestroyed) {
|
|
242
|
-
editor.destroy()
|
|
243
|
-
}
|
|
244
|
-
})
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}, [])
|
|
159
|
+
// the deps array is used to re-initialize the editor instance
|
|
160
|
+
editorInstance = createEditor(mostRecentOptions)
|
|
161
|
+
setEditor(editorInstance)
|
|
162
|
+
return () => destroyUnusedEditor(editorInstance)
|
|
163
|
+
}, deps)
|
|
248
164
|
|
|
249
165
|
// The default behavior is to re-render on each transaction
|
|
250
166
|
// This is legacy behavior that will be removed in future versions
|