@tiptap/react 3.0.0 → 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.
Files changed (215) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +5 -1
  3. package/dist/index.cjs +1030 -1163
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +350 -0
  6. package/dist/index.d.ts +350 -0
  7. package/dist/index.js +970 -1138
  8. package/dist/index.js.map +1 -1
  9. package/dist/menus/index.cjs +142 -0
  10. package/dist/menus/index.cjs.map +1 -0
  11. package/dist/menus/index.d.cts +19 -0
  12. package/dist/menus/index.d.ts +19 -0
  13. package/dist/menus/index.js +104 -0
  14. package/dist/menus/index.js.map +1 -0
  15. package/package.json +34 -21
  16. package/src/Context.tsx +18 -12
  17. package/src/Editor.ts +10 -11
  18. package/src/EditorContent.tsx +104 -64
  19. package/src/NodeViewContent.tsx +13 -8
  20. package/src/NodeViewWrapper.tsx +3 -2
  21. package/src/ReactMarkViewRenderer.tsx +111 -0
  22. package/src/ReactNodeViewRenderer.tsx +184 -67
  23. package/src/ReactRenderer.tsx +152 -51
  24. package/src/index.ts +2 -3
  25. package/src/menus/BubbleMenu.tsx +68 -0
  26. package/src/menus/FloatingMenu.tsx +68 -0
  27. package/src/menus/index.ts +2 -0
  28. package/src/types.ts +6 -0
  29. package/src/useEditor.ts +286 -166
  30. package/src/useEditorState.ts +133 -85
  31. package/src/useReactNodeView.ts +21 -5
  32. package/dist/index.umd.js +0 -1219
  33. package/dist/index.umd.js.map +0 -1
  34. package/dist/packages/core/src/CommandManager.d.ts +0 -20
  35. package/dist/packages/core/src/Editor.d.ts +0 -161
  36. package/dist/packages/core/src/EventEmitter.d.ts +0 -11
  37. package/dist/packages/core/src/Extension.d.ts +0 -343
  38. package/dist/packages/core/src/ExtensionManager.d.ts +0 -55
  39. package/dist/packages/core/src/InputRule.d.ts +0 -42
  40. package/dist/packages/core/src/Mark.d.ts +0 -451
  41. package/dist/packages/core/src/Node.d.ts +0 -611
  42. package/dist/packages/core/src/NodePos.d.ts +0 -44
  43. package/dist/packages/core/src/NodeView.d.ts +0 -31
  44. package/dist/packages/core/src/PasteRule.d.ts +0 -50
  45. package/dist/packages/core/src/Tracker.d.ts +0 -11
  46. package/dist/packages/core/src/commands/blur.d.ts +0 -13
  47. package/dist/packages/core/src/commands/clearContent.d.ts +0 -14
  48. package/dist/packages/core/src/commands/clearNodes.d.ts +0 -13
  49. package/dist/packages/core/src/commands/command.d.ts +0 -18
  50. package/dist/packages/core/src/commands/createParagraphNear.d.ts +0 -13
  51. package/dist/packages/core/src/commands/cut.d.ts +0 -20
  52. package/dist/packages/core/src/commands/deleteCurrentNode.d.ts +0 -13
  53. package/dist/packages/core/src/commands/deleteNode.d.ts +0 -15
  54. package/dist/packages/core/src/commands/deleteRange.d.ts +0 -14
  55. package/dist/packages/core/src/commands/deleteSelection.d.ts +0 -13
  56. package/dist/packages/core/src/commands/enter.d.ts +0 -13
  57. package/dist/packages/core/src/commands/exitCode.d.ts +0 -13
  58. package/dist/packages/core/src/commands/extendMarkRange.d.ts +0 -25
  59. package/dist/packages/core/src/commands/first.d.ts +0 -14
  60. package/dist/packages/core/src/commands/focus.d.ts +0 -27
  61. package/dist/packages/core/src/commands/forEach.d.ts +0 -14
  62. package/dist/packages/core/src/commands/index.d.ts +0 -55
  63. package/dist/packages/core/src/commands/insertContent.d.ts +0 -34
  64. package/dist/packages/core/src/commands/insertContentAt.d.ts +0 -47
  65. package/dist/packages/core/src/commands/join.d.ts +0 -41
  66. package/dist/packages/core/src/commands/joinItemBackward.d.ts +0 -13
  67. package/dist/packages/core/src/commands/joinItemForward.d.ts +0 -13
  68. package/dist/packages/core/src/commands/joinTextblockBackward.d.ts +0 -12
  69. package/dist/packages/core/src/commands/joinTextblockForward.d.ts +0 -12
  70. package/dist/packages/core/src/commands/keyboardShortcut.d.ts +0 -14
  71. package/dist/packages/core/src/commands/lift.d.ts +0 -17
  72. package/dist/packages/core/src/commands/liftEmptyBlock.d.ts +0 -13
  73. package/dist/packages/core/src/commands/liftListItem.d.ts +0 -15
  74. package/dist/packages/core/src/commands/newlineInCode.d.ts +0 -13
  75. package/dist/packages/core/src/commands/resetAttributes.d.ts +0 -16
  76. package/dist/packages/core/src/commands/scrollIntoView.d.ts +0 -13
  77. package/dist/packages/core/src/commands/selectAll.d.ts +0 -13
  78. package/dist/packages/core/src/commands/selectNodeBackward.d.ts +0 -13
  79. package/dist/packages/core/src/commands/selectNodeForward.d.ts +0 -13
  80. package/dist/packages/core/src/commands/selectParentNode.d.ts +0 -13
  81. package/dist/packages/core/src/commands/selectTextblockEnd.d.ts +0 -13
  82. package/dist/packages/core/src/commands/selectTextblockStart.d.ts +0 -13
  83. package/dist/packages/core/src/commands/setContent.d.ts +0 -40
  84. package/dist/packages/core/src/commands/setMark.d.ts +0 -15
  85. package/dist/packages/core/src/commands/setMeta.d.ts +0 -15
  86. package/dist/packages/core/src/commands/setNode.d.ts +0 -16
  87. package/dist/packages/core/src/commands/setNodeSelection.d.ts +0 -14
  88. package/dist/packages/core/src/commands/setTextSelection.d.ts +0 -14
  89. package/dist/packages/core/src/commands/sinkListItem.d.ts +0 -15
  90. package/dist/packages/core/src/commands/splitBlock.d.ts +0 -17
  91. package/dist/packages/core/src/commands/splitListItem.d.ts +0 -15
  92. package/dist/packages/core/src/commands/toggleList.d.ts +0 -18
  93. package/dist/packages/core/src/commands/toggleMark.d.ts +0 -30
  94. package/dist/packages/core/src/commands/toggleNode.d.ts +0 -17
  95. package/dist/packages/core/src/commands/toggleWrap.d.ts +0 -16
  96. package/dist/packages/core/src/commands/undoInputRule.d.ts +0 -13
  97. package/dist/packages/core/src/commands/unsetAllMarks.d.ts +0 -13
  98. package/dist/packages/core/src/commands/unsetMark.d.ts +0 -25
  99. package/dist/packages/core/src/commands/updateAttributes.d.ts +0 -24
  100. package/dist/packages/core/src/commands/wrapIn.d.ts +0 -16
  101. package/dist/packages/core/src/commands/wrapInList.d.ts +0 -16
  102. package/dist/packages/core/src/extensions/clipboardTextSerializer.d.ts +0 -5
  103. package/dist/packages/core/src/extensions/commands.d.ts +0 -3
  104. package/dist/packages/core/src/extensions/editable.d.ts +0 -2
  105. package/dist/packages/core/src/extensions/focusEvents.d.ts +0 -2
  106. package/dist/packages/core/src/extensions/index.d.ts +0 -6
  107. package/dist/packages/core/src/extensions/keymap.d.ts +0 -2
  108. package/dist/packages/core/src/extensions/tabindex.d.ts +0 -2
  109. package/dist/packages/core/src/helpers/combineTransactionSteps.d.ts +0 -10
  110. package/dist/packages/core/src/helpers/createChainableState.d.ts +0 -10
  111. package/dist/packages/core/src/helpers/createDocument.d.ts +0 -12
  112. package/dist/packages/core/src/helpers/createNodeFromContent.d.ts +0 -15
  113. package/dist/packages/core/src/helpers/defaultBlockAt.d.ts +0 -7
  114. package/dist/packages/core/src/helpers/findChildren.d.ts +0 -9
  115. package/dist/packages/core/src/helpers/findChildrenInRange.d.ts +0 -10
  116. package/dist/packages/core/src/helpers/findParentNode.d.ts +0 -16
  117. package/dist/packages/core/src/helpers/findParentNodeClosestToPos.d.ts +0 -17
  118. package/dist/packages/core/src/helpers/generateHTML.d.ts +0 -8
  119. package/dist/packages/core/src/helpers/generateJSON.d.ts +0 -8
  120. package/dist/packages/core/src/helpers/generateText.d.ts +0 -12
  121. package/dist/packages/core/src/helpers/getAttributes.d.ts +0 -9
  122. package/dist/packages/core/src/helpers/getAttributesFromExtensions.d.ts +0 -6
  123. package/dist/packages/core/src/helpers/getChangedRanges.d.ts +0 -11
  124. package/dist/packages/core/src/helpers/getDebugJSON.d.ts +0 -8
  125. package/dist/packages/core/src/helpers/getExtensionField.d.ts +0 -9
  126. package/dist/packages/core/src/helpers/getHTMLFromFragment.d.ts +0 -2
  127. package/dist/packages/core/src/helpers/getMarkAttributes.d.ts +0 -3
  128. package/dist/packages/core/src/helpers/getMarkRange.d.ts +0 -3
  129. package/dist/packages/core/src/helpers/getMarkType.d.ts +0 -2
  130. package/dist/packages/core/src/helpers/getMarksBetween.d.ts +0 -3
  131. package/dist/packages/core/src/helpers/getNodeAtPosition.d.ts +0 -11
  132. package/dist/packages/core/src/helpers/getNodeAttributes.d.ts +0 -3
  133. package/dist/packages/core/src/helpers/getNodeType.d.ts +0 -2
  134. package/dist/packages/core/src/helpers/getRenderedAttributes.d.ts +0 -3
  135. package/dist/packages/core/src/helpers/getSchema.d.ts +0 -4
  136. package/dist/packages/core/src/helpers/getSchemaByResolvedExtensions.d.ts +0 -10
  137. package/dist/packages/core/src/helpers/getSchemaTypeByName.d.ts +0 -8
  138. package/dist/packages/core/src/helpers/getSchemaTypeNameByName.d.ts +0 -8
  139. package/dist/packages/core/src/helpers/getSplittedAttributes.d.ts +0 -9
  140. package/dist/packages/core/src/helpers/getText.d.ts +0 -15
  141. package/dist/packages/core/src/helpers/getTextBetween.d.ts +0 -14
  142. package/dist/packages/core/src/helpers/getTextContentFromNodes.d.ts +0 -8
  143. package/dist/packages/core/src/helpers/getTextSerializersFromSchema.d.ts +0 -8
  144. package/dist/packages/core/src/helpers/index.d.ts +0 -50
  145. package/dist/packages/core/src/helpers/injectExtensionAttributesToParseRule.d.ts +0 -9
  146. package/dist/packages/core/src/helpers/isActive.d.ts +0 -2
  147. package/dist/packages/core/src/helpers/isAtEndOfNode.d.ts +0 -2
  148. package/dist/packages/core/src/helpers/isAtStartOfNode.d.ts +0 -2
  149. package/dist/packages/core/src/helpers/isExtensionRulesEnabled.d.ts +0 -2
  150. package/dist/packages/core/src/helpers/isList.d.ts +0 -2
  151. package/dist/packages/core/src/helpers/isMarkActive.d.ts +0 -3
  152. package/dist/packages/core/src/helpers/isNodeActive.d.ts +0 -3
  153. package/dist/packages/core/src/helpers/isNodeEmpty.d.ts +0 -2
  154. package/dist/packages/core/src/helpers/isNodeSelection.d.ts +0 -2
  155. package/dist/packages/core/src/helpers/isTextSelection.d.ts +0 -2
  156. package/dist/packages/core/src/helpers/posToDOMRect.d.ts +0 -2
  157. package/dist/packages/core/src/helpers/resolveFocusPosition.d.ts +0 -4
  158. package/dist/packages/core/src/helpers/selectionToInsertionEnd.d.ts +0 -2
  159. package/dist/packages/core/src/helpers/splitExtensions.d.ts +0 -9
  160. package/dist/packages/core/src/index.d.ts +0 -24
  161. package/dist/packages/core/src/inputRules/index.d.ts +0 -5
  162. package/dist/packages/core/src/inputRules/markInputRule.d.ts +0 -13
  163. package/dist/packages/core/src/inputRules/nodeInputRule.d.ts +0 -23
  164. package/dist/packages/core/src/inputRules/textInputRule.d.ts +0 -10
  165. package/dist/packages/core/src/inputRules/textblockTypeInputRule.d.ts +0 -15
  166. package/dist/packages/core/src/inputRules/wrappingInputRule.d.ts +0 -28
  167. package/dist/packages/core/src/pasteRules/index.d.ts +0 -3
  168. package/dist/packages/core/src/pasteRules/markPasteRule.d.ts +0 -13
  169. package/dist/packages/core/src/pasteRules/nodePasteRule.d.ts +0 -13
  170. package/dist/packages/core/src/pasteRules/textPasteRule.d.ts +0 -10
  171. package/dist/packages/core/src/style.d.ts +0 -1
  172. package/dist/packages/core/src/types.d.ts +0 -255
  173. package/dist/packages/core/src/utilities/callOrReturn.d.ts +0 -9
  174. package/dist/packages/core/src/utilities/createStyleTag.d.ts +0 -1
  175. package/dist/packages/core/src/utilities/deleteProps.d.ts +0 -6
  176. package/dist/packages/core/src/utilities/elementFromString.d.ts +0 -1
  177. package/dist/packages/core/src/utilities/escapeForRegEx.d.ts +0 -1
  178. package/dist/packages/core/src/utilities/findDuplicates.d.ts +0 -1
  179. package/dist/packages/core/src/utilities/fromString.d.ts +0 -1
  180. package/dist/packages/core/src/utilities/index.d.ts +0 -20
  181. package/dist/packages/core/src/utilities/isAndroid.d.ts +0 -1
  182. package/dist/packages/core/src/utilities/isEmptyObject.d.ts +0 -1
  183. package/dist/packages/core/src/utilities/isFunction.d.ts +0 -1
  184. package/dist/packages/core/src/utilities/isMacOS.d.ts +0 -1
  185. package/dist/packages/core/src/utilities/isNumber.d.ts +0 -1
  186. package/dist/packages/core/src/utilities/isPlainObject.d.ts +0 -1
  187. package/dist/packages/core/src/utilities/isRegExp.d.ts +0 -1
  188. package/dist/packages/core/src/utilities/isString.d.ts +0 -1
  189. package/dist/packages/core/src/utilities/isiOS.d.ts +0 -1
  190. package/dist/packages/core/src/utilities/mergeAttributes.d.ts +0 -1
  191. package/dist/packages/core/src/utilities/mergeDeep.d.ts +0 -1
  192. package/dist/packages/core/src/utilities/minMax.d.ts +0 -1
  193. package/dist/packages/core/src/utilities/objectIncludes.d.ts +0 -8
  194. package/dist/packages/core/src/utilities/removeDuplicates.d.ts +0 -8
  195. package/dist/packages/extension-bubble-menu/src/bubble-menu-plugin.d.ts +0 -76
  196. package/dist/packages/extension-bubble-menu/src/bubble-menu.d.ts +0 -15
  197. package/dist/packages/extension-bubble-menu/src/index.d.ts +0 -4
  198. package/dist/packages/extension-floating-menu/src/floating-menu-plugin.d.ts +0 -66
  199. package/dist/packages/extension-floating-menu/src/floating-menu.d.ts +0 -15
  200. package/dist/packages/extension-floating-menu/src/index.d.ts +0 -4
  201. package/dist/packages/react/src/BubbleMenu.d.ts +0 -11
  202. package/dist/packages/react/src/Context.d.ts +0 -23
  203. package/dist/packages/react/src/Editor.d.ts +0 -12
  204. package/dist/packages/react/src/EditorContent.d.ts +0 -24
  205. package/dist/packages/react/src/FloatingMenu.d.ts +0 -10
  206. package/dist/packages/react/src/NodeViewContent.d.ts +0 -6
  207. package/dist/packages/react/src/NodeViewWrapper.d.ts +0 -6
  208. package/dist/packages/react/src/ReactNodeViewRenderer.d.ts +0 -16
  209. package/dist/packages/react/src/ReactRenderer.d.ts +0 -62
  210. package/dist/packages/react/src/index.d.ts +0 -13
  211. package/dist/packages/react/src/useEditor.d.ts +0 -39
  212. package/dist/packages/react/src/useEditorState.d.ts +0 -22
  213. package/dist/packages/react/src/useReactNodeView.d.ts +0 -6
  214. package/src/BubbleMenu.tsx +0 -57
  215. package/src/FloatingMenu.tsx +0 -64
