@tiptap/react 3.0.0-beta.1 → 3.0.0-beta.10
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 +52 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -11
- package/dist/index.d.ts +15 -11
- package/dist/index.js +47 -12
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
- package/src/ReactMarkViewRenderer.tsx +7 -5
- package/src/ReactNodeViewRenderer.tsx +14 -11
- package/src/ReactRenderer.tsx +98 -10
- package/src/index.ts +1 -0
- package/src/types.ts +6 -0
- package/src/useEditorState.ts +1 -1
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import type { DecorationWithType, Editor,
|
|
1
|
+
import type { DecorationWithType, Editor, NodeViewRenderer, NodeViewRendererOptions } from '@tiptap/core'
|
|
2
2
|
import { getRenderedAttributes, NodeView } from '@tiptap/core'
|
|
3
3
|
import type { Node, Node as ProseMirrorNode } from '@tiptap/pm/model'
|
|
4
4
|
import type { Decoration, DecorationSource, NodeView as ProseMirrorNodeView } from '@tiptap/pm/view'
|
|
5
|
-
import type { ComponentType } from 'react'
|
|
6
|
-
import
|
|
5
|
+
import type { ComponentType, NamedExoticComponent } from 'react'
|
|
6
|
+
import { createElement, createRef, memo } from 'react'
|
|
7
7
|
|
|
8
8
|
import type { EditorWithContentComponent } from './Editor.js'
|
|
9
9
|
import { ReactRenderer } from './ReactRenderer.js'
|
|
10
|
+
import type { ReactNodeViewProps } from './types.js'
|
|
10
11
|
import type { ReactNodeViewContextProps } from './useReactNodeView.js'
|
|
11
12
|
import { ReactNodeViewContext } from './useReactNodeView.js'
|
|
12
13
|
|
|
@@ -45,14 +46,15 @@ export interface ReactNodeViewRendererOptions extends NodeViewRendererOptions {
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
export class ReactNodeView<
|
|
48
|
-
|
|
49
|
+
T = HTMLElement,
|
|
50
|
+
Component extends ComponentType<ReactNodeViewProps<T>> = ComponentType<ReactNodeViewProps<T>>,
|
|
49
51
|
NodeEditor extends Editor = Editor,
|
|
50
52
|
Options extends ReactNodeViewRendererOptions = ReactNodeViewRendererOptions,
|
|
51
53
|
> extends NodeView<Component, NodeEditor, Options> {
|
|
52
54
|
/**
|
|
53
55
|
* The renderer instance.
|
|
54
56
|
*/
|
|
55
|
-
renderer!: ReactRenderer<unknown,
|
|
57
|
+
renderer!: ReactRenderer<unknown, ReactNodeViewProps<T>>
|
|
56
58
|
|
|
57
59
|
/**
|
|
58
60
|
* The element that holds the rich-text content of the node.
|
|
@@ -76,7 +78,8 @@ export class ReactNodeView<
|
|
|
76
78
|
getPos: () => this.getPos(),
|
|
77
79
|
updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
|
|
78
80
|
deleteNode: () => this.deleteNode(),
|
|
79
|
-
|
|
81
|
+
ref: createRef<T>(),
|
|
82
|
+
} satisfies ReactNodeViewProps<T>
|
|
80
83
|
|
|
81
84
|
if (!(this.component as any).displayName) {
|
|
82
85
|
const capitalizeFirstChar = (string: string): string => {
|
|
@@ -96,10 +99,10 @@ export class ReactNodeView<
|
|
|
96
99
|
const Component = this.component
|
|
97
100
|
// For performance reasons, we memoize the provider component
|
|
98
101
|
// And all of the things it requires are declared outside of the component, so it doesn't need to re-render
|
|
99
|
-
const ReactNodeViewProvider:
|
|
102
|
+
const ReactNodeViewProvider: NamedExoticComponent<ReactNodeViewProps<T>> = memo(componentProps => {
|
|
100
103
|
return (
|
|
101
104
|
<ReactNodeViewContext.Provider value={context}>
|
|
102
|
-
{
|
|
105
|
+
{createElement(Component, componentProps)}
|
|
103
106
|
</ReactNodeViewContext.Provider>
|
|
104
107
|
)
|
|
105
108
|
})
|
|
@@ -302,8 +305,8 @@ export class ReactNodeView<
|
|
|
302
305
|
/**
|
|
303
306
|
* Create a React node view renderer.
|
|
304
307
|
*/
|
|
305
|
-
export function ReactNodeViewRenderer(
|
|
306
|
-
component: ComponentType<
|
|
308
|
+
export function ReactNodeViewRenderer<T = HTMLElement>(
|
|
309
|
+
component: ComponentType<ReactNodeViewProps<T>>,
|
|
307
310
|
options?: Partial<ReactNodeViewRendererOptions>,
|
|
308
311
|
): NodeViewRenderer {
|
|
309
312
|
return props => {
|
|
@@ -314,6 +317,6 @@ export function ReactNodeViewRenderer(
|
|
|
314
317
|
return {} as unknown as ProseMirrorNodeView
|
|
315
318
|
}
|
|
316
319
|
|
|
317
|
-
return new ReactNodeView(component, props, options)
|
|
320
|
+
return new ReactNodeView<T>(component, props, options)
|
|
318
321
|
}
|
|
319
322
|
}
|
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
|
|
|
@@ -120,14 +196,26 @@ export class ReactRenderer<R = unknown, P extends Record<string, any> = object>
|
|
|
120
196
|
const props = this.props
|
|
121
197
|
const editor = this.editor as EditorWithContentComponent
|
|
122
198
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
199
|
+
// Handle ref forwarding with React 18/19 compatibility
|
|
200
|
+
const isReact19 = isReact19Plus()
|
|
201
|
+
const componentCanReceiveRef = canReceiveRef(Component)
|
|
202
|
+
|
|
203
|
+
const elementProps = { ...props }
|
|
204
|
+
|
|
205
|
+
// Always remove ref if the component cannot receive it (unless React 19+)
|
|
206
|
+
if (elementProps.ref && !(isReact19 || componentCanReceiveRef)) {
|
|
207
|
+
delete elementProps.ref
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Only assign our own ref if allowed
|
|
211
|
+
if (!elementProps.ref && (isReact19 || componentCanReceiveRef)) {
|
|
212
|
+
// @ts-ignore - Setting ref prop for compatible components
|
|
213
|
+
elementProps.ref = (ref: R) => {
|
|
126
214
|
this.ref = ref
|
|
127
215
|
}
|
|
128
216
|
}
|
|
129
217
|
|
|
130
|
-
this.reactElement = <Component {...
|
|
218
|
+
this.reactElement = <Component {...elementProps} />
|
|
131
219
|
|
|
132
220
|
editor?.contentComponent?.setRenderer(this.id, this)
|
|
133
221
|
}
|
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
|
|