@tiptap/react 2.5.7 → 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 -112
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +46 -112
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +46 -112
- package/dist/index.umd.js.map +1 -1
- package/package.json +7 -7
- package/src/useEditor.ts +56 -144
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,167 +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.
|
|
102
128
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
+
}
|
|
112
138
|
|
|
113
|
-
|
|
114
|
-
editorInstance = new Editor(options)
|
|
139
|
+
let editorInstance = mostRecentEditor.current
|
|
115
140
|
|
|
141
|
+
if (!editorInstance) {
|
|
142
|
+
editorInstance = createEditor(mostRecentOptions)
|
|
116
143
|
setEditor(editorInstance)
|
|
117
|
-
|
|
144
|
+
return () => destroyUnusedEditor(editorInstance)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!Array.isArray(deps) || deps.length === 0) {
|
|
118
148
|
// if the editor does exist & deps are empty, we don't need to re-initialize the editor
|
|
119
149
|
// we can fast-path to update the editor options on the existing instance
|
|
120
150
|
editorInstance.setOptions(options)
|
|
121
|
-
}
|
|
122
|
-
}, deps)
|
|
123
|
-
|
|
124
|
-
const {
|
|
125
|
-
onBeforeCreate,
|
|
126
|
-
onBlur,
|
|
127
|
-
onCreate,
|
|
128
|
-
onDestroy,
|
|
129
|
-
onFocus,
|
|
130
|
-
onSelectionUpdate,
|
|
131
|
-
onTransaction,
|
|
132
|
-
onUpdate,
|
|
133
|
-
onContentError,
|
|
134
|
-
} = options
|
|
135
|
-
|
|
136
|
-
const onBeforeCreateRef = useRef(onBeforeCreate)
|
|
137
|
-
const onBlurRef = useRef(onBlur)
|
|
138
|
-
const onCreateRef = useRef(onCreate)
|
|
139
|
-
const onDestroyRef = useRef(onDestroy)
|
|
140
|
-
const onFocusRef = useRef(onFocus)
|
|
141
|
-
const onSelectionUpdateRef = useRef(onSelectionUpdate)
|
|
142
|
-
const onTransactionRef = useRef(onTransaction)
|
|
143
|
-
const onUpdateRef = useRef(onUpdate)
|
|
144
|
-
const onContentErrorRef = useRef(onContentError)
|
|
145
|
-
|
|
146
|
-
// This effect will handle updating the editor instance
|
|
147
|
-
// when the event handlers change.
|
|
148
|
-
useEffect(() => {
|
|
149
|
-
if (!editor) {
|
|
150
|
-
return
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (onBeforeCreate) {
|
|
154
|
-
editor.off('beforeCreate', onBeforeCreateRef.current)
|
|
155
|
-
editor.on('beforeCreate', onBeforeCreate)
|
|
156
|
-
|
|
157
|
-
onBeforeCreateRef.current = onBeforeCreate
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (onBlur) {
|
|
161
|
-
editor.off('blur', onBlurRef.current)
|
|
162
|
-
editor.on('blur', onBlur)
|
|
163
|
-
|
|
164
|
-
onBlurRef.current = onBlur
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (onCreate) {
|
|
168
|
-
editor.off('create', onCreateRef.current)
|
|
169
|
-
editor.on('create', onCreate)
|
|
170
|
-
|
|
171
|
-
onCreateRef.current = onCreate
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (onDestroy) {
|
|
175
|
-
editor.off('destroy', onDestroyRef.current)
|
|
176
|
-
editor.on('destroy', onDestroy)
|
|
177
|
-
|
|
178
|
-
onDestroyRef.current = onDestroy
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (onFocus) {
|
|
182
|
-
editor.off('focus', onFocusRef.current)
|
|
183
|
-
editor.on('focus', onFocus)
|
|
184
151
|
|
|
185
|
-
|
|
152
|
+
return () => destroyUnusedEditor(editorInstance)
|
|
186
153
|
}
|
|
187
154
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
onSelectionUpdateRef.current = onSelectionUpdate
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (onTransaction) {
|
|
196
|
-
editor.off('transaction', onTransactionRef.current)
|
|
197
|
-
editor.on('transaction', onTransaction)
|
|
198
|
-
|
|
199
|
-
onTransactionRef.current = onTransaction
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (onUpdate) {
|
|
203
|
-
editor.off('update', onUpdateRef.current)
|
|
204
|
-
editor.on('update', onUpdate)
|
|
205
|
-
|
|
206
|
-
onUpdateRef.current = onUpdate
|
|
207
|
-
}
|
|
155
|
+
// We need to destroy the editor instance and re-initialize it
|
|
156
|
+
// when the deps array changes
|
|
157
|
+
editorInstance.destroy()
|
|
208
158
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
}, [
|
|
216
|
-
onBeforeCreate,
|
|
217
|
-
onBlur,
|
|
218
|
-
onCreate,
|
|
219
|
-
onDestroy,
|
|
220
|
-
onFocus,
|
|
221
|
-
onSelectionUpdate,
|
|
222
|
-
onTransaction,
|
|
223
|
-
onUpdate,
|
|
224
|
-
onContentError,
|
|
225
|
-
editor,
|
|
226
|
-
])
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Destroy the editor instance when the component completely unmounts
|
|
230
|
-
* As opposed to the cleanup function in the effect above, this will
|
|
231
|
-
* only be called when the component is removed from the DOM, since it has no deps.
|
|
232
|
-
* */
|
|
233
|
-
useEffect(() => {
|
|
234
|
-
isMounted.current = true
|
|
235
|
-
return () => {
|
|
236
|
-
isMounted.current = false
|
|
237
|
-
if (editor) {
|
|
238
|
-
// We need to destroy the editor asynchronously to avoid memory leaks
|
|
239
|
-
// because the editor instance is still being used in the component.
|
|
240
|
-
|
|
241
|
-
setTimeout(() => {
|
|
242
|
-
// re-use the editor instance if it hasn't been destroyed yet
|
|
243
|
-
// and the component is still mounted
|
|
244
|
-
// otherwise, asynchronously destroy the editor instance
|
|
245
|
-
if (!isMounted.current && !editor.isDestroyed) {
|
|
246
|
-
editor.destroy()
|
|
247
|
-
}
|
|
248
|
-
})
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}, [])
|
|
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)
|
|
252
164
|
|
|
253
165
|
// The default behavior is to re-render on each transaction
|
|
254
166
|
// This is legacy behavior that will be removed in future versions
|