@@ -1,7 +1,16 @@
1
- import { Editor } from '@tiptap/core'
2
- import React from 'react'
3
-
4
- import { Editor as ExtendedEditor } from './Editor.js'
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 { version as reactVersion } from 'react'
11
+ import { flushSync } from 'react-dom'
12
+
13
+ import type { EditorWithContentComponent } from './Editor.js'
5
14
 
6
15
  /**
7
16
  * Check if a component is a class component.
@@ -9,11 +18,7 @@ import { Editor as ExtendedEditor } from './Editor.js'
9
18
  * @returns {boolean}
10
19
  */
11
20
  function isClassComponent(Component: any) {
12
- return !!(
13
- typeof Component === 'function'
14
- && Component.prototype
15
- && Component.prototype.isReactComponent
16
- )
21
+ return !!(typeof Component === 'function' && Component.prototype && Component.prototype.isReactComponent)
17
22
  }
18
23
 
19
24
  /**
@@ -23,31 +28,96 @@ function isClassComponent(Component: any) {
23
28
  */
24
29
  function isForwardRefComponent(Component: any) {
25
30
  return !!(
26
- typeof Component === 'object'
27
- && Component.$$typeof?.toString() === 'Symbol(react.forward_ref)'
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')
28
48
  )
29
49
  }
