@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.
- package/LICENSE.md +21 -0
- package/README.md +5 -1
- package/dist/index.cjs +1030 -1163
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +350 -0
- package/dist/index.d.ts +350 -0
- package/dist/index.js +970 -1138
- package/dist/index.js.map +1 -1
- package/dist/menus/index.cjs +142 -0
- package/dist/menus/index.cjs.map +1 -0
- package/dist/menus/index.d.cts +19 -0
- package/dist/menus/index.d.ts +19 -0
- package/dist/menus/index.js +104 -0
- package/dist/menus/index.js.map +1 -0
- package/package.json +34 -21
- package/src/Context.tsx +18 -12
- package/src/Editor.ts +10 -11
- package/src/EditorContent.tsx +104 -64
- package/src/NodeViewContent.tsx +13 -8
- package/src/NodeViewWrapper.tsx +3 -2
- package/src/ReactMarkViewRenderer.tsx +111 -0
- package/src/ReactNodeViewRenderer.tsx +184 -67
- package/src/ReactRenderer.tsx +152 -51
- package/src/index.ts +2 -3
- package/src/menus/BubbleMenu.tsx +68 -0
- package/src/menus/FloatingMenu.tsx +68 -0
- package/src/menus/index.ts +2 -0
- package/src/types.ts +6 -0
- package/src/useEditor.ts +286 -166
- package/src/useEditorState.ts +133 -85
- package/src/useReactNodeView.ts +21 -5
- package/dist/index.umd.js +0 -1219
- package/dist/index.umd.js.map +0 -1
- package/dist/packages/core/src/CommandManager.d.ts +0 -20
- package/dist/packages/core/src/Editor.d.ts +0 -161
- package/dist/packages/core/src/EventEmitter.d.ts +0 -11
- package/dist/packages/core/src/Extension.d.ts +0 -343
- package/dist/packages/core/src/ExtensionManager.d.ts +0 -55
- package/dist/packages/core/src/InputRule.d.ts +0 -42
- package/dist/packages/core/src/Mark.d.ts +0 -451
- package/dist/packages/core/src/Node.d.ts +0 -611
- package/dist/packages/core/src/NodePos.d.ts +0 -44
- package/dist/packages/core/src/NodeView.d.ts +0 -31
- package/dist/packages/core/src/PasteRule.d.ts +0 -50
- package/dist/packages/core/src/Tracker.d.ts +0 -11
- package/dist/packages/core/src/commands/blur.d.ts +0 -13
- package/dist/packages/core/src/commands/clearContent.d.ts +0 -14
- package/dist/packages/core/src/commands/clearNodes.d.ts +0 -13
- package/dist/packages/core/src/commands/command.d.ts +0 -18
- package/dist/packages/core/src/commands/createParagraphNear.d.ts +0 -13
- package/dist/packages/core/src/commands/cut.d.ts +0 -20
- package/dist/packages/core/src/commands/deleteCurrentNode.d.ts +0 -13
- package/dist/packages/core/src/commands/deleteNode.d.ts +0 -15
- package/dist/packages/core/src/commands/deleteRange.d.ts +0 -14
- package/dist/packages/core/src/commands/deleteSelection.d.ts +0 -13
- package/dist/packages/core/src/commands/enter.d.ts +0 -13
- package/dist/packages/core/src/commands/exitCode.d.ts +0 -13
- package/dist/packages/core/src/commands/extendMarkRange.d.ts +0 -25
- package/dist/packages/core/src/commands/first.d.ts +0 -14
- package/dist/packages/core/src/commands/focus.d.ts +0 -27
- package/dist/packages/core/src/commands/forEach.d.ts +0 -14
- package/dist/packages/core/src/commands/index.d.ts +0 -55
- package/dist/packages/core/src/commands/insertContent.d.ts +0 -34
- package/dist/packages/core/src/commands/insertContentAt.d.ts +0 -47
- package/dist/packages/core/src/commands/join.d.ts +0 -41
- package/dist/packages/core/src/commands/joinItemBackward.d.ts +0 -13
- package/dist/packages/core/src/commands/joinItemForward.d.ts +0 -13
- package/dist/packages/core/src/commands/joinTextblockBackward.d.ts +0 -12
- package/dist/packages/core/src/commands/joinTextblockForward.d.ts +0 -12
- package/dist/packages/core/src/commands/keyboardShortcut.d.ts +0 -14
- package/dist/packages/core/src/commands/lift.d.ts +0 -17
- package/dist/packages/core/src/commands/liftEmptyBlock.d.ts +0 -13
- package/dist/packages/core/src/commands/liftListItem.d.ts +0 -15
- package/dist/packages/core/src/commands/newlineInCode.d.ts +0 -13
- package/dist/packages/core/src/commands/resetAttributes.d.ts +0 -16
- package/dist/packages/core/src/commands/scrollIntoView.d.ts +0 -13
- package/dist/packages/core/src/commands/selectAll.d.ts +0 -13
- package/dist/packages/core/src/commands/selectNodeBackward.d.ts +0 -13
- package/dist/packages/core/src/commands/selectNodeForward.d.ts +0 -13
- package/dist/packages/core/src/commands/selectParentNode.d.ts +0 -13
- package/dist/packages/core/src/commands/selectTextblockEnd.d.ts +0 -13
- package/dist/packages/core/src/commands/selectTextblockStart.d.ts +0 -13
- package/dist/packages/core/src/commands/setContent.d.ts +0 -40
- package/dist/packages/core/src/commands/setMark.d.ts +0 -15
- package/dist/packages/core/src/commands/setMeta.d.ts +0 -15
- package/dist/packages/core/src/commands/setNode.d.ts +0 -16
- package/dist/packages/core/src/commands/setNodeSelection.d.ts +0 -14
- package/dist/packages/core/src/commands/setTextSelection.d.ts +0 -14
- package/dist/packages/core/src/commands/sinkListItem.d.ts +0 -15
- package/dist/packages/core/src/commands/splitBlock.d.ts +0 -17
- package/dist/packages/core/src/commands/splitListItem.d.ts +0 -15
- package/dist/packages/core/src/commands/toggleList.d.ts +0 -18
- package/dist/packages/core/src/commands/toggleMark.d.ts +0 -30
- package/dist/packages/core/src/commands/toggleNode.d.ts +0 -17
- package/dist/packages/core/src/commands/toggleWrap.d.ts +0 -16
- package/dist/packages/core/src/commands/undoInputRule.d.ts +0 -13
- package/dist/packages/core/src/commands/unsetAllMarks.d.ts +0 -13
- package/dist/packages/core/src/commands/unsetMark.d.ts +0 -25
- package/dist/packages/core/src/commands/updateAttributes.d.ts +0 -24
- package/dist/packages/core/src/commands/wrapIn.d.ts +0 -16
- package/dist/packages/core/src/commands/wrapInList.d.ts +0 -16
- package/dist/packages/core/src/extensions/clipboardTextSerializer.d.ts +0 -5
- package/dist/packages/core/src/extensions/commands.d.ts +0 -3
- package/dist/packages/core/src/extensions/editable.d.ts +0 -2
- package/dist/packages/core/src/extensions/focusEvents.d.ts +0 -2
- package/dist/packages/core/src/extensions/index.d.ts +0 -6
- package/dist/packages/core/src/extensions/keymap.d.ts +0 -2
- package/dist/packages/core/src/extensions/tabindex.d.ts +0 -2
- package/dist/packages/core/src/helpers/combineTransactionSteps.d.ts +0 -10
- package/dist/packages/core/src/helpers/createChainableState.d.ts +0 -10
- package/dist/packages/core/src/helpers/createDocument.d.ts +0 -12
- package/dist/packages/core/src/helpers/createNodeFromContent.d.ts +0 -15
- package/dist/packages/core/src/helpers/defaultBlockAt.d.ts +0 -7
- package/dist/packages/core/src/helpers/findChildren.d.ts +0 -9
- package/dist/packages/core/src/helpers/findChildrenInRange.d.ts +0 -10
- package/dist/packages/core/src/helpers/findParentNode.d.ts +0 -16
- package/dist/packages/core/src/helpers/findParentNodeClosestToPos.d.ts +0 -17
- package/dist/packages/core/src/helpers/generateHTML.d.ts +0 -8
- package/dist/packages/core/src/helpers/generateJSON.d.ts +0 -8
- package/dist/packages/core/src/helpers/generateText.d.ts +0 -12
- package/dist/packages/core/src/helpers/getAttributes.d.ts +0 -9
- package/dist/packages/core/src/helpers/getAttributesFromExtensions.d.ts +0 -6
- package/dist/packages/core/src/helpers/getChangedRanges.d.ts +0 -11
- package/dist/packages/core/src/helpers/getDebugJSON.d.ts +0 -8
- package/dist/packages/core/src/helpers/getExtensionField.d.ts +0 -9
- package/dist/packages/core/src/helpers/getHTMLFromFragment.d.ts +0 -2
- package/dist/packages/core/src/helpers/getMarkAttributes.d.ts +0 -3
- package/dist/packages/core/src/helpers/getMarkRange.d.ts +0 -3
- package/dist/packages/core/src/helpers/getMarkType.d.ts +0 -2
- package/dist/packages/core/src/helpers/getMarksBetween.d.ts +0 -3
- package/dist/packages/core/src/helpers/getNodeAtPosition.d.ts +0 -11
- package/dist/packages/core/src/helpers/getNodeAttributes.d.ts +0 -3
- package/dist/packages/core/src/helpers/getNodeType.d.ts +0 -2
- package/dist/packages/core/src/helpers/getRenderedAttributes.d.ts +0 -3
- package/dist/packages/core/src/helpers/getSchema.d.ts +0 -4
- package/dist/packages/core/src/helpers/getSchemaByResolvedExtensions.d.ts +0 -10
- package/dist/packages/core/src/helpers/getSchemaTypeByName.d.ts +0 -8
- package/dist/packages/core/src/helpers/getSchemaTypeNameByName.d.ts +0 -8
- package/dist/packages/core/src/helpers/getSplittedAttributes.d.ts +0 -9
- package/dist/packages/core/src/helpers/getText.d.ts +0 -15
- package/dist/packages/core/src/helpers/getTextBetween.d.ts +0 -14
- package/dist/packages/core/src/helpers/getTextContentFromNodes.d.ts +0 -8
- package/dist/packages/core/src/helpers/getTextSerializersFromSchema.d.ts +0 -8
- package/dist/packages/core/src/helpers/index.d.ts +0 -50
- package/dist/packages/core/src/helpers/injectExtensionAttributesToParseRule.d.ts +0 -9
- package/dist/packages/core/src/helpers/isActive.d.ts +0 -2
- package/dist/packages/core/src/helpers/isAtEndOfNode.d.ts +0 -2
- package/dist/packages/core/src/helpers/isAtStartOfNode.d.ts +0 -2
- package/dist/packages/core/src/helpers/isExtensionRulesEnabled.d.ts +0 -2
- package/dist/packages/core/src/helpers/isList.d.ts +0 -2
- package/dist/packages/core/src/helpers/isMarkActive.d.ts +0 -3
- package/dist/packages/core/src/helpers/isNodeActive.d.ts +0 -3
- package/dist/packages/core/src/helpers/isNodeEmpty.d.ts +0 -2
- package/dist/packages/core/src/helpers/isNodeSelection.d.ts +0 -2
- package/dist/packages/core/src/helpers/isTextSelection.d.ts +0 -2
- package/dist/packages/core/src/helpers/posToDOMRect.d.ts +0 -2
- package/dist/packages/core/src/helpers/resolveFocusPosition.d.ts +0 -4
- package/dist/packages/core/src/helpers/selectionToInsertionEnd.d.ts +0 -2
- package/dist/packages/core/src/helpers/splitExtensions.d.ts +0 -9
- package/dist/packages/core/src/index.d.ts +0 -24
- package/dist/packages/core/src/inputRules/index.d.ts +0 -5
- package/dist/packages/core/src/inputRules/markInputRule.d.ts +0 -13
- package/dist/packages/core/src/inputRules/nodeInputRule.d.ts +0 -23
- package/dist/packages/core/src/inputRules/textInputRule.d.ts +0 -10
- package/dist/packages/core/src/inputRules/textblockTypeInputRule.d.ts +0 -15
- package/dist/packages/core/src/inputRules/wrappingInputRule.d.ts +0 -28
- package/dist/packages/core/src/pasteRules/index.d.ts +0 -3
- package/dist/packages/core/src/pasteRules/markPasteRule.d.ts +0 -13
- package/dist/packages/core/src/pasteRules/nodePasteRule.d.ts +0 -13
- package/dist/packages/core/src/pasteRules/textPasteRule.d.ts +0 -10
- package/dist/packages/core/src/style.d.ts +0 -1
- package/dist/packages/core/src/types.d.ts +0 -255
- package/dist/packages/core/src/utilities/callOrReturn.d.ts +0 -9
- package/dist/packages/core/src/utilities/createStyleTag.d.ts +0 -1
- package/dist/packages/core/src/utilities/deleteProps.d.ts +0 -6
- package/dist/packages/core/src/utilities/elementFromString.d.ts +0 -1
- package/dist/packages/core/src/utilities/escapeForRegEx.d.ts +0 -1
- package/dist/packages/core/src/utilities/findDuplicates.d.ts +0 -1
- package/dist/packages/core/src/utilities/fromString.d.ts +0 -1
- package/dist/packages/core/src/utilities/index.d.ts +0 -20
- package/dist/packages/core/src/utilities/isAndroid.d.ts +0 -1
- package/dist/packages/core/src/utilities/isEmptyObject.d.ts +0 -1
- package/dist/packages/core/src/utilities/isFunction.d.ts +0 -1
- package/dist/packages/core/src/utilities/isMacOS.d.ts +0 -1
- package/dist/packages/core/src/utilities/isNumber.d.ts +0 -1
- package/dist/packages/core/src/utilities/isPlainObject.d.ts +0 -1
- package/dist/packages/core/src/utilities/isRegExp.d.ts +0 -1
- package/dist/packages/core/src/utilities/isString.d.ts +0 -1
- package/dist/packages/core/src/utilities/isiOS.d.ts +0 -1
- package/dist/packages/core/src/utilities/mergeAttributes.d.ts +0 -1
- package/dist/packages/core/src/utilities/mergeDeep.d.ts +0 -1
- package/dist/packages/core/src/utilities/minMax.d.ts +0 -1
- package/dist/packages/core/src/utilities/objectIncludes.d.ts +0 -8
- package/dist/packages/core/src/utilities/removeDuplicates.d.ts +0 -8
- package/dist/packages/extension-bubble-menu/src/bubble-menu-plugin.d.ts +0 -76
- package/dist/packages/extension-bubble-menu/src/bubble-menu.d.ts +0 -15
- package/dist/packages/extension-bubble-menu/src/index.d.ts +0 -4
- package/dist/packages/extension-floating-menu/src/floating-menu-plugin.d.ts +0 -66
- package/dist/packages/extension-floating-menu/src/floating-menu.d.ts +0 -15
- package/dist/packages/extension-floating-menu/src/index.d.ts +0 -4
- package/dist/packages/react/src/BubbleMenu.d.ts +0 -11
- package/dist/packages/react/src/Context.d.ts +0 -23
- package/dist/packages/react/src/Editor.d.ts +0 -12
- package/dist/packages/react/src/EditorContent.d.ts +0 -24
- package/dist/packages/react/src/FloatingMenu.d.ts +0 -10
- package/dist/packages/react/src/NodeViewContent.d.ts +0 -6
- package/dist/packages/react/src/NodeViewWrapper.d.ts +0 -6
- package/dist/packages/react/src/ReactNodeViewRenderer.d.ts +0 -16
- package/dist/packages/react/src/ReactRenderer.d.ts +0 -62
- package/dist/packages/react/src/index.d.ts +0 -13
- package/dist/packages/react/src/useEditor.d.ts +0 -39
- package/dist/packages/react/src/useEditorState.d.ts +0 -22
- package/dist/packages/react/src/useReactNodeView.d.ts +0 -6
- package/src/BubbleMenu.tsx +0 -57
- 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
|
-
|
|
4
|
-
NodeViewProps,
|
|
3
|
+
Editor,
|
|
5
4
|
NodeViewRenderer,
|
|
6
5
|
NodeViewRendererOptions,
|
|
7
6
|
NodeViewRendererProps,
|
|
8
7
|
} from '@tiptap/core'
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
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 {
|
|
14
|
+
import type { EditorWithContentComponent } from './Editor.js'
|
|
14
15
|
import { ReactRenderer } from './ReactRenderer.js'
|
|
15
|
-
import {
|
|
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
|
-
|
|
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
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
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
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (element
|
|
66
|
-
element.
|
|
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
|
-
{
|
|
73
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
156
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
214
|
-
|
|
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
|
|
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
|
|
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)
|
|
342
|
+
return new ReactNodeView<T>(component, props, options)
|
|
226
343
|
}
|
|
227
344
|
}
|