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

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 (35) hide show
  1. package/dist/cjs/ReactEditorView.js +2 -0
  2. package/dist/cjs/components/ChildNodeViews.js +14 -9
  3. package/dist/cjs/components/CursorWrapper.js +6 -9
  4. package/dist/cjs/components/TextNodeView.js +109 -38
  5. package/dist/cjs/components/TrailingHackView.js +29 -0
  6. package/dist/cjs/hooks/useComponentEventListeners.js +46 -5
  7. package/dist/cjs/hooks/useEditor.js +3 -6
  8. package/dist/cjs/hooks/useNodeViewDescription.js +37 -16
  9. package/dist/cjs/plugins/beforeInputPlugin.js +41 -43
  10. package/dist/cjs/viewdesc.js +10 -3
  11. package/dist/esm/ReactEditorView.js +2 -0
  12. package/dist/esm/components/ChildNodeViews.js +14 -9
  13. package/dist/esm/components/CursorWrapper.js +7 -10
  14. package/dist/esm/components/TextNodeView.js +109 -38
  15. package/dist/esm/components/TrailingHackView.js +30 -1
  16. package/dist/esm/hooks/useComponentEventListeners.js +53 -12
  17. package/dist/esm/hooks/useEditor.js +3 -6
  18. package/dist/esm/hooks/useNodeViewDescription.js +38 -17
  19. package/dist/esm/plugins/beforeInputPlugin.js +41 -43
  20. package/dist/esm/viewdesc.js +10 -3
  21. package/dist/tsconfig.tsbuildinfo +1 -1
  22. package/dist/types/ReactEditorView.d.ts +4 -0
  23. package/dist/types/components/TextNodeView.d.ts +14 -4
  24. package/dist/types/components/TrailingHackView.d.ts +1 -1
  25. package/dist/types/constants.d.ts +1 -1
  26. package/dist/types/contexts/EditorContext.d.ts +1 -1
  27. package/dist/types/hooks/useComponentEventListeners.d.ts +11 -10
  28. package/dist/types/hooks/useEditor.d.ts +2 -2
  29. package/dist/types/hooks/useEditorEventListener.d.ts +1 -1
  30. package/dist/types/props.d.ts +26 -26
  31. package/dist/types/viewdesc.d.ts +2 -2
  32. package/package.json +2 -1
  33. package/dist/cjs/tiptap/utils/ssrJSDOMPatch.js +0 -59
  34. package/dist/esm/tiptap/utils/ssrJSDOMPatch.js +0 -56
  35. package/dist/types/tiptap/utils/ssrJSDOMPatch.d.ts +0 -1
@@ -33,6 +33,7 @@ let ReactEditorView = class ReactEditorView extends _prosemirrorview.EditorView
33
33
  nextProps;
34
34
  prevState;
35
35
  _destroyed;
36
+ deferPendingEffects;
36
37
  constructor(place, props){
37
38
  // Prevent the base class from destroying the React-managed nodes.
38
39
  // Restore them below after invoking the base class constructor.
@@ -85,6 +86,7 @@ let ReactEditorView = class ReactEditorView extends _prosemirrorview.EditorView
85
86
  // @ts-expect-error this violates the typing but class does it, too.
86
87
  this.docView = null;
87
88
  this._destroyed = false;
89
+ this.deferPendingEffects = false;
88
90
  }
