@tiptap/react 2.6.6 → 2.7.0-pre.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/dist/index.cjs +201 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +198 -20
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +1501 -1322
- package/dist/index.umd.js.map +1 -1
- package/dist/packages/core/src/NodeView.d.ts +20 -8
- package/dist/packages/core/src/index.d.ts +2 -0
- package/dist/packages/core/src/plugins/DropPlugin.d.ts +3 -0
- package/dist/packages/core/src/plugins/PastePlugin.d.ts +3 -0
- package/dist/packages/core/src/types.d.ts +35 -15
- package/dist/packages/react/src/ReactNodeViewRenderer.d.ts +86 -7
- package/dist/packages/react/src/ReactRenderer.d.ts +19 -10
- package/dist/packages/react/src/useEditorState.d.ts +23 -1
- package/package.json +8 -7
- package/src/ReactNodeViewRenderer.tsx +152 -41
- package/src/ReactRenderer.tsx +25 -18
- package/src/useEditor.ts +2 -0
- package/src/useEditorState.ts +43 -8
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Mark as ProseMirrorMark, Node as ProseMirrorNode, NodeType, ParseOptions } from '@tiptap/pm/model';
|
|
1
|
+
import { Mark as ProseMirrorMark, Node as ProseMirrorNode, NodeType, ParseOptions, Slice } from '@tiptap/pm/model';
|
|
2
2
|
import { EditorState, Transaction } from '@tiptap/pm/state';
|
|
3
|
-
import { Decoration, EditorProps, EditorView, NodeView } from '@tiptap/pm/view';
|
|
3
|
+
import { Decoration, EditorProps, EditorView, NodeView, NodeViewConstructor } from '@tiptap/pm/view';
|
|
4
4
|
import { Editor } from './Editor.js';
|
|
5
5
|
import { Extension } from './Extension.js';
|
|
6
6
|
import { Commands, ExtensionConfig, MarkConfig, NodeConfig } from './index.js';
|
|
@@ -79,7 +79,24 @@ export interface EditorOptions {
|
|
|
79
79
|
};
|
|
80
80
|
enableInputRules: EnableRules;
|
|
81
81
|
enablePasteRules: EnableRules;
|
|
82
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Determines whether core extensions are enabled.
|
|
84
|
+
*
|
|
85
|
+
* If set to `false`, all core extensions will be disabled.
|
|
86
|
+
* To disable specific core extensions, provide an object where the keys are the extension names and the values are `false`.
|
|
87
|
+
* Extensions not listed in the object will remain enabled.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* // Disable all core extensions
|
|
91
|
+
* enabledCoreExtensions: false
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* // Disable only the keymap core extension
|
|
95
|
+
* enabledCoreExtensions: { keymap: false }
|
|
96
|
+
*
|
|
97
|
+
* @default true
|
|
98
|
+
*/
|
|
99
|
+
enableCoreExtensions?: boolean | Partial<Record<'editable' | 'clipboardTextSerializer' | 'commands' | 'focusEvents' | 'keymap' | 'tabindex', false>>;
|
|
83
100
|
/**
|
|
84
101
|
* If `true`, the editor will check the content for errors on initialization.
|
|
85
102
|
* Emitting the `contentError` event if the content is invalid.
|
|
@@ -100,6 +117,8 @@ export interface EditorOptions {
|
|
|
100
117
|
onFocus: (props: EditorEvents['focus']) => void;
|
|
101
118
|
onBlur: (props: EditorEvents['blur']) => void;
|
|
102
119
|
onDestroy: (props: EditorEvents['destroy']) => void;
|
|
120
|
+
onPaste: (e: ClipboardEvent, slice: Slice) => void;
|
|
121
|
+
onDrop: (e: DragEvent, slice: Slice, moved: boolean) => void;
|
|
103
122
|
}
|
|
104
123
|
export type HTMLContent = string;
|
|
105
124
|
export type JSONContent = {
|
|
@@ -170,19 +189,18 @@ export type ValuesOf<T> = T[keyof T];
|
|
|
170
189
|
export type KeysWithTypeOf<T, Type> = {
|
|
171
190
|
[P in keyof T]: T[P] extends Type ? P : never;
|
|
172
191
|
}[keyof T];
|
|
192
|
+
export type Simplify<T> = {
|
|
193
|
+
[KeyType in keyof T]: T[KeyType];
|
|
194
|
+
} & {};
|
|
173
195
|
export type DecorationWithType = Decoration & {
|
|
174
196
|
type: NodeType;
|
|
175
197
|
};
|
|
176
|
-
export type NodeViewProps = {
|
|
177
|
-
|
|
178
|
-
node: ProseMirrorNode;
|
|
179
|
-
decorations: DecorationWithType[];
|
|
198
|
+
export type NodeViewProps = Simplify<Omit<NodeViewRendererProps, 'decorations'> & {
|
|
199
|
+
decorations: readonly DecorationWithType[];
|
|
180
200
|
selected: boolean;
|
|
181
|
-
extension: Node;
|
|
182
|
-
getPos: () => number;
|
|
183
201
|
updateAttributes: (attributes: Record<string, any>) => void;
|
|
184
202
|
deleteNode: () => void;
|
|
185
|
-
}
|
|
203
|
+
}>;
|
|
186
204
|
export interface NodeViewRendererOptions {
|
|
187
205
|
stopEvent: ((props: {
|
|
188
206
|
event: Event;
|
|
@@ -196,14 +214,16 @@ export interface NodeViewRendererOptions {
|
|
|
196
214
|
contentDOMElementTag: string;
|
|
197
215
|
}
|
|
198
216
|
export type NodeViewRendererProps = {
|
|
217
|
+
node: Parameters<NodeViewConstructor>[0];
|
|
218
|
+
view: Parameters<NodeViewConstructor>[1];
|
|
219
|
+
getPos: () => number;
|
|
220
|
+
decorations: Parameters<NodeViewConstructor>[3];
|
|
221
|
+
innerDecorations: Parameters<NodeViewConstructor>[4];
|
|
199
222
|
editor: Editor;
|
|
200
|
-
node: ProseMirrorNode;
|
|
201
|
-
getPos: (() => number) | boolean;
|
|
202
|
-
HTMLAttributes: Record<string, any>;
|
|
203
|
-
decorations: Decoration[];
|
|
204
223
|
extension: Node;
|
|
224
|
+
HTMLAttributes: Record<string, any>;
|
|
205
225
|
};
|
|
206
|
-
export type NodeViewRenderer = (props: NodeViewRendererProps) => NodeView
|
|
226
|
+
export type NodeViewRenderer = (props: NodeViewRendererProps) => NodeView;
|
|
207
227
|
export type AnyCommands = Record<string, (...args: any[]) => Command>;
|
|
208
228
|
export type UnionCommands<T = Command> = UnionToIntersection<ValuesOf<Pick<Commands<T>, KeysWithTypeOf<Commands<T>, {}>>>>;
|
|
209
229
|
export type RawCommands = {
|
|
@@ -1,16 +1,95 @@
|
|
|
1
|
-
import { NodeViewRenderer, NodeViewRendererOptions } from '@tiptap/core';
|
|
2
|
-
import { Node as ProseMirrorNode } from '@tiptap/pm/model';
|
|
3
|
-
import { Decoration } from '@tiptap/pm/view';
|
|
1
|
+
import { Editor, NodeView, NodeViewProps, NodeViewRenderer, NodeViewRendererOptions } from '@tiptap/core';
|
|
2
|
+
import { Node, Node as ProseMirrorNode } from '@tiptap/pm/model';
|
|
3
|
+
import { Decoration, DecorationSource } from '@tiptap/pm/view';
|
|
4
|
+
import { ComponentType } from 'react';
|
|
5
|
+
import { ReactRenderer } from './ReactRenderer.js';
|
|
4
6
|
export interface ReactNodeViewRendererOptions extends NodeViewRendererOptions {
|
|
7
|
+
/**
|
|
8
|
+
* This function is called when the node view is updated.
|
|
9
|
+
* It allows you to compare the old node with the new node and decide if the component should update.
|
|
10
|
+
*/
|
|
5
11
|
update: ((props: {
|
|
6
12
|
oldNode: ProseMirrorNode;
|
|
7
|
-
oldDecorations: Decoration[];
|
|
13
|
+
oldDecorations: readonly Decoration[];
|
|
14
|
+
oldInnerDecorations: DecorationSource;
|
|
8
15
|
newNode: ProseMirrorNode;
|
|
9
|
-
newDecorations: Decoration[];
|
|
16
|
+
newDecorations: readonly Decoration[];
|
|
17
|
+
innerDecorations: DecorationSource;
|
|
10
18
|
updateProps: () => void;
|
|
11
19
|
}) => boolean) | null;
|
|
20
|
+
/**
|
|
21
|
+
* The tag name of the element wrapping the React component.
|
|
22
|
+
*/
|
|
12
23
|
as?: string;
|
|
24
|
+
/**
|
|
25
|
+
* The class name of the element wrapping the React component.
|
|
26
|
+
*/
|
|
13
27
|
className?: string;
|
|
14
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Attributes that should be applied to the element wrapping the React component.
|
|
30
|
+
* If this is a function, it will be called each time the node view is updated.
|
|
31
|
+
* If this is an object, it will be applied once when the node view is mounted.
|
|
32
|
+
*/
|
|
33
|
+
attrs?: Record<string, string> | ((props: {
|
|
34
|
+
node: ProseMirrorNode;
|
|
35
|
+
HTMLAttributes: Record<string, any>;
|
|
36
|
+
}) => Record<string, string>);
|
|
15
37
|
}
|
|
16
|
-
export declare
|
|
38
|
+
export declare class ReactNodeView<Component extends ComponentType<NodeViewProps> = ComponentType<NodeViewProps>, NodeEditor extends Editor = Editor, Options extends ReactNodeViewRendererOptions = ReactNodeViewRendererOptions> extends NodeView<Component, NodeEditor, Options> {
|
|
39
|
+
/**
|
|
40
|
+
* The renderer instance.
|
|
41
|
+
*/
|
|
42
|
+
renderer: ReactRenderer<unknown, NodeViewProps>;
|
|
43
|
+
/**
|
|
44
|
+
* The element that holds the rich-text content of the node.
|
|
45
|
+
*/
|
|
46
|
+
contentDOMElement: HTMLElement | null;
|
|
47
|
+
/**
|
|
48
|
+
* Setup the React component.
|
|
49
|
+
* Called on initialization.
|
|
50
|
+
*/
|
|
51
|
+
mount(): void;
|
|
52
|
+
/**
|
|
53
|
+
* Return the DOM element.
|
|
54
|
+
* This is the element that will be used to display the node view.
|
|
55
|
+
*/
|
|
56
|
+
get dom(): HTMLElement;
|
|
57
|
+
/**
|
|
58
|
+
* Return the content DOM element.
|
|
59
|
+
* This is the element that will be used to display the rich-text content of the node.
|
|
60
|
+
*/
|
|
61
|
+
get contentDOM(): HTMLElement | null;
|
|
62
|
+
/**
|
|
63
|
+
* On editor selection update, check if the node is selected.
|
|
64
|
+
* If it is, call `selectNode`, otherwise call `deselectNode`.
|
|
65
|
+
*/
|
|
66
|
+
handleSelectionUpdate(): void;
|
|
67
|
+
/**
|
|
68
|
+
* On update, update the React component.
|
|
69
|
+
* To prevent unnecessary updates, the `update` option can be used.
|
|
70
|
+
*/
|
|
71
|
+
update(node: Node, decorations: readonly Decoration[], innerDecorations: DecorationSource): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Select the node.
|
|
74
|
+
* Add the `selected` prop and the `ProseMirror-selectednode` class.
|
|
75
|
+
*/
|
|
76
|
+
selectNode(): void;
|
|
77
|
+
/**
|
|
78
|
+
* Deselect the node.
|
|
79
|
+
* Remove the `selected` prop and the `ProseMirror-selectednode` class.
|
|
80
|
+
*/
|
|
81
|
+
deselectNode(): void;
|
|
82
|
+
/**
|
|
83
|
+
* Destroy the React component instance.
|
|
84
|
+
*/
|
|
85
|
+
destroy(): void;
|
|
86
|
+
/**
|
|
87
|
+
* Update the attributes of the top-level element that holds the React component.
|
|
88
|
+
* Applying the attributes defined in the `attrs` option.
|
|
89
|
+
*/
|
|
90
|
+
updateElementAttributes(): void;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Create a React node view renderer.
|
|
94
|
+
*/
|
|
95
|
+
export declare function ReactNodeViewRenderer(component: ComponentType<NodeViewProps>, options?: Partial<ReactNodeViewRendererOptions>): NodeViewRenderer;
|
|
@@ -25,13 +25,6 @@ export interface ReactRendererOptions {
|
|
|
25
25
|
* @example 'foo bar'
|
|
26
26
|
*/
|
|
27
27
|
className?: string;
|
|
28
|
-
/**
|
|
29
|
-
* The attributes of the element.
|
|
30
|
-
* @type {Record<string, string>}
|
|
31
|
-
* @default {}
|
|
32
|
-
* @example { 'data-foo': 'bar' }
|
|
33
|
-
*/
|
|
34
|
-
attrs?: Record<string, string>;
|
|
35
28
|
}
|
|
36
29
|
type ComponentType<R, P> = React.ComponentClass<P> | React.FunctionComponent<P> | React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<R>>;
|
|
37
30
|
/**
|
|
@@ -45,17 +38,33 @@ type ComponentType<R, P> = React.ComponentClass<P> | React.FunctionComponent<P>
|
|
|
45
38
|
* as: 'span',
|
|
46
39
|
* })
|
|
47
40
|
*/
|
|
48
|
-
export declare class ReactRenderer<R = unknown, P =
|
|
41
|
+
export declare class ReactRenderer<R = unknown, P extends Record<string, any> = {}> {
|
|
49
42
|
id: string;
|
|
50
43
|
editor: Editor;
|
|
51
44
|
component: any;
|
|
52
45
|
element: Element;
|
|
53
|
-
props:
|
|
46
|
+
props: P;
|
|
54
47
|
reactElement: React.ReactNode;
|
|
55
48
|
ref: R | null;
|
|
56
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Immediately creates element and renders the provided React component.
|
|
51
|
+
*/
|
|
52
|
+
constructor(component: ComponentType<R, P>, { editor, props, as, className, }: ReactRendererOptions);
|
|
53
|
+
/**
|
|
54
|
+
* Render the React component.
|
|
55
|
+
*/
|
|
57
56
|
render(): void;
|
|
57
|
+
/**
|
|
58
|
+
* Re-renders the React component with new props.
|
|
59
|
+
*/
|
|
58
60
|
updateProps(props?: Record<string, any>): void;
|
|
61
|
+
/**
|
|
62
|
+
* Destroy the React component.
|
|
63
|
+
*/
|
|
59
64
|
destroy(): void;
|
|
65
|
+
/**
|
|
66
|
+
* Update the attributes of the element that holds the React component.
|
|
67
|
+
*/
|
|
68
|
+
updateAttributes(attributes: Record<string, string>): void;
|
|
60
69
|
}
|
|
61
70
|
export {};
|
|
@@ -14,9 +14,31 @@ export type UseEditorStateOptions<TSelectorResult, TEditor extends Editor | null
|
|
|
14
14
|
selector: (context: EditorStateSnapshot<TEditor>) => TSelectorResult;
|
|
15
15
|
/**
|
|
16
16
|
* A custom equality function to determine if the editor should re-render.
|
|
17
|
-
* @default `
|
|
17
|
+
* @default `deepEqual` from `fast-deep-equal`
|
|
18
18
|
*/
|
|
19
19
|
equalityFn?: (a: TSelectorResult, b: TSelectorResult | null) => boolean;
|
|
20
20
|
};
|
|
21
|
+
/**
|
|
22
|
+
* This hook allows you to watch for changes on the editor instance.
|
|
23
|
+
* It will allow you to select a part of the editor state and re-render the component when it changes.
|
|
24
|
+
* @example
|
|
25
|
+
* ```tsx
|
|
26
|
+
* const editor = useEditor({...options})
|
|
27
|
+
* const { currentSelection } = useEditorState({
|
|
28
|
+
* editor,
|
|
29
|
+
* selector: snapshot => ({ currentSelection: snapshot.editor.state.selection }),
|
|
30
|
+
* })
|
|
31
|
+
*/
|
|
21
32
|
export declare function useEditorState<TSelectorResult>(options: UseEditorStateOptions<TSelectorResult, Editor>): TSelectorResult;
|
|
33
|
+
/**
|
|
34
|
+
* This hook allows you to watch for changes on the editor instance.
|
|
35
|
+
* It will allow you to select a part of the editor state and re-render the component when it changes.
|
|
36
|
+
* @example
|
|
37
|
+
* ```tsx
|
|
38
|
+
* const editor = useEditor({...options})
|
|
39
|
+
* const { currentSelection } = useEditorState({
|
|
40
|
+
* editor,
|
|
41
|
+
* selector: snapshot => ({ currentSelection: snapshot.editor.state.selection }),
|
|
42
|
+
* })
|
|
43
|
+
*/
|
|
22
44
|
export declare function useEditorState<TSelectorResult>(options: UseEditorStateOptions<TSelectorResult, Editor | null>): TSelectorResult | null;
|
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.
|
|
4
|
+
"version": "2.7.0-pre.0",
|
|
5
5
|
"homepage": "https://tiptap.dev",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"tiptap",
|
|
@@ -29,22 +29,23 @@
|
|
|
29
29
|
"dist"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@tiptap/extension-bubble-menu": "^2.
|
|
33
|
-
"@tiptap/extension-floating-menu": "^2.
|
|
32
|
+
"@tiptap/extension-bubble-menu": "^2.7.0-pre.0",
|
|
33
|
+
"@tiptap/extension-floating-menu": "^2.7.0-pre.0",
|
|
34
34
|
"@types/use-sync-external-store": "^0.0.6",
|
|
35
|
+
"fast-deep-equal": "^3",
|
|
35
36
|
"use-sync-external-store": "^1.2.2"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
38
|
-
"@tiptap/core": "^2.
|
|
39
|
-
"@tiptap/pm": "^2.
|
|
39
|
+
"@tiptap/core": "^2.7.0-pre.0",
|
|
40
|
+
"@tiptap/pm": "^2.7.0-pre.0",
|
|
40
41
|
"@types/react": "^18.2.14",
|
|
41
42
|
"@types/react-dom": "^18.2.6",
|
|
42
43
|
"react": "^18.0.0",
|
|
43
44
|
"react-dom": "^18.0.0"
|
|
44
45
|
},
|
|
45
46
|
"peerDependencies": {
|
|
46
|
-
"@tiptap/core": "^2.
|
|
47
|
-
"@tiptap/pm": "^2.
|
|
47
|
+
"@tiptap/core": "^2.7.0-pre.0",
|
|
48
|
+
"@tiptap/pm": "^2.7.0-pre.0",
|
|
48
49
|
"react": "^17.0.0 || ^18.0.0",
|
|
49
50
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
50
51
|
},
|
|
@@ -1,55 +1,90 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DecorationWithType,
|
|
3
3
|
Editor,
|
|
4
|
+
getRenderedAttributes,
|
|
4
5
|
NodeView,
|
|
5
6
|
NodeViewProps,
|
|
6
7
|
NodeViewRenderer,
|
|
7
8
|
NodeViewRendererOptions,
|
|
8
|
-
NodeViewRendererProps,
|
|
9
9
|
} from '@tiptap/core'
|
|
10
|
-
import { Node as ProseMirrorNode } from '@tiptap/pm/model'
|
|
11
|
-
import { Decoration, NodeView as ProseMirrorNodeView } from '@tiptap/pm/view'
|
|
12
|
-
import React from 'react'
|
|
10
|
+
import { Node, Node as ProseMirrorNode } from '@tiptap/pm/model'
|
|
11
|
+
import { Decoration, DecorationSource, NodeView as ProseMirrorNodeView } from '@tiptap/pm/view'
|
|
12
|
+
import React, { ComponentType } from 'react'
|
|
13
13
|
|
|
14
14
|
import { EditorWithContentComponent } from './Editor.js'
|
|
15
15
|
import { ReactRenderer } from './ReactRenderer.js'
|
|
16
16
|
import { ReactNodeViewContext, ReactNodeViewContextProps } from './useReactNodeView.js'
|
|
17
17
|
|
|
18
18
|
export interface ReactNodeViewRendererOptions extends NodeViewRendererOptions {
|
|
19
|
+
/**
|
|
20
|
+
* This function is called when the node view is updated.
|
|
21
|
+
* It allows you to compare the old node with the new node and decide if the component should update.
|
|
22
|
+
*/
|
|
19
23
|
update:
|
|
20
24
|
| ((props: {
|
|
21
|
-
oldNode: ProseMirrorNode
|
|
22
|
-
oldDecorations: Decoration[]
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
oldNode: ProseMirrorNode;
|
|
26
|
+
oldDecorations: readonly Decoration[];
|
|
27
|
+
oldInnerDecorations: DecorationSource;
|
|
28
|
+
newNode: ProseMirrorNode;
|
|
29
|
+
newDecorations: readonly Decoration[];
|
|
30
|
+
innerDecorations: DecorationSource;
|
|
31
|
+
updateProps: () => void;
|
|
26
32
|
}) => boolean)
|
|
27
|
-
| null
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
| null;
|
|
34
|
+
/**
|
|
35
|
+
* The tag name of the element wrapping the React component.
|
|
36
|
+
*/
|
|
37
|
+
as?: string;
|
|
38
|
+
/**
|
|
39
|
+
* The class name of the element wrapping the React component.
|
|
40
|
+
*/
|
|
41
|
+
className?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Attributes that should be applied to the element wrapping the React component.
|
|
44
|
+
* If this is a function, it will be called each time the node view is updated.
|
|
45
|
+
* If this is an object, it will be applied once when the node view is mounted.
|
|
46
|
+
*/
|
|
47
|
+
attrs?:
|
|
48
|
+
| Record<string, string>
|
|
49
|
+
| ((props: {
|
|
50
|
+
node: ProseMirrorNode;
|
|
51
|
+
HTMLAttributes: Record<string, any>;
|
|
52
|
+
}) => Record<string, string>);
|
|
31
53
|
}
|
|
32
54
|
|
|
33
|
-
class ReactNodeView
|
|
34
|
-
|
|
35
|
-
Editor,
|
|
36
|
-
ReactNodeViewRendererOptions
|
|
37
|
-
> {
|
|
38
|
-
|
|
39
|
-
|
|
55
|
+
export class ReactNodeView<
|
|
56
|
+
Component extends ComponentType<NodeViewProps> = ComponentType<NodeViewProps>,
|
|
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, NodeViewProps>
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The element that holds the rich-text content of the node.
|
|
67
|
+
*/
|
|
40
68
|
contentDOMElement!: HTMLElement | null
|
|
41
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Setup the React component.
|
|
72
|
+
* Called on initialization.
|
|
73
|
+
*/
|
|
42
74
|
mount() {
|
|
43
|
-
const props
|
|
75
|
+
const props = {
|
|
44
76
|
editor: this.editor,
|
|
45
77
|
node: this.node,
|
|
46
|
-
decorations: this.decorations,
|
|
78
|
+
decorations: this.decorations as DecorationWithType[],
|
|
79
|
+
innerDecorations: this.innerDecorations,
|
|
80
|
+
view: this.view,
|
|
47
81
|
selected: false,
|
|
48
82
|
extension: this.extension,
|
|
83
|
+
HTMLAttributes: this.HTMLAttributes,
|
|
49
84
|
getPos: () => this.getPos(),
|
|
50
85
|
updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
|
|
51
86
|
deleteNode: () => this.deleteNode(),
|
|
52
|
-
}
|
|
87
|
+
} satisfies NodeViewProps
|
|
53
88
|
|
|
54
89
|
if (!(this.component as any).displayName) {
|
|
55
90
|
const capitalizeFirstChar = (string: string): string => {
|
|
@@ -69,13 +104,15 @@ class ReactNodeView extends NodeView<
|
|
|
69
104
|
const Component = this.component
|
|
70
105
|
// For performance reasons, we memoize the provider component
|
|
71
106
|
// And all of the things it requires are declared outside of the component, so it doesn't need to re-render
|
|
72
|
-
const ReactNodeViewProvider: React.FunctionComponent = React.memo(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
107
|
+
const ReactNodeViewProvider: React.FunctionComponent<NodeViewProps> = React.memo(
|
|
108
|
+
componentProps => {
|
|
109
|
+
return (
|
|
110
|
+
<ReactNodeViewContext.Provider value={context}>
|
|
111
|
+
{React.createElement(Component, componentProps)}
|
|
112
|
+
</ReactNodeViewContext.Provider>
|
|
113
|
+
)
|
|
114
|
+
},
|
|
115
|
+
)
|
|
79
116
|
|
|
80
117
|
ReactNodeViewProvider.displayName = 'ReactNodeView'
|
|
81
118
|
|
|
@@ -88,6 +125,7 @@ class ReactNodeView extends NodeView<
|
|
|
88
125
|
}
|
|
89
126
|
|
|
90
127
|
if (this.contentDOMElement) {
|
|
128
|
+
this.contentDOMElement.dataset.nodeViewContentReact = ''
|
|
91
129
|
// For some reason the whiteSpace prop is not inherited properly in Chrome and Safari
|
|
92
130
|
// With this fix it seems to work fine
|
|
93
131
|
// See: https://github.com/ueberdosis/tiptap/issues/1197
|
|
@@ -110,10 +148,15 @@ class ReactNodeView extends NodeView<
|
|
|
110
148
|
props,
|
|
111
149
|
as,
|
|
112
150
|
className: `node-${this.node.type.name} ${className}`.trim(),
|
|
113
|
-
attrs: this.options.attrs,
|
|
114
151
|
})
|
|
152
|
+
|
|
153
|
+
this.updateElementAttributes()
|
|
115
154
|
}
|
|
116
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Return the DOM element.
|
|
158
|
+
* This is the element that will be used to display the node view.
|
|
159
|
+
*/
|
|
117
160
|
get dom() {
|
|
118
161
|
if (
|
|
119
162
|
this.renderer.element.firstElementChild
|
|
@@ -125,6 +168,10 @@ class ReactNodeView extends NodeView<
|
|
|
125
168
|
return this.renderer.element as HTMLElement
|
|
126
169
|
}
|
|
127
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Return the content DOM element.
|
|
173
|
+
* This is the element that will be used to display the rich-text content of the node.
|
|
174
|
+
*/
|
|
128
175
|
get contentDOM() {
|
|
129
176
|
if (this.node.isLeaf) {
|
|
130
177
|
return null
|
|
@@ -133,10 +180,19 @@ class ReactNodeView extends NodeView<
|
|
|
133
180
|
return this.contentDOMElement
|
|
134
181
|
}
|
|
135
182
|
|
|
183
|
+
/**
|
|
184
|
+
* On editor selection update, check if the node is selected.
|
|
185
|
+
* If it is, call `selectNode`, otherwise call `deselectNode`.
|
|
186
|
+
*/
|
|
136
187
|
handleSelectionUpdate() {
|
|
137
188
|
const { from, to } = this.editor.state.selection
|
|
189
|
+
const pos = this.getPos()
|
|
190
|
+
|
|
191
|
+
if (typeof pos !== 'number') {
|
|
192
|
+
return
|
|
193
|
+
}
|
|
138
194
|
|
|
139
|
-
if (from <=
|
|
195
|
+
if (from <= pos && to >= pos + this.node.nodeSize) {
|
|
140
196
|
if (this.renderer.props.selected) {
|
|
141
197
|
return
|
|
142
198
|
}
|
|
@@ -151,9 +207,20 @@ class ReactNodeView extends NodeView<
|
|
|
151
207
|
}
|
|
152
208
|
}
|
|
153
209
|
|
|
154
|
-
|
|
155
|
-
|
|
210
|
+
/**
|
|
211
|
+
* On update, update the React component.
|
|
212
|
+
* To prevent unnecessary updates, the `update` option can be used.
|
|
213
|
+
*/
|
|
214
|
+
update(
|
|
215
|
+
node: Node,
|
|
216
|
+
decorations: readonly Decoration[],
|
|
217
|
+
innerDecorations: DecorationSource,
|
|
218
|
+
): boolean {
|
|
219
|
+
const rerenderComponent = (props?: Record<string, any>) => {
|
|
156
220
|
this.renderer.updateProps(props)
|
|
221
|
+
if (typeof this.options.attrs === 'function') {
|
|
222
|
+
this.updateElementAttributes()
|
|
223
|
+
}
|
|
157
224
|
}
|
|
158
225
|
|
|
159
226
|
if (node.type !== this.node.type) {
|
|
@@ -163,31 +230,44 @@ class ReactNodeView extends NodeView<
|
|
|
163
230
|
if (typeof this.options.update === 'function') {
|
|
164
231
|
const oldNode = this.node
|
|
165
232
|
const oldDecorations = this.decorations
|
|
233
|
+
const oldInnerDecorations = this.innerDecorations
|
|
166
234
|
|
|
167
235
|
this.node = node
|
|
168
236
|
this.decorations = decorations
|
|
237
|
+
this.innerDecorations = innerDecorations
|
|
169
238
|
|
|
170
239
|
return this.options.update({
|
|
171
240
|
oldNode,
|
|
172
241
|
oldDecorations,
|
|
173
242
|
newNode: node,
|
|
174
243
|
newDecorations: decorations,
|
|
175
|
-
|
|
244
|
+
oldInnerDecorations,
|
|
245
|
+
innerDecorations,
|
|
246
|
+
updateProps: () => rerenderComponent({ node, decorations, innerDecorations }),
|
|
176
247
|
})
|
|
177
248
|
}
|
|
178
249
|
|
|
179
|
-
if (
|
|
250
|
+
if (
|
|
251
|
+
node === this.node
|
|
252
|
+
&& this.decorations === decorations
|
|
253
|
+
&& this.innerDecorations === innerDecorations
|
|
254
|
+
) {
|
|
180
255
|
return true
|
|
181
256
|
}
|
|
182
257
|
|
|
183
258
|
this.node = node
|
|
184
259
|
this.decorations = decorations
|
|
260
|
+
this.innerDecorations = innerDecorations
|
|
185
261
|
|
|
186
|
-
|
|
262
|
+
rerenderComponent({ node, decorations, innerDecorations })
|
|
187
263
|
|
|
188
264
|
return true
|
|
189
265
|
}
|
|
190
266
|
|
|
267
|
+
/**
|
|
268
|
+
* Select the node.
|
|
269
|
+
* Add the `selected` prop and the `ProseMirror-selectednode` class.
|
|
270
|
+
*/
|
|
191
271
|
selectNode() {
|
|
192
272
|
this.renderer.updateProps({
|
|
193
273
|
selected: true,
|
|
@@ -195,6 +275,10 @@ class ReactNodeView extends NodeView<
|
|
|
195
275
|
this.renderer.element.classList.add('ProseMirror-selectednode')
|
|
196
276
|
}
|
|
197
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Deselect the node.
|
|
280
|
+
* Remove the `selected` prop and the `ProseMirror-selectednode` class.
|
|
281
|
+
*/
|
|
198
282
|
deselectNode() {
|
|
199
283
|
this.renderer.updateProps({
|
|
200
284
|
selected: false,
|
|
@@ -202,25 +286,52 @@ class ReactNodeView extends NodeView<
|
|
|
202
286
|
this.renderer.element.classList.remove('ProseMirror-selectednode')
|
|
203
287
|
}
|
|
204
288
|
|
|
289
|
+
/**
|
|
290
|
+
* Destroy the React component instance.
|
|
291
|
+
*/
|
|
205
292
|
destroy() {
|
|
206
293
|
this.renderer.destroy()
|
|
207
294
|
this.editor.off('selectionUpdate', this.handleSelectionUpdate)
|
|
208
295
|
this.contentDOMElement = null
|
|
209
296
|
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Update the attributes of the top-level element that holds the React component.
|
|
300
|
+
* Applying the attributes defined in the `attrs` option.
|
|
301
|
+
*/
|
|
302
|
+
updateElementAttributes() {
|
|
303
|
+
if (this.options.attrs) {
|
|
304
|
+
let attrsObj: Record<string, string> = {}
|
|
305
|
+
|
|
306
|
+
if (typeof this.options.attrs === 'function') {
|
|
307
|
+
const extensionAttributes = this.editor.extensionManager.attributes
|
|
308
|
+
const HTMLAttributes = getRenderedAttributes(this.node, extensionAttributes)
|
|
309
|
+
|
|
310
|
+
attrsObj = this.options.attrs({ node: this.node, HTMLAttributes })
|
|
311
|
+
} else {
|
|
312
|
+
attrsObj = this.options.attrs
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
this.renderer.updateAttributes(attrsObj)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
210
318
|
}
|
|
211
319
|
|
|
320
|
+
/**
|
|
321
|
+
* Create a React node view renderer.
|
|
322
|
+
*/
|
|
212
323
|
export function ReactNodeViewRenderer(
|
|
213
|
-
component:
|
|
324
|
+
component: ComponentType<NodeViewProps>,
|
|
214
325
|
options?: Partial<ReactNodeViewRendererOptions>,
|
|
215
326
|
): NodeViewRenderer {
|
|
216
|
-
return
|
|
327
|
+
return props => {
|
|
217
328
|
// try to get the parent component
|
|
218
329
|
// this is important for vue devtools to show the component hierarchy correctly
|
|
219
330
|
// maybe it’s `undefined` because <editor-content> isn’t rendered yet
|
|
220
331
|
if (!(props.editor as EditorWithContentComponent).contentComponent) {
|
|
221
|
-
return {}
|
|
332
|
+
return {} as unknown as ProseMirrorNodeView
|
|
222
333
|
}
|
|
223
334
|
|
|
224
|
-
return new ReactNodeView(component, props, options)
|
|
335
|
+
return new ReactNodeView(component, props, options)
|
|
225
336
|
}
|
|
226
337
|
}
|