@tiptap/react 3.22.0 → 3.22.2
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 +41 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -4
- package/dist/index.d.ts +24 -4
- package/dist/index.js +42 -28
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/Editor.ts +4 -1
- package/src/EditorContent.tsx +4 -35
- package/src/ReactNodeViewRenderer.tsx +60 -2
- package/src/ReactRenderer.tsx +5 -6
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiptap/react",
|
|
3
3
|
"description": "React components for tiptap",
|
|
4
|
-
"version": "3.22.
|
|
4
|
+
"version": "3.22.2",
|
|
5
5
|
"homepage": "https://tiptap.dev",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"tiptap",
|
|
@@ -48,20 +48,20 @@
|
|
|
48
48
|
"@types/react-dom": "^19.0.0",
|
|
49
49
|
"react": "^19.0.0",
|
|
50
50
|
"react-dom": "^19.0.0",
|
|
51
|
-
"@tiptap/core": "^3.22.
|
|
52
|
-
"@tiptap/pm": "^3.22.
|
|
51
|
+
"@tiptap/core": "^3.22.2",
|
|
52
|
+
"@tiptap/pm": "^3.22.2"
|
|
53
53
|
},
|
|
54
54
|
"optionalDependencies": {
|
|
55
|
-
"@tiptap/extension-
|
|
56
|
-
"@tiptap/extension-
|
|
55
|
+
"@tiptap/extension-floating-menu": "^3.22.2",
|
|
56
|
+
"@tiptap/extension-bubble-menu": "^3.22.2"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
60
60
|
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
61
61
|
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
62
62
|
"@types/react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
63
|
-
"@tiptap/core": "^3.22.
|
|
64
|
-
"@tiptap/pm": "^3.22.
|
|
63
|
+
"@tiptap/core": "^3.22.2",
|
|
64
|
+
"@tiptap/pm": "^3.22.2"
|
|
65
65
|
},
|
|
66
66
|
"repository": {
|
|
67
67
|
"type": "git",
|
package/src/Editor.ts
CHANGED
|
@@ -3,7 +3,10 @@ import type { ReactPortal } from 'react'
|
|
|
3
3
|
|
|
4
4
|
import type { ReactRenderer } from './ReactRenderer.js'
|
|
5
5
|
|
|
6
|
-
export type EditorWithContentComponent = Editor & {
|
|
6
|
+
export type EditorWithContentComponent = Editor & {
|
|
7
|
+
contentComponent?: ContentComponent | null
|
|
8
|
+
isEditorContentInitialized?: boolean
|
|
9
|
+
}
|
|
7
10
|
export type ContentComponent = {
|
|
8
11
|
setRenderer(id: string, renderer: ReactRenderer): void
|
|
9
12
|
removeRenderer(id: string): void
|
package/src/EditorContent.tsx
CHANGED
|
@@ -89,18 +89,9 @@ export class PureEditorContent extends React.Component<
|
|
|
89
89
|
> {
|
|
90
90
|
editorContentRef: React.RefObject<any>
|
|
91
91
|
|
|
92
|
-
initialized: boolean
|
|
93
|
-
|
|
94
|
-
unsubscribeToContentComponent?: () => void
|
|
95
|
-
|
|
96
92
|
constructor(props: EditorContentProps) {
|
|
97
93
|
super(props)
|
|
98
94
|
this.editorContentRef = React.createRef()
|
|
99
|
-
this.initialized = false
|
|
100
|
-
|
|
101
|
-
this.state = {
|
|
102
|
-
hasContentComponentInitialized: Boolean((props.editor as EditorWithContentComponent | null)?.contentComponent),
|
|
103
|
-
}
|
|
104
95
|
}
|
|
105
96
|
|
|
106
97
|
componentDidMount() {
|
|
@@ -129,29 +120,11 @@ export class PureEditorContent extends React.Component<
|
|
|
129
120
|
|
|
130
121
|
editor.contentComponent = getInstance()
|
|
131
122
|
|
|
132
|
-
// Has the content component been initialized?
|
|
133
|
-
if (!this.state.hasContentComponentInitialized) {
|
|
134
|
-
// Subscribe to the content component
|
|
135
|
-
this.unsubscribeToContentComponent = editor.contentComponent.subscribe(() => {
|
|
136
|
-
this.setState(prevState => {
|
|
137
|
-
if (!prevState.hasContentComponentInitialized) {
|
|
138
|
-
return {
|
|
139
|
-
hasContentComponentInitialized: true,
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
return prevState
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
// Unsubscribe to previous content component
|
|
146
|
-
if (this.unsubscribeToContentComponent) {
|
|
147
|
-
this.unsubscribeToContentComponent()
|
|
148
|
-
}
|
|
149
|
-
})
|
|
150
|
-
}
|
|
151
|
-
|
|
152
123
|
editor.createNodeViews()
|
|
153
124
|
|
|
154
|
-
|
|
125
|
+
editor.isEditorContentInitialized = true
|
|
126
|
+
|
|
127
|
+
this.forceUpdate()
|
|
155
128
|
}
|
|
156
129
|
}
|
|
157
130
|
|
|
@@ -162,7 +135,7 @@ export class PureEditorContent extends React.Component<
|
|
|
162
135
|
return
|
|
163
136
|
}
|
|
164
137
|
|
|
165
|
-
|
|
138
|
+
editor.isEditorContentInitialized = false
|
|
166
139
|
|
|
167
140
|
if (!editor.isDestroyed) {
|
|
168
141
|
editor.view.setProps({
|
|
@@ -170,10 +143,6 @@ export class PureEditorContent extends React.Component<
|
|
|
170
143
|
})
|
|
171
144
|
}
|
|
172
145
|
|
|
173
|
-
if (this.unsubscribeToContentComponent) {
|
|
174
|
-
this.unsubscribeToContentComponent()
|
|
175
|
-
}
|
|
176
|
-
|
|
177
146
|
editor.contentComponent = null
|
|
178
147
|
|
|
179
148
|
// try to reset the editor element
|
|
@@ -5,7 +5,7 @@ import type {
|
|
|
5
5
|
NodeViewRendererOptions,
|
|
6
6
|
NodeViewRendererProps,
|
|
7
7
|
} from '@tiptap/core'
|
|
8
|
-
import { getRenderedAttributes, NodeView } from '@tiptap/core'
|
|
8
|
+
import { cancelPositionCheck, getRenderedAttributes, NodeView, schedulePositionCheck } from '@tiptap/core'
|
|
9
9
|
import type { Node, Node as ProseMirrorNode } from '@tiptap/pm/model'
|
|
10
10
|
import type { Decoration, DecorationSource, NodeView as ProseMirrorNodeView } from '@tiptap/pm/view'
|
|
11
11
|
import type { ComponentType, NamedExoticComponent } from 'react'
|
|
@@ -72,6 +72,18 @@ export class ReactNodeView<
|
|
|
72
72
|
*/
|
|
73
73
|
selectionRafId: number | null = null
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* The last known position of this node view, used to detect position-only
|
|
77
|
+
* changes that don't produce a new node object reference.
|
|
78
|
+
*/
|
|
79
|
+
private currentPos: number | undefined
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Callback registered with the per-editor position-update registry.
|
|
83
|
+
* Stored so it can be unregistered in destroy().
|
|
84
|
+
*/
|
|
85
|
+
private positionCheckCallback: (() => void) | null = null
|
|
86
|
+
|
|
75
87
|
constructor(component: Component, props: NodeViewRendererProps, options?: Partial<Options>) {
|
|
76
88
|
super(component, props, options)
|
|
77
89
|
|
|
@@ -196,6 +208,26 @@ export class ReactNodeView<
|
|
|
196
208
|
|
|
197
209
|
this.editor.on('selectionUpdate', this.handleSelectionUpdate)
|
|
198
210
|
this.updateElementAttributes()
|
|
211
|
+
this.currentPos = this.getPos()
|
|
212
|
+
|
|
213
|
+
this.positionCheckCallback = () => {
|
|
214
|
+
const newPos = this.getPos()
|
|
215
|
+
|
|
216
|
+
if (typeof newPos !== 'number' || newPos === this.currentPos) {
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
this.currentPos = newPos
|
|
221
|
+
|
|
222
|
+
// Pass a fresh getPos reference so React's memo detects a prop change.
|
|
223
|
+
this.renderer.updateProps({ getPos: () => this.getPos() })
|
|
224
|
+
|
|
225
|
+
if (typeof this.options.attrs === 'function') {
|
|
226
|
+
this.updateElementAttributes()
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
schedulePositionCheck(this.editor, this.positionCheckCallback)
|
|
199
231
|
}
|
|
200
232
|
|
|
201
233
|
/**
|
|
@@ -238,7 +270,8 @@ export class ReactNodeView<
|
|
|
238
270
|
this.selectionRafId = requestAnimationFrame(() => {
|
|
239
271
|
this.selectionRafId = null
|
|
240
272
|
const { from, to } = this.editor.state.selection
|
|
241
|
-
|
|
273
|
+
// Avoid resolving getPos() after ProseMirror has detached this node view.
|
|
274
|
+
const pos = this.currentPos
|
|
242
275
|
if (typeof pos !== 'number') {
|
|
243
276
|
return
|
|
244
277
|
}
|
|
@@ -283,6 +316,7 @@ export class ReactNodeView<
|
|
|
283
316
|
this.node = node
|
|
284
317
|
this.decorations = decorations
|
|
285
318
|
this.innerDecorations = innerDecorations
|
|
319
|
+
this.currentPos = this.getPos()
|
|
286
320
|
|
|
287
321
|
return this.options.update({
|
|
288
322
|
oldNode,
|
|
@@ -296,13 +330,31 @@ export class ReactNodeView<
|
|
|
296
330
|
})
|
|
297
331
|
}
|
|
298
332
|
|
|
333
|
+
const newPos = this.getPos()
|
|
334
|
+
|
|
299
335
|
if (node === this.node && this.decorations === decorations && this.innerDecorations === innerDecorations) {
|
|
336
|
+
if (newPos === this.currentPos) {
|
|
337
|
+
return true
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Position changed without a content/decoration change — trigger re-render
|
|
341
|
+
// so the component receives an up-to-date value from getPos().
|
|
342
|
+
// Pass a fresh getPos reference so React's memo detects a prop change.
|
|
343
|
+
this.currentPos = newPos
|
|
344
|
+
rerenderComponent({
|
|
345
|
+
node,
|
|
346
|
+
decorations,
|
|
347
|
+
innerDecorations,
|
|
348
|
+
extension: this.extensionWithSyncedStorage,
|
|
349
|
+
getPos: () => this.getPos(),
|
|
350
|
+
})
|
|
300
351
|
return true
|
|
301
352
|
}
|
|
302
353
|
|
|
303
354
|
this.node = node
|
|
304
355
|
this.decorations = decorations
|
|
305
356
|
this.innerDecorations = innerDecorations
|
|
357
|
+
this.currentPos = newPos
|
|
306
358
|
|
|
307
359
|
rerenderComponent({ node, decorations, innerDecorations, extension: this.extensionWithSyncedStorage })
|
|
308
360
|
|
|
@@ -337,6 +389,12 @@ export class ReactNodeView<
|
|
|
337
389
|
destroy() {
|
|
338
390
|
this.renderer.destroy()
|
|
339
391
|
this.editor.off('selectionUpdate', this.handleSelectionUpdate)
|
|
392
|
+
|
|
393
|
+
if (this.positionCheckCallback) {
|
|
394
|
+
cancelPositionCheck(this.editor, this.positionCheckCallback)
|
|
395
|
+
this.positionCheckCallback = null
|
|
396
|
+
}
|
|
397
|
+
|
|
340
398
|
this.contentDOMElement = null
|
|
341
399
|
|
|
342
400
|
if (this.selectionRafId) {
|
package/src/ReactRenderer.tsx
CHANGED
|
@@ -147,7 +147,7 @@ type ComponentType<R, P> =
|
|
|
147
147
|
export class ReactRenderer<R = unknown, P extends Record<string, any> = object> {
|
|
148
148
|
id: string
|
|
149
149
|
|
|
150
|
-
editor:
|
|
150
|
+
editor: EditorWithContentComponent
|
|
151
151
|
|
|
152
152
|
component: any
|
|
153
153
|
|
|
@@ -173,7 +173,7 @@ export class ReactRenderer<R = unknown, P extends Record<string, any> = object>
|
|
|
173
173
|
) {
|
|
174
174
|
this.id = Math.floor(Math.random() * 0xffffffff).toString()
|
|
175
175
|
this.component = component
|
|
176
|
-
this.editor = editor
|
|
176
|
+
this.editor = editor
|
|
177
177
|
this.props = props as P
|
|
178
178
|
this.element = document.createElement(as)
|
|
179
179
|
this.element.classList.add('react-renderer')
|
|
@@ -182,10 +182,9 @@ export class ReactRenderer<R = unknown, P extends Record<string, any> = object>
|
|
|
182
182
|
this.element.classList.add(...className.split(' '))
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if (this.editor.isInitialized) {
|
|
185
|
+
if (this.editor.isEditorContentInitialized) {
|
|
186
|
+
// If EditorContent is mounted, flush synchronously to maintain cursor positioning consistency.
|
|
187
|
+
// Subsequent renders can be async without affecting cursor behavior.
|
|
189
188
|
flushSync(() => {
|
|
190
189
|
this.render()
|
|
191
190
|
})
|