@handlewithcare/react-prosemirror 3.1.0-tiptap.51 → 3.1.0-tiptap.52

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 (39) hide show
  1. package/dist/cjs/ReactEditorView.js +0 -2
  2. package/dist/cjs/components/ChildNodeViews.js +9 -14
  3. package/dist/cjs/components/CursorWrapper.js +9 -6
  4. package/dist/cjs/components/TextNodeView.js +38 -109
  5. package/dist/cjs/components/TrailingHackView.js +0 -29
  6. package/dist/cjs/components/WidgetView.js +1 -0
  7. package/dist/cjs/hooks/useComponentEventListeners.js +6 -14
  8. package/dist/cjs/hooks/useNodeViewDescription.js +16 -37
  9. package/dist/cjs/plugins/beforeInputPlugin.js +43 -41
  10. package/dist/cjs/plugins/componentEventListeners.js +2 -9
  11. package/dist/cjs/tiptap/utils/ssrJSDOMPatch.js +59 -0
  12. package/dist/cjs/viewdesc.js +3 -10
  13. package/dist/esm/ReactEditorView.js +0 -2
  14. package/dist/esm/components/ChildNodeViews.js +9 -14
  15. package/dist/esm/components/CursorWrapper.js +10 -7
  16. package/dist/esm/components/TextNodeView.js +38 -109
  17. package/dist/esm/components/TrailingHackView.js +1 -30
  18. package/dist/esm/components/WidgetView.js +1 -0
  19. package/dist/esm/hooks/useComponentEventListeners.js +6 -14
  20. package/dist/esm/hooks/useNodeViewDescription.js +17 -38
  21. package/dist/esm/plugins/beforeInputPlugin.js +43 -41
  22. package/dist/esm/plugins/componentEventListeners.js +2 -9
  23. package/dist/esm/tiptap/hooks/useTiptapEditor.js +7 -1
  24. package/dist/esm/tiptap/utils/ssrJSDOMPatch.js +56 -0
  25. package/dist/esm/viewdesc.js +3 -10
  26. package/dist/tsconfig.tsbuildinfo +1 -1
  27. package/dist/types/ReactEditorView.d.ts +0 -4
  28. package/dist/types/components/CursorWrapper.d.ts +2 -4
  29. package/dist/types/components/TextNodeView.d.ts +4 -14
  30. package/dist/types/components/TrailingHackView.d.ts +1 -1
  31. package/dist/types/components/WidgetViewComponentProps.d.ts +4 -3
  32. package/dist/types/constants.d.ts +1 -1
  33. package/dist/types/hooks/useComponentEventListeners.d.ts +1 -1
  34. package/dist/types/plugins/componentEventListeners.d.ts +2 -3
  35. package/dist/types/props.d.ts +26 -26
  36. package/dist/types/tiptap/hooks/useTiptapEditor.d.ts +7 -0
  37. package/dist/types/tiptap/utils/ssrJSDOMPatch.d.ts +1 -0
  38. package/dist/types/viewdesc.d.ts +2 -2
  39. package/package.json +1 -2
@@ -0,0 +1,59 @@
1
+ /**
2
+ * This file is used to patch global DOM variables in a NodeJS environment.
3
+ * This is needed for ProseMirror to work in a NodeJS environment.
4
+ */ "use strict";
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ if (typeof window === "undefined") {
9
+ // Make sure to import JSDOM only in a NodeJS environment.
10
+ // The magic comments prevent bundlers from statically analyzing this require:
11
+ // - webpackIgnore: true → webpack / Next.js
12
+ // - @vite-ignore → Vite
13
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
14
+ const jsdom = require(/* webpackIgnore: true */ /* @vite-ignore */ "jsdom");
15
+ const html = `
16
+ <!DOCTYPE html>
17
+ <html>
18
+ <head>
19
+ <title>Testing</title>
20
+ </head>
21
+ <body></body>
22
+ </html>
23
+ `;
24
+ const { window: window1 } = new jsdom.JSDOM(html);
25
+ global.window = window1;
26
+ global.document = window1.document;
27
+ // Use Object.defineProperty for navigator since it's read-only in Node.js 22+
28
+ Object.defineProperty(global, "navigator", {
29
+ value: window1.navigator,
30
+ writable: true,
31
+ configurable: true
32
+ });
33
+ global.innerHeight = 0;
34
+ global.SVGElement = window1.SVGElement;
35
+ // @ts-expect-error stub getSelection for SSR
36
+ document.getSelection = ()=>({});
37
+ document.createRange = ()=>({
38
+ setStart () {},
39
+ setEnd () {},
40
+ // @ts-expect-error stub getBoundingClientRect for SSR
41
+ getClientRects () {
42
+ return {
43
+ left: 0,
44
+ top: 0,
45
+ right: 0,
46
+ bottom: 0
47
+ };
48
+ },
49
+ // @ts-expect-error stub getBoundingClientRect for SSR
50
+ getBoundingClientRect () {
51
+ return {
52
+ left: 0,
53
+ top: 0,
54
+ right: 0,
55
+ bottom: 0
56
+ };
57
+ }
58
+ });
59
+ }
@@ -266,9 +266,7 @@ let ViewDesc = class ViewDesc {
266
266
  prev = i ? this.children[i - 1] : null;
267
267
  if (!prev || prev.dom.parentNode == this.contentDOM) break;
268
268
  }
