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