@tiptap/react 3.0.0-next.8 → 3.0.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 +80 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -11
- package/dist/index.d.ts +16 -11
- package/dist/index.js +75 -28
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
- package/src/EditorContent.tsx +1 -1
- package/src/ReactMarkViewRenderer.tsx +8 -6
- package/src/ReactNodeViewRenderer.tsx +52 -27
- package/src/ReactRenderer.tsx +104 -13
- package/src/index.ts +1 -0
- package/src/types.ts +6 -0
- package/src/useEditor.ts +1 -1
- package/src/useEditorState.ts +2 -2
package/src/ReactRenderer.tsx
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import type { Editor } from '@tiptap/core'
|
|
2
|
-
import
|
|
2
|
+
import type {
|
|
3
|
+
ComponentClass,
|
|
4
|
+
ForwardRefExoticComponent,
|
|
5
|
+
FunctionComponent,
|
|
6
|
+
PropsWithoutRef,
|
|
7
|
+
ReactNode,
|
|
8
|
+
RefAttributes,
|
|
9
|
+
} from 'react'
|
|
10
|
+
import { version as reactVersion } from 'react'
|
|
3
11
|
import { flushSync } from 'react-dom'
|
|
4
12
|
|
|
5
13
|
import type { EditorWithContentComponent } from './Editor.js'
|
|
@@ -19,7 +27,75 @@ function isClassComponent(Component: any) {
|
|
|
19
27
|
* @returns {boolean}
|
|
20
28
|
*/
|
|
21
29
|
function isForwardRefComponent(Component: any) {
|
|
22
|
-
return !!(
|
|
30
|
+
return !!(
|
|
31
|
+
typeof Component === 'object' &&
|
|
32
|
+
Component.$$typeof &&
|
|
33
|
+
(Component.$$typeof.toString() === 'Symbol(react.forward_ref)' ||
|
|
34
|
+
Component.$$typeof.description === 'react.forward_ref')
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check if a component is a memoized component.
|
|
40
|
+
* @param Component
|
|
41
|
+
* @returns {boolean}
|
|
42
|
+
*/
|
|
43
|
+
function isMemoComponent(Component: any) {
|
|
44
|
+
return !!(
|
|
45
|
+
typeof Component === 'object' &&
|
|
46
|
+
Component.$$typeof &&
|
|
47
|
+
(Component.$$typeof.toString() === 'Symbol(react.memo)' || Component.$$typeof.description === 'react.memo')
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check if a component can safely receive a ref prop.
|
|
53
|
+
* This includes class components, forwardRef components, and memoized components
|
|
54
|
+
* that wrap forwardRef or class components.
|
|
55
|
+
* @param Component
|
|
56
|
+
* @returns {boolean}
|
|
57
|
+
*/
|
|
58
|
+
function canReceiveRef(Component: any) {
|
|
59
|
+
// Check if it's a class component
|
|
60
|
+
if (isClassComponent(Component)) {
|
|
61
|
+
return true
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Check if it's a forwardRef component
|
|
65
|
+
if (isForwardRefComponent(Component)) {
|
|
66
|
+
return true
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Check if it's a memoized component
|
|
70
|
+
if (isMemoComponent(Component)) {
|
|
71
|
+
// For memoized components, check the wrapped component
|
|
72
|
+
const wrappedComponent = Component.type
|
|
73
|
+
if (wrappedComponent) {
|
|
74
|
+
return isClassComponent(wrappedComponent) || isForwardRefComponent(wrappedComponent)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Check if we're running React 19+ by detecting if function components support ref props
|
|
83
|
+
* @returns {boolean}
|
|
84
|
+
*/
|
|
85
|
+
function isReact19Plus(): boolean {
|
|
86
|
+
// React 19 is detected by checking React version if available
|
|
87
|
+
// In practice, we'll use a more conservative approach and assume React 18 behavior
|
|
88
|
+
// unless we can definitively detect React 19
|
|
89
|
+
try {
|
|
90
|
+
// @ts-ignore
|
|
91
|
+
if (reactVersion) {
|
|
92
|
+
const majorVersion = parseInt(reactVersion.split('.')[0], 10)
|
|
93
|
+
return majorVersion >= 19
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
// Fallback to React 18 behavior if we can't determine version
|
|
97
|
+
}
|
|
98
|
+
return false
|
|
23
99
|
}
|
|
24
100
|
|
|
25
101
|
export interface ReactRendererOptions {
|
|
@@ -53,9 +129,9 @@ export interface ReactRendererOptions {
|
|
|
53
129
|
}
|
|
54
130
|
|
|
55
131
|
type ComponentType<R, P> =
|
|
56
|
-
|
|
|
57
|
-
|
|
|
58
|
-
|
|
|
132
|
+
| ComponentClass<P>
|
|
133
|
+
| FunctionComponent<P>
|
|
134
|
+
| ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<R>>
|
|
59
135
|
|
|
60
136
|
/**
|
|
61
137
|
* The ReactRenderer class. It's responsible for rendering React components inside the editor.
|
|
@@ -79,7 +155,7 @@ export class ReactRenderer<R = unknown, P extends Record<string, any> = object>
|
|
|
79
155
|
|
|
80
156
|
props: P
|
|
81
157
|
|
|
82
|
-
reactElement:
|
|
158
|
+
reactElement: ReactNode
|
|
83
159
|
|
|
84
160
|
ref: R | null = null
|
|
85
161
|
|
|
@@ -101,14 +177,17 @@ export class ReactRenderer<R = unknown, P extends Record<string, any> = object>
|
|
|
101
177
|
this.element.classList.add(...className.split(' '))
|
|
102
178
|
}
|
|
103
179
|
|
|
180
|
+
// If the editor is already initialized, we will need to
|
|
181
|
+
// synchronously render the component to ensure it renders
|
|
182
|
+
// together with Prosemirror's rendering.
|
|
104
183
|
if (this.editor.isInitialized) {
|
|
105
|
-
// On first render, we need to flush the render synchronously
|
|
106
|
-
// Renders afterwards can be async, but this fixes a cursor positioning issue
|
|
107
184
|
flushSync(() => {
|
|
108
185
|
this.render()
|
|
109
186
|
})
|
|
110
187
|
} else {
|
|
111
|
-
|
|
188
|
+
queueMicrotask(() => {
|
|
189
|
+
this.render()
|
|
190
|
+
})
|
|
112
191
|
}
|
|
113
192
|
}
|
|
114
193
|
|
|
@@ -120,14 +199,26 @@ export class ReactRenderer<R = unknown, P extends Record<string, any> = object>
|
|
|
120
199
|
const props = this.props
|
|
121
200
|
const editor = this.editor as EditorWithContentComponent
|
|
122
201
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
202
|
+
// Handle ref forwarding with React 18/19 compatibility
|
|
203
|
+
const isReact19 = isReact19Plus()
|
|
204
|
+
const componentCanReceiveRef = canReceiveRef(Component)
|
|
205
|
+
|
|
206
|
+
const elementProps = { ...props }
|
|
207
|
+
|
|
208
|
+
// Always remove ref if the component cannot receive it (unless React 19+)
|
|
209
|
+
if (elementProps.ref && !(isReact19 || componentCanReceiveRef)) {
|
|
210
|
+
delete elementProps.ref
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Only assign our own ref if allowed
|
|
214
|
+
if (!elementProps.ref && (isReact19 || componentCanReceiveRef)) {
|
|
215
|
+
// @ts-ignore - Setting ref prop for compatible components
|
|
216
|
+
elementProps.ref = (ref: R) => {
|
|
126
217
|
this.ref = ref
|
|
127
218
|
}
|
|
128
219
|
}
|
|
129
220
|
|
|
130
|
-
this.reactElement = <Component {...
|
|
221
|
+
this.reactElement = <Component {...elementProps} />
|
|
131
222
|
|
|
132
223
|
editor?.contentComponent?.setRenderer(this.id, this)
|
|
133
224
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ export * from './NodeViewWrapper.js'
|
|
|
5
5
|
export * from './ReactMarkViewRenderer.js'
|
|
6
6
|
export * from './ReactNodeViewRenderer.js'
|
|
7
7
|
export * from './ReactRenderer.js'
|
|
8
|
+
export * from './types.js'
|
|
8
9
|
export * from './useEditor.js'
|
|
9
10
|
export * from './useEditorState.js'
|
|
10
11
|
export * from './useReactNodeView.js'
|
package/src/types.ts
ADDED
package/src/useEditor.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type EditorOptions, Editor } from '@tiptap/core'
|
|
2
2
|
import type { DependencyList, MutableRefObject } from 'react'
|
|
3
3
|
import { useDebugValue, useEffect, useRef, useState } from 'react'
|
|
4
|
-
import { useSyncExternalStore } from 'use-sync-external-store/shim'
|
|
4
|
+
import { useSyncExternalStore } from 'use-sync-external-store/shim/index.js'
|
|
5
5
|
|
|
6
6
|
import { useEditorState } from './useEditorState.js'
|
|
7
7
|
|
package/src/useEditorState.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Editor } from '@tiptap/core'
|
|
2
|
-
import deepEqual from 'fast-deep-equal/es6/react'
|
|
2
|
+
import deepEqual from 'fast-deep-equal/es6/react.js'
|
|
3
3
|
import { useDebugValue, useEffect, useLayoutEffect, useState } from 'react'
|
|
4
|
-
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector'
|
|
4
|
+
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector.js'
|
|
5
5
|
|
|
6
6
|
const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect
|
|
7
7
|
|