@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.
- package/dist/cjs/ReactEditorView.js +2 -0
- package/dist/cjs/components/ChildNodeViews.js +14 -9
- package/dist/cjs/components/CursorWrapper.js +6 -9
- package/dist/cjs/components/TextNodeView.js +109 -38
- package/dist/cjs/components/TrailingHackView.js +29 -0
- package/dist/cjs/hooks/useComponentEventListeners.js +46 -5
- package/dist/cjs/hooks/useEditor.js +3 -6
- package/dist/cjs/hooks/useNodeViewDescription.js +37 -16
- package/dist/cjs/plugins/beforeInputPlugin.js +41 -43
- package/dist/cjs/viewdesc.js +10 -3
- package/dist/esm/ReactEditorView.js +2 -0
- package/dist/esm/components/ChildNodeViews.js +14 -9
- package/dist/esm/components/CursorWrapper.js +7 -10
- package/dist/esm/components/TextNodeView.js +109 -38
- package/dist/esm/components/TrailingHackView.js +30 -1
- package/dist/esm/hooks/useComponentEventListeners.js +53 -12
- package/dist/esm/hooks/useEditor.js +3 -6
- package/dist/esm/hooks/useNodeViewDescription.js +38 -17
- package/dist/esm/plugins/beforeInputPlugin.js +41 -43
- package/dist/esm/viewdesc.js +10 -3
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/ReactEditorView.d.ts +4 -0
- package/dist/types/components/TextNodeView.d.ts +14 -4
- package/dist/types/components/TrailingHackView.d.ts +1 -1
- package/dist/types/constants.d.ts +1 -1
- package/dist/types/contexts/EditorContext.d.ts +1 -1
- package/dist/types/hooks/useComponentEventListeners.d.ts +11 -10
- package/dist/types/hooks/useEditor.d.ts +2 -2
- package/dist/types/hooks/useEditorEventListener.d.ts +1 -1
- package/dist/types/props.d.ts +26 -26
- package/dist/types/viewdesc.d.ts +2 -2
- package/package.json +2 -1
- package/dist/cjs/tiptap/utils/ssrJSDOMPatch.js +0 -59
- package/dist/esm/tiptap/utils/ssrJSDOMPatch.js +0 -56
- 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(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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) +
|
|
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) +
|
|
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
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
73
|
+
while(textNode?.firstChild){
|
|
61
74
|
textNode = textNode.firstChild;
|
|
62
75
|
}
|
|
63
|
-
if (!
|
|
64
|
-
|
|
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
|
-
|
|
76
|
-
|
|
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 {
|
|
91
|
-
|
|
92
|
-
|
|
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 {
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
65
|
-
view.
|
|
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
|
-
|
|
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
|
-
|
|
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":
|