@tiptap/react 2.5.9 → 2.6.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.js CHANGED
@@ -5,127 +5,6 @@ import { Editor as Editor$1, NodeView } from '@tiptap/core';
5
5
  export * from '@tiptap/core';
6
6
  import { FloatingMenuPlugin } from '@tiptap/extension-floating-menu';
7
7
 
8
- const mergeRefs = (...refs) => {
9
- return (node) => {
10
- refs.forEach(ref => {
11
- if (typeof ref === 'function') {
12
- ref(node);
13
- }
14
- else if (ref) {
15
- ref.current = node;
16
- }
17
- });
18
- };
19
- };
20
- const Portals = ({ renderers }) => {
21
- return (React.createElement(React.Fragment, null, Object.entries(renderers).map(([key, renderer]) => {
22
- return ReactDOM.createPortal(renderer.reactElement, renderer.element, key);
23
- })));
24
- };
25
- class PureEditorContent extends React.Component {
26
- constructor(props) {
27
- super(props);
28
- this.editorContentRef = React.createRef();
29
- this.initialized = false;
30
- this.state = {
31
- renderers: {},
32
- };
33
- }
34
- componentDidMount() {
35
- this.init();
36
- }
37
- componentDidUpdate() {
38
- this.init();
39
- }
40
- init() {
41
- const { editor } = this.props;
42
- if (editor && !editor.isDestroyed && editor.options.element) {
43
- if (editor.contentComponent) {
44
- return;
45
- }
46
- const element = this.editorContentRef.current;
47
- element.append(...editor.options.element.childNodes);
48
- editor.setOptions({
49
- element,
50
- });
51
- editor.contentComponent = this;
52
- editor.createNodeViews();
53
- this.initialized = true;
54
- }
55
- }
56
- maybeFlushSync(fn) {
57
- // Avoid calling flushSync until the editor is initialized.
58
- // Initialization happens during the componentDidMount or componentDidUpdate
59
- // lifecycle methods, and React doesn't allow calling flushSync from inside
60
- // a lifecycle method.
61
- if (this.initialized) {
62
- flushSync(fn);
63
- }
64
- else {
65
- fn();
66
- }
67
- }
68
- setRenderer(id, renderer) {
69
- this.maybeFlushSync(() => {
70
- this.setState(({ renderers }) => ({
71
- renderers: {
72
- ...renderers,
73
- [id]: renderer,
74
- },
75
- }));
76
- });
77
- }
78
- removeRenderer(id) {
79
- this.maybeFlushSync(() => {
80
- this.setState(({ renderers }) => {
81
- const nextRenderers = { ...renderers };
82
- delete nextRenderers[id];
83
- return { renderers: nextRenderers };
84
- });
85
- });
86
- }
87
- componentWillUnmount() {
88
- const { editor } = this.props;
89
- if (!editor) {
90
- return;
91
- }
92
- this.initialized = false;
93
- if (!editor.isDestroyed) {
94
- editor.view.setProps({
95
- nodeViews: {},
96
- });
97
- }
98
- editor.contentComponent = null;
99
- if (!editor.options.element.firstChild) {
100
- return;
101
- }
102
- const newElement = document.createElement('div');
103
- newElement.append(...editor.options.element.childNodes);
104
- editor.setOptions({
105
- element: newElement,
106
- });
107
- }
108
- render() {
109
- const { editor, innerRef, ...rest } = this.props;
110
- return (React.createElement(React.Fragment, null,
111
- React.createElement("div", { ref: mergeRefs(innerRef, this.editorContentRef), ...rest }),
112
- React.createElement(Portals, { renderers: this.state.renderers })));
113
- }
114
- }
115
- // EditorContent should be re-created whenever the Editor instance changes
116
- const EditorContentWithKey = forwardRef((props, ref) => {
117
- const key = React.useMemo(() => {
118
- return Math.floor(Math.random() * 0xFFFFFFFF).toString();
119
- }, [props.editor]);
120
- // Can't use JSX here because it conflicts with the type definition of Vue's JSX, so use createElement
121
- return React.createElement(PureEditorContent, {
122
- key,
123
- innerRef: ref,
124
- ...props,
125
- });
126
- });
127
- const EditorContent = React.memo(EditorContentWithKey);
128
-
129
8
  var shim = {exports: {}};
