@handlewithcare/react-prosemirror 3.1.0-tiptap.48 → 3.1.0-tiptap.49
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/cjs/ReactEditorView.js +0 -2
- package/dist/cjs/components/ChildNodeViews.js +7 -12
- package/dist/cjs/components/CursorWrapper.js +9 -8
- package/dist/cjs/components/TextNodeView.js +38 -112
- package/dist/cjs/hooks/useEditor.js +5 -13
- package/dist/cjs/hooks/useNodeViewDescription.js +15 -38
- package/dist/cjs/plugins/beforeInputPlugin.js +41 -47
- package/dist/cjs/tiptap/hooks/useEditor.js +349 -0
- package/dist/cjs/tiptap/hooks/useTiptapEditor.js +2 -2
- package/dist/cjs/tiptap/utils/ssrJSDOMPatch.js +59 -0
- package/dist/cjs/viewdesc.js +3 -10
- package/dist/esm/ReactEditorView.js +0 -2
- package/dist/esm/components/ChildNodeViews.js +7 -12
- package/dist/esm/components/CursorWrapper.js +10 -9
- package/dist/esm/components/TextNodeView.js +38 -112
- package/dist/esm/hooks/useEditor.js +5 -13
- package/dist/esm/hooks/useNodeViewDescription.js +16 -39
- package/dist/esm/plugins/beforeInputPlugin.js +41 -47
- package/dist/esm/tiptap/hooks/useEditor.js +339 -0
- package/dist/esm/tiptap/hooks/useTiptapEditor.js +1 -1
- package/dist/esm/tiptap/utils/ssrJSDOMPatch.js +56 -0
- package/dist/esm/viewdesc.js +3 -10
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/ReactEditorView.d.ts +0 -4
- package/dist/types/components/TextNodeView.d.ts +4 -14
- package/dist/types/tiptap/hooks/useEditor.d.ts +38 -0
- package/dist/types/tiptap/hooks/useTiptapEditor.d.ts +1 -1
- package/dist/types/tiptap/utils/ssrJSDOMPatch.d.ts +1 -0
- package/dist/types/viewdesc.d.ts +1 -1
- package/package.json +1 -2
|
@@ -33,7 +33,6 @@ let ReactEditorView = class ReactEditorView extends _prosemirrorview.EditorView
|
|
|
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 @@ let ReactEditorView = class ReactEditorView extends _prosemirrorview.EditorView
|
|
|
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;
|
|
@@ -110,18 +110,13 @@ 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(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
parentRef: parentRef,
|
|
121
|
-
decorations: child.outerDeco,
|
|
122
|
-
registerEventListener: registerEventListener,
|
|
123
|
-
unregisterEventListener: unregisterEventListener
|
|
124
|
-
});
|
|
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
|
|
125
120
|
});
|
|
126
121
|
}) : /*#__PURE__*/ _react.default.createElement(_NodeView.NodeView, {
|
|
127
122
|
key: child.key,
|
|
@@ -54,7 +54,6 @@ 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);
|
|
58
57
|
const innerRef = (0, _react.useRef)(null);
|
|
59
58
|
(0, _react.useImperativeHandle)(ref, ()=>{
|
|
60
59
|
return innerRef.current;
|
|
@@ -66,20 +65,22 @@ const CursorWrapper = /*#__PURE__*/ (0, _react.forwardRef)(function CursorWrappe
|
|
|
66
65
|
// @ts-expect-error Internal property - domSelection
|
|
67
66
|
const domSel = view.domSelection();
|
|
68
67
|
const node = innerRef.current;
|
|
69
|
-
|
|
70
|
-
|
|
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
|
+
}
|
|
71
75
|
// @ts-expect-error Internal property - domObserver
|
|
72
76
|
view.domObserver.connectSelection();
|
|
73
|
-
setTimeout(()=>{
|
|
74
|
-
setShouldRender(false);
|
|
75
|
-
});
|
|
76
77
|
}, []);
|
|
77
|
-
return
|
|
78
|
+
return /*#__PURE__*/ _react.default.createElement("img", {
|
|
78
79
|
ref: innerRef,
|
|
79
80
|
className: "ProseMirror-separator",
|
|
80
81
|
// eslint-disable-next-line react/no-unknown-property
|
|
81
82
|
"mark-placeholder": "true",
|
|
82
83
|
alt: "",
|
|
83
84
|
...props
|
|
84
|
-
})
|
|
85
|
+
});
|
|
85
86
|
});
|
|
@@ -8,10 +8,8 @@ Object.defineProperty(exports, "TextNodeView", {
|
|
|
8
8
|
return TextNodeView;
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
|
-
const _prosemirrorstate = require("prosemirror-state");
|
|
12
11
|
const _prosemirrorview = require("prosemirror-view");
|
|
13
12
|
const _react = require("react");
|
|
14
|
-
const _ReactEditorView = require("../ReactEditorView.js");
|
|
15
13
|
const _findDOMNode = require("../findDOMNode.js");
|
|
16
14
|
const _viewdesc = require("../viewdesc.js");
|
|
17
15
|
const _ChildNodeViews = require("./ChildNodeViews.js");
|
|
@@ -40,144 +38,72 @@ function shallowEqual(objA, objB) {
|
|
|
40
38
|
let TextNodeView = class TextNodeView extends _react.Component {
|
|
41
39
|
viewDescRef = null;
|
|
42
40
|
renderRef = null;
|
|
43
|
-
|
|
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 || // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
59
|
-
to > pos + node.nodeSize) {
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
return this.containsCompositionNodeText;
|
|
63
|
-
}
|
|
64
|
-
handleCompositionEnd = ()=>{
|
|
65
|
-
if (!this.wasProtecting) return;
|
|
66
|
-
this.forceUpdate();
|
|
67
|
-
return;
|
|
68
|
-
};
|
|
69
|
-
create() {
|
|
41
|
+
updateEffect() {
|
|
70
42
|
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
|
|
71
45
|
const dom = (0, _findDOMNode.findDOMNode)(this);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
let viewDesc;
|
|
81
|
-
if (this.shouldProtect(this.props)) {
|
|
82
|
-
viewDesc = new _viewdesc.CompositionViewDesc(parentRef.current, getPos, // If we can't
|
|
83
|
-
// actually find the correct DOM nodes from here (
|
|
84
|
-
// which is the case in a composition in a newly
|
|
85
|
-
// created text node), we let our parent do it.
|
|
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.
|
|
86
54
|
// Passing a valid element here just so that the
|
|
87
55
|
// ViewDesc constructor doesn't blow up.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (!dom || !textNode) return null;
|
|
91
|
-
viewDesc = new _viewdesc.TextViewDesc(parentRef.current, [], getPos, node, decorations, _prosemirrorview.DecorationSet.empty, dom, textNode);
|
|
56
|
+
document.createElement("div"), document.createTextNode(node.text ?? ""), node.text ?? "");
|
|
57
|
+
return;
|
|
92
58
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
update() {
|
|
98
|
-
const { view, node, decorations } = this.props;
|
|
99
|
-
if (!(view instanceof _ReactEditorView.ReactEditorView)) return false;
|
|
100
|
-
const viewDesc = this.viewDescRef;
|
|
101
|
-
if (!viewDesc) return false;
|
|
102
|
-
if (this.shouldProtect(this.props) !== viewDesc instanceof _viewdesc.CompositionViewDesc) {
|
|
103
|
-
return false;
|
|
59
|
+
let textNode = dom;
|
|
60
|
+
while(textNode.firstChild){
|
|
61
|
+
textNode = textNode.firstChild;
|
|
104
62
|
}
|
|
105
|
-
if (
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (siblings.includes(viewDesc)) {
|
|
117
|
-
const index = siblings.indexOf(viewDesc);
|
|
118
|
-
siblings.splice(index, 1);
|
|
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;
|
|
119
74
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (!this.update()) {
|
|
123
|
-
this.destroy();
|
|
124
|
-
this.viewDescRef = this.create();
|
|
75
|
+
if (!siblingsRef.current.includes(this.viewDescRef)) {
|
|
76
|
+
siblingsRef.current.push(this.viewDescRef);
|
|
125
77
|
}
|
|
126
|
-
|
|
127
|
-
const { view, node } = this.props;
|
|
128
|
-
if (!(view instanceof _ReactEditorView.ReactEditorView)) {
|
|
129
|
-
this.containsCompositionNodeText = true;
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
const textNode = view.input.compositionNode;
|
|
133
|
-
if (!textNode) {
|
|
134
|
-
this.containsCompositionNodeText = true;
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
138
|
-
const text = textNode.nodeValue;
|
|
139
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
140
|
-
this.containsCompositionNodeText = node.text === text;
|
|
141
|
-
});
|
|
78
|
+
siblingsRef.current.sort(_viewdesc.sortViewDescs);
|
|
142
79
|
}
|
|
143
80
|
shouldComponentUpdate(nextProps) {
|
|
144
|
-
// When leaving the protected state, force a re-render so React's
|
|
145
|
-
// virtual DOM resyncs with whatever the IME wrote into the real DOM
|
|
146
|
-
// while we were returning a stale renderRef.
|
|
147
|
-
if (this.wasProtecting && !this.shouldProtect(nextProps)) {
|
|
148
|
-
return true;
|
|
149
|
-
}
|
|
150
81
|
return !shallowEqual(this.props, nextProps);
|
|
151
82
|
}
|
|
152
83
|
componentDidMount() {
|
|
153
|
-
this.viewDescRef = null;
|
|
154
|
-
// After a composition, force an update so that we re-check whether we need
|
|
155
|
-
// to be protecting our rendered content and allow React to re-sync with the
|
|
156
|
-
// DOM.
|
|
157
|
-
const { registerEventListener } = this.props;
|
|
158
|
-
registerEventListener("compositionend", this.handleCompositionEnd);
|
|
159
84
|
this.updateEffect();
|
|
160
85
|
}
|
|
161
86
|
componentDidUpdate() {
|
|
162
87
|
this.updateEffect();
|
|
163
88
|
}
|
|
164
89
|
componentWillUnmount() {
|
|
165
|
-
const {
|
|
166
|
-
|
|
167
|
-
this.
|
|
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
|
+
}
|
|
168
96
|
}
|
|
169
97
|
render() {
|
|
170
|
-
const { node, decorations } = this.props;
|
|
98
|
+
const { view, getPos, node, decorations } = this.props;
|
|
171
99
|
// During a composition, it's crucial that we don't try to
|
|
172
100
|
// update the DOM that the user is working in. If there's
|
|
173
101
|
// an active composition and the selection is in this node,
|
|
174
102
|
// we freeze the DOM of this element so that it doesn't
|
|
175
103
|
// interrupt the composition
|
|
176
|
-
if (
|
|
177
|
-
this.wasProtecting = true;
|
|
104
|
+
if (view.composing && view.state.selection.from >= getPos() && view.state.selection.from <= getPos() + node.nodeSize) {
|
|
178
105
|
return this.renderRef;
|
|
179
106
|
}
|
|
180
|
-
this.wasProtecting = false;
|
|
181
107
|
this.renderRef = decorations.reduce(_ChildNodeViews.wrapInDeco, node.text);
|
|
182
108
|
return this.renderRef;
|
|
183
109
|
}
|
|
@@ -103,19 +103,11 @@ function useEditor(mount, options) {
|
|
|
103
103
|
// running effects. Running effects will reattach selection
|
|
104
104
|
// change listeners if the EditorView has been destroyed.
|
|
105
105
|
if (view instanceof _ReactEditorView.ReactEditorView && !view.isDestroyed) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
});
|
|
112
|
-
} else {
|
|
113
|
-
// Plugins might dispatch transactions from their
|
|
114
|
-
// view update lifecycle hooks
|
|
115
|
-
flushSyncRef.current = false;
|
|
116
|
-
view.commitPendingEffects();
|
|
117
|
-
flushSyncRef.current = true;
|
|
118
|
-
}
|
|
106
|
+
// Plugins might dispatch transactions from their
|
|
107
|
+
// view update lifecycle hooks
|
|
108
|
+
flushSyncRef.current = false;
|
|
109
|
+
view.commitPendingEffects();
|
|
110
|
+
flushSyncRef.current = true;
|
|
119
111
|
}
|
|
120
112
|
});
|
|
121
113
|
view.update(directEditorProps);
|
|
@@ -10,10 +10,8 @@ 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");
|
|
14
13
|
const _ChildDescriptionsContext = require("../contexts/ChildDescriptionsContext.js");
|
|
15
14
|
const _EditorContext = require("../contexts/EditorContext.js");
|
|
16
|
-
const _ReactWidgetType = require("../decorations/ReactWidgetType.js");
|
|
17
15
|
const _viewdesc = require("../viewdesc.js");
|
|
18
16
|
const _useClientLayoutEffect = require("./useClientLayoutEffect.js");
|
|
19
17
|
const _useEffectEvent = require("./useEffectEvent.js");
|
|
@@ -143,46 +141,25 @@ function useNodeViewDescription(getDOM, getContentDOM, constructor, props) {
|
|
|
143
141
|
children.sort(_viewdesc.sortViewDescs);
|
|
144
142
|
for (const child of children){
|
|
145
143
|
child.parent = viewDesc;
|
|
146
|
-
}
|
|
147
|
-
setTimeout(()=>{
|
|
148
144
|
// Because TextNodeViews can't locate the DOM nodes
|
|
149
145
|
// for compositions, we need to override them here
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
let compositionTopDOM = null;
|
|
157
|
-
let search = children[compositionChildIndex - 1];
|
|
158
|
-
while(search instanceof _viewdesc.MarkViewDesc){
|
|
159
|
-
search = search.children[0];
|
|
160
|
-
}
|
|
161
|
-
if (search instanceof _viewdesc.WidgetViewDesc && search.widget.type instanceof _ReactWidgetType.ReactWidgetType && search.widget.type.Component === _CursorWrapper.CursorWrapper) {
|
|
162
|
-
compositionTopDOM = search.dom.nextSibling;
|
|
163
|
-
} else {
|
|
164
|
-
for (const childNode of viewDescRef.current.contentDOM.childNodes){
|
|
165
|
-
if (children.every((child)=>child.dom !== childNode)) {
|
|
166
|
-
compositionTopDOM = childNode;
|
|
167
|
-
break;
|
|
168
|
-
}
|
|
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;
|
|
169
152
|
}
|
|
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);
|
|
170
161
|
}
|
|
171
|
-
|
|
172
|
-
let textDOM = compositionTopDOM;
|
|
173
|
-
while(textDOM.firstChild){
|
|
174
|
-
textDOM = textDOM.firstChild;
|
|
175
|
-
}
|
|
176
|
-
if (!textDOM || !(textDOM instanceof Text)) {
|
|
177
|
-
console.error(compositionTopDOM, textDOM);
|
|
178
|
-
throw new Error(`Started a composition but couldn't find the text node it belongs to.`);
|
|
179
|
-
}
|
|
180
|
-
compositionViewDesc.dom = compositionTopDOM;
|
|
181
|
-
compositionViewDesc.textDOM = textDOM;
|
|
182
|
-
compositionViewDesc.text = textDOM.data;
|
|
183
|
-
compositionViewDesc.textDOM.pmViewDesc = compositionViewDesc;
|
|
184
|
-
view.input.compositionNodes.push(compositionViewDesc);
|
|
185
|
-
});
|
|
162
|
+
}
|
|
186
163
|
});
|
|
187
164
|
const childContextValue = (0, _react.useMemo)(()=>({
|
|
188
165
|
parentRef: viewDescRef,
|
|
@@ -10,7 +10,6 @@ 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");
|
|
14
13
|
const _CursorWrapper = require("../components/CursorWrapper.js");
|
|
15
14
|
const _ReactWidgetType = require("../decorations/ReactWidgetType.js");
|
|
16
15
|
function insertText(view, eventData) {
|
|
@@ -57,23 +56,31 @@ function handleGapCursorComposition(view) {
|
|
|
57
56
|
}
|
|
58
57
|
function beforeInputPlugin(setCursorWrapper) {
|
|
59
58
|
let compositionMarks = null;
|
|
59
|
+
let precompositionSnapshot = null;
|
|
60
60
|
return new _prosemirrorstate.Plugin({
|
|
61
61
|
props: {
|
|
62
62
|
handleDOMEvents: {
|
|
63
63
|
compositionstart (view) {
|
|
64
|
-
|
|
65
|
-
compositionMarks = view.state.storedMarks;
|
|
64
|
+
compositionMarks = view.state.storedMarks ?? view.state.selection.$from.marks();
|
|
66
65
|
view.dispatch(view.state.tr.deleteSelection());
|
|
67
66
|
handleGapCursorComposition(view);
|
|
68
67
|
const { state } = view;
|
|
69
|
-
|
|
70
|
-
if (compositionMarks
|
|
68
|
+
const $pos = state.selection.$from;
|
|
69
|
+
if (compositionMarks) {
|
|
71
70
|
setCursorWrapper((0, _ReactWidgetType.widget)(state.selection.from, _CursorWrapper.CursorWrapper, {
|
|
72
71
|
key: "cursor-wrapper",
|
|
73
|
-
marks: compositionMarks
|
|
74
|
-
side: 1
|
|
72
|
+
marks: compositionMarks
|
|
75
73
|
}));
|
|
76
74
|
}
|
|
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
|
|
77
84
|
view.input.composing = true;
|
|
78
85
|
return true;
|
|
79
86
|
},
|
|
@@ -81,17 +88,36 @@ function beforeInputPlugin(setCursorWrapper) {
|
|
|
81
88
|
return true;
|
|
82
89
|
},
|
|
83
90
|
compositionend (view, event) {
|
|
84
|
-
|
|
85
|
-
if (!view.composing) return false;
|
|
91
|
+
// @ts-expect-error Internal property - input
|
|
86
92
|
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
|
+
}
|
|
87
118
|
compositionMarks = null;
|
|
119
|
+
precompositionSnapshot = null;
|
|
88
120
|
setCursorWrapper(null);
|
|
89
|
-
if (view.input.compositionNode && !view.input.compositionNode.pmViewDesc && (view.input.compositionNode instanceof Text || view.input.compositionNode instanceof Element)) {
|
|
90
|
-
view.input.compositionNode.remove();
|
|
91
|
-
}
|
|
92
|
-
view.input.compositionEndedAt = event.timeStamp;
|
|
93
|
-
view.input.compositionNode = null;
|
|
94
|
-
view.input.compositionID++;
|
|
95
121
|
return true;
|
|
96
122
|
},
|
|
97
123
|
beforeinput (view, event) {
|
|
@@ -140,38 +166,6 @@ function beforeInputPlugin(setCursorWrapper) {
|
|
|
140
166
|
insertText(view, event.data);
|
|
141
167
|
break;
|
|
142
168
|
}
|
|
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
|
-
// When we insert the text that corresponds to an ongoing composition,
|
|
161
|
-
// the relevant TextNodeView will pause re-rendering so that React doesn't
|
|
162
|
-
// clobber the composition in the DOM. This means that we have to wait for
|
|
163
|
-
// the browser to update the DOM itself before attempting to reconcile
|
|
164
|
-
// the selection, so we specifically defer pending effects to the next
|
|
165
|
-
// macro task
|
|
166
|
-
if (view instanceof _ReactEditorView.ReactEditorView) {
|
|
167
|
-
view.deferPendingEffects = true;
|
|
168
|
-
}
|
|
169
|
-
view.dispatch(tr);
|
|
170
|
-
if (view instanceof _ReactEditorView.ReactEditorView) {
|
|
171
|
-
view.deferPendingEffects = false;
|
|
172
|
-
}
|
|
173
|
-
break;
|
|
174
|
-
}
|
|
175
169
|
case "deleteWordBackward":
|
|
176
170
|
case "deleteHardLineBackward":
|
|
177
171
|
case "deleteSoftLineBackward":
|