89
91
  get props() {
90
92
  return this.nextProps;
@@ -110,13 +110,18 @@ const ChildView = /*#__PURE__*/ (0, _react.memo)(function ChildView(param) {
110
110
  key: child.key
111
111
  }, (param)=>{
112
112
  let { siblingsRef, parentRef } = param;
113
- return /*#__PURE__*/ _react.default.createElement(_TextNodeView.TextNodeView, {
114
- view: view,
115
- node: child.node,
116
- getPos: getPos,
117
- siblingsRef: siblingsRef,
118
- parentRef: parentRef,
119
- decorations: child.outerDeco
113
+ return /*#__PURE__*/ _react.default.createElement(_EditorContext.EditorContext.Consumer, null, (param)=>{
114
+ let { registerEventListener, unregisterEventListener } = param;
115
+ return /*#__PURE__*/ _react.default.createElement(_TextNodeView.TextNodeView, {
116
+ view: view,
117
+ node: child.node,
118
+ getPos: getPos,
119
+ siblingsRef: siblingsRef,
120
+ parentRef: parentRef,
121
+ decorations: child.outerDeco,
122
+ registerEventListener: registerEventListener,
123
+ unregisterEventListener: unregisterEventListener
124
+ });
120
125
  });
121
126
  }) : /*#__PURE__*/ _react.default.createElement(_NodeView.NodeView, {
122
127
  key: child.key,
@@ -399,14 +404,14 @@ const ChildNodeViews = /*#__PURE__*/ (0, _react.memo)(function ChildNodeViews(pa
399
404
  component: _SeparatorHackView.SeparatorHackView,
400
405
  marks: [],
401
406
  offset: lastChild?.offset ?? 0,
402
- index: (lastChild?.index ?? 0) + 2,
407
+ index: (lastChild?.index ?? 0) + 1,
403
408
  key: "trailing-hack-img"
404
409
  }, {
405
410
  type: "hack",
406
411
  component: _TrailingHackView.TrailingHackView,
407
412
  marks: [],
408
413
  offset: lastChild?.offset ?? 0,
409
- index: (lastChild?.index ?? 0) + 1,
414
+ index: (lastChild?.index ?? 0) + 2,
410
415
  key: "trailing-hack-br"
411
416
  });
412
417
  }
@@ -54,6 +54,7 @@ function _interop_require_wildcard(obj, nodeInterop) {
54
54
  }
55
55
  const CursorWrapper = /*#__PURE__*/ (0, _react.forwardRef)(function CursorWrapper(param, ref) {
56
56
  let { widget, getPos, ...props } = param;
57
+ const [shouldRender, setShouldRender] = (0, _react.useState)(true);
57
58
  const innerRef = (0, _react.useRef)(null);
58
59
  (0, _react.useImperativeHandle)(ref, ()=>{
59
60
  return innerRef.current;
@@ -65,22 +66,18 @@ const CursorWrapper = /*#__PURE__*/ (0, _react.forwardRef)(function CursorWrappe
65
66
  // @ts-expect-error Internal property - domSelection
66
67
  const domSel = view.domSelection();
67
68
  const node = innerRef.current;
68
- const img = node.nodeName == "IMG";
69
- if (img) {
70
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
71
- domSel.collapse(node.parentNode, (0, _dom.domIndex)(node) + 1);
72
- } else {
73
- domSel.collapse(node, 0);
74
- }
69
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
70
+ domSel.collapse(node.parentNode, (0, _dom.domIndex)(node) + 1);
75
71
  // @ts-expect-error Internal property - domObserver
76
72
  view.domObserver.connectSelection();
73
+ setShouldRender(false);
77
74
  }, []);
78
- return /*#__PURE__*/ _react.default.createElement("img", {
75
+ return shouldRender ? /*#__PURE__*/ _react.default.createElement("img", {
79
76
  ref: innerRef,
80
77
  className: "ProseMirror-separator",
81
78
  // eslint-disable-next-line react/no-unknown-property
82
79
  "mark-placeholder": "true",
83
80
  alt: "",
84
81
  ...props
85
- });
82
+ }) : null;
86
83
  });
@@ -8,8 +8,10 @@ Object.defineProperty(exports, "TextNodeView", {
8
8
  return TextNodeView;
9
9
  }
10
10
  });
11
+ const _prosemirrorstate = require("prosemirror-state");
11
12
  const _prosemirrorview = require("prosemirror-view");
12
13
  const _react = require("react");
14
+ const _ReactEditorView = require("../ReactEditorView.js");
13
15
  const _findDOMNode = require("../findDOMNode.js");
14
16
  const _viewdesc = require("../viewdesc.js");
15
17
  const _ChildNodeViews = require("./ChildNodeViews.js");
@@ -38,72 +40,141 @@ function shallowEqual(objA, objB) {
38
40
  let TextNodeView = class TextNodeView extends _react.Component {
39
41
  viewDescRef = null;
40
42
  renderRef = null;
41
- updateEffect() {
43
+ wasProtecting = false;
44
+ containsCompositionNodeText = true;
45
+ // This is basically NodeViewDesc.localCompositionInfo
46
+ // from prosemirror-view. It's been slightly adjusted so that
47
+ // it can be used accurately during render, before we've
48
+ // necessarily found (or even let the browser create)
49
+ // view.input.compositionNode
50
+ shouldProtect(props) {
51
+ const { view, getPos, node } = props;
52
+ if (!(view instanceof _ReactEditorView.ReactEditorView)) return false;
53
+ if (!view.composing) {
54
+ return false;
55
+ }
56
+ const pos = getPos();
57
+ const { from, to } = view.state.selection;
58
+ if (!(view.state.selection instanceof _prosemirrorstate.TextSelection) || from < pos || to > pos + node.nodeSize) {
59
+ return false;
60
+ }
61
+ return this.containsCompositionNodeText;
62
+ }
63
+ handleCompositionEnd = ()=>{
64
+ if (!this.wasProtecting) return;
65
+ this.forceUpdate();
66
+ return;
67
+ };
68
+ create() {
42
69
  const { view, decorations, siblingsRef, parentRef, getPos, node } = this.props;
43
- // There simply is no other way to ref a text node
44
- // eslint-disable-next-line react/no-find-dom-node
45
70
  const dom = (0, _findDOMNode.findDOMNode)(this);
46
- // We only need to explicitly create a CompositionViewDesc
47
- // when a composition was started that produces a new text node.
48
- // Otherwise we just rely on re-rendering the renderRef
49
- if (!dom) {
50
- if (!view.composing) return;
51
- this.viewDescRef = new _viewdesc.CompositionViewDesc(parentRef.current, getPos, // These are just placeholders/dummies. We can't
52
- // actually find the correct DOM nodes from here,
53
- // so we let our parent do it.
54
- // Passing a valid element here just so that the
55
- // ViewDesc constructor doesn't blow up.
56
- document.createElement("div"), document.createTextNode(node.text ?? ""), node.text ?? "");
57
- return;
58
- }
71
+ if (!dom && !view.composing) return null;
59
72
  let textNode = dom;
60
- while(textNode.firstChild){
73
+ while(textNode?.firstChild){
61
74
  textNode = textNode.firstChild;
62
75
  }
63
- if (!this.viewDescRef || this.viewDescRef instanceof _viewdesc.CompositionViewDesc) {
64
- this.viewDescRef = new _viewdesc.TextViewDesc(undefined, [], getPos, node, decorations, _prosemirrorview.DecorationSet.empty, dom, textNode);
65
- } else {
66
- this.viewDescRef.parent = parentRef.current;
67
- this.viewDescRef.children = [];
68
- this.viewDescRef.node = node;
69
- this.viewDescRef.outerDeco = decorations;
70
- this.viewDescRef.innerDeco = _prosemirrorview.DecorationSet.empty;
71
- this.viewDescRef.dom = dom;
72
- this.viewDescRef.dom.pmViewDesc = this.viewDescRef;
73
- this.viewDescRef.nodeDOM = textNode;
76
+ if (!(textNode instanceof Text)) {
77
+ textNode = null;
74
78
  }
75
- if (!siblingsRef.current.includes(this.viewDescRef)) {
76
- siblingsRef.current.push(this.viewDescRef);
79
+ let viewDesc;
80
+ if (this.shouldProtect(this.props)) {
81
+ viewDesc = new _viewdesc.CompositionViewDesc(parentRef.current, getPos, // If we can't
82
+ // actually find the correct DOM nodes from here (
83
+ // which is the case in a composition in a newly
84
+ // created text node), we let our parent do it.
85
+ // Passing a valid element here just so that the
86
+ // ViewDesc constructor doesn't blow up.
87
+ dom ?? document.createElement("div"), textNode ?? document.createTextNode(node.text ?? ""), node.text ?? "");
88
+ } else {
89
+ if (!dom || !textNode) return null;
90
+ viewDesc = new _viewdesc.TextViewDesc(parentRef.current, [], getPos, node, decorations, _prosemirrorview.DecorationSet.empty, dom, textNode);
77
91
  }
92
+ siblingsRef.current.push(viewDesc);
78
93
  siblingsRef.current.sort(_viewdesc.sortViewDescs);
94
+ return viewDesc;
95
+ }
96
+ update() {
97
+ const { view, node, decorations } = this.props;
98
+ if (!(view instanceof _ReactEditorView.ReactEditorView)) return false;
99
+ const viewDesc = this.viewDescRef;
100
+ if (!viewDesc) return false;
101
+ if (this.shouldProtect(this.props) !== viewDesc instanceof _viewdesc.CompositionViewDesc) {
102
+ return false;
103
+ }
104
+ if (viewDesc instanceof _viewdesc.CompositionViewDesc) return false;
105
+ const dom = (0, _findDOMNode.findDOMNode)(this);
106
+ if (!dom || dom !== viewDesc.dom) return false;
107
+ if (!dom.contains(viewDesc.nodeDOM)) return false;
108
+ return viewDesc.matchesNode(node, decorations, _prosemirrorview.DecorationSet.empty) || viewDesc.update(node, decorations, _prosemirrorview.DecorationSet.empty, view);
109
+ }
110
+ destroy() {
111
+ const viewDesc = this.viewDescRef;
112
+ if (!viewDesc) return;
113
+ viewDesc.destroy();
114
+ const siblings = this.props.siblingsRef.current;
115
+ if (siblings.includes(viewDesc)) {
116
+ const index = siblings.indexOf(viewDesc);
117
+ siblings.splice(index, 1);
118
+ }
119
+ }
120
+ updateEffect() {
121
+ if (!this.update()) {
122
+ this.destroy();
123
+ this.viewDescRef = this.create();
124
+ }
125
+ const { view, node } = this.props;
126
+ if (!(view instanceof _ReactEditorView.ReactEditorView)) {
127
+ this.containsCompositionNodeText = true;
128
+ return;
129
+ }
130
+ const textNode = view.input.compositionNode;
131
+ if (!textNode) {
132
+ this.containsCompositionNodeText = true;
133
+ return;
134
+ }
135
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
136
+ const text = textNode.nodeValue;
137
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
138
+ this.containsCompositionNodeText = node.text === text;
79
139
  }
80
140
  shouldComponentUpdate(nextProps) {
141
+ // When leaving the protected state, force a re-render so React's
142
+ // virtual DOM resyncs with whatever the IME wrote into the real DOM
143
+ // while we were returning a stale renderRef.
144
+ if (this.wasProtecting && !this.shouldProtect(nextProps)) {
145
+ return true;
146
+ }
81
147
  return !shallowEqual(this.props, nextProps);
82
148
  }
83
149
  componentDidMount() {
150
+ this.viewDescRef = null;
151
+ // After a composition, force an update so that we re-check whether we need
152
+ // to be protecting our rendered content and allow React to re-sync with the
153
+ // DOM.
154
+ const { registerEventListener } = this.props;
155
+ registerEventListener("compositionend", this.handleCompositionEnd);
84
156
  this.updateEffect();
85
157
  }
86
158
  componentDidUpdate() {
87
159
  this.updateEffect();
88
160
  }
89
161
  componentWillUnmount() {
90
- const { siblingsRef } = this.props;
91
- if (!this.viewDescRef) return;
92
- if (siblingsRef.current.includes(this.viewDescRef)) {
93
- const index = siblingsRef.current.indexOf(this.viewDescRef);
94
- siblingsRef.current.splice(index, 1);
95
- }
162
+ const { unregisterEventListener } = this.props;
163
+ unregisterEventListener("compositionend", this.handleCompositionEnd);
164
+ this.destroy();
96
165
  }
97
166
  render() {
98
- const { view, getPos, node, decorations } = this.props;
167
+ const { node, decorations } = this.props;
99
168
  // During a composition, it's crucial that we don't try to
100
169
  // update the DOM that the user is working in. If there's
101
170
  // an active composition and the selection is in this node,
102
171
  // we freeze the DOM of this element so that it doesn't
103
172
  // interrupt the composition
104
- if (view.composing && view.state.selection.from >= getPos() && view.state.selection.from <= getPos() + node.nodeSize) {
173
+ if (this.shouldProtect(this.props)) {
174
+ this.wasProtecting = true;
105
175
  return this.renderRef;
106
176
  }
177
+ this.wasProtecting = false;
107
178
  this.renderRef = decorations.reduce(_ChildNodeViews.wrapInDeco, node.text);
108
179
  return this.renderRef;
109
180
  }
@@ -11,6 +11,8 @@ Object.defineProperty(exports, "TrailingHackView", {
11
11
  const _react = /*#__PURE__*/ _interop_require_wildcard(require("react"));
12
12
  const _ChildDescriptionsContext = require("../contexts/ChildDescriptionsContext.js");
13
13
  const _useClientLayoutEffect = require("../hooks/useClientLayoutEffect.js");
14
+ const _useEditorEffect = require("../hooks/useEditorEffect.js");
15
+ const _useEditorEventListener = require("../hooks/useEditorEventListener.js");
14
16
  const _viewdesc = require("../viewdesc.js");
15
17
  function _getRequireWildcardCache(nodeInterop) {
16
18
  if (typeof WeakMap !== "function") return null;
@@ -55,6 +57,7 @@ function _interop_require_wildcard(obj, nodeInterop) {
55
57
  }
56
58
  function TrailingHackView(param) {
57
59
  let { getPos } = param;
60
+ const [shouldRender, setShouldRender] = (0, _react.useState)(true);
58
61
  const { siblingsRef, parentRef } = (0, _react.useContext)(_ChildDescriptionsContext.ChildDescriptionsContext);
59
62
  const viewDescRef = (0, _react.useRef)(null);
60
63
  const ref = (0, _react.useRef)(null);
@@ -83,6 +86,32 @@ function TrailingHackView(param) {
83
86
  }
84
87
  siblingsRef.current.sort(_viewdesc.sortViewDescs);
85
88
  });
89
+ // At the start of a composition, the browser will automatically delete
90
+ // the trailing hack br element. We need to unmount ourselves _before_
91
+ // that happens, so that React doesn't try to remove the already-removed
92
+ // br node when this component gets unmounted
93
+ (0, _useEditorEventListener.useEditorEventListener)("compositionstart", (view)=>{
94
+ const { from } = view.state.selection;
95
+ if (from === getPos()) {
96
+ setShouldRender(false);
97
+ }
98
+ });
99
+ // We need to run the same composition check when we first get mounted,
100
+ // in case we got mounted in the same render batch as the beginning of
101
+ // a composition
102
+ (0, _useEditorEffect.useEditorEffect)((view)=>{
103
+ if (!view.composing) return;
104
+ const { from } = view.state.selection;
105
+ if (from === getPos()) {
106
+ setShouldRender(false);
107
+ }
108
+ }, [
109
+ getPos
110
+ ]);
111
+ (0, _useEditorEventListener.useEditorEventListener)("compositionend", ()=>{
112
+ setShouldRender(true);
113
+ });
114
+ if (!shouldRender) return null;
86
115
  return /*#__PURE__*/ _react.default.createElement("br", {
87
116
  ref: ref,
88
117
  className: "ProseMirror-trailingBreak"
@@ -9,9 +9,17 @@ Object.defineProperty(exports, "useComponentEventListeners", {
9
9
  }
10
10
  });
11
11
  const _react = require("react");
12
- const _componentEventListeners = require("../plugins/componentEventListeners.js");
13
- function useComponentEventListeners() {
14
- const [registry, setRegistry] = (0, _react.useState)(new Map());
12
+ const _reactdom = require("react-dom");
13
+ function useComponentEventListeners(existingHandlers) {
14
+ const [registry, setRegistry] = (0, _react.useState)(new Map(Object.entries(existingHandlers ?? {}).map((param)=>{
15
+ let [eventName, handler] = param;
16
+ return [
17
+ eventName,
18
+ handler ? [
19
+ handler
20
+ ] : []
21
+ ];
22
+ })));
15
23
  const registerEventListener = (0, _react.useCallback)((eventType, handler)=>{
16
24
  const handlers = registry.get(eventType) ?? [];
17
25
  handlers.unshift(handler);
@@ -28,12 +36,45 @@ function useComponentEventListeners() {
28
36
  }, [
29
37
  registry
30
38
  ]);
31
- const componentEventListenersPlugin = (0, _react.useMemo)(()=>(0, _componentEventListeners.componentEventListeners)(registry), [
39
+ (0, _react.useLayoutEffect)(()=>{
40
+ if (!existingHandlers) return;
41
+ for (const [eventType, handler] of Object.entries(existingHandlers)){
42
+ if (!handler) return;
43
+ registerEventListener(eventType, handler);
44
+ }
45
+ return ()=>{
46
+ for (const [eventType, handler] of Object.entries(existingHandlers)){
47
+ if (!handler) return;
48
+ unregisterEventListener(eventType, handler);
49
+ }
50
+ };
51
+ }, [
52
+ existingHandlers,
53
+ registerEventListener,
54
+ unregisterEventListener
55
+ ]);
56
+ const handleDOMEvents = (0, _react.useMemo)(()=>{
57
+ const domEventHandlers = {};
58
+ for (const [eventType, handlers] of registry.entries()){
59
+ function handleEvent(view, event) {
60
+ for (const handler of handlers){
61
+ let handled = false;
62
+ (0, _reactdom.unstable_batchedUpdates)(()=>{
63
+ handled = !!handler(view, event);
64
+ });
65
+ if (handled || event.defaultPrevented) return true;
66
+ }
67
+ return false;
68
+ }
69
+ domEventHandlers[eventType] = handleEvent;
70
+ }
71
+ return domEventHandlers;
72
+ }, [
32
73
  registry
33
74
  ]);
34
75
  return {
35
76
  registerEventListener,
36
77
  unregisterEventListener,
37
- componentEventListenersPlugin
78
+ handleDOMEvents
38
79
  };
39
80
  }
@@ -32,7 +32,7 @@ function useEditor(mount, options) {
32
32
  const defaultState = options.defaultState ?? _constants.EMPTY_STATE;
33
33
  const [_state, setState] = (0, _react.useState)(defaultState);
34
34
  const state = options.state ?? _state;
35
- const { componentEventListenersPlugin, registerEventListener, unregisterEventListener } = (0, _useComponentEventListeners.useComponentEventListeners)();
35
+ const { handleDOMEvents, registerEventListener, unregisterEventListener } = (0, _useComponentEventListeners.useComponentEventListeners)(options.handleDOMEvents);
36
36
  const setCursorWrapper = (0, _react.useCallback)((deco)=>{
37
37
  (0, _reactdom.flushSync)(()=>{
38
38
  _setCursorWrapper(deco);
@@ -40,11 +40,9 @@ function useEditor(mount, options) {
40
40
  }, []);
41
41
  const plugins = (0, _react.useMemo)(()=>[
42
42
  ...options.plugins ?? [],
43
- componentEventListenersPlugin,
44
43
  (0, _beforeInputPlugin.beforeInputPlugin)(setCursorWrapper)
45
44
  ], [
46
45
  options.plugins,
47
- componentEventListenersPlugin,
48
46
  setCursorWrapper
49
47
  ]);
50
48
  const dispatchTransaction = (0, _react.useCallback)(function dispatchTransaction(tr) {
@@ -73,7 +71,8 @@ function useEditor(mount, options) {
73
71
  ...options,
74
72
  state,
75
73
  plugins,
76
- dispatchTransaction
74
+ dispatchTransaction,
75
+ handleDOMEvents
77
76
  };
78
77
  const [view, setView] = (0, _react.useState)(()=>{
79
78
  return new _StaticEditorView.StaticEditorView(directEditorProps);
@@ -103,8 +102,6 @@ function useEditor(mount, options) {
103
102
  // running effects. Running effects will reattach selection
104
103
  // change listeners if the EditorView has been destroyed.
105
104
  if (view instanceof _ReactEditorView.ReactEditorView && !view.isDestroyed) {
106
- // Plugins might dispatch transactions from their
107
- // view update lifecycle hooks
108
105
  flushSyncRef.current = false;
109
106
  view.commitPendingEffects();
110
107
  flushSyncRef.current = true;
@@ -10,8 +10,10 @@ Object.defineProperty(exports, "useNodeViewDescription", {
10
10
  });
11
11
  const _react = require("react");
12
12
  const _ReactEditorView = require("../ReactEditorView.js");
13
+ const _CursorWrapper = require("../components/CursorWrapper.js");
13
14
  const _ChildDescriptionsContext = require("../contexts/ChildDescriptionsContext.js");
14
15
  const _EditorContext = require("../contexts/EditorContext.js");
16
+ const _ReactWidgetType = require("../decorations/ReactWidgetType.js");
15
17
  const _viewdesc = require("../viewdesc.js");
16
18
  const _useClientLayoutEffect = require("./useClientLayoutEffect.js");
17
19
  const _useEffectEvent = require("./useEffectEvent.js");
@@ -141,25 +143,44 @@ function useNodeViewDescription(getDOM, getContentDOM, constructor, props) {
141
143
  children.sort(_viewdesc.sortViewDescs);
142
144
  for (const child of children){
143
145
  child.parent = viewDesc;
144
- // Because TextNodeViews can't locate the DOM nodes
145
- // for compositions, we need to override them here
146
- if (child instanceof _viewdesc.CompositionViewDesc) {
147
- const compositionTopDOM = viewDesc?.contentDOM?.firstChild;
148
- if (!compositionTopDOM) throw new Error(`Started a composition but couldn't find the text node it belongs to.`);
149
- let textDOM = compositionTopDOM;
150
- while(textDOM.firstChild){
151
- textDOM = textDOM.firstChild;
146
+ }
147
+ if (!props.node.isTextblock) return;
148
+ // Because TextNodeViews can't locate the DOM nodes
149
+ // for compositions, we need to override them here
150
+ if (!viewDescRef.current?.contentDOM) return;
151
+ const compositionChildIndex = children.findIndex((child)=>child instanceof _viewdesc.CompositionViewDesc);
152
+ if (compositionChildIndex === -1) return;
153
+ const compositionViewDesc = children[compositionChildIndex];
154
+ if (!(compositionViewDesc instanceof _viewdesc.CompositionViewDesc)) return;
155
+ let compositionTopDOM = null;
156
+ let search = children[compositionChildIndex - 1];
157
+ while(search instanceof _viewdesc.MarkViewDesc){
158
+ search = search.children[0];
159
+ }
160
+ if (search instanceof _viewdesc.WidgetViewDesc && search.widget.type instanceof _ReactWidgetType.ReactWidgetType && search.widget.type.Component === _CursorWrapper.CursorWrapper) {
161
+ compositionTopDOM = search.dom.nextSibling;
162
+ } else {
163
+ for (const childNode of viewDescRef.current.contentDOM.childNodes){
164
+ if (children.every((child)=>child.dom !== childNode)) {
165
+ compositionTopDOM = childNode;
166
+ break;
152
167
  }
153
- if (!textDOM || !(textDOM instanceof Text)) throw new Error(`Started a composition but couldn't find the text node it belongs to.`);
154
- child.dom = compositionTopDOM;
155
- child.textDOM = textDOM;
156
- child.text = textDOM.data;
157
- child.textDOM.pmViewDesc = child;
158
- // It should not be possible to be in a composition because one could
159
- // not start between the renders that switch the view type.
160
- view.input.compositionNodes.push(child);
161
168
  }
162
169
  }
170
+ if (!compositionTopDOM) return;
171
+ let textDOM = compositionTopDOM;
172
+ while(textDOM.firstChild){
173
+ textDOM = textDOM.firstChild;
174
+ }
175
+ if (!textDOM || !(textDOM instanceof Text)) {
176
+ console.error(compositionTopDOM, textDOM);
177
+ throw new Error(`Started a composition but couldn't find the text node it belongs to.`);
178
+ }
179
+ compositionViewDesc.dom = compositionTopDOM;
180
+ compositionViewDesc.textDOM = textDOM;
181
+ compositionViewDesc.text = textDOM.data;
182
+ compositionViewDesc.textDOM.pmViewDesc = compositionViewDesc;
183
+ view.input.compositionNodes.push(compositionViewDesc);
163
184
  });
164
185
  const childContextValue = (0, _react.useMemo)(()=>({
165
186
  parentRef: viewDescRef,
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "beforeInputPlugin", {
10
10
  });
11
11
  const _prosemirrormodel = require("prosemirror-model");
12
12
  const _prosemirrorstate = require("prosemirror-state");
13
+ const _ReactEditorView = require("../ReactEditorView.js");
13
14
  const _CursorWrapper = require("../components/CursorWrapper.js");
14
15
  const _ReactWidgetType = require("../decorations/ReactWidgetType.js");
15
16
  function insertText(view, eventData) {
@@ -56,68 +57,41 @@ function handleGapCursorComposition(view) {
56
57
  }
57
58
  function beforeInputPlugin(setCursorWrapper) {
58
59
  let compositionMarks = null;
59
- let precompositionSnapshot = null;
60
60
  return new _prosemirrorstate.Plugin({
61
61
  props: {
62
62
  handleDOMEvents: {
63
63
  compositionstart (view) {
64
- compositionMarks = view.state.storedMarks ?? view.state.selection.$from.marks();
65
- view.dispatch(view.state.tr.deleteSelection());
64
+ if (!(view instanceof _ReactEditorView.ReactEditorView)) return false;
65
+ view.input.composing = true;
66
+ compositionMarks = view.state.storedMarks;
67
+ const tr = view.state.tr.deleteSelection().setStoredMarks(null);
68
+ view.dispatch(tr);
66
69
  handleGapCursorComposition(view);
67
70
  const { state } = view;
68
- const $pos = state.selection.$from;
69
- if (compositionMarks) {
71
+ if (compositionMarks?.length) {
70
72
  setCursorWrapper((0, _ReactWidgetType.widget)(state.selection.from, _CursorWrapper.CursorWrapper, {
71
73
  key: "cursor-wrapper",
72
- marks: compositionMarks
74
+ marks: compositionMarks,
75
+ side: 1
73
76
  }));
74
77
  }
75
- // Snapshot the siblings of the node that contains the
76
- // current cursor. We'll restore this later, so that React
77
- // doesn't panic about unknown DOM nodes.
78
- const { node: parent } = view.domAtPos($pos.pos);
79
- precompositionSnapshot = [];
80
- for (const node of parent.childNodes){
81
- precompositionSnapshot.push(node);
82
- }
83
- // @ts-expect-error Internal property - input
84
- view.input.composing = true;
85
78
  return true;
86
79
  },
87
80
  compositionupdate () {
88
81
  return true;
89
82
  },
90
83
  compositionend (view, event) {
91
- // @ts-expect-error Internal property - input
84
+ if (!(view instanceof _ReactEditorView.ReactEditorView)) return false;
85
+ if (!view.composing) return false;
92
86
  view.input.composing = false;
93
- const { state } = view;
94
- const { node: parent } = view.domAtPos(state.selection.from);
95
- if (precompositionSnapshot) {
96
- // Restore the snapshot of the parent node's children
97
- // from before the composition started. This gives us a
98
- // clean slate from which to dispatch our transaction
99
- // and trigger a React update.
100
- precompositionSnapshot.forEach((prevNode, i)=>{
101
- if (parent.childNodes.length <= i) {
102
- parent.appendChild(prevNode);
103
- return;
104
- }
105
- parent.replaceChild(prevNode, parent.childNodes.item(i));
106
- });
107
- if (parent.childNodes.length > precompositionSnapshot.length) {
108
- for(let i = precompositionSnapshot.length; i < parent.childNodes.length; i++){
109
- parent.removeChild(parent.childNodes.item(i));
110
- }
111
- }
112
- }
113
- if (event.data) {
114
- insertText(view, event.data, {
115
- marks: compositionMarks
116
- });
117
- }
118
87
  compositionMarks = null;
119
- precompositionSnapshot = null;
120
88
  setCursorWrapper(null);
89
+ if (view.input.compositionNode && !view.input.compositionNode.pmViewDesc) {
90
+ view.input.compositionNode.remove();
91
+ }
92
+ view.input.compositionEndedAt = event.timeStamp;
93
+ view.input.compositionNode = null;
94
+ view.input.compositionID++;
121
95
  return true;
122
96
  },
123
97
  beforeinput (view, event) {
@@ -166,6 +140,30 @@ function beforeInputPlugin(setCursorWrapper) {
166
140
  insertText(view, event.data);
167
141
  break;
168
142
  }
143
+ case "insertCompositionText":
144
+ {
145
+ const { tr } = view.state;
146
+ // There's always a range on insertCompositionText beforeinput events
147
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
148
+ const range = event.getTargetRanges()[0];
149
+ const start = view.posAtDOM(range.startContainer, range.startOffset);
150
+ const end = view.posAtDOM(range.endContainer, range.endOffset, 1);
151
+ if (view.state.doc.textBetween(start, end, "**", "*") === event.data) {
152
+ return;
153
+ }
154
+ if (event.data) {
155
+ if (compositionMarks) tr.ensureMarks(compositionMarks);
156
+ tr.insertText(event.data, start, end);
157
+ } else {
158
+ tr.delete(start, end);
159
+ }
160
+ view.dom.addEventListener("input", ()=>{
161
+ view.dispatch(tr);
162
+ }, {
163
+ once: true
164
+ });
165
+ break;
166
+ }
169
167
  case "deleteWordBackward":
170
168
  case "deleteHardLineBackward":
171
169
  case "deleteSoftLineBackward":