30
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
99
+ }
100
+
31
101
  export interface ReactRendererOptions {
32
102
  /**
33
103
  * The editor instance.
34
104
  * @type {Editor}
35
105
  */
36
- editor: Editor,
106
+ editor: Editor
37
107
 
38
108
  /**
39
109
  * The props for the component.
40
110
  * @type {Record<string, any>}
41
111
  * @default {}
42
112
  */
43
- props?: Record<string, any>,
113
+ props?: Record<string, any>
44
114
 
45
115
  /**
46
116
  * The tag name of the element.
47
117
  * @type {string}
48
118
  * @default 'div'
49
119
  */
50
- as?: string,
120
+ as?: string
51
121
 
52
122
  /**
53
123
  * The class name of the element.
@@ -55,21 +125,13 @@ export interface ReactRendererOptions {
55
125
  * @default ''
56
126
  * @example 'foo bar'
57
127
  */
58
- className?: string,
59
-
60
- /**
61
- * The attributes of the element.
62
- * @type {Record<string, string>}
63
- * @default {}
64
- * @example { 'data-foo': 'bar' }
65
- */
66
- attrs?: Record<string, string>,
128
+ className?: string
67
129
  }
68
130
 
69
131
  type ComponentType<R, P> =
70
- React.ComponentClass<P> |
71
- React.FunctionComponent<P> |
72
- React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<R>>;
132
+ | ComponentClass<P>
133
+ | FunctionComponent<P>
134
+ | ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<R>>
73
135
 
