@tiptap/react 3.0.0 → 3.0.2-beta.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.
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
@@ -0,0 +1,111 @@
1
+ /* eslint-disable @typescript-eslint/no-shadow */
2
+ import type { MarkViewProps, MarkViewRenderer, MarkViewRendererOptions } from '@tiptap/core'
3
+ import { MarkView } from '@tiptap/core'
4
+ import React from 'react'
5
+
6
+ // import { flushSync } from 'react-dom'
7
+ import { ReactRenderer } from './ReactRenderer.js'
8
+
9
+ export interface MarkViewContextProps {
10
+ markViewContentRef: (element: HTMLElement | null) => void
11
+ }
12
+ export const ReactMarkViewContext = React.createContext<MarkViewContextProps>({
13
+ markViewContentRef: () => {
14
+ // do nothing
15
+ },
16
+ })
17
+
18
+ export type MarkViewContentProps<T extends keyof React.JSX.IntrinsicElements = 'span'> = {
19
+ as?: T
20
+ } & Omit<React.ComponentProps<T>, 'as'>
21
+
22
+ export const MarkViewContent = <T extends keyof React.JSX.IntrinsicElements = 'span'>(
23
+ props: MarkViewContentProps<T>,
24
+ ) => {
25
+ const { as: Tag = 'span', ...rest } = props
26
+ const { markViewContentRef } = React.useContext(ReactMarkViewContext)
27
+
28
+ return (
29
+ // @ts-ignore
30
+ <Tag {...rest} ref={markViewContentRef} data-mark-view-content="" />
31
+ )
32
+ }
33
+
34
+ export interface ReactMarkViewRendererOptions extends MarkViewRendererOptions {
35
+ /**
36
+ * The tag name of the element wrapping the React component.
37
+ */
38
+ as?: string
39
+ className?: string
40
+ attrs?: { [key: string]: string }
41
+ }
42
+
43
+ export class ReactMarkView extends MarkView<React.ComponentType<MarkViewProps>, ReactMarkViewRendererOptions> {
44
+ renderer: ReactRenderer
45
+ contentDOMElement: HTMLElement | null
46
+ didMountContentDomElement = false
47
+
48
+ constructor(
49
+ component: React.ComponentType<MarkViewProps>,
50
+ props: MarkViewProps,
51
+ options?: Partial<ReactMarkViewRendererOptions>,
52
+ ) {
53
+ super(component, props, options)
54
+
55
+ const { as = 'span', attrs, className = '' } = options || {}
56
+ const componentProps = { ...props, updateAttributes: this.updateAttributes.bind(this) } satisfies MarkViewProps
57
+
58
+ this.contentDOMElement = document.createElement('span')
59
+
60
+ const markViewContentRef: MarkViewContextProps['markViewContentRef'] = el => {
61
+ if (el && this.contentDOMElement && el.firstChild !== this.contentDOMElement) {
62
+ el.appendChild(this.contentDOMElement)
63
+ this.didMountContentDomElement = true
64
+ }
65
+ }
66
+ const context: MarkViewContextProps = {
67
+ markViewContentRef,
68
+ }
69
+
70
+ // For performance reasons, we memoize the provider component
71
+ // And all of the things it requires are declared outside of the component, so it doesn't need to re-render
72
+ const ReactMarkViewProvider: React.FunctionComponent<MarkViewProps> = React.memo(componentProps => {
73
+ return (
74
+ <ReactMarkViewContext.Provider value={context}>
75
+ {React.createElement(component, componentProps)}
76
+ </ReactMarkViewContext.Provider>
77
+ )
78
+ })
79
+
80
+ ReactMarkViewProvider.displayName = 'ReactNodeView'
81
+
82
+ this.renderer = new ReactRenderer(ReactMarkViewProvider, {
83
+ editor: props.editor,
84
+ props: componentProps,
85
+ as,
86
+ className: `mark-${props.mark.type.name} ${className}`.trim(),
87
+ })
88
+
89
+ if (attrs) {
90
+ this.renderer.updateAttributes(attrs)
91
+ }
92
+ }
93
+
94
+ get dom() {
95
+ return this.renderer.element as HTMLElement
96
+ }
97
+
98
+ get contentDOM() {
99
+ if (!this.didMountContentDomElement) {
100
+ return null
101
+ }
102
+ return this.contentDOMElement as HTMLElement
103
+ }
104
+ }
105
+
106
+ export function ReactMarkViewRenderer(
107
+ component: React.ComponentType<MarkViewProps>,
108
+ options: Partial<ReactMarkViewRendererOptions> = {},
109
+ ): MarkViewRenderer {
110
+ return props => new ReactMarkView(component, props, options)
111
+ }
@@ -1,54 +1,119 @@
1
- import {
1
+ import type {
2
2
  DecorationWithType,
3
- NodeView,
4
- NodeViewProps,
3
+ Editor,
5
4
  NodeViewRenderer,
6
5
  NodeViewRendererOptions,
7
6
  NodeViewRendererProps,
8
7
  } from '@tiptap/core'
9
- import { Node as ProseMirrorNode } from '@tiptap/pm/model'
10
- import { Decoration, NodeView as ProseMirrorNodeView } from '@tiptap/pm/view'
11
- import React from 'react'
8
+ import { getRenderedAttributes, NodeView } from '@tiptap/core'
9
+ import type { Node, Node as ProseMirrorNode } from '@tiptap/pm/model'
10
+ import type { Decoration, DecorationSource, NodeView as ProseMirrorNodeView } from '@tiptap/pm/view'
11
+ import type { ComponentType, NamedExoticComponent } from 'react'
12
+ import { createElement, createRef, memo } from 'react'
12
13
 
13
- import { Editor } from './Editor.js'
14
+ import type { EditorWithContentComponent } from './Editor.js'
14
15
  import { ReactRenderer } from './ReactRenderer.js'
15
- import { ReactNodeViewContext, ReactNodeViewContextProps } from './useReactNodeView.js'
16
+ import type { ReactNodeViewProps } from './types.js'
17
+ import type { ReactNodeViewContextProps } from './useReactNodeView.js'
18
+ import { ReactNodeViewContext } from './useReactNodeView.js'
16
19
 
17
20
  export interface ReactNodeViewRendererOptions extends NodeViewRendererOptions {
21
+ /**
22
+ * This function is called when the node view is updated.
23
+ * It allows you to compare the old node with the new node and decide if the component should update.
24
+ */
18
25
  update:
19
26
  | ((props: {
20
27
  oldNode: ProseMirrorNode
21
- oldDecorations: Decoration[]
28
+ oldDecorations: readonly Decoration[]
29
+ oldInnerDecorations: DecorationSource
22
30
  newNode: ProseMirrorNode
23
- newDecorations: Decoration[]
31
+ newDecorations: readonly Decoration[]
32
+ innerDecorations: DecorationSource
24
33
  updateProps: () => void
25
34
  }) => boolean)
26
35
  | null
36
+ /**
37
+ * The tag name of the element wrapping the React component.
38
+ */
27
39
  as?: string
40
+ /**
41
+ * The class name of the element wrapping the React component.
42
+ */
28
43
  className?: string
29
- attrs?: Record<string, string>
44
+ /**
45
+ * Attributes that should be applied to the element wrapping the React component.
46
+ * If this is a function, it will be called each time the node view is updated.
47
+ * If this is an object, it will be applied once when the node view is mounted.
48
+ */
49
+ attrs?:
50
+ | Record<string, string>
51
+ | ((props: { node: ProseMirrorNode; HTMLAttributes: Record<string, any> }) => Record<string, string>)
30
52
  }
31
53
 
32
- class ReactNodeView extends NodeView<
33
- React.FunctionComponent,
34
- Editor,
35
- ReactNodeViewRendererOptions
36
- > {
37
- renderer!: ReactRenderer
38
-
54
+ export class ReactNodeView<
55
+ T = HTMLElement,
56
+ Component extends ComponentType<ReactNodeViewProps<T>> = ComponentType<ReactNodeViewProps<T>>,
57
+ NodeEditor extends Editor = Editor,
58
+ Options extends ReactNodeViewRendererOptions = ReactNodeViewRendererOptions,
59
+ > extends NodeView<Component, NodeEditor, Options> {
60
+ /**
61
+ * The renderer instance.
62
+ */
63
+ renderer!: ReactRenderer<unknown, ReactNodeViewProps<T>>
64
+
65
+ /**
66
+ * The element that holds the rich-text content of the node.
67
+ */
39
68
  contentDOMElement!: HTMLElement | null
40
69
 
70
+ constructor(component: Component, props: NodeViewRendererProps, options?: Partial<Options>) {
71
+ super(component, props, options)
72
+
73
+ if (!this.node.isLeaf) {
74
+ if (this.options.contentDOMElementTag) {
75
+ this.contentDOMElement = document.createElement(this.options.contentDOMElementTag)
76
+ } else {
77
+ this.contentDOMElement = document.createElement(this.node.isInline ? 'span' : 'div')
78
+ }
79
+
80
+ this.contentDOMElement.dataset.nodeViewContentReact = ''
81
+ this.contentDOMElement.dataset.nodeViewWrapper = ''
82
+
83
+ // For some reason the whiteSpace prop is not inherited properly in Chrome and Safari
84
+ // With this fix it seems to work fine
85
+ // See: https://github.com/ueberdosis/tiptap/issues/1197
86
+ this.contentDOMElement.style.whiteSpace = 'inherit'
87
+
88
+ const contentTarget = this.dom.querySelector('[data-node-view-content]')
89
+
90
+ if (!contentTarget) {
91
+ return
92
+ }
93
+
94
+ contentTarget.appendChild(this.contentDOMElement)
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Setup the React component.
100
+ * Called on initialization.
101
+ */
41
102
  mount() {
42
- const props: NodeViewProps = {
103
+ const props = {
43
104
  editor: this.editor,
44
105
  node: this.node,
45
- decorations: this.decorations,
106
+ decorations: this.decorations as DecorationWithType[],
107
+ innerDecorations: this.innerDecorations,
108
+ view: this.view,
46
109
  selected: false,
47
110
  extension: this.extension,
111
+ HTMLAttributes: this.HTMLAttributes,
48
112
  getPos: () => this.getPos(),
49
113
  updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
50
114
  deleteNode: () => this.deleteNode(),
51
- }
115
+ ref: createRef<T>(),
116
+ } satisfies ReactNodeViewProps<T>
52
117
 
53
118
  if (!(this.component as any).displayName) {
54
119
  const capitalizeFirstChar = (string: string): string => {
@@ -58,43 +123,30 @@ class ReactNodeView extends NodeView<
58
123
  this.component.displayName = capitalizeFirstChar(this.extension.name)
59
124
  }
60
125
 
61
- const ReactNodeViewProvider: React.FunctionComponent = componentProps => {
62
- const Component = this.component
63
- const onDragStart = this.onDragStart.bind(this)
64
- const nodeViewContentRef: ReactNodeViewContextProps['nodeViewContentRef'] = element => {
65
- if (element && this.contentDOMElement && element.firstChild !== this.contentDOMElement) {
66
- element.appendChild(this.contentDOMElement)
126
+ const onDragStart = this.onDragStart.bind(this)
127
+ const nodeViewContentRef: ReactNodeViewContextProps['nodeViewContentRef'] = element => {
128
+ if (element && this.contentDOMElement && element.firstChild !== this.contentDOMElement) {
129
+ // remove the nodeViewWrapper attribute from the element
130
+ if (element.hasAttribute('data-node-view-wrapper')) {
131
+ element.removeAttribute('data-node-view-wrapper')
67
132
  }
133
+ element.appendChild(this.contentDOMElement)
68
134
  }
69
-
135
+ }
136
+ const context = { onDragStart, nodeViewContentRef }
137
+ const Component = this.component
138
+ // For performance reasons, we memoize the provider component
139
+ // And all of the things it requires are declared outside of the component, so it doesn't need to re-render
140
+ const ReactNodeViewProvider: NamedExoticComponent<ReactNodeViewProps<T>> = memo(componentProps => {
70
141
  return (
71
- <>
72
- {/* @ts-ignore */}
73
- <ReactNodeViewContext.Provider value={{ onDragStart, nodeViewContentRef }}>
74
- {/* @ts-ignore */}
75
- <Component {...componentProps} />
76
- </ReactNodeViewContext.Provider>
77
- </>
142
+ <ReactNodeViewContext.Provider value={context}>
143
+ {createElement(Component, componentProps)}
144
+ </ReactNodeViewContext.Provider>
78
145
  )
79
- }
146
+ })
80
147
 
81
148
  ReactNodeViewProvider.displayName = 'ReactNodeView'
82
149
 
83
- if (this.node.isLeaf) {
84
- this.contentDOMElement = null
85
- } else if (this.options.contentDOMElementTag) {
86
- this.contentDOMElement = document.createElement(this.options.contentDOMElementTag)
87
- } else {
88
- this.contentDOMElement = document.createElement(this.node.isInline ? 'span' : 'div')
89
- }
90
-
91
- if (this.contentDOMElement) {
92
- // For some reason the whiteSpace prop is not inherited properly in Chrome and Safari
93
- // With this fix it seems to work fine
94
- // See: https://github.com/ueberdosis/tiptap/issues/1197
95
- this.contentDOMElement.style.whiteSpace = 'inherit'
96
- }
97
-
98
150
  let as = this.node.isInline ? 'span' : 'div'
99
151
 
100
152
  if (this.options.as) {
@@ -104,21 +156,26 @@ class ReactNodeView extends NodeView<
104
156
  const { className = '' } = this.options
105
157
 
106
158
  this.handleSelectionUpdate = this.handleSelectionUpdate.bind(this)
107
- this.editor.on('selectionUpdate', this.handleSelectionUpdate)
108
159
 
109
160
  this.renderer = new ReactRenderer(ReactNodeViewProvider, {
110
161
  editor: this.editor,
111
162
  props,
112
163
  as,
113
164
  className: `node-${this.node.type.name} ${className}`.trim(),
114
- attrs: this.options.attrs,
115
165
  })
166
+
167
+ this.editor.on('selectionUpdate', this.handleSelectionUpdate)
168
+ this.updateElementAttributes()
116
169
  }
117
170
 
171
+ /**
172
+ * Return the DOM element.
173
+ * This is the element that will be used to display the node view.
174
+ */
118
175
  get dom() {
119
176
  if (
120
- this.renderer.element.firstElementChild
121
- && !this.renderer.element.firstElementChild?.hasAttribute('data-node-view-wrapper')
177
+ this.renderer.element.firstElementChild &&
178
+ !this.renderer.element.firstElementChild?.hasAttribute('data-node-view-wrapper')
122
179
  ) {
123
180
  throw Error('Please use the NodeViewWrapper component for your node view.')
124
181
  }
@@ -126,6 +183,10 @@ class ReactNodeView extends NodeView<
126
183
  return this.renderer.element as HTMLElement
127
184
  }
128
185
 
186
+ /**
187
+ * Return the content DOM element.
188
+ * This is the element that will be used to display the rich-text content of the node.
189
+ */
129
190
  get contentDOM() {
130
191
  if (this.node.isLeaf) {
131
192
  return null
@@ -134,10 +195,19 @@ class ReactNodeView extends NodeView<
134
195
  return this.contentDOMElement
135
196
  }
136
197
 
198
+ /**
199
+ * On editor selection update, check if the node is selected.
200
+ * If it is, call `selectNode`, otherwise call `deselectNode`.
201
+ */
137
202
  handleSelectionUpdate() {
138
203
  const { from, to } = this.editor.state.selection
204
+ const pos = this.getPos()
139
205
 
140
- if (from <= this.getPos() && to >= this.getPos() + this.node.nodeSize) {
206
+ if (typeof pos !== 'number') {
207
+ return
208
+ }
209
+
210
+ if (from <= pos && to >= pos + this.node.nodeSize) {
141
211
  if (this.renderer.props.selected) {
142
212
  return
143
213
  }
@@ -152,9 +222,16 @@ class ReactNodeView extends NodeView<
152
222
  }
153
223
  }
154
224
 
155
- update(node: ProseMirrorNode, decorations: DecorationWithType[]) {
156
- const updateProps = (props?: Record<string, any>) => {
225
+ /**
226
+ * On update, update the React component.
227
+ * To prevent unnecessary updates, the `update` option can be used.
228
+ */
229
+ update(node: Node, decorations: readonly Decoration[], innerDecorations: DecorationSource): boolean {
230
+ const rerenderComponent = (props?: Record<string, any>) => {
157
231
  this.renderer.updateProps(props)
232
+ if (typeof this.options.attrs === 'function') {
233
+ this.updateElementAttributes()
234
+ }
158
235
  }
159
236
 
160
237
  if (node.type !== this.node.type) {
@@ -164,31 +241,40 @@ class ReactNodeView extends NodeView<
164
241
  if (typeof this.options.update === 'function') {
165
242
  const oldNode = this.node
166
243
  const oldDecorations = this.decorations
244
+ const oldInnerDecorations = this.innerDecorations
167
245
 
168
246
  this.node = node
169
247
  this.decorations = decorations
248
+ this.innerDecorations = innerDecorations
170
249
 
171
250
  return this.options.update({
172
251
  oldNode,
173
252
  oldDecorations,
174
253
  newNode: node,
175
254
  newDecorations: decorations,
176
- updateProps: () => updateProps({ node, decorations }),
255
+ oldInnerDecorations,
256
+ innerDecorations,
257
+ updateProps: () => rerenderComponent({ node, decorations, innerDecorations }),
177
258
  })
178
259
  }
179
260
 
180
- if (node === this.node && this.decorations === decorations) {
261
+ if (node === this.node && this.decorations === decorations && this.innerDecorations === innerDecorations) {
181
262
  return true
182
263
  }
183
264
 
184
265
  this.node = node
185
266
  this.decorations = decorations
267
+ this.innerDecorations = innerDecorations
186
268
 
187
- updateProps({ node, decorations })
269
+ rerenderComponent({ node, decorations, innerDecorations })
188
270
 
189
271
  return true
190
272
  }
191
273
 
274
+ /**
275
+ * Select the node.
276
+ * Add the `selected` prop and the `ProseMirror-selectednode` class.
277
+ */
192
278
  selectNode() {
193
279
  this.renderer.updateProps({
194
280
  selected: true,
@@ -196,6 +282,10 @@ class ReactNodeView extends NodeView<
196
282
  this.renderer.element.classList.add('ProseMirror-selectednode')
197
283
  }
198
284
 
285
+ /**
286
+ * Deselect the node.
287
+ * Remove the `selected` prop and the `ProseMirror-selectednode` class.
288
+ */
199
289
  deselectNode() {
200
290
  this.renderer.updateProps({
201
291
  selected: false,
@@ -203,25 +293,52 @@ class ReactNodeView extends NodeView<
203
293
  this.renderer.element.classList.remove('ProseMirror-selectednode')
204
294
  }
205
295
 
296
+ /**
297
+ * Destroy the React component instance.
298
+ */
206
299
  destroy() {
207
300
  this.renderer.destroy()
208
301
  this.editor.off('selectionUpdate', this.handleSelectionUpdate)
209
302
  this.contentDOMElement = null
210
303
  }
304
+
305
+ /**
306
+ * Update the attributes of the top-level element that holds the React component.
307
+ * Applying the attributes defined in the `attrs` option.
308
+ */
309
+ updateElementAttributes() {
310
+ if (this.options.attrs) {
311
+ let attrsObj: Record<string, string> = {}
312
+
313
+ if (typeof this.options.attrs === 'function') {
314
+ const extensionAttributes = this.editor.extensionManager.attributes
315
+ const HTMLAttributes = getRenderedAttributes(this.node, extensionAttributes)
316
+
317
+ attrsObj = this.options.attrs({ node: this.node, HTMLAttributes })
318
+ } else {
319
+ attrsObj = this.options.attrs
320
+ }
321
+
322
+ this.renderer.updateAttributes(attrsObj)
323
+ }
324
+ }
211
325
  }
212
326
 
213
- export function ReactNodeViewRenderer(
214
- component: any,
327
+ /**
328
+ * Create a React node view renderer.
329
+ */
330
+ export function ReactNodeViewRenderer<T = HTMLElement>(
331
+ component: ComponentType<ReactNodeViewProps<T>>,
215
332
  options?: Partial<ReactNodeViewRendererOptions>,
216
333
  ): NodeViewRenderer {
217
- return (props: NodeViewRendererProps) => {
334
+ return props => {
218
335
  // try to get the parent component
219
336
  // this is important for vue devtools to show the component hierarchy correctly
220
337
  // maybe it’s `undefined` because <editor-content> isn’t rendered yet
221
- if (!(props.editor as Editor).contentComponent) {
222
- return {}
338
+ if (!(props.editor as EditorWithContentComponent).contentComponent) {
339
+ return {} as unknown as ProseMirrorNodeView
223
340
  }
224
341
 
225
- return new ReactNodeView(component, props, options) as unknown as ProseMirrorNodeView
342
+ return new ReactNodeView<T>(component, props, options)
226
343
  }
227
344
  }