@tiptap/react 2.12.0 → 2.14.0

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.
@@ -1,19 +1,17 @@
1
- import {
2
- DecorationWithType,
3
- Editor,
4
- getRenderedAttributes,
5
- NodeView,
6
- NodeViewProps,
7
- NodeViewRenderer,
8
- NodeViewRendererOptions,
1
+ import type {
2
+ DecorationWithType, Editor, NodeViewRenderer, NodeViewRendererOptions,
9
3
  } from '@tiptap/core'
10
- import { Node, Node as ProseMirrorNode } from '@tiptap/pm/model'
11
- import { Decoration, DecorationSource, NodeView as ProseMirrorNodeView } from '@tiptap/pm/view'
12
- import React, { ComponentType } from 'react'
4
+ import { getRenderedAttributes, NodeView } from '@tiptap/core'
5
+ import type { Node, Node as ProseMirrorNode } from '@tiptap/pm/model'
6
+ import type { Decoration, DecorationSource, NodeView as ProseMirrorNodeView } from '@tiptap/pm/view'
7
+ import type { ComponentType, NamedExoticComponent } from 'react'
8
+ import React, { createElement, createRef, memo } from 'react'
13
9
 
14
10
  import { EditorWithContentComponent } from './Editor.js'
15
11
  import { ReactRenderer } from './ReactRenderer.js'
16
- import { ReactNodeViewContext, ReactNodeViewContextProps } from './useReactNodeView.js'
12
+ import type { ReactNodeViewProps } from './types.js'
13
+ import type { ReactNodeViewContextProps } from './useReactNodeView.js'
14
+ import { ReactNodeViewContext } from './useReactNodeView.js'
17
15
 
18
16
  export interface ReactNodeViewRendererOptions extends NodeViewRendererOptions {
19
17
  /**
@@ -53,14 +51,15 @@ export interface ReactNodeViewRendererOptions extends NodeViewRendererOptions {
53
51
  }
54
52
 
55
53
  export class ReactNodeView<
56
- Component extends ComponentType<NodeViewProps> = ComponentType<NodeViewProps>,
54
+ T = HTMLElement,
55
+ Component extends ComponentType<ReactNodeViewProps<T>> = ComponentType<ReactNodeViewProps<T>>,
57
56
  NodeEditor extends Editor = Editor,
58
57
  Options extends ReactNodeViewRendererOptions = ReactNodeViewRendererOptions,
59
58
  > extends NodeView<Component, NodeEditor, Options> {
60
59
  /**
61
60
  * The renderer instance.
62
61
  */
63
- renderer!: ReactRenderer<unknown, NodeViewProps>
62
+ renderer!: ReactRenderer<unknown, ReactNodeViewProps<T>>
64
63
 
65
64
  /**
66
65
  * The element that holds the rich-text content of the node.
@@ -84,7 +83,8 @@ export class ReactNodeView<
84
83
  getPos: () => this.getPos(),
85
84
  updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
86
85
  deleteNode: () => this.deleteNode(),
87
- } satisfies NodeViewProps
86
+ ref: createRef<T>(),
87
+ } satisfies ReactNodeViewProps<T>
88
88
 
89
89
  if (!(this.component as any).displayName) {
90
90
  const capitalizeFirstChar = (string: string): string => {
@@ -104,15 +104,13 @@ export class ReactNodeView<
104
104
  const Component = this.component
105
105
  // For performance reasons, we memoize the provider component
106
106
  // And all of the things it requires are declared outside of the component, so it doesn't need to re-render
107
- const ReactNodeViewProvider: React.FunctionComponent<NodeViewProps> = React.memo(
108
- componentProps => {
109
- return (
110
- <ReactNodeViewContext.Provider value={context}>
111
- {React.createElement(Component, componentProps)}
112
- </ReactNodeViewContext.Provider>
113
- )
114
- },
115
- )
107
+ const ReactNodeViewProvider: NamedExoticComponent<ReactNodeViewProps<T>> = memo(componentProps => {
108
+ return (
109
+ <ReactNodeViewContext.Provider value={context}>
110
+ {createElement(Component, componentProps)}
111
+ </ReactNodeViewContext.Provider>
112
+ )
113
+ })
116
114
 
117
115
  ReactNodeViewProvider.displayName = 'ReactNodeView'
118
116
 
@@ -320,8 +318,8 @@ export class ReactNodeView<
320
318
  /**
321
319
  * Create a React node view renderer.
322
320
  */
323
- export function ReactNodeViewRenderer(
324
- component: ComponentType<NodeViewProps>,
321
+ export function ReactNodeViewRenderer<T = HTMLElement>(
322
+ component: ComponentType<ReactNodeViewProps<T>>,
325
323
  options?: Partial<ReactNodeViewRendererOptions>,
326
324
  ): NodeViewRenderer {
327
325
  return props => {
@@ -332,6 +330,6 @@ export function ReactNodeViewRenderer(
332
330
  return {} as unknown as ProseMirrorNodeView
333
331
  }
334
332
 
335
- return new ReactNodeView(component, props, options)
333
+ return new ReactNodeView<T>(component, props, options)
336
334
  }
337
335
  }
@@ -1,5 +1,13 @@
1
- import { Editor } from '@tiptap/core'
2
- import React from 'react'
1
+ import type { Editor } from '@tiptap/core'
2
+ import type {
3
+ ComponentClass,
4
+ ForwardRefExoticComponent,
5
+ FunctionComponent,
6
+ PropsWithoutRef,
7
+ ReactNode,
8
+ RefAttributes,
9
+ } from 'react'
10
+ import React, { version as reactVersion } from 'react'
3
11
  import { flushSync } from 'react-dom'
4
12
 
5
13
  import { EditorWithContentComponent } from './Editor.js'
@@ -29,6 +37,27 @@ function isForwardRefComponent(Component: any) {
29
37
  )
30
38
  }
31
39
 
40
+ /**
41
+ * Check if we're running React 19+ by detecting if function components support ref props
42
+ * @returns {boolean}
43
+ */
44
+ function isReact19Plus(): boolean {
45
+ // React 19 is detected by checking React version if available
46
+ // In practice, we'll use a more conservative approach and assume React 18 behavior
47
+ // unless we can definitively detect React 19
48
+ try {
49
+ // @ts-ignore
50
+ if (reactVersion) {
51
+ const majorVersion = parseInt(reactVersion.split('.')[0], 10)
52
+
53
+ return majorVersion >= 19
54
+ }
55
+ } catch {
56
+ // Fallback to React 18 behavior if we can't determine version
57
+ }
58
+ return false
59
+ }
60
+
32
61
  export interface ReactRendererOptions {
33
62
  /**
34
63
  * The editor instance.
@@ -60,9 +89,9 @@ export interface ReactRendererOptions {
60
89
  }
61
90
 
62
91
  type ComponentType<R, P> =
63
- React.ComponentClass<P> |
64
- React.FunctionComponent<P> |
65
- React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<R>>;
92
+ | ComponentClass<P>
93
+ | FunctionComponent<P>
94
+ | ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<R>>
66
95
 
67
96
  /**
68
97
  * The ReactRenderer class. It's responsible for rendering React components inside the editor.
@@ -86,7 +115,7 @@ export class ReactRenderer<R = unknown, P extends Record<string, any> = object>
86
115
 
87
116
  props: P
88
117
 
89
- reactElement: React.ReactNode
118
+ reactElement: ReactNode
90
119
 
91
120
  ref: R | null = null
92
121
 
@@ -129,14 +158,32 @@ export class ReactRenderer<R = unknown, P extends Record<string, any> = object>
129
158
  const props = this.props
130
159
  const editor = this.editor as EditorWithContentComponent
131
160
 
132
- if (isClassComponent(Component) || isForwardRefComponent(Component)) {
133
- // @ts-ignore This is a hack to make the ref work
134
- props.ref = (ref: R) => {
135
- this.ref = ref
161
+ // Handle ref forwarding with React 18/19 compatibility
162
+ const isReact19 = isReact19Plus()
163
+ const isClassComp = isClassComponent(Component)
164
+ const isForwardRefComp = isForwardRefComponent(Component)
165
+
166
+ const elementProps = { ...props }
167
+
168
+ if (!elementProps.ref) {
169
+ if (isReact19) {
170
+ // React 19: ref is a standard prop for all components
171
+ // @ts-ignore - Setting ref prop for React 19 compatibility
172
+ elementProps.ref = (ref: R) => {
173
+ this.ref = ref
174
+ }
175
+ } else if (isClassComp || isForwardRefComp) {
176
+ // React 18 and prior: only set ref for class components and forwardRef components
177
+ // @ts-ignore - Setting ref prop for React 18 class/forwardRef components
178
+ elementProps.ref = (ref: R) => {
179
+ this.ref = ref
180
+ }
136
181
  }
182
+ // For function components in React 18, we can't use ref - the component won't receive it
183
+ // This is a limitation we have to accept for React 18 function components without forwardRef
137
184
  }
138
185
 
139
- this.reactElement = <Component {...props} />
186
+ this.reactElement = <Component {...elementProps} />
140
187
 
141
188
  editor?.contentComponent?.setRenderer(this.id, this)
142
189
  }
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ export * from './NodeViewContent.js'
6
6
  export * from './NodeViewWrapper.js'
7
7
  export * from './ReactNodeViewRenderer.js'
8
8
  export * from './ReactRenderer.js'
9
+ export * from './types.js'
9
10
  export * from './useEditor.js'
10
11
  export * from './useEditorState.js'
11
12
  export * from './useReactNodeView.js'
package/src/types.ts ADDED
@@ -0,0 +1,6 @@
1
+ import type { NodeViewProps as CoreNodeViewProps } from '@tiptap/core'
2
+ import type React from 'react'
3
+
4
+ export type ReactNodeViewProps<T = HTMLElement> = CoreNodeViewProps & {
5
+ ref: React.RefObject<T | null>
6
+ }