269
- if (prev && side && enter && !prev.border && !prev.domAtom) {
270
- return prev.domFromPos(prev.size, side);
271
- }
269
+ if (prev && side && enter && !prev.border && !prev.domAtom) return prev.domFromPos(prev.size, side);
272
270
  return {
273
271
  node: this.contentDOM,
274
272
  offset: prev ? (0, _dom.domIndex)(prev.dom) + 1 : 0
@@ -403,9 +401,7 @@ let ViewDesc = class ViewDesc {
403
401
  const after = selRange.focusNode.childNodes[selRange.focusOffset];
404
402
  if (after && after.contentEditable == "false") force = true;
405
403
  }
406
- if (!(force || brKludge && _browser.browser.safari) && (0, _dom.isEquivalentPosition)(anchorDOM.node, anchorDOM.offset, selRange.anchorNode, selRange.anchorOffset) && (0, _dom.isEquivalentPosition)(headDOM.node, headDOM.offset, selRange.focusNode, selRange.focusOffset)) {
407
- return;
408
- }
404
+ if (!(force || brKludge && _browser.browser.safari) && (0, _dom.isEquivalentPosition)(anchorDOM.node, anchorDOM.offset, selRange.anchorNode, selRange.anchorOffset) && (0, _dom.isEquivalentPosition)(headDOM.node, headDOM.offset, selRange.focusNode, selRange.focusOffset)) return;
409
405
  // Selection.extend can be used to create an 'inverted' selection
410
406
  // (one where the focus is before the anchor), but not all
411
407
  // browsers support it yet.
@@ -670,10 +666,7 @@ let TextViewDesc = class TextViewDesc extends NodeViewDesc {
670
666
  skip: skip || true
671
667
  };
672
668
  }
