@tiptap/react 2.11.7 → 3.0.0-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 (59) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +5 -1
  3. package/dist/index.cjs +967 -1473
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +345 -0
  6. package/dist/index.d.ts +344 -12
  7. package/dist/index.js +916 -1452
  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 +30 -18
  16. package/src/Context.tsx +18 -15
  17. package/src/Editor.ts +8 -8
  18. package/src/EditorContent.tsx +14 -22
  19. package/src/NodeViewContent.tsx +12 -8
  20. package/src/NodeViewWrapper.tsx +2 -2
  21. package/src/ReactMarkViewRenderer.tsx +109 -0
  22. package/src/ReactNodeViewRenderer.tsx +32 -50
  23. package/src/ReactRenderer.tsx +17 -26
  24. package/src/index.ts +1 -2
  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/useEditor.ts +32 -26
  29. package/src/useEditorState.ts +14 -19
  30. package/src/useReactNodeView.ts +21 -5
  31. package/dist/BubbleMenu.d.ts +0 -12
  32. package/dist/BubbleMenu.d.ts.map +0 -1
  33. package/dist/Context.d.ts +0 -25
  34. package/dist/Context.d.ts.map +0 -1
  35. package/dist/Editor.d.ts +0 -14
  36. package/dist/Editor.d.ts.map +0 -1
  37. package/dist/EditorContent.d.ts +0 -21
  38. package/dist/EditorContent.d.ts.map +0 -1
  39. package/dist/FloatingMenu.d.ts +0 -11
  40. package/dist/FloatingMenu.d.ts.map +0 -1
  41. package/dist/NodeViewContent.d.ts +0 -7
  42. package/dist/NodeViewContent.d.ts.map +0 -1
  43. package/dist/NodeViewWrapper.d.ts +0 -7
  44. package/dist/NodeViewWrapper.d.ts.map +0 -1
  45. package/dist/ReactNodeViewRenderer.d.ts +0 -96
  46. package/dist/ReactNodeViewRenderer.d.ts.map +0 -1
  47. package/dist/ReactRenderer.d.ts +0 -71
  48. package/dist/ReactRenderer.d.ts.map +0 -1
  49. package/dist/index.d.ts.map +0 -1
  50. package/dist/index.umd.js +0 -1540
  51. package/dist/index.umd.js.map +0 -1
  52. package/dist/useEditor.d.ts +0 -39
  53. package/dist/useEditor.d.ts.map +0 -1
  54. package/dist/useEditorState.d.ts +0 -45
  55. package/dist/useEditorState.d.ts.map +0 -1
  56. package/dist/useReactNodeView.d.ts +0 -7
  57. package/dist/useReactNodeView.d.ts.map +0 -1
  58. package/src/BubbleMenu.tsx +0 -57
  59. package/src/FloatingMenu.tsx +0 -64
