@tiptap/react 2.0.0-beta.20 → 2.0.0-beta.200

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 (34) hide show
  1. package/README.md +2 -2
  2. package/dist/packages/react/src/BubbleMenu.d.ts +6 -3
  3. package/dist/packages/react/src/Editor.d.ts +1 -1
  4. package/dist/packages/react/src/EditorContent.d.ts +2 -2
  5. package/dist/packages/react/src/FloatingMenu.d.ts +6 -3
  6. package/dist/packages/react/src/NodeViewContent.d.ts +1 -1
  7. package/dist/packages/react/src/NodeViewWrapper.d.ts +1 -1
  8. package/dist/packages/react/src/ReactNodeViewRenderer.d.ts +12 -6
  9. package/dist/packages/react/src/ReactRenderer.d.ts +12 -9
  10. package/dist/packages/react/src/index.d.ts +6 -6
  11. package/dist/packages/react/src/useEditor.d.ts +2 -1
  12. package/dist/packages/react/src/useReactNodeView.d.ts +1 -0
  13. package/dist/tiptap-react.cjs.js +226 -151
  14. package/dist/tiptap-react.cjs.js.map +1 -1
  15. package/dist/tiptap-react.esm.js +226 -149
  16. package/dist/tiptap-react.esm.js.map +1 -1
  17. package/dist/tiptap-react.umd.js +228 -153
  18. package/dist/tiptap-react.umd.js.map +1 -1
  19. package/package.json +18 -12
  20. package/src/BubbleMenu.tsx +37 -15
  21. package/src/Editor.ts +2 -1
  22. package/src/EditorContent.tsx +9 -7
  23. package/src/FloatingMenu.tsx +37 -14
  24. package/src/NodeViewContent.tsx +10 -3
  25. package/src/NodeViewWrapper.tsx +11 -8
  26. package/src/ReactNodeViewRenderer.tsx +75 -31
  27. package/src/ReactRenderer.tsx +58 -25
  28. package/src/index.ts +6 -6
  29. package/src/useEditor.ts +16 -4
  30. package/src/useReactNodeView.ts +1 -0
  31. package/CHANGELOG.md +0 -178
  32. package/LICENSE.md +0 -21
  33. package/dist/tiptap-react.bundle.umd.min.js +0 -54
  34. package/dist/tiptap-react.bundle.umd.min.js.map +0 -1
package/README.md CHANGED
@@ -7,8 +7,8 @@
7
7
  ## Introduction
8
8
  tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as *New York Times*, *The Guardian* or *Atlassian*.
9
9
 