673
- update(node, outerDeco, _innerDeco, _view) {
674
- if (this.dirty == NODE_DIRTY || this.dirty != NOT_DIRTY && !this.inParent() || !node.sameMarkup(this.node)) return false;
675
- this.updateOuterDeco(outerDeco);
676
- this.node = node;
669
+ update(_node, _outerDeco, _innerDeco, _view) {
677
670
  this.dirty = NOT_DIRTY;
678
671
  return true;
679
672
  }
@@ -33,7 +33,6 @@ function changedNodeViews(a, b) {
33
33
  nextProps;
34
34
  prevState;
35
35
  _destroyed;
36
- deferPendingEffects;
37
36
  constructor(place, props){
38
37
  // Prevent the base class from destroying the React-managed nodes.
39
38
  // Restore them below after invoking the base class constructor.
@@ -86,7 +85,6 @@ function changedNodeViews(a, b) {
86
85
  // @ts-expect-error this violates the typing but class does it, too.
87
86
  this.docView = null;
88
87
  this._destroyed = false;
89
- this.deferPendingEffects = false;
90
88
  }
91
89
  get props() {
92
90
  return this.nextProps;
@@ -51,18 +51,13 @@ const ChildView = /*#__PURE__*/ memo(function ChildView(param) {
51
51
  key: child.key
52
52
  }, (param)=>{
53
53
  let { siblingsRef, parentRef } = param;
54
- return /*#__PURE__*/ React.createElement(EditorContext.Consumer, null, (param)=>{
55
- let { registerEventListener, unregisterEventListener } = param;
56
- return /*#__PURE__*/ React.createElement(TextNodeView, {
57
- view: view,
58
- node: child.node,
59
- getPos: getPos,
60
- siblingsRef: siblingsRef,
61
- parentRef: parentRef,
62
- decorations: child.outerDeco,
63
- registerEventListener: registerEventListener,
64
- unregisterEventListener: unregisterEventListener
65
- });
54
+ return /*#__PURE__*/ React.createElement(TextNodeView, {
55
+ view: view,
56
+ node: child.node,
57
+ getPos: getPos,
58
+ siblingsRef: siblingsRef,
59
+ parentRef: parentRef,
60
+ decorations: child.outerDeco
66
61
  });
67
62
  }) : /*#__PURE__*/ React.createElement(NodeView, {
68
63
  key: child.key,
@@ -345,14 +340,14 @@ export const ChildNodeViews = /*#__PURE__*/ memo(function ChildNodeViews(param)
345
340
  component: SeparatorHackView,
346
341
  marks: [],
347
342
  offset: lastChild?.offset ?? 0,
348
- index: (lastChild?.index ?? 0) + 1,
343
+ index: (lastChild?.index ?? 0) + 2,
349
344
  key: "trailing-hack-img"
350
345
  }, {
351
346
  type: "hack",
352
347
  component: TrailingHackView,
353
348
  marks: [],
354
349
  offset: lastChild?.offset ?? 0,
355
- index: (lastChild?.index ?? 0) + 2,
350
+ index: (lastChild?.index ?? 0) + 1,
356
351
  key: "trailing-hack-br"
357
352
  });
358
353
  }
@@ -1,9 +1,8 @@
1
- import React, { forwardRef, useImperativeHandle, useRef, useState } from "react";
1
+ import React, { forwardRef, useImperativeHandle, useRef } from "react";
2
2
  import { domIndex } from "../dom.js";
3
3
  import { useEditorEffect } from "../hooks/useEditorEffect.js";
4
4
  export const CursorWrapper = /*#__PURE__*/ forwardRef(function CursorWrapper(param, ref) {
5
5
  let { widget, getPos, ...props } = param;
6
- const [shouldRender, setShouldRender] = useState(true);
7
6
  const innerRef = useRef(null);
8
7
  useImperativeHandle(ref, ()=>{
9
8
  return innerRef.current;
@@ -15,18 +14,22 @@ export const CursorWrapper = /*#__PURE__*/ forwardRef(function CursorWrapper(par
15
14
  // @ts-expect-error Internal property - domSelection
16
15
  const domSel = view.domSelection();
17
16
  const node = innerRef.current;
18
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
19
- domSel.collapse(node.parentNode, domIndex(node) + 1);
17
+ const img = node.nodeName == "IMG";
18
+ if (img) {
19
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
20
+ domSel.collapse(node.parentNode, domIndex(node) + 1);
21
+ } else {
22
+ domSel.collapse(node, 0);
23
+ }
20
24
  // @ts-expect-error Internal property - domObserver
21
25
  view.domObserver.connectSelection();
22
- setShouldRender(false);
23
26
  }, []);
24
- return shouldRender ? /*#__PURE__*/ React.createElement("img", {
27
+ return /*#__PURE__*/ React.createElement("img", {
25
28
  ref: innerRef,
26
29
  className: "ProseMirror-separator",
27
30
  // eslint-disable-next-line react/no-unknown-property
28
31
  "mark-placeholder": "true",
29
32
  alt: "",
30
33
  ...props
31
- }) : null;
34
+ });
32
35
  });
@@ -1,7 +1,5 @@
1
- import { TextSelection } from "prosemirror-state";
2
1
  import { DecorationSet } from "prosemirror-view";
3
2
  import { Component } from "react";
4
- import { ReactEditorView } from "../ReactEditorView.js";
5
3
  import { findDOMNode } from "../findDOMNode.js";
6
4
  import { CompositionViewDesc, TextViewDesc, sortViewDescs } from "../viewdesc.js";
7
5
  import { wrapInDeco } from "./ChildNodeViews.js";
@@ -30,141 +28,72 @@ function shallowEqual(objA, objB) {
30
28
  export class TextNodeView extends Component {
31
29
  viewDescRef = null;
32
30
  renderRef = null;
33
- wasProtecting = false;
34
- containsCompositionNodeText = true;
35
- // This is basically NodeViewDesc.localCompositionInfo
36
- // from prosemirror-view. It's been slightly adjusted so that
37
- // it can be used accurately during render, before we've
38
- // necessarily found (or even let the browser create)
39
- // view.input.compositionNode
40
- shouldProtect(props) {
41
- const { view, getPos, node } = props;
42
- if (!(view instanceof ReactEditorView)) return false;
43
- if (!view.composing) {
44
- return false;
45
- }
46
- const pos = getPos();
47
- const { from, to } = view.state.selection;
48
- if (!(view.state.selection instanceof TextSelection) || from < pos || to > pos + node.nodeSize) {
49
- return false;
50
- }
51
- return this.containsCompositionNodeText;
52
- }
53
- handleCompositionEnd = ()=>{
54
- if (!this.wasProtecting) return;
55
- this.forceUpdate();
56
- return;
57
- };
58
- create() {
31
+ updateEffect() {
59
32
  const { view, decorations, siblingsRef, parentRef, getPos, node } = this.props;
33
+ // There simply is no other way to ref a text node
34
+ // eslint-disable-next-line react/no-find-dom-node
60
35
  const dom = findDOMNode(this);
61
- if (!dom && !view.composing) return null;
62
- let textNode = dom;
63
- while(textNode?.firstChild){
64
- textNode = textNode.firstChild;
65
- }
66
- if (!(textNode instanceof Text)) {
67
- textNode = null;
68
- }
69
- let viewDesc;
70
- if (this.shouldProtect(this.props)) {
71
- viewDesc = new CompositionViewDesc(parentRef.current, getPos, // If we can't
72
- // actually find the correct DOM nodes from here (
73
- // which is the case in a composition in a newly
74
- // created text node), we let our parent do it.
36
+ // We only need to explicitly create a CompositionViewDesc
37
+ // when a composition was started that produces a new text node.
38
+ // Otherwise we just rely on re-rendering the renderRef
39
+ if (!dom) {
40
+ if (!view.composing) return;
41
+ this.viewDescRef = new CompositionViewDesc(parentRef.current, getPos, // These are just placeholders/dummies. We can't
42
+ // actually find the correct DOM nodes from here,
43
+ // so we let our parent do it.
75
44
  // Passing a valid element here just so that the
76
45
  // ViewDesc constructor doesn't blow up.
77
- dom ?? document.createElement("div"), textNode ?? document.createTextNode(node.text ?? ""), node.text ?? "");
78
- } else {
79
- if (!dom || !textNode) return null;
80
- viewDesc = new TextViewDesc(parentRef.current, [], getPos, node, decorations, DecorationSet.empty, dom, textNode);
81
- }
82
- siblingsRef.current.push(viewDesc);
83
- siblingsRef.current.sort(sortViewDescs);
84
- return viewDesc;
85
- }
86
- update() {
87
- const { view, node, decorations } = this.props;
88
- if (!(view instanceof ReactEditorView)) return false;
89
- const viewDesc = this.viewDescRef;
90
- if (!viewDesc) return false;
91
- if (this.shouldProtect(this.props) !== viewDesc instanceof CompositionViewDesc) {
92
- return false;
93
- }
94
- if (viewDesc instanceof CompositionViewDesc) return false;
95
- const dom = findDOMNode(this);
96
- if (!dom || dom !== viewDesc.dom) return false;
97
- if (!dom.contains(viewDesc.nodeDOM)) return false;
98
- return viewDesc.matchesNode(node, decorations, DecorationSet.empty) || viewDesc.update(node, decorations, DecorationSet.empty, view);
99
- }
100
- destroy() {
101
- const viewDesc = this.viewDescRef;
102
- if (!viewDesc) return;
103
- viewDesc.destroy();
104
- const siblings = this.props.siblingsRef.current;
105
- if (siblings.includes(viewDesc)) {
106
- const index = siblings.indexOf(viewDesc);
107
- siblings.splice(index, 1);
46
+ document.createElement("div"), document.createTextNode(node.text ?? ""), node.text ?? "");
47
+ return;
108
48
  }
109
- }
110
- updateEffect() {
111
- if (!this.update()) {
112
- this.destroy();
113
- this.viewDescRef = this.create();
49
+ let textNode = dom;
50
+ while(textNode.firstChild){
51
+ textNode = textNode.firstChild;
114
52
  }
115
- const { view, node } = this.props;
116
- if (!(view instanceof ReactEditorView)) {
117
- this.containsCompositionNodeText = true;
118
- return;
53
+ if (!this.viewDescRef || this.viewDescRef instanceof CompositionViewDesc) {
54
+ this.viewDescRef = new TextViewDesc(undefined, [], getPos, node, decorations, DecorationSet.empty, dom, textNode);
55
+ } else {
56
+ this.viewDescRef.parent = parentRef.current;
57
+ this.viewDescRef.children = [];
58
+ this.viewDescRef.node = node;
59
+ this.viewDescRef.outerDeco = decorations;
60
+ this.viewDescRef.innerDeco = DecorationSet.empty;
61
+ this.viewDescRef.dom = dom;
62
+ this.viewDescRef.dom.pmViewDesc = this.viewDescRef;
63
+ this.viewDescRef.nodeDOM = textNode;
119
64
  }
120
- const textNode = view.input.compositionNode;
121
- if (!textNode) {
122
- this.containsCompositionNodeText = true;
123
- return;
65
+ if (!siblingsRef.current.includes(this.viewDescRef)) {
66
+ siblingsRef.current.push(this.viewDescRef);
124
67
  }
125
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
126
- const text = textNode.nodeValue;
127
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
128
- this.containsCompositionNodeText = node.text === text;
68
+ siblingsRef.current.sort(sortViewDescs);
129
69
  }
130
70
  shouldComponentUpdate(nextProps) {
131
- // When leaving the protected state, force a re-render so React's
132
- // virtual DOM resyncs with whatever the IME wrote into the real DOM
133
- // while we were returning a stale renderRef.
134
- if (this.wasProtecting && !this.shouldProtect(nextProps)) {
135
- return true;
136
- }
137
71
  return !shallowEqual(this.props, nextProps);
138
72
  }
139
73
  componentDidMount() {
140
- this.viewDescRef = null;
141
- // After a composition, force an update so that we re-check whether we need
142
- // to be protecting our rendered content and allow React to re-sync with the
143
- // DOM.
144
- const { registerEventListener } = this.props;
145
- registerEventListener("compositionend", this.handleCompositionEnd);
146
74
  this.updateEffect();
147
75
  }
148
76
  componentDidUpdate() {
149
77
  this.updateEffect();
150
78
  }
151
79
  componentWillUnmount() {
152
- const { unregisterEventListener } = this.props;
153
- unregisterEventListener("compositionend", this.handleCompositionEnd);
154
- this.destroy();
80
+ const { siblingsRef } = this.props;
81
+ if (!this.viewDescRef) return;
82
+ if (siblingsRef.current.includes(this.viewDescRef)) {
83
+ const index = siblingsRef.current.indexOf(this.viewDescRef);
84
+ siblingsRef.current.splice(index, 1);
85
+ }
155
86
  }
156
87
  render() {
157
- const { node, decorations } = this.props;
88
+ const { view, getPos, node, decorations } = this.props;
158
89
  // During a composition, it's crucial that we don't try to
159
90
  // update the DOM that the user is working in. If there's
160
91
  // an active composition and the selection is in this node,
161
92
  // we freeze the DOM of this element so that it doesn't
162
93
  // interrupt the composition
163
- if (this.shouldProtect(this.props)) {
164
- this.wasProtecting = true;
94
+ if (view.composing && view.state.selection.from >= getPos() && view.state.selection.from <= getPos() + node.nodeSize) {
165
95
  return this.renderRef;
166
96
  }
167
- this.wasProtecting = false;
168
97
  this.renderRef = decorations.reduce(wrapInDeco, node.text);
169
98
  return this.renderRef;
170
99
  }
@@ -1,12 +1,9 @@
1
- import React, { useContext, useRef, useState } from "react";
1
+ import React, { useContext, useRef } from "react";
2
2
  import { ChildDescriptionsContext } from "../contexts/ChildDescriptionsContext.js";
3
3
  import { useClientLayoutEffect } from "../hooks/useClientLayoutEffect.js";
4
- import { useEditorEffect } from "../hooks/useEditorEffect.js";
5
- import { useEditorEventListener } from "../hooks/useEditorEventListener.js";
6
4
  import { TrailingHackViewDesc, sortViewDescs } from "../viewdesc.js";
7
5
  export function TrailingHackView(param) {
8
6
  let { getPos } = param;
9
- const [shouldRender, setShouldRender] = useState(true);
10
7
  const { siblingsRef, parentRef } = useContext(ChildDescriptionsContext);
11
8
  const viewDescRef = useRef(null);
12
9
  const ref = useRef(null);
@@ -35,32 +32,6 @@ export function TrailingHackView(param) {
35
32
  }
36
33
  siblingsRef.current.sort(sortViewDescs);
37
34
  });
38
- // At the start of a composition, the browser will automatically delete
39
- // the trailing hack br element. We need to unmount ourselves _before_
40
- // that happens, so that React doesn't try to remove the already-removed
41
- // br node when this component gets unmounted
42
- useEditorEventListener("compositionstart", (view)=>{
43
- const { from } = view.state.selection;
44
- if (from === getPos()) {
45
- setShouldRender(false);
46
- }
47
- });
48
- // We need to run the same composition check when we first get mounted,
49
- // in case we got mounted in the same render batch as the beginning of
50
- // a composition
51
- useEditorEffect((view)=>{
52
- if (!view.composing) return;
53
- const { from } = view.state.selection;
54
- if (from === getPos()) {
55
- setShouldRender(false);
56
- }
57
- }, [
58
- getPos
59
- ]);
60
- useEditorEventListener("compositionend", ()=>{
61
- setShouldRender(true);
62
- });
63
- if (!shouldRender) return null;
64
35
  return /*#__PURE__*/ React.createElement("br", {
65
36
  ref: ref,
66
37
  className: "ProseMirror-trailingBreak"
@@ -27,6 +27,7 @@ export function WidgetView(param) {
27
27
  viewDescRef.current.parent = parentRef.current;
28
28
  viewDescRef.current.widget = widget;
29
29
  viewDescRef.current.dom = domRef.current;
30
+ viewDescRef.current.dom.pmViewDesc = viewDescRef.current;
30
31
  }
31
32
  if (!siblingsRef.current.includes(viewDescRef.current)) {
32
33
  siblingsRef.current.push(viewDescRef.current);
@@ -25,16 +25,8 @@ import { unstable_batchedUpdates as batch } from "react-dom";
25
25
  *
26
26
  * To accomplish this, we shallowly clone the registry whenever a new event
27
27
  * type is registered.
28
- */ export function useComponentEventListeners(existingHandlers) {
29
- const [registry, setRegistry] = useState(new Map(Object.entries(existingHandlers ?? {}).map((param)=>{
30
- let [eventName, handler] = param;
31
- return [
32
- eventName,
33
- handler ? [
34
- handler
35
- ] : []
36
- ];
37
- })));
28
+ */ export function useComponentEventListeners(handleDOMEventsProp) {
29
+ const [registry, setRegistry] = useState(new Map());
38
30
  const registerEventListener = useCallback((eventType, handler)=>{
39
31
  const handlers = registry.get(eventType) ?? [];
40
32
  handlers.unshift(handler);
@@ -52,19 +44,19 @@ import { unstable_batchedUpdates as batch } from "react-dom";
52
44
  registry
53
45
  ]);
54
46
  useLayoutEffect(()=>{
55
- if (!existingHandlers) return;
56
- for (const [eventType, handler] of Object.entries(existingHandlers)){
47
+ if (!handleDOMEventsProp) return;
48
+ for (const [eventType, handler] of Object.entries(handleDOMEventsProp)){
57
49
  if (!handler) return;
58
50
  registerEventListener(eventType, handler);
59
51
  }
60
52
  return ()=>{
61
- for (const [eventType, handler] of Object.entries(existingHandlers)){
53
+ for (const [eventType, handler] of Object.entries(handleDOMEventsProp)){
62
54
  if (!handler) return;
63
55
  unregisterEventListener(eventType, handler);
64
56
  }
65
57
  };
66
58
  }, [
67
- existingHandlers,
59
+ handleDOMEventsProp,
68
60
  registerEventListener,
69
61
  unregisterEventListener
70
62
  ]);
@@ -1,10 +1,8 @@
1
1
  import { useCallback, useContext, useMemo, useRef } from "react";
2
2
  import { ReactEditorView } from "../ReactEditorView.js";
3
- import { CursorWrapper } from "../components/CursorWrapper.js";
4
3
  import { ChildDescriptionsContext } from "../contexts/ChildDescriptionsContext.js";
5
4
  import { EditorContext } from "../contexts/EditorContext.js";
6
- import { ReactWidgetType } from "../decorations/ReactWidgetType.js";
7
- import { CompositionViewDesc, MarkViewDesc, ReactNodeViewDesc, WidgetViewDesc, sortViewDescs } from "../viewdesc.js";
5
+ import { CompositionViewDesc, ReactNodeViewDesc, sortViewDescs } from "../viewdesc.js";
8
6
  import { useClientLayoutEffect } from "./useClientLayoutEffect.js";
9
7
  import { useEffectEvent } from "./useEffectEvent.js";
10
8
  export function useNodeViewDescription(getDOM, getContentDOM, constructor, props) {
@@ -133,44 +131,25 @@ export function useNodeViewDescription(getDOM, getContentDOM, constructor, props
133
131
  children.sort(sortViewDescs);
134
132
  for (const child of children){
135
133
  child.parent = viewDesc;
136
- }
137
- if (!props.node.isTextblock) return;
138
- // Because TextNodeViews can't locate the DOM nodes
139
- // for compositions, we need to override them here
140
- if (!viewDescRef.current?.contentDOM) return;
141
- const compositionChildIndex = children.findIndex((child)=>child instanceof CompositionViewDesc);
142
- if (compositionChildIndex === -1) return;
143
- const compositionViewDesc = children[compositionChildIndex];
144
- if (!(compositionViewDesc instanceof CompositionViewDesc)) return;
145
- let compositionTopDOM = null;
146
- let search = children[compositionChildIndex - 1];
147
- while(search instanceof MarkViewDesc){
148
- search = search.children[0];
149
- }
150
- if (search instanceof WidgetViewDesc && search.widget.type instanceof ReactWidgetType && search.widget.type.Component === CursorWrapper) {
151
- compositionTopDOM = search.dom.nextSibling;
152
- } else {
153
- for (const childNode of viewDescRef.current.contentDOM.childNodes){
154
- if (children.every((child)=>child.dom !== childNode)) {
155
- compositionTopDOM = childNode;
156
- break;
134
+ // Because TextNodeViews can't locate the DOM nodes
135
+ // for compositions, we need to override them here
136
+ if (child instanceof CompositionViewDesc) {
137
+ const compositionTopDOM = viewDesc?.contentDOM?.firstChild;
138
+ if (!compositionTopDOM) throw new Error(`Started a composition but couldn't find the text node it belongs to.`);
139
+ let textDOM = compositionTopDOM;
140
+ while(textDOM.firstChild){
141
+ textDOM = textDOM.firstChild;
157
142
  }
143
+ if (!textDOM || !(textDOM instanceof Text)) throw new Error(`Started a composition but couldn't find the text node it belongs to.`);
144
+ child.dom = compositionTopDOM;
145
+ child.textDOM = textDOM;
146
+ child.text = textDOM.data;
147
+ child.textDOM.pmViewDesc = child;
148
+ // It should not be possible to be in a composition because one could
149
+ // not start between the renders that switch the view type.
150
+ view.input.compositionNodes.push(child);
158
151
  }
159
152
  }
160
- if (!compositionTopDOM) return;
161
- let textDOM = compositionTopDOM;
162
- while(textDOM.firstChild){
163
- textDOM = textDOM.firstChild;
164
- }
165
- if (!textDOM || !(textDOM instanceof Text)) {
166
- console.error(compositionTopDOM, textDOM);
167
- throw new Error(`Started a composition but couldn't find the text node it belongs to.`);
168
- }
169
- compositionViewDesc.dom = compositionTopDOM;
170
- compositionViewDesc.textDOM = textDOM;
171
- compositionViewDesc.text = textDOM.data;
172
- compositionViewDesc.textDOM.pmViewDesc = compositionViewDesc;
173
- view.input.compositionNodes.push(compositionViewDesc);
174
153
  });
175
154
  const childContextValue = useMemo(()=>({
176
155
  parentRef: viewDescRef,