@@ -0,0 +1,19 @@
1
+ import { BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu';
2
+ import React from 'react';
3
+ import { FloatingMenuPluginProps } from '@tiptap/extension-floating-menu';
4
+
5
+ type Optional$1<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
6
+ type BubbleMenuProps = Optional$1<Omit<Optional$1<BubbleMenuPluginProps, 'pluginKey'>, 'element'>, 'editor'> & React.HTMLAttributes<HTMLDivElement>;
7
+ declare const BubbleMenu: React.ForwardRefExoticComponent<Pick<Partial<Omit<Optional$1<BubbleMenuPluginProps, "pluginKey">, "element">>, "editor"> & Omit<Omit<Optional$1<BubbleMenuPluginProps, "pluginKey">, "element">, "editor"> & React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
8
+
9
+ type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
10
+ type FloatingMenuProps = Omit<Optional<FloatingMenuPluginProps, 'pluginKey'>, 'element' | 'editor'> & {
11
+ editor: FloatingMenuPluginProps['editor'] | null;
12
+ options?: FloatingMenuPluginProps['options'];
13
+ } & React.HTMLAttributes<HTMLDivElement>;
14
+ declare const FloatingMenu: React.ForwardRefExoticComponent<Omit<Optional<FloatingMenuPluginProps, "pluginKey">, "editor" | "element"> & {
15
+ editor: FloatingMenuPluginProps["editor"] | null;
16
+ options?: FloatingMenuPluginProps["options"];
17
+ } & React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
18
+
19
+ export { BubbleMenu, type BubbleMenuProps, FloatingMenu, type FloatingMenuProps };
@@ -0,0 +1,104 @@
1
+ // src/menus/BubbleMenu.tsx
2
+ import { BubbleMenuPlugin } from "@tiptap/extension-bubble-menu";
3
+ import { useCurrentEditor } from "@tiptap/react";
4
+ import React, { useEffect, useRef } from "react";
5
+ import { createPortal } from "react-dom";
6
+ import { jsx } from "react/jsx-runtime";
7
+ var BubbleMenu = React.forwardRef(
8
+ ({ pluginKey = "bubbleMenu", editor, updateDelay, resizeDelay, shouldShow = null, options, children, ...restProps }, ref) => {
9
+ const menuEl = useRef(document.createElement("div"));
10
+ if (typeof ref === "function") {
11
+ ref(menuEl.current);
12
+ } else if (ref) {
13
+ ref.current = menuEl.current;
14
+ }
15
+ const { editor: currentEditor } = useCurrentEditor();
16
+ useEffect(() => {
17
+ const bubbleMenuElement = menuEl.current;
18
+ bubbleMenuElement.style.visibility = "hidden";
19
+ bubbleMenuElement.style.position = "absolute";
20
+ if ((editor == null ? void 0 : editor.isDestroyed) || (currentEditor == null ? void 0 : currentEditor.isDestroyed)) {
21
+ return;
22
+ }
23
+ const attachToEditor = editor || currentEditor;
24
+ if (!attachToEditor) {
25
+ console.warn("BubbleMenu component is not rendered inside of an editor component or does not have editor prop.");
26
+ return;
27
+ }
28
+ const plugin = BubbleMenuPlugin({
29
+ updateDelay,
30
+ resizeDelay,
31
+ editor: attachToEditor,
32
+ element: bubbleMenuElement,
33
+ pluginKey,
34
+ shouldShow,
35
+ options
36
+ });
37
+ attachToEditor.registerPlugin(plugin);
38
+ return () => {
39
+ attachToEditor.unregisterPlugin(pluginKey);
40
+ window.requestAnimationFrame(() => {
41
+ if (bubbleMenuElement.parentNode) {
42
+ bubbleMenuElement.parentNode.removeChild(bubbleMenuElement);
43
+ }
44
+ });
45
+ };
46
+ }, [editor, currentEditor]);
47
+ return createPortal(/* @__PURE__ */ jsx("div", { ...restProps, children }), menuEl.current);
48
+ }
49
+ );
50
+
51
+ // src/menus/FloatingMenu.tsx
52
+ import { FloatingMenuPlugin } from "@tiptap/extension-floating-menu";
53
+ import { useCurrentEditor as useCurrentEditor2 } from "@tiptap/react";
54
+ import React2, { useEffect as useEffect2, useRef as useRef2 } from "react";
55
+ import { createPortal as createPortal2 } from "react-dom";
56
+ import { jsx as jsx2 } from "react/jsx-runtime";
57
+ var FloatingMenu = React2.forwardRef(
58
+ ({ pluginKey = "floatingMenu", editor, shouldShow = null, options, children, ...restProps }, ref) => {
59
+ const menuEl = useRef2(document.createElement("div"));
60
+ if (typeof ref === "function") {
61
+ ref(menuEl.current);
62
+ } else if (ref) {
63
+ ref.current = menuEl.current;
64
+ }
65
+ const { editor: currentEditor } = useCurrentEditor2();
66
+ useEffect2(() => {
67
+ const floatingMenuElement = menuEl.current;
68
+ floatingMenuElement.style.visibility = "hidden";
69
+ floatingMenuElement.style.position = "absolute";
70
+ if ((editor == null ? void 0 : editor.isDestroyed) || (currentEditor == null ? void 0 : currentEditor.isDestroyed)) {
71
+ return;
72
+ }
73
+ const attachToEditor = editor || currentEditor;
74
+ if (!attachToEditor) {
75
+ console.warn(
76
+ "FloatingMenu component is not rendered inside of an editor component or does not have editor prop."
77
+ );
78
+ return;
79
+ }
80
+ const plugin = FloatingMenuPlugin({
81
+ editor: attachToEditor,
82
+ element: floatingMenuElement,
83
+ pluginKey,
84
+ shouldShow,
85
+ options
86
+ });
87
+ attachToEditor.registerPlugin(plugin);
88
+ return () => {
89
+ attachToEditor.unregisterPlugin(pluginKey);
90
+ window.requestAnimationFrame(() => {
91
+ if (floatingMenuElement.parentNode) {
92
+ floatingMenuElement.parentNode.removeChild(floatingMenuElement);
93
+ }
94
+ });
95
+ };
96
+ }, [editor, currentEditor]);
97
+ return createPortal2(/* @__PURE__ */ jsx2("div", { ...restProps, children }), menuEl.current);
98
+ }
99
+ );
100
+ export {
101
+ BubbleMenu,
102
+ FloatingMenu
103
+ };
104
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/menus/BubbleMenu.tsx","../../src/menus/FloatingMenu.tsx"],"sourcesContent":["import { type BubbleMenuPluginProps, BubbleMenuPlugin } from '@tiptap/extension-bubble-menu'\nimport { useCurrentEditor } from '@tiptap/react'\nimport React, { useEffect, useRef } from 'react'\nimport { createPortal } from 'react-dom'\n\ntype Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>\n\nexport type BubbleMenuProps = Optional<Omit<Optional<BubbleMenuPluginProps, 'pluginKey'>, 'element'>, 'editor'> &\n React.HTMLAttributes<HTMLDivElement>\n\nexport const BubbleMenu = React.forwardRef<HTMLDivElement, BubbleMenuProps>(\n (\n { pluginKey = 'bubbleMenu', editor, updateDelay, resizeDelay, shouldShow = null, options, children, ...restProps },\n ref,\n ) => {\n const menuEl = useRef(document.createElement('div'))\n\n if (typeof ref === 'function') {\n ref(menuEl.current)\n } else if (ref) {\n ref.current = menuEl.current\n }\n\n const { editor: currentEditor } = useCurrentEditor()\n\n useEffect(() => {\n const bubbleMenuElement = menuEl.current\n\n bubbleMenuElement.style.visibility = 'hidden'\n bubbleMenuElement.style.position = 'absolute'\n\n if (editor?.isDestroyed || (currentEditor as any)?.isDestroyed) {\n return\n }\n\n const attachToEditor = editor || currentEditor\n\n if (!attachToEditor) {\n console.warn('BubbleMenu component is not rendered inside of an editor component or does not have editor prop.')\n return\n }\n\n const plugin = BubbleMenuPlugin({\n updateDelay,\n resizeDelay,\n editor: attachToEditor,\n element: bubbleMenuElement,\n pluginKey,\n shouldShow,\n options,\n })\n\n attachToEditor.registerPlugin(plugin)\n\n return () => {\n attachToEditor.unregisterPlugin(pluginKey)\n window.requestAnimationFrame(() => {\n if (bubbleMenuElement.parentNode) {\n bubbleMenuElement.parentNode.removeChild(bubbleMenuElement)\n }\n })\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [editor, currentEditor])\n\n return createPortal(<div {...restProps}>{children}</div>, menuEl.current)\n },\n)\n","import type { FloatingMenuPluginProps } from '@tiptap/extension-floating-menu'\nimport { FloatingMenuPlugin } from '@tiptap/extension-floating-menu'\nimport { useCurrentEditor } from '@tiptap/react'\nimport React, { useEffect, useRef } from 'react'\nimport { createPortal } from 'react-dom'\n\ntype Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>\n\nexport type FloatingMenuProps = Omit<Optional<FloatingMenuPluginProps, 'pluginKey'>, 'element' | 'editor'> & {\n editor: FloatingMenuPluginProps['editor'] | null\n options?: FloatingMenuPluginProps['options']\n} & React.HTMLAttributes<HTMLDivElement>\n\nexport const FloatingMenu = React.forwardRef<HTMLDivElement, FloatingMenuProps>(\n ({ pluginKey = 'floatingMenu', editor, shouldShow = null, options, children, ...restProps }, ref) => {\n const menuEl = useRef(document.createElement('div'))\n\n if (typeof ref === 'function') {\n ref(menuEl.current)\n } else if (ref) {\n ref.current = menuEl.current\n }\n\n const { editor: currentEditor } = useCurrentEditor()\n\n useEffect(() => {\n const floatingMenuElement = menuEl.current\n\n floatingMenuElement.style.visibility = 'hidden'\n floatingMenuElement.style.position = 'absolute'\n\n if (editor?.isDestroyed || (currentEditor as any)?.isDestroyed) {\n return\n }\n\n const attachToEditor = editor || currentEditor\n\n if (!attachToEditor) {\n console.warn(\n 'FloatingMenu component is not rendered inside of an editor component or does not have editor prop.',\n )\n return\n }\n\n const plugin = FloatingMenuPlugin({\n editor: attachToEditor,\n element: floatingMenuElement,\n pluginKey,\n shouldShow,\n options,\n })\n\n attachToEditor.registerPlugin(plugin)\n\n return () => {\n attachToEditor.unregisterPlugin(pluginKey)\n window.requestAnimationFrame(() => {\n if (floatingMenuElement.parentNode) {\n floatingMenuElement.parentNode.removeChild(floatingMenuElement)\n }\n })\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [editor, currentEditor])\n\n return createPortal(<div {...restProps}>{children}</div>, menuEl.current)\n },\n)\n"],"mappings":"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","names":["useCurrentEditor","React","useEffect","useRef","createPortal","jsx"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tiptap/react",
3
3
  "description": "React components for tiptap",
4
- "version": "2.11.7",
4
+ "version": "3.0.0-beta.0",
5
5
  "homepage": "https://tiptap.dev",
6
6
  "keywords": [
7
7
  "tiptap",
@@ -14,14 +14,24 @@
14
14
  },
15
15
  "exports": {
16
16
  ".": {
17
- "types": "./dist/index.d.ts",
17
+ "types": {
18
+ "import": "./dist/index.d.ts",
19
+ "require": "./dist/index.d.cts"
20
+ },
18
21
  "import": "./dist/index.js",
19
22
  "require": "./dist/index.cjs"
23
+ },
24
+ "./menus": {
25
+ "types": {
26
+ "import": "./dist/menus/index.d.ts",
27
+ "require": "./dist/menus/index.d.cts"
28
+ },
29
+ "import": "./dist/menus/index.js",
30
+ "require": "./dist/menus/index.cjs"
20
31
  }
21
32
  },
22
33
  "main": "dist/index.cjs",
23
34
  "module": "dist/index.js",
24
- "umd": "dist/index.umd.js",
25
35
  "types": "dist/index.d.ts",
26
36
  "type": "module",
27
37
  "files": [
@@ -29,23 +39,25 @@
29
39
  "dist"
30
40
  ],
31
41
  "dependencies": {
32
- "@tiptap/extension-bubble-menu": "^2.11.7",
33
- "@tiptap/extension-floating-menu": "^2.11.7",
34
42
  "@types/use-sync-external-store": "^0.0.6",
35
- "fast-deep-equal": "^3",
36
- "use-sync-external-store": "^1"
43
+ "fast-deep-equal": "^3.1.3",
44
+ "use-sync-external-store": "^1.4.0"
37
45
  },
38
46
  "devDependencies": {
39
- "@tiptap/core": "^2.11.7",
40
- "@tiptap/pm": "^2.11.7",
41
- "@types/react": "^18.0.0",
42
- "@types/react-dom": "^18.0.0",
43
- "react": "^18.0.0",
44
- "react-dom": "^18.0.0"
47
+ "@tiptap/core": "^3.0.0-beta.0",
48
+ "@tiptap/pm": "^3.0.0-beta.0",
49
+ "@types/react": "^18.3.18",
50
+ "@types/react-dom": "^18.3.5",
51
+ "react": "^18.3.1",
52
+ "react-dom": "^18.3.1"
53
+ },
54
+ "optionalDependencies": {
55
+ "@tiptap/extension-bubble-menu": "^3.0.0-beta.0",
56
+ "@tiptap/extension-floating-menu": "^3.0.0-beta.0"
45
57
  },
46
58
  "peerDependencies": {
47
- "@tiptap/core": "^2.7.0",
48
- "@tiptap/pm": "^2.7.0",
59
+ "@tiptap/core": "^3.0.0-beta.0",
60
+ "@tiptap/pm": "^3.0.0-beta.0",
49
61
  "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
50
62
  "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
51
63
  },
@@ -56,7 +68,7 @@
56
68
  },
57
69
  "sideEffects": false,
58
70
  "scripts": {
59
- "clean": "rm -rf dist",
60
- "build": "npm run clean && rollup -c"
71
+ "build": "tsup",
72
+ "lint": "prettier ./src/ --check && eslint --cache --quiet --no-error-on-unmatched-pattern ./src/"
61
73
  }
62
- }
74
+ }
package/src/Context.tsx CHANGED
@@ -1,13 +1,13 @@
1
- import { Editor } from '@tiptap/core'
2
- import React, {
3
- createContext, HTMLAttributes, ReactNode, useContext,
4
- } from 'react'
1
+ import type { Editor } from '@tiptap/core'
2
+ import type { HTMLAttributes, ReactNode } from 'react'
3
+ import React, { createContext, useContext, useMemo } from 'react'
5
4
 
6
5
  import { EditorContent } from './EditorContent.js'
7
- import { useEditor, UseEditorOptions } from './useEditor.js'
6
+ import type { UseEditorOptions } from './useEditor.js'
7
+ import { useEditor } from './useEditor.js'
8
8
 
9
9
  export type EditorContextValue = {
10
- editor: Editor | null;
10
+ editor: Editor | null
11
11
  }
12
12
 
13
13
  export const EditorContext = createContext<EditorContextValue>({
@@ -22,10 +22,10 @@ export const EditorConsumer = EditorContext.Consumer
22
22
  export const useCurrentEditor = () => useContext(EditorContext)
23
23
 
24
24
  export type EditorProviderProps = {
25
- children?: ReactNode;
26
- slotBefore?: ReactNode;
27
- slotAfter?: ReactNode;
28
- editorContainerProps?: HTMLAttributes<HTMLDivElement>;
25
+ children?: ReactNode
26
+ slotBefore?: ReactNode
27
+ slotAfter?: ReactNode
28
+ editorContainerProps?: HTMLAttributes<HTMLDivElement>
29
29
  } & UseEditorOptions
30
30
 
31
31
  /**
@@ -34,21 +34,24 @@ export type EditorProviderProps = {
34
34
  * with `useCurrentEditor`.
35
35
  */
36
36
  export function EditorProvider({
37
- children, slotAfter, slotBefore, editorContainerProps = {}, ...editorOptions
37
+ children,
38
+ slotAfter,
39
+ slotBefore,
40
+ editorContainerProps = {},
41
+ ...editorOptions
38
42
  }: EditorProviderProps) {
39
43
  const editor = useEditor(editorOptions)
44
+ const contextValue = useMemo(() => ({ editor }), [editor])
40
45
 
41
46
  if (!editor) {
42
47
  return null
43
48
  }
44
49
 
45
50
  return (
46
- <EditorContext.Provider value={{ editor }}>
51
+ <EditorContext.Provider value={contextValue}>
47
52
  {slotBefore}
48
53
  <EditorConsumer>
49
- {({ editor: currentEditor }) => (
50
- <EditorContent editor={currentEditor} {...editorContainerProps} />
51
- )}
54
+ {({ editor: currentEditor }) => <EditorContent editor={currentEditor} {...editorContainerProps} />}
52
55
  </EditorConsumer>
53
56
  {children}
54
57
  {slotAfter}
package/src/Editor.ts CHANGED
@@ -1,13 +1,13 @@
1
- import { Editor } from '@tiptap/core'
2
- import { ReactPortal } from 'react'
1
+ import type { Editor } from '@tiptap/core'
2
+ import type { ReactPortal } from 'react'
3
3
 
4
- import { ReactRenderer } from './ReactRenderer.js'
4
+ import type { ReactRenderer } from './ReactRenderer.js'
5
5
 
6
6
  export type EditorWithContentComponent = Editor & { contentComponent?: ContentComponent | null }
7
7
  export type ContentComponent = {
8
- setRenderer(id: string, renderer: ReactRenderer): void;
9
- removeRenderer(id: string): void;
10
- subscribe: (callback: () => void) => () => void;
11
- getSnapshot: () => Record<string, ReactPortal>;
12
- getServerSnapshot: () => Record<string, ReactPortal>;
8
+ setRenderer(id: string, renderer: ReactRenderer): void
9
+ removeRenderer(id: string): void
10
+ subscribe: (callback: () => void) => () => void
11
+ getSnapshot: () => Record<string, ReactPortal>
12
+ getServerSnapshot: () => Record<string, ReactPortal>
13
13
  }
@@ -1,22 +1,19 @@
1
- import { Editor } from '@tiptap/core'
2
- import React, {
3
- ForwardedRef, forwardRef, HTMLProps, LegacyRef, MutableRefObject,
4
- } from 'react'
1
+ import type { Editor } from '@tiptap/core'
2
+ import type { ForwardedRef, HTMLProps, LegacyRef, MutableRefObject } from 'react'
3
+ import React, { forwardRef } from 'react'
5
4
  import ReactDOM from 'react-dom'
6
5
  import { useSyncExternalStore } from 'use-sync-external-store/shim'
7
6
 
8
- import { ContentComponent, EditorWithContentComponent } from './Editor.js'
9
- import { ReactRenderer } from './ReactRenderer.js'
7
+ import type { ContentComponent, EditorWithContentComponent } from './Editor.js'
8
+ import type { ReactRenderer } from './ReactRenderer.js'
10
9
 
11
- const mergeRefs = <T extends HTMLDivElement>(
12
- ...refs: Array<MutableRefObject<T> | LegacyRef<T> | undefined>
13
- ) => {
10
+ const mergeRefs = <T extends HTMLDivElement>(...refs: Array<MutableRefObject<T> | LegacyRef<T> | undefined>) => {
14
11
  return (node: T) => {
15
12
  refs.forEach(ref => {
16
13
  if (typeof ref === 'function') {
17
14
  ref(node)
18
15
  } else if (ref) {
19
- (ref as MutableRefObject<T | null>).current = node
16
+ ;(ref as MutableRefObject<T | null>).current = node
20
17
  }
21
18
  })
22
19
  }
@@ -25,9 +22,7 @@ const mergeRefs = <T extends HTMLDivElement>(
25
22
  /**
26
23
  * This component renders all of the editor's node views.
27
24
  */
28
- const Portals: React.FC<{ contentComponent: ContentComponent }> = ({
29
- contentComponent,
30
- }) => {
25
+ const Portals: React.FC<{ contentComponent: ContentComponent }> = ({ contentComponent }) => {
31
26
  // For performance reasons, we render the node view portals on state changes only
32
27
  const renderers = useSyncExternalStore(
33
28
  contentComponent.subscribe,
@@ -36,16 +31,12 @@ const Portals: React.FC<{ contentComponent: ContentComponent }> = ({
36
31
  )
37
32
 
38
33
  // This allows us to directly render the portals without any additional wrapper
39
- return (
40
- <>
41
- {Object.values(renderers)}
42
- </>
43
- )
34
+ return <>{Object.values(renderers)}</>
44
35
  }
45
36
 
46
37
  export interface EditorContentProps extends HTMLProps<HTMLDivElement> {
47
- editor: Editor | null;
48
- innerRef?: ForwardedRef<HTMLDivElement | null>;
38
+ editor: Editor | null
39
+ innerRef?: ForwardedRef<HTMLDivElement | null>
49
40
  }
50
41
 
51
42
  function getInstance(): ContentComponent {
@@ -185,10 +176,11 @@ export class PureEditorContent extends React.Component<
185
176
 
186
177
  editor.contentComponent = null
187
178
 
188
- if (!editor.options.element.firstChild) {
179
+ if (!editor.options.element?.firstChild) {
189
180
  return
190
181
  }
191
182
 
183
+ // TODO using the new editor.mount method might allow us to remove this
192
184
  const newElement = document.createElement('div')
193
185
 
194
186
  newElement.append(...editor.options.element.childNodes)
@@ -216,7 +208,7 @@ const EditorContentWithKey = forwardRef<HTMLDivElement, EditorContentProps>(
216
208
  (props: Omit<EditorContentProps, 'innerRef'>, ref) => {
217
209
  const key = React.useMemo(() => {
218
210
  return Math.floor(Math.random() * 0xffffffff).toString()
219
- // eslint-disable-next-line react-hooks/exhaustive-deps
211
+ // eslint-disable-next-line react-hooks/exhaustive-deps
220
212
  }, [props.editor])
221
213
 
222
214
  // Can't use JSX here because it conflicts with the type definition of Vue's JSX, so use createElement
@@ -1,15 +1,17 @@
1
+ import type { ComponentProps } from 'react'
1
2
  import React from 'react'
2
3
 
3
4
  import { useReactNodeView } from './useReactNodeView.js'
4
5
 
5
- export interface NodeViewContentProps {
6
- [key: string]: any,
7
- as?: React.ElementType,
8
- }
6
+ export type NodeViewContentProps<T extends keyof React.JSX.IntrinsicElements = 'div'> = {
7
+ as?: NoInfer<T>
8
+ } & ComponentProps<T>
9
9
 
10
- export const NodeViewContent: React.FC<NodeViewContentProps> = props => {
11
- const Tag = props.as || 'div'
12
- const { nodeViewContentRef } = useReactNodeView()
10
+ export function NodeViewContent<T extends keyof React.JSX.IntrinsicElements = 'div'>({
11
+ as: Tag = 'div' as T,
12
+ ...props
13
+ }: NodeViewContentProps<T>) {
14
+ const { nodeViewContentRef, nodeViewContentChildren } = useReactNodeView()
13
15
 
14
16
  return (
15
17
  // @ts-ignore
@@ -21,6 +23,8 @@ export const NodeViewContent: React.FC<NodeViewContentProps> = props => {
21
23
  whiteSpace: 'pre-wrap',
22
24
  ...props.style,
23
25
  }}
24
- />
26
+ >
27
+ {nodeViewContentChildren}
28
+ </Tag>
25
29
  )
26
30
  }
@@ -3,8 +3,8 @@ import React from 'react'
3
3
  import { useReactNodeView } from './useReactNodeView.js'
4
4
 
5
5
  export interface NodeViewWrapperProps {
6
- [key: string]: any,
7
- as?: React.ElementType,
6
+ [key: string]: any
7
+ as?: React.ElementType
8
8
  }
9
9
 
10
10
  export const NodeViewWrapper: React.FC<NodeViewWrapperProps> = React.forwardRef((props, ref) => {
@@ -0,0 +1,109 @@
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?: NoInfer<T>
20
+ } & React.ComponentProps<T>
21
+
22
+ export const MarkViewContent: React.FC<MarkViewContentProps> = props => {
23
+ const Tag = props.as || 'span'
24
+ const { markViewContentRef } = React.useContext(ReactMarkViewContext)
25
+
26
+ return (
27
+ // @ts-ignore
28
+ <Tag {...props} ref={markViewContentRef} data-mark-view-content="" />
29
+ )
30
+ }
31
+
32
+ export interface ReactMarkViewRendererOptions extends MarkViewRendererOptions {
33
+ /**
34
+ * The tag name of the element wrapping the React component.
35
+ */
36
+ as?: string
37
+ className?: string
38
+ attrs?: { [key: string]: string }
39
+ }
40
+
41
+ export class ReactMarkView extends MarkView<React.ComponentType<MarkViewProps>, ReactMarkViewRendererOptions> {
42
+ renderer: ReactRenderer
43
+ contentDOMElement: HTMLElement | null
44
+ didMountContentDomElement = false
45
+
46
+ constructor(
47
+ component: React.ComponentType<MarkViewProps>,
48
+ props: MarkViewProps,
49
+ options?: Partial<ReactMarkViewRendererOptions>,
50
+ ) {
51
+ super(component, props, options)
52
+
53
+ const { as = 'span', attrs, className = '' } = options || {}
54
+ const componentProps = props satisfies MarkViewProps
55
+
56
+ this.contentDOMElement = document.createElement('span')
57
+
58
+ const markViewContentRef: MarkViewContextProps['markViewContentRef'] = el => {
59
+ if (el && this.contentDOMElement && el.firstChild !== this.contentDOMElement) {
60
+ el.appendChild(this.contentDOMElement)
61
+ this.didMountContentDomElement = true
62
+ }
63
+ }
64
+ const context: MarkViewContextProps = {
65
+ markViewContentRef,
66
+ }
67
+
68
+ // For performance reasons, we memoize the provider component
69
+ // And all of the things it requires are declared outside of the component, so it doesn't need to re-render
70
+ const ReactMarkViewProvider: React.FunctionComponent<MarkViewProps> = React.memo(componentProps => {
71
+ return (
72
+ <ReactMarkViewContext.Provider value={context}>
73
+ {React.createElement(component, componentProps)}
74
+ </ReactMarkViewContext.Provider>
75
+ )
76
+ })
77
+
78
+ ReactMarkViewProvider.displayName = 'ReactNodeView'
79
+
80
+ this.renderer = new ReactRenderer(ReactMarkViewProvider, {
81
+ editor: props.editor,
82
+ props: componentProps,
83
+ as,
84
+ className: `mark-${props.mark.type.name} ${className}`.trim(),
85
+ })
86
+
87
+ if (attrs) {
88
+ this.renderer.updateAttributes(attrs)
89
+ }
90
+ }
91
+
92
+ get dom() {
93
+ return this.renderer.element as HTMLElement
94
+ }
95
+
96
+ get contentDOM() {
97
+ if (!this.didMountContentDomElement) {
98
+ return null
99
+ }
100
+ return this.contentDOMElement as HTMLElement
101
+ }
102
+ }
103
+
104
+ export function ReactMarkViewRenderer(
105
+ component: React.ComponentType<MarkViewProps>,
106
+ options: Partial<ReactMarkViewRendererOptions> = {},
107
+ ): MarkViewRenderer {
108
+ return props => new ReactMarkView(component, props, options)
109
+ }