10
- ## Offical Documentation
10
+ ## Official Documentation
11
11
  Documentation can be found on the [tiptap website](https://tiptap.dev).
12
12
 
13
13
  ## License
14
- tiptap is open sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap-next/blob/main/LICENSE.md).
14
+ tiptap is open sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap/blob/main/LICENSE.md).
@@ -1,6 +1,9 @@
1
- import React from 'react';
2
1
  import { BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu';
3
- export declare type BubbleMenuProps = Omit<BubbleMenuPluginProps, 'element'> & {
2
+ import React from 'react';
3
+ declare type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
4
+ export declare type BubbleMenuProps = Omit<Optional<BubbleMenuPluginProps, 'pluginKey'>, 'element'> & {
4
5
  className?: string;
6
+ children: React.ReactNode;
5
7
  };
6
- export declare const BubbleMenu: React.FC<BubbleMenuProps>;
8
+ export declare const BubbleMenu: (props: BubbleMenuProps) => JSX.Element;
9
+ export {};
@@ -1,5 +1,5 @@
1
- import React from 'react';
2
1
  import { Editor as CoreEditor } from '@tiptap/core';
2
+ import React from 'react';
3
3
  import { EditorContentProps, EditorContentState } from './EditorContent';
4
4
  export declare class Editor extends CoreEditor {
5
5
  contentComponent: React.Component<EditorContentProps, EditorContentState> | null;
@@ -1,7 +1,7 @@
1
- import React from 'react';
1
+ import React, { HTMLProps } from 'react';
2
2
  import { Editor } from './Editor';
3
3
  import { ReactRenderer } from './ReactRenderer';
4
- export interface EditorContentProps {
4
+ export interface EditorContentProps extends HTMLProps<HTMLDivElement> {
5
5
  editor: Editor | null;
6
6
  }
7
7
  export interface EditorContentState {
@@ -1,6 +1,9 @@
1
- import React from 'react';
2
1
  import { FloatingMenuPluginProps } from '@tiptap/extension-floating-menu';
3
- export declare type FloatingMenuProps = Omit<FloatingMenuPluginProps, 'element'> & {
2
+ import React from 'react';
3
+ declare type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
4
+ export declare type FloatingMenuProps = Omit<Optional<FloatingMenuPluginProps, 'pluginKey'>, 'element'> & {
4
5
  className?: string;
6
+ children: React.ReactNode;
5
7
  };
6
- export declare const FloatingMenu: React.FC<FloatingMenuProps>;
8
+ export declare const FloatingMenu: (props: FloatingMenuProps) => JSX.Element;
9
+ export {};
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  export interface NodeViewContentProps {
3
- className?: string;
3
+ [key: string]: any;
4
4
  as?: React.ElementType;
5
5
  }
6
6
  export declare const NodeViewContent: React.FC<NodeViewContentProps>;
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  export interface NodeViewWrapperProps {
3
- className?: string;
3
+ [key: string]: any;
4
4
  as?: React.ElementType;
5
5
  }
6
6
  export declare const NodeViewWrapper: React.FC<NodeViewWrapperProps>;
@@ -1,9 +1,15 @@
1
- import { NodeViewRenderer } from '@tiptap/core';
2
- import { Decoration } from 'prosemirror-view';
1
+ import { NodeViewRenderer, NodeViewRendererOptions } from '@tiptap/core';
3
2
  import { Node as ProseMirrorNode } from 'prosemirror-model';
4
- interface ReactNodeViewRendererOptions {
5
- stopEvent: ((event: Event) => boolean) | null;
6
- update: ((node: ProseMirrorNode, decorations: Decoration[]) => boolean) | null;
3
+ import { Decoration } from 'prosemirror-view';
4
+ export interface ReactNodeViewRendererOptions extends NodeViewRendererOptions {
5
+ update: ((props: {
6
+ oldNode: ProseMirrorNode;
7
+ oldDecorations: Decoration[];
8
+ newNode: ProseMirrorNode;
9
+ newDecorations: Decoration[];
10
+ updateProps: () => void;
11
+ }) => boolean) | null;
12
+ as?: string;
13
+ className?: string;
7
14
  }
8
15
  export declare function ReactNodeViewRenderer(component: any, options?: Partial<ReactNodeViewRendererOptions>): NodeViewRenderer;
9
- export {};
@@ -1,21 +1,24 @@
1
+ import { Editor } from '@tiptap/core';
1
2
  import React from 'react';
2
- import { AnyObject } from '@tiptap/core';
3
- import { Editor } from './Editor';
3
+ import { Editor as ExtendedEditor } from './Editor';
4
4
  export interface ReactRendererOptions {
5
5
  editor: Editor;
6
- props?: AnyObject;
6
+ props?: Record<string, any>;
7
7
  as?: string;
8
+ className?: string;
8
9
  }
9
- export declare class ReactRenderer {
10
+ declare type ComponentType<R, P> = React.ComponentClass<P> | React.FunctionComponent<P> | React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<R>>;
11
+ export declare class ReactRenderer<R = unknown, P = unknown> {
10
12
  id: string;
11
- editor: Editor;
13
+ editor: ExtendedEditor;
12
14
  component: any;
13
15
  element: Element;
14
- props: AnyObject;
16
+ props: Record<string, any>;
15
17
  reactElement: React.ReactNode;
16
- ref: React.Component | null;
17
- constructor(component: React.Component | React.FunctionComponent, { editor, props, as }: ReactRendererOptions);
18
+ ref: R | null;
19
+ constructor(component: ComponentType<R, P>, { editor, props, as, className, }: ReactRendererOptions);
18
20
  render(): void;
19
- updateProps(props?: AnyObject): void;
21
+ updateProps(props?: Record<string, any>): void;
20
22
  destroy(): void;
21
23
  }
24
+ export {};
@@ -1,10 +1,10 @@
1
- export * from '@tiptap/core';
2
1
  export * from './BubbleMenu';
3
2
  export { Editor } from './Editor';
4
- export * from './FloatingMenu';
5
- export * from './useEditor';
6
- export * from './ReactRenderer';
7
- export * from './ReactNodeViewRenderer';
8
3
  export * from './EditorContent';
9
- export * from './NodeViewWrapper';
4
+ export * from './FloatingMenu';
10
5
  export * from './NodeViewContent';
6
+ export * from './NodeViewWrapper';
7
+ export * from './ReactNodeViewRenderer';
8
+ export * from './ReactRenderer';
9
+ export * from './useEditor';
10
+ export * from '@tiptap/core';
@@ -1,3 +1,4 @@
1
1
  import { EditorOptions } from '@tiptap/core';
2
+ import { DependencyList } from 'react';
2
3
  import { Editor } from './Editor';
3
- export declare const useEditor: (options?: Partial<EditorOptions>) => Editor | null;
4
+ export declare const useEditor: (options?: Partial<EditorOptions>, deps?: DependencyList) => Editor | null;
@@ -1,6 +1,7 @@
1
1
  /// <reference types="react" />
2
2
  export interface ReactNodeViewContextProps {
3
3
  onDragStart: (event: DragEvent) => void;
4
+ nodeViewContentRef: (element: HTMLElement | null) => void;
4
5
  }
5
6
  export declare const ReactNodeViewContext: import("react").Context<Partial<ReactNodeViewContextProps>>;
6
7
  export declare const useReactNodeView: () => Partial<ReactNodeViewContextProps>;
@@ -2,31 +2,41 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var core = require('@tiptap/core');
6
- var React = require('react');
7
5
  var extensionBubbleMenu = require('@tiptap/extension-bubble-menu');
8
- var extensionFloatingMenu = require('@tiptap/extension-floating-menu');
6
+ var React = require('react');
7
+ var core = require('@tiptap/core');
9
8
  var ReactDOM = require('react-dom');
9
+ var extensionFloatingMenu = require('@tiptap/extension-floating-menu');
10
10
 
11
11
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
12
12
 
13
13
  var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
14
14
  var ReactDOM__default = /*#__PURE__*/_interopDefaultLegacy(ReactDOM);
15
15
 
16
- const BubbleMenu = props => {
17
- const element = React.useRef(null);
16
+ const BubbleMenu = (props) => {
17
+ const [element, setElement] = React.useState(null);
18
18
  React.useEffect(() => {
19
- const { editor, keepInBounds = true } = props;
20
- editor.registerPlugin(extensionBubbleMenu.BubbleMenuPlugin({
19
+ if (!element) {
20
+ return;
21
+ }
22
+ if (props.editor.isDestroyed) {
23
+ return;
24
+ }
25
+ const { pluginKey = 'bubbleMenu', editor, tippyOptions = {}, shouldShow = null, } = props;
26
+ const plugin = extensionBubbleMenu.BubbleMenuPlugin({
27
+ pluginKey,
21
28
  editor,
22
- element: element.current,
23
- keepInBounds,
24
- }));
25
- return () => {
26
- editor.unregisterPlugin(extensionBubbleMenu.BubbleMenuPluginKey);
27
- };
28
- }, []);
29
- return (React__default['default'].createElement("div", { ref: element, className: props.className, style: { visibility: 'hidden' } }, props.children));
29
+ element,
30
+ tippyOptions,
31
+ shouldShow,
32
+ });
33
+ editor.registerPlugin(plugin);
34
+ return () => editor.unregisterPlugin(pluginKey);
35
+ }, [
36
+ props.editor,
37
+ element,
38
+ ]);
39
+ return (React__default["default"].createElement("div", { ref: setElement, className: props.className, style: { visibility: 'hidden' } }, props.children));
30
40
  };
31
41
 
32
42
  class Editor extends core.Editor {
@@ -36,46 +46,130 @@ class Editor extends core.Editor {
36
46
  }
37
47
  }
38
48
 
39
- const FloatingMenu = props => {
40
- const element = React.useRef(null);
49
+ const Portals = ({ renderers }) => {
50
+ return (React__default["default"].createElement(React__default["default"].Fragment, null, Array.from(renderers).map(([key, renderer]) => {
51
+ return ReactDOM__default["default"].createPortal(renderer.reactElement, renderer.element, key);
52
+ })));
53
+ };
54
+ class PureEditorContent extends React__default["default"].Component {
55
+ constructor(props) {
56
+ super(props);
57
+ this.editorContentRef = React__default["default"].createRef();
58
+ this.state = {
59
+ renderers: new Map(),
60
+ };
61
+ }
62
+ componentDidMount() {
63
+ this.init();
64
+ }
65
+ componentDidUpdate() {
66
+ this.init();
67
+ }
68
+ init() {
69
+ const { editor } = this.props;
70
+ if (editor && editor.options.element) {
71
+ if (editor.contentComponent) {
72
+ return;
73
+ }
74
+ const element = this.editorContentRef.current;
75
+ element.append(...editor.options.element.childNodes);
76
+ editor.setOptions({
77
+ element,
78
+ });
79
+ editor.contentComponent = this;
80
+ editor.createNodeViews();
81
+ }
82
+ }
83
+ componentWillUnmount() {
84
+ const { editor } = this.props;
85
+ if (!editor) {
86
+ return;
87
+ }
88
+ if (!editor.isDestroyed) {
89
+ editor.view.setProps({
90
+ nodeViews: {},
91
+ });
92
+ }
93
+ editor.contentComponent = null;
94
+ if (!editor.options.element.firstChild) {
95
+ return;
96
+ }
97
+ const newElement = document.createElement('div');
98
+ newElement.append(...editor.options.element.childNodes);
99
+ editor.setOptions({
100
+ element: newElement,
101
+ });
102
+ }
103
+ render() {
104
+ const { editor, ...rest } = this.props;
105
+ return (React__default["default"].createElement(React__default["default"].Fragment, null,
106
+ React__default["default"].createElement("div", { ref: this.editorContentRef, ...rest }),
107
+ React__default["default"].createElement(Portals, { renderers: this.state.renderers })));
108
+ }
109
+ }
110
+ const EditorContent = React__default["default"].memo(PureEditorContent);
111
+
112
+ const FloatingMenu = (props) => {
113
+ const [element, setElement] = React.useState(null);
41
114
  React.useEffect(() => {
42
- const { editor } = props;
43
- editor.registerPlugin(extensionFloatingMenu.FloatingMenuPlugin({
115
+ if (!element) {
116
+ return;
117
+ }
118
+ if (props.editor.isDestroyed) {
119
+ return;
120
+ }
121
+ const { pluginKey = 'floatingMenu', editor, tippyOptions = {}, shouldShow = null, } = props;
122
+ const plugin = extensionFloatingMenu.FloatingMenuPlugin({
123
+ pluginKey,
44
124
  editor,
45
- element: element.current,
46
- }));
47
- return () => {
48
- editor.unregisterPlugin(extensionFloatingMenu.FloatingMenuPluginKey);
49
- };
50
- }, []);
51
- return (React__default['default'].createElement("div", { ref: element, className: props.className, style: { visibility: 'hidden' } }, props.children));
125
+ element,
126
+ tippyOptions,
127
+ shouldShow,
128
+ });
129
+ editor.registerPlugin(plugin);
130
+ return () => editor.unregisterPlugin(pluginKey);
131
+ }, [
132
+ props.editor,
133
+ element,
134
+ ]);
135
+ return (React__default["default"].createElement("div", { ref: setElement, className: props.className, style: { visibility: 'hidden' } }, props.children));
52
136
  };
53
137
 
54
- function useForceUpdate() {
55
- const [, setValue] = React.useState(0);
56
- return () => setValue(value => value + 1);
57
- }
58
- const useEditor = (options = {}) => {
59
- const [editor, setEditor] = React.useState(null);
60
- const forceUpdate = useForceUpdate();
61
- React.useEffect(() => {
62
- const instance = new Editor(options);
63
- setEditor(instance);
64
- instance.on('transaction', forceUpdate);
65
- return () => {
66
- instance.destroy();
67
- };
68
- }, []);
69
- return editor;
138
+ const ReactNodeViewContext = React.createContext({
139
+ onDragStart: undefined,
140
+ });
141
+ const useReactNodeView = () => React.useContext(ReactNodeViewContext);
142
+
143
+ const NodeViewContent = props => {
144
+ const Tag = props.as || 'div';
145
+ const { nodeViewContentRef } = useReactNodeView();
146
+ return (React__default["default"].createElement(Tag, { ...props, ref: nodeViewContentRef, "data-node-view-content": "", style: {
147
+ whiteSpace: 'pre-wrap',
148
+ ...props.style,
149
+ } }));
70
150
  };
71
151
 
152
+ const NodeViewWrapper = React__default["default"].forwardRef((props, ref) => {
153
+ const { onDragStart } = useReactNodeView();
154
+ const Tag = props.as || 'div';
155
+ return (React__default["default"].createElement(Tag, { ...props, ref: ref, "data-node-view-wrapper": "", onDragStart: onDragStart, style: {
156
+ whiteSpace: 'normal',
157
+ ...props.style,
158
+ } }));
159
+ });
160
+
72
161
  function isClassComponent(Component) {
73
162
  return !!(typeof Component === 'function'
74
163
  && Component.prototype
75
164
  && Component.prototype.isReactComponent);
76
165
  }
166
+ function isForwardRefComponent(Component) {
167
+ var _a;
168
+ return !!(typeof Component === 'object'
169
+ && ((_a = Component.$$typeof) === null || _a === void 0 ? void 0 : _a.toString()) === 'Symbol(react.forward_ref)');
170
+ }
77
171
  class ReactRenderer {
78
- constructor(component, { editor, props = {}, as = 'div' }) {
172
+ constructor(component, { editor, props = {}, as = 'div', className = '', }) {
79
173
  this.ref = null;
80
174
  this.id = Math.floor(Math.random() * 0xFFFFFFFF).toString();
81
175
  this.component = component;
@@ -83,23 +177,30 @@ class ReactRenderer {
83
177
  this.props = props;
84
178
  this.element = document.createElement(as);
85
179
  this.element.classList.add('react-renderer');
180
+ if (className) {
181
+ this.element.classList.add(...className.split(' '));
182
+ }
86
183
  this.render();
87
184
  }
88
185
  render() {
89
- var _a;
90
186
  const Component = this.component;
91
187
  const props = this.props;
92
- if (isClassComponent(Component)) {
188
+ if (isClassComponent(Component) || isForwardRefComponent(Component)) {
93
189
  props.ref = (ref) => {
94
190
  this.ref = ref;
95
191
  };
96
192
  }
97
- this.reactElement = React__default['default'].createElement(Component, Object.assign({}, props));
98
- if ((_a = this.editor) === null || _a === void 0 ? void 0 : _a.contentComponent) {
99
- this.editor.contentComponent.setState({
100
- renderers: this.editor.contentComponent.state.renderers.set(this.id, this),
193
+ this.reactElement = React__default["default"].createElement(Component, { ...props });
194
+ queueMicrotask(() => {
195
+ ReactDOM.flushSync(() => {
196
+ var _a;
197
+ if ((_a = this.editor) === null || _a === void 0 ? void 0 : _a.contentComponent) {
198
+ this.editor.contentComponent.setState({
199
+ renderers: this.editor.contentComponent.state.renderers.set(this.id, this),
200
+ });
201
+ }
101
202
  });
102
- }
203
+ });
103
204
  }
104
205
  updateProps(props = {}) {
105
206
  this.props = {
@@ -109,22 +210,21 @@ class ReactRenderer {
109
210
  this.render();
110
211
  }
111
212
  destroy() {
112
- var _a;
113
- if ((_a = this.editor) === null || _a === void 0 ? void 0 : _a.contentComponent) {
114
- const { renderers } = this.editor.contentComponent.state;
115
- renderers.delete(this.id);
116
- this.editor.contentComponent.setState({
117
- renderers,
213
+ queueMicrotask(() => {
214
+ ReactDOM.flushSync(() => {
215
+ var _a;
216
+ if ((_a = this.editor) === null || _a === void 0 ? void 0 : _a.contentComponent) {
217
+ const { renderers } = this.editor.contentComponent.state;
218
+ renderers.delete(this.id);
219
+ this.editor.contentComponent.setState({
220
+ renderers,
221
+ });
222
+ }
118
223
  });
119
- }
224
+ });
120
225
  }
121
226
  }
122
227
 
123
- const ReactNodeViewContext = React.createContext({
124
- onDragStart: undefined,
125
- });
126
- const useReactNodeView = () => React.useContext(ReactNodeViewContext);
127
-
128
228
  class ReactNodeView extends core.NodeView {
129
229
  mount() {
130
230
  const props = {
@@ -135,6 +235,7 @@ class ReactNodeView extends core.NodeView {
135
235
  extension: this.extension,
136
236
  getPos: () => this.getPos(),
137
237
  updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
238
+ deleteNode: () => this.deleteNode(),
138
239
  };
139
240
  if (!this.component.displayName) {
140
241
  const capitalizeFirstChar = (string) => {
@@ -143,21 +244,36 @@ class ReactNodeView extends core.NodeView {
143
244
  this.component.displayName = capitalizeFirstChar(this.extension.name);
144
245
  }
145
246
  const ReactNodeViewProvider = componentProps => {
146
- const onDragStart = this.onDragStart.bind(this);
147
247
  const Component = this.component;
148
- return (React__default['default'].createElement(ReactNodeViewContext.Provider, { value: { onDragStart } },
149
- React__default['default'].createElement(Component, Object.assign({}, componentProps))));
248
+ const onDragStart = this.onDragStart.bind(this);
249
+ const nodeViewContentRef = element => {
250
+ if (element && this.contentDOMElement && element.firstChild !== this.contentDOMElement) {
251
+ element.appendChild(this.contentDOMElement);
252
+ }
253
+ };
254
+ return (React__default["default"].createElement(ReactNodeViewContext.Provider, { value: { onDragStart, nodeViewContentRef } },
255
+ React__default["default"].createElement(Component, { ...componentProps })));
150
256
  };
151
257
  ReactNodeViewProvider.displayName = 'ReactNodeView';
152
258
  this.contentDOMElement = this.node.isLeaf
153
259
  ? null
154
260
  : document.createElement(this.node.isInline ? 'span' : 'div');
261
+ if (this.contentDOMElement) {
262
+ // For some reason the whiteSpace prop is not inherited properly in Chrome and Safari
263
+ // With this fix it seems to work fine
264
+ // See: https://github.com/ueberdosis/tiptap/issues/1197
265
+ this.contentDOMElement.style.whiteSpace = 'inherit';
266
+ }
267
+ let as = this.node.isInline ? 'span' : 'div';
268
+ if (this.options.as) {
269
+ as = this.options.as;
270
+ }
271
+ const { className = '' } = this.options;
155
272
  this.renderer = new ReactRenderer(ReactNodeViewProvider, {
156
273
  editor: this.editor,
157
274
  props,
158
- as: this.node.isInline
159
- ? 'span'
160
- : 'div',
275
+ as,
276
+ className: `node-${this.node.type.name} ${className}`.trim(),
161
277
  });
162
278
  }
163
279
  get dom() {
@@ -172,27 +288,34 @@ class ReactNodeView extends core.NodeView {
172
288
  if (this.node.isLeaf) {
173
289
  return null;
174
290
  }
175
- const contentElement = this.dom.querySelector('[data-node-view-content]');
176
- if (this.contentDOMElement
177
- && contentElement
178
- && !contentElement.contains(this.contentDOMElement)) {
179
- contentElement.appendChild(this.contentDOMElement);
180
- }
181
291
  return this.contentDOMElement;
182
292
  }
183
293
  update(node, decorations) {
184
- if (typeof this.options.update === 'function') {
185
- return this.options.update(node, decorations);
186
- }
294
+ const updateProps = (props) => {
295
+ this.renderer.updateProps(props);
296
+ };
187
297
  if (node.type !== this.node.type) {
188
298
  return false;
189
299
  }
300
+ if (typeof this.options.update === 'function') {
301
+ const oldNode = this.node;
302
+ const oldDecorations = this.decorations;
303
+ this.node = node;
304
+ this.decorations = decorations;
305
+ return this.options.update({
306
+ oldNode,
307
+ oldDecorations,
308
+ newNode: node,
309
+ newDecorations: decorations,
310
+ updateProps: () => updateProps({ node, decorations }),
311
+ });
312
+ }
190
313
  if (node === this.node && this.decorations === decorations) {
191
314
  return true;
192
315
  }
193
316
  this.node = node;
194
317
  this.decorations = decorations;
195
- this.renderer.updateProps({ node, decorations });
318
+ updateProps({ node, decorations });
196
319
  return true;
197
320
  }
198
321
  selectNode() {
@@ -222,78 +345,32 @@ function ReactNodeViewRenderer(component, options) {
222
345
  };
223
346
  }
224
347
 
225
- const Portals = ({ renderers }) => {
226
- return (React__default['default'].createElement(React__default['default'].Fragment, null, Array.from(renderers).map(([key, renderer]) => {
227
- return ReactDOM__default['default'].createPortal(renderer.reactElement, renderer.element, key);
228
- })));
229
- };
230
- class PureEditorContent extends React__default['default'].Component {
231
- constructor(props) {
232
- super(props);
233
- this.editorContentRef = React__default['default'].createRef();
234
- this.state = {
235
- renderers: new Map(),
236
- };
237
- }
238
- componentDidMount() {
239
- this.init();
240
- }
241
- componentDidUpdate() {
242
- this.init();
243
- }
244
- init() {
245
- const { editor } = this.props;
246
- if (editor && editor.options.element) {
247
- if (editor.contentComponent) {
248
- return;
249
- }
250
- const element = this.editorContentRef.current;
251
- element.appendChild(editor.options.element.firstChild);
252
- editor.setOptions({
253
- element,
254
- });
255
- editor.contentComponent = this;
256
- // TODO: alternative to setTimeout?
257
- setTimeout(() => editor.createNodeViews(), 0);
258
- }
259
- }
260
- componentWillUnmount() {
261
- const { editor } = this.props;
262
- if (!editor) {
263
- return;
264
- }
265
- if (!editor.isDestroyed) {
266
- editor.view.setProps({
267
- nodeViews: {},
348
+ function useForceUpdate() {
349
+ const [, setValue] = React.useState(0);
350
+ return () => setValue(value => value + 1);
351
+ }
352
+ const useEditor = (options = {}, deps = []) => {
353
+ const [editor, setEditor] = React.useState(null);
354
+ const forceUpdate = useForceUpdate();
355
+ React.useEffect(() => {
356
+ let isMounted = true;
357
+ const instance = new Editor(options);
358
+ setEditor(instance);
359
+ instance.on('transaction', () => {
360
+ requestAnimationFrame(() => {
361
+ requestAnimationFrame(() => {
362
+ if (isMounted) {
363
+ forceUpdate();
364
+ }
365
+ });
268
366
  });
269
- }
270
- editor.contentComponent = null;
271
- if (!editor.options.element.firstChild) {
272
- return;
273
- }
274
- const newElement = document.createElement('div');
275
- newElement.appendChild(editor.options.element.firstChild);
276
- editor.setOptions({
277
- element: newElement,
278
367
  });
279
- }
280
- render() {
281
- return (React__default['default'].createElement(React__default['default'].Fragment, null,
282
- React__default['default'].createElement("div", { ref: this.editorContentRef }),
283
- React__default['default'].createElement(Portals, { renderers: this.state.renderers })));
284
- }
285
- }
286
- const EditorContent = React__default['default'].memo(PureEditorContent);
287
-
288
- const NodeViewWrapper = props => {
289
- const { onDragStart } = useReactNodeView();
290
- const Tag = props.as || 'div';
291
- return (React__default['default'].createElement(Tag, { className: props.className, "data-node-view-wrapper": "", onDragStart: onDragStart, style: { whiteSpace: 'normal' } }, props.children));
292
- };
293
-
294
- const NodeViewContent = props => {
295
- const Tag = props.as || 'div';
296
- return (React__default['default'].createElement(Tag, { className: props.className, "data-node-view-content": "", style: { whiteSpace: 'pre-wrap' } }));
368
+ return () => {
369
+ instance.destroy();
370
+ isMounted = false;
371
+ };
372
+ }, deps);
373
+ return editor;
297
374
  };
298
375
 
299
376
  exports.BubbleMenu = BubbleMenu;
@@ -309,9 +386,7 @@ exports.useEditor = useEditor;
309
386
  Object.keys(core).forEach(function (k) {
310
387
  if (k !== 'default' && !exports.hasOwnProperty(k)) Object.defineProperty(exports, k, {
311
388
  enumerable: true,
312
- get: function () {
313
- return core[k];
314
- }
389
+ get: function () { return core[k]; }
315
390
  });
316
391
  });
317
392
  //# sourceMappingURL=tiptap-react.cjs.js.map