130
9
 
131
10
  var useSyncExternalStoreShim_production_min = {};
@@ -404,12 +283,161 @@ if (process.env.NODE_ENV === 'production') {
404
283
 
405
284
  var shimExports = shim.exports;
406
285
 
407
- class Editor extends Editor$1 {
408
- constructor() {
409
- super(...arguments);
410
- this.contentComponent = null;
286
+ const mergeRefs = (...refs) => {
287
+ return (node) => {
288
+ refs.forEach(ref => {
289
+ if (typeof ref === 'function') {
290
+ ref(node);
291
+ }
292
+ else if (ref) {
293
+ ref.current = node;
294
+ }
295
+ });
296
+ };
297
+ };
298
+ /**
299
+ * This component renders all of the editor's node views.
300
+ */
301
+ const Portals = ({ contentComponent, }) => {
302
+ // For performance reasons, we render the node view portals on state changes only
303
+ const renderers = shimExports.useSyncExternalStore(contentComponent.subscribe, contentComponent.getSnapshot, contentComponent.getServerSnapshot);
304
+ // This allows us to directly render the portals without any additional wrapper
305
+ return (React.createElement(React.Fragment, null, Object.values(renderers)));
306
+ };
307
+ function getInstance() {
308
+ const subscribers = new Set();
309
+ let renderers = {};
310
+ return {
311
+ /**
312
+ * Subscribe to the editor instance's changes.
313
+ */
314
+ subscribe(callback) {
315
+ subscribers.add(callback);
316
+ return () => {
317
+ subscribers.delete(callback);
318
+ };
319
+ },
320
+ getSnapshot() {
321
+ return renderers;
322
+ },
323
+ getServerSnapshot() {
324
+ return renderers;
325
+ },
326
+ /**
327
+ * Adds a new NodeView Renderer to the editor.
328
+ */
329
+ setRenderer(id, renderer) {
330
+ renderers = {
331
+ ...renderers,
332
+ [id]: ReactDOM.createPortal(renderer.reactElement, renderer.element, id),
333
+ };
334
+ subscribers.forEach(subscriber => subscriber());
335
+ },
336
+ /**
337
+ * Removes a NodeView Renderer from the editor.
338
+ */
339
+ removeRenderer(id) {
340
+ const nextRenderers = { ...renderers };
341
+ delete nextRenderers[id];
342
+ renderers = nextRenderers;
343
+ subscribers.forEach(subscriber => subscriber());
344
+ },
345
+ };
346
+ }
347
+ class PureEditorContent extends React.Component {
348
+ constructor(props) {
349
+ var _a;
350
+ super(props);
351
+ this.editorContentRef = React.createRef();
352
+ this.initialized = false;
353
+ this.state = {
354
+ hasContentComponentInitialized: Boolean((_a = props.editor) === null || _a === void 0 ? void 0 : _a.contentComponent),
355
+ };
356
+ }
357
+ componentDidMount() {
358
+ this.init();
359
+ }
360
+ componentDidUpdate() {
361
+ this.init();
362
+ }
363
+ init() {
364
+ const { editor } = this.props;
365
+ if (editor && !editor.isDestroyed && editor.options.element) {
366
+ if (editor.contentComponent) {
367
+ return;
368
+ }
369
+ const element = this.editorContentRef.current;
370
+ element.append(...editor.options.element.childNodes);
371
+ editor.setOptions({
372
+ element,
373
+ });
374
+ editor.contentComponent = getInstance();
375
+ // Has the content component been initialized?
376
+ if (!this.state.hasContentComponentInitialized) {
377
+ // Subscribe to the content component
378
+ this.unsubscribeToContentComponent = editor.contentComponent.subscribe(() => {
379
+ this.setState(prevState => {
380
+ if (!prevState.hasContentComponentInitialized) {
381
+ return {
382
+ hasContentComponentInitialized: true,
383
+ };
384
+ }
385
+ return prevState;
386
+ });
387
+ // Unsubscribe to previous content component
388
+ if (this.unsubscribeToContentComponent) {
389
+ this.unsubscribeToContentComponent();
390
+ }
391
+ });
392
+ }
393
+ editor.createNodeViews();
394
+ this.initialized = true;
395
+ }
396
+ }
397
+ componentWillUnmount() {
398
+ const { editor } = this.props;
399
+ if (!editor) {
400
+ return;
401
+ }
402
+ this.initialized = false;
403
+ if (!editor.isDestroyed) {
404
+ editor.view.setProps({
405
+ nodeViews: {},
406
+ });
407
+ }
408
+ if (this.unsubscribeToContentComponent) {
409
+ this.unsubscribeToContentComponent();
410
+ }
411
+ editor.contentComponent = null;
412
+ if (!editor.options.element.firstChild) {
413
+ return;
414
+ }
415
+ const newElement = document.createElement('div');
416
+ newElement.append(...editor.options.element.childNodes);
417
+ editor.setOptions({
418
+ element: newElement,
419
+ });
420
+ }
421
+ render() {
422
+ const { editor, innerRef, ...rest } = this.props;
423
+ return (React.createElement(React.Fragment, null,
424
+ React.createElement("div", { ref: mergeRefs(innerRef, this.editorContentRef), ...rest }),
425
+ (editor === null || editor === void 0 ? void 0 : editor.contentComponent) && React.createElement(Portals, { contentComponent: editor.contentComponent })));
411
426
  }
412
427
  }
428
+ // EditorContent should be re-created whenever the Editor instance changes
429
+ const EditorContentWithKey = forwardRef((props, ref) => {
430
+ const key = React.useMemo(() => {
431
+ return Math.floor(Math.random() * 0xffffffff).toString();
432
+ }, [props.editor]);
433
+ // Can't use JSX here because it conflicts with the type definition of Vue's JSX, so use createElement
434
+ return React.createElement(PureEditorContent, {
435
+ key,
436
+ innerRef: ref,
437
+ ...props,
438
+ });
439
+ });
440
+ const EditorContent = React.memo(EditorContentWithKey);
413
441
 
414
442
  var withSelector = {exports: {}};
415
443
 
@@ -769,17 +797,20 @@ class EditorInstanceManager {
769
797
  * Create a new editor instance. And attach event listeners.
770
798
  */
771
799
  createEditor() {
772
- const editor = new Editor(this.options.current);
773
- // Always call the most recent version of the callback function by default
774
- editor.on('beforeCreate', (...args) => { var _a, _b; return (_b = (_a = this.options.current).onBeforeCreate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
775
- editor.on('blur', (...args) => { var _a, _b; return (_b = (_a = this.options.current).onBlur) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
776
- editor.on('create', (...args) => { var _a, _b; return (_b = (_a = this.options.current).onCreate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
777
- editor.on('destroy', (...args) => { var _a, _b; return (_b = (_a = this.options.current).onDestroy) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
778
- editor.on('focus', (...args) => { var _a, _b; return (_b = (_a = this.options.current).onFocus) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
779
- editor.on('selectionUpdate', (...args) => { var _a, _b; return (_b = (_a = this.options.current).onSelectionUpdate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
780
- editor.on('transaction', (...args) => { var _a, _b; return (_b = (_a = this.options.current).onTransaction) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
781
- editor.on('update', (...args) => { var _a, _b; return (_b = (_a = this.options.current).onUpdate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
782
- editor.on('contentError', (...args) => { var _a, _b; return (_b = (_a = this.options.current).onContentError) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
800
+ const optionsToApply = {
801
+ ...this.options.current,
802
+ // Always call the most recent version of the callback function by default
803
+ onBeforeCreate: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onBeforeCreate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
804
+ onBlur: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onBlur) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
805
+ onCreate: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onCreate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
806
+ onDestroy: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onDestroy) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
807
+ onFocus: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onFocus) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
808
+ onSelectionUpdate: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onSelectionUpdate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
809
+ onTransaction: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onTransaction) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
810
+ onUpdate: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onUpdate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
811
+ onContentError: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onContentError) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
812
+ };
813
+ const editor = new Editor$1(optionsToApply);
783
814
  // no need to keep track of the event listeners, they will be removed when the editor is destroyed
784
815
  return editor;
785
816
  }
@@ -971,6 +1002,13 @@ const BubbleMenu = (props) => {
971
1002
  return (React.createElement("div", { ref: setElement, className: props.className, style: { visibility: 'hidden' } }, props.children));
972
1003
  };
973
1004
 
1005
+ class Editor extends Editor$1 {
1006
+ constructor() {
1007
+ super(...arguments);
1008
+ this.contentComponent = null;
1009
+ }
1010
+ }
1011
+
974
1012
  const FloatingMenu = (props) => {
975
1013
  const [element, setElement] = useState(null);
976
1014
  const { editor: currentEditor } = useCurrentEditor();
@@ -1022,7 +1060,9 @@ const NodeViewContent = props => {
1022
1060
  const NodeViewWrapper = React.forwardRef((props, ref) => {
1023
1061
  const { onDragStart } = useReactNodeView();
1024
1062
  const Tag = props.as || 'div';
1025
- return (React.createElement(Tag, { ...props, ref: ref, "data-node-view-wrapper": "", onDragStart: onDragStart, style: {
1063
+ return (
1064
+ // @ts-ignore
1065
+ React.createElement(Tag, { ...props, ref: ref, "data-node-view-wrapper": "", onDragStart: onDragStart, style: {
1026
1066
  whiteSpace: 'normal',
1027
1067
  ...props.style,
1028
1068
  } }));
@@ -1076,7 +1116,16 @@ class ReactRenderer {
1076
1116
  this.element.setAttribute(key, attrs[key]);
1077
1117
  });
1078
1118
  }
1079
- this.render();
1119
+ if (this.editor.isInitialized) {
1120
+ // On first render, we need to flush the render synchronously
1121
+ // Renders afterwards can be async, but this fixes a cursor positioning issue
1122
+ flushSync(() => {
1123
+ this.render();
1124
+ });
1125
+ }
1126
+ else {
1127
+ this.render();
1128
+ }
1080
1129
  }
1081
1130
  render() {
1082
1131
  var _a, _b;
@@ -1087,7 +1136,7 @@ class ReactRenderer {
1087
1136
  this.ref = ref;
1088
1137
  };
1089
1138
  }
1090
- this.reactElement = React.createElement(Component, { ...props });
1139
+ this.reactElement = React.createElement(Component, props);
1091
1140
  (_b = (_a = this.editor) === null || _a === void 0 ? void 0 : _a.contentComponent) === null || _b === void 0 ? void 0 : _b.setRenderer(this.id, this);
1092
1141
  }
1093
1142
  updateProps(props = {}) {
@@ -1121,18 +1170,19 @@ class ReactNodeView extends NodeView {
1121
1170
  };
1122
1171
  this.component.displayName = capitalizeFirstChar(this.extension.name);
1123
1172
  }
1124
- const ReactNodeViewProvider = componentProps => {
1125
- const Component = this.component;
1126
- const onDragStart = this.onDragStart.bind(this);
1127
- const nodeViewContentRef = element => {
1128
- if (element && this.contentDOMElement && element.firstChild !== this.contentDOMElement) {
1129
- element.appendChild(this.contentDOMElement);
1130
- }
1131
- };
1132
- return (React.createElement(React.Fragment, null,
1133
- React.createElement(ReactNodeViewContext.Provider, { value: { onDragStart, nodeViewContentRef } },
1134
- React.createElement(Component, { ...componentProps }))));
1173
+ const onDragStart = this.onDragStart.bind(this);
1174
+ const nodeViewContentRef = element => {
1175
+ if (element && this.contentDOMElement && element.firstChild !== this.contentDOMElement) {
1176
+ element.appendChild(this.contentDOMElement);
1177
+ }
1135
1178
  };
1179
+ const context = { onDragStart, nodeViewContentRef };
1180
+ const Component = this.component;
1181
+ // For performance reasons, we memoize the provider component
1182
+ // And all of the things it requires are declared outside of the component, so it doesn't need to re-render
1183
+ const ReactNodeViewProvider = React.memo(componentProps => {
1184
+ return (React.createElement(ReactNodeViewContext.Provider, { value: context }, React.createElement(Component, componentProps)));
1185
+ });
1136
1186
  ReactNodeViewProvider.displayName = 'ReactNodeView';
1137
1187
  if (this.node.isLeaf) {
1138
1188
  this.contentDOMElement = null;