74
136
  /**
75
137
  * The ReactRenderer class. It's responsible for rendering React components inside the editor.
@@ -81,33 +143,33 @@ type ComponentType<R, P> =
81
143
  * },
82
144
  * as: 'span',
83
145
  * })
84
- */
85
- export class ReactRenderer<R = unknown, P = unknown> {
146
+ */
147
+ export class ReactRenderer<R = unknown, P extends Record<string, any> = object> {
86
148
  id: string
87
149
 
88
- editor: ExtendedEditor
150
+ editor: Editor
89
151
 
90
152
  component: any
91
153
 
92
154
  element: Element
93
155
 
94
- props: Record<string, any>
156
+ props: P
95
157
 
96
- reactElement: React.ReactNode
158
+ reactElement: ReactNode
97
159
 
98
160
  ref: R | null = null
99
161
 
100
- constructor(component: ComponentType<R, P>, {
101
- editor,
102
- props = {},
103
- as = 'div',
104
- className = '',
105
- attrs,
106
- }: ReactRendererOptions) {
107
- this.id = Math.floor(Math.random() * 0xFFFFFFFF).toString()
162
+ /**
163
+ * Immediately creates element and renders the provided React component.
164
+ */
165
+ constructor(
166
+ component: ComponentType<R, P>,
167
+ { editor, props = {}, as = 'div', className = '' }: ReactRendererOptions,
168
+ ) {
169
+ this.id = Math.floor(Math.random() * 0xffffffff).toString()
108
170
  this.component = component
109
- this.editor = editor as ExtendedEditor
110
- this.props = props
171
+ this.editor = editor as EditorWithContentComponent
172
+ this.props = props as P
111
173
  this.element = document.createElement(as)
112
174
  this.element.classList.add('react-renderer')
113
175
 
@@ -115,30 +177,55 @@ export class ReactRenderer<R = unknown, P = unknown> {
115
177
  this.element.classList.add(...className.split(' '))
116
178
  }
117
179
 
118
- if (attrs) {
119
- Object.keys(attrs).forEach(key => {
120
- this.element.setAttribute(key, attrs[key])
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.
183
+ if (this.editor.isInitialized) {
184
+ flushSync(() => {
185
+ this.render()
186
+ })
187
+ } else {
188
+ queueMicrotask(() => {
189
+ this.render()
121
190
  })
122
191
  }
123
-
124
- this.render()
125
192
  }
126
193
 
194
+ /**
195
+ * Render the React component.
196
+ */
127
197
  render(): void {
128
198
  const Component = this.component
129
199
  const props = this.props
200
+ const editor = this.editor as EditorWithContentComponent
201
+
202
+ // Handle ref forwarding with React 18/19 compatibility
203
+ const isReact19 = isReact19Plus()
204
+ const componentCanReceiveRef = canReceiveRef(Component)
130
205
 
131
- if (isClassComponent(Component) || isForwardRefComponent(Component)) {
132
- props.ref = (ref: R) => {
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) => {
133
217
  this.ref = ref
134
218
  }
135
219
  }
136
220
 
137
- this.reactElement = <Component {...props } />
221
+ this.reactElement = <Component {...elementProps} />
138
222
 
139
- this.editor?.contentComponent?.setRenderer(this.id, this)
223
+ editor?.contentComponent?.setRenderer(this.id, this)
140
224
  }
141
225
 
226
+ /**
227
+ * Re-renders the React component with new props.
228
+ */
142
229
  updateProps(props: Record<string, any> = {}): void {
143
230
  this.props = {
144
231
  ...this.props,
@@ -148,7 +235,21 @@ export class ReactRenderer<R = unknown, P = unknown> {
148
235
  this.render()
149
236
  }
150
237
 
238
+ /**
239
+ * Destroy the React component.
240
+ */
151
241
  destroy(): void {
152
- this.editor?.contentComponent?.removeRenderer(this.id)
242
+ const editor = this.editor as EditorWithContentComponent
243
+
244
+ editor?.contentComponent?.removeRenderer(this.id)
245
+ }
246
+
247
+ /**
248
+ * Update the attributes of the element that holds the React component.
249
+ */
250
+ updateAttributes(attributes: Record<string, string>): void {
251
+ Object.keys(attributes).forEach(key => {
252
+ this.element.setAttribute(key, attributes[key])
253
+ })
153
254
  }
154
255
  }
package/src/index.ts CHANGED
@@ -1,12 +1,11 @@
1
- export * from './BubbleMenu.js'
2
1
  export * from './Context.js'
3
- export { Editor } from './Editor.js'
4
2
  export * from './EditorContent.js'
5
- export * from './FloatingMenu.js'
6
3
  export * from './NodeViewContent.js'
7
4
  export * from './NodeViewWrapper.js'
5
+ export * from './ReactMarkViewRenderer.js'
8
6
  export * from './ReactNodeViewRenderer.js'
9
7
  export * from './ReactRenderer.js'
8
+ export * from './types.js'
10
9
  export * from './useEditor.js'
11
10
  export * from './useEditorState.js'
12
11
  export * from './useReactNodeView.js'
@@ -0,0 +1,68 @@
1
+ import { type BubbleMenuPluginProps, BubbleMenuPlugin } from '@tiptap/extension-bubble-menu'
2
+ import { useCurrentEditor } from '@tiptap/react'
3
+ import React, { useEffect, useRef } from 'react'
4
+ import { createPortal } from 'react-dom'
5
+
6
+ type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>
7
+
8
+ export type BubbleMenuProps = Optional<Omit<Optional<BubbleMenuPluginProps, 'pluginKey'>, 'element'>, 'editor'> &
9
+ React.HTMLAttributes<HTMLDivElement>
10
+
11
+ export const BubbleMenu = React.forwardRef<HTMLDivElement, BubbleMenuProps>(
12
+ (
13
+ { pluginKey = 'bubbleMenu', editor, updateDelay, resizeDelay, shouldShow = null, options, children, ...restProps },
14
+ ref,
15
+ ) => {
16
+ const menuEl = useRef(document.createElement('div'))
17
+
18
+ if (typeof ref === 'function') {
19
+ ref(menuEl.current)
20
+ } else if (ref) {
21
+ ref.current = menuEl.current
22
+ }
23
+
24
+ const { editor: currentEditor } = useCurrentEditor()
25
+
26
+ useEffect(() => {
27
+ const bubbleMenuElement = menuEl.current
28
+
29
+ bubbleMenuElement.style.visibility = 'hidden'
30
+ bubbleMenuElement.style.position = 'absolute'
31
+
32
+ if (editor?.isDestroyed || (currentEditor as any)?.isDestroyed) {
33
+ return
34
+ }
35
+
36
+ const attachToEditor = editor || currentEditor
37
+
38
+ if (!attachToEditor) {
39
+ console.warn('BubbleMenu component is not rendered inside of an editor component or does not have editor prop.')
40
+ return
41
+ }
42
+
43
+ const plugin = BubbleMenuPlugin({
44
+ updateDelay,
45
+ resizeDelay,
46
+ editor: attachToEditor,
47
+ element: bubbleMenuElement,
48
+ pluginKey,
49
+ shouldShow,
50
+ options,
51
+ })
52
+
53
+ attachToEditor.registerPlugin(plugin)
54
+
55
+ return () => {
56
+ attachToEditor.unregisterPlugin(pluginKey)
57
+ window.requestAnimationFrame(() => {
58
+ if (bubbleMenuElement.parentNode) {
59
+ bubbleMenuElement.parentNode.removeChild(bubbleMenuElement)
60
+ }
61
+ })
62
+ }
63
+ // eslint-disable-next-line react-hooks/exhaustive-deps
64
+ }, [editor, currentEditor])
65
+
66
+ return createPortal(<div {...restProps}>{children}</div>, menuEl.current)
67
+ },
68
+ )
@@ -0,0 +1,68 @@
1
+ import type { FloatingMenuPluginProps } from '@tiptap/extension-floating-menu'
2
+ import { FloatingMenuPlugin } from '@tiptap/extension-floating-menu'
3
+ import { useCurrentEditor } from '@tiptap/react'
4
+ import React, { useEffect, useRef } from 'react'
5
+ import { createPortal } from 'react-dom'
6
+
7
+ type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>
8
+
9
+ export type FloatingMenuProps = Omit<Optional<FloatingMenuPluginProps, 'pluginKey'>, 'element' | 'editor'> & {
10
+ editor: FloatingMenuPluginProps['editor'] | null
11
+ options?: FloatingMenuPluginProps['options']
12
+ } & React.HTMLAttributes<HTMLDivElement>
13
+
14
+ export const FloatingMenu = React.forwardRef<HTMLDivElement, FloatingMenuProps>(
15
+ ({ pluginKey = 'floatingMenu', editor, shouldShow = null, options, children, ...restProps }, ref) => {
16
+ const menuEl = useRef(document.createElement('div'))
17
+
18
+ if (typeof ref === 'function') {
19
+ ref(menuEl.current)
20
+ } else if (ref) {
21
+ ref.current = menuEl.current
22
+ }
23
+
24
+ const { editor: currentEditor } = useCurrentEditor()
25
+
26
+ useEffect(() => {
27
+ const floatingMenuElement = menuEl.current
28
+
29
+ floatingMenuElement.style.visibility = 'hidden'
30
+ floatingMenuElement.style.position = 'absolute'
31
+
32
+ if (editor?.isDestroyed || (currentEditor as any)?.isDestroyed) {
33
+ return
34
+ }
35
+
36
+ const attachToEditor = editor || currentEditor
37
+
38
+ if (!attachToEditor) {
39
+ console.warn(
40
+ 'FloatingMenu component is not rendered inside of an editor component or does not have editor prop.',
41
+ )
42
+ return
43
+ }
44
+
45
+ const plugin = FloatingMenuPlugin({
46
+ editor: attachToEditor,
47
+ element: floatingMenuElement,
48
+ pluginKey,
49
+ shouldShow,
50
+ options,
51
+ })
52
+
53
+ attachToEditor.registerPlugin(plugin)
54
+
55
+ return () => {
56
+ attachToEditor.unregisterPlugin(pluginKey)
57
+ window.requestAnimationFrame(() => {
58
+ if (floatingMenuElement.parentNode) {
59
+ floatingMenuElement.parentNode.removeChild(floatingMenuElement)
60
+ }
61
+ })
62
+ }
63
+ // eslint-disable-next-line react-hooks/exhaustive-deps
64
+ }, [editor, currentEditor])
65
+
66
+ return createPortal(<div {...restProps}>{children}</div>, menuEl.current)
67
+ },
68
+ )
@@ -0,0 +1,2 @@
1
+ export * from './BubbleMenu.js'
2
+ export * from './FloatingMenu.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
+ }