@handlewithcare/react-prosemirror 3.1.0-tiptap.53 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/cjs/ReactEditorView.js +18 -7
- package/dist/cjs/components/ChildNodeViews.js +10 -16
- package/dist/cjs/components/CursorWrapper.js +6 -4
- package/dist/cjs/components/ProseMirror.js +12 -4
- package/dist/cjs/components/TextNodeView.js +7 -216
- package/dist/cjs/components/TrailingHackView.js +0 -70
- package/dist/cjs/components/nodes/NodeView.js +40 -4
- package/dist/cjs/contexts/ChildDescriptionsContext.js +1 -3
- package/dist/cjs/contexts/CompositionContext.js +14 -0
- package/dist/cjs/hooks/useMarkViewDescription.js +2 -63
- package/dist/cjs/hooks/useNodeViewDescription.js +2 -66
- package/dist/cjs/plugins/beforeInputPlugin.js +130 -120
- package/dist/cjs/plugins/reactKeys.js +16 -4
- package/dist/cjs/tiptap/tiptapNodeView.js +10 -9
- package/dist/cjs/viewdesc.js +4 -1
- package/dist/esm/ReactEditorView.js +18 -7
- package/dist/esm/components/ChildNodeViews.js +12 -18
- package/dist/esm/components/CursorWrapper.js +6 -4
- package/dist/esm/components/ProseMirror.js +12 -4
- package/dist/esm/components/TextNodeView.js +5 -165
- package/dist/esm/components/TrailingHackView.js +1 -71
- package/dist/esm/components/nodes/NodeView.js +38 -5
- package/dist/esm/contexts/ChildDescriptionsContext.js +1 -3
- package/dist/esm/contexts/CompositionContext.js +4 -0
- package/dist/esm/hooks/useIsEditorStatic.js +4 -1
- package/dist/esm/hooks/useMarkViewDescription.js +3 -64
- package/dist/esm/hooks/useNodeViewDescription.js +3 -67
- package/dist/esm/plugins/beforeInputPlugin.js +131 -121
- package/dist/esm/plugins/reactKeys.js +16 -4
- package/dist/esm/tiptap/ReactProseMirrorNodeView.js +1 -1
- package/dist/esm/tiptap/TiptapEditorContent.js +8 -1
- package/dist/esm/tiptap/hooks/useIsInReactProseMirror.js +5 -1
- package/dist/esm/tiptap/tiptapNodeView.js +13 -14
- package/dist/esm/viewdesc.js +4 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/ReactEditorView.d.ts +8 -4
- package/dist/types/components/ChildNodeViews.d.ts +2 -2
- package/dist/types/components/CursorWrapper.d.ts +1 -1
- package/dist/types/components/TextNodeView.d.ts +6 -18
- package/dist/types/components/TrailingHackView.d.ts +1 -1
- package/dist/types/components/marks/DefaultMarkView.d.ts +1 -1
- package/dist/types/components/marks/MarkView.d.ts +1 -1
- package/dist/types/components/marks/MarkViewConstructorView.d.ts +1 -1
- package/dist/types/components/marks/ReactMarkView.d.ts +1 -1
- package/dist/types/components/nodes/DefaultNodeView.d.ts +1 -1
- package/dist/types/components/nodes/NodeView.d.ts +3 -1
- package/dist/types/components/nodes/NodeViewConstructorView.d.ts +1 -1
- package/dist/types/components/nodes/ReactNodeView.d.ts +1 -1
- package/dist/types/contexts/ChildDescriptionsContext.d.ts +1 -2
- package/dist/types/contexts/CompositionContext.d.ts +4 -0
- package/dist/types/hooks/useEditor.d.ts +2 -2
- package/dist/types/hooks/useIsEditorStatic.d.ts +4 -0
- package/dist/types/hooks/useMarkViewDescription.d.ts +1 -2
- package/dist/types/hooks/useNodeViewDescription.d.ts +1 -2
- package/dist/types/hooks/useReactKeys.d.ts +2 -5
- package/dist/types/plugins/reactKeys.d.ts +5 -5
- package/dist/types/props.d.ts +225 -225
- package/dist/types/tiptap/ReactProseMirrorNodeView.d.ts +1 -1
- package/dist/types/tiptap/TiptapEditorContent.d.ts +10 -1
- package/dist/types/tiptap/hooks/useIsInReactProseMirror.d.ts +5 -0
- package/dist/types/tiptap/tiptapNodeView.d.ts +5 -6
- package/dist/types/viewdesc.d.ts +2 -1
- package/package.json +20 -6
- package/dist/cjs/plugins/componentEventListeners.js +0 -28
- package/dist/cjs/plugins/componentEventListenersPlugin.js +0 -35
- package/dist/cjs/tiptap/utils/ssrJSDOMPatch.js +0 -59
- package/dist/esm/plugins/componentEventListeners.js +0 -18
- package/dist/esm/plugins/componentEventListenersPlugin.js +0 -25
- package/dist/esm/tiptap/utils/ssrJSDOMPatch.js +0 -56
- package/dist/types/plugins/componentEventListeners.d.ts +0 -3
- package/dist/types/plugins/componentEventListenersPlugin.d.ts +0 -4
- package/dist/types/tiptap/utils/ssrJSDOMPatch.d.ts +0 -1
|
@@ -120,71 +120,10 @@ function useMarkViewDescription(getDOM, getContentDOM, constructor, props) {
|
|
|
120
120
|
child.parent = viewDesc;
|
|
121
121
|
}
|
|
122
122
|
});
|
|
123
|
-
const findCompositionDOM = (0, _react.useCallback)((compositionViewDesc)=>{
|
|
124
|
-
const children = childrenRef.current;
|
|
125
|
-
// Because TextNodeViews can't locate the DOM nodes
|
|
126
|
-
// for compositions, we need to override them here
|
|
127
|
-
if (!viewDescRef.current?.contentDOM) return;
|
|
128
|
-
let compositionTopDOM = null;
|
|
129
|
-
for (const childNode of viewDescRef.current.contentDOM.childNodes){
|
|
130
|
-
if (children.every((child)=>child.dom !== childNode)) {
|
|
131
|
-
compositionTopDOM = childNode;
|
|
132
|
-
break;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
if (!compositionTopDOM) {
|
|
136
|
-
// Otherwise the IME extended an existing tracked text node. Take it over.
|
|
137
|
-
const reactView = view;
|
|
138
|
-
const imeTextNode = reactView.input.compositionNode;
|
|
139
|
-
if (!imeTextNode || !viewDescRef.current.contentDOM.contains(imeTextNode.parentNode)) {
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
const claimedDesc = imeTextNode.pmViewDesc;
|
|
143
|
-
if (!(claimedDesc instanceof _viewdesc.TextViewDesc)) return;
|
|
144
|
-
if (claimedDesc.node.text === imeTextNode.nodeValue) return; // not extended
|
|
145
|
-
// Walk up to the direct child of contentDOM that contains the IME text node
|
|
146
|
-
// (could be the text node itself, could be wrapped in a mark span).
|
|
147
|
-
let topDOM = imeTextNode;
|
|
148
|
-
while(topDOM.parentNode !== viewDescRef.current.contentDOM){
|
|
149
|
-
const next = topDOM.parentNode;
|
|
150
|
-
if (!next) return;
|
|
151
|
-
topDOM = next;
|
|
152
|
-
}
|
|
153
|
-
// Detach the displaced TextViewDesc from the sibling list so sibling-size
|
|
154
|
-
// accounting (used by posBeforeChild) doesn't double-count this text node.
|
|
155
|
-
const displacedIdx = children.indexOf(claimedDesc);
|
|
156
|
-
if (displacedIdx >= 0) children.splice(displacedIdx, 1);
|
|
157
|
-
compositionViewDesc.dom = topDOM;
|
|
158
|
-
compositionViewDesc.textDOM = imeTextNode;
|
|
159
|
-
compositionViewDesc.text = imeTextNode.data;
|
|
160
|
-
imeTextNode.pmViewDesc = compositionViewDesc;
|
|
161
|
-
compositionViewDesc._displacedDesc = claimedDesc;
|
|
162
|
-
reactView.input.compositionNodes.push(compositionViewDesc);
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
let textDOM = compositionTopDOM;
|
|
166
|
-
while(textDOM.firstChild){
|
|
167
|
-
textDOM = textDOM.firstChild;
|
|
168
|
-
}
|
|
169
|
-
if (!textDOM || !(textDOM instanceof Text)) {
|
|
170
|
-
console.error(compositionTopDOM, textDOM);
|
|
171
|
-
throw new Error(`Started a composition but couldn't find the text node it belongs to.`);
|
|
172
|
-
}
|
|
173
|
-
compositionViewDesc.dom = compositionTopDOM;
|
|
174
|
-
compositionViewDesc.textDOM = textDOM;
|
|
175
|
-
compositionViewDesc.text = textDOM.data;
|
|
176
|
-
compositionViewDesc.textDOM.pmViewDesc = compositionViewDesc;
|
|
177
|
-
view.input.compositionNodes.push(compositionViewDesc);
|
|
178
|
-
}, [
|
|
179
|
-
view
|
|
180
|
-
]);
|
|
181
123
|
const childContextValue = (0, _react.useMemo)(()=>({
|
|
182
124
|
parentRef: viewDescRef,
|
|
183
|
-
siblingsRef: childrenRef
|
|
184
|
-
|
|
185
|
-
}), [
|
|
186
|
-
findCompositionDOM
|
|
187
|
-
]);
|
|
125
|
+
siblingsRef: childrenRef
|
|
126
|
+
}), []);
|
|
188
127
|
return {
|
|
189
128
|
childContextValue,
|
|
190
129
|
contentDOM: contentDOMRef.current ?? viewDescRef.current?.dom,
|
|
@@ -143,74 +143,10 @@ function useNodeViewDescription(getDOM, getContentDOM, constructor, props) {
|
|
|
143
143
|
child.parent = viewDesc;
|
|
144
144
|
}
|
|
145
145
|
});
|
|
146
|
-
const findCompositionDOM = (0, _react.useCallback)((compositionViewDesc)=>{
|
|
147
|
-
if (!props.node.isTextblock) return;
|
|
148
|
-
const children = childrenRef.current;
|
|
149
|
-
// Because TextNodeViews can't locate the DOM nodes
|
|
150
|
-
// for compositions, we need to override them here
|
|
151
|
-
if (!viewDescRef.current?.contentDOM) return;
|
|
152
|
-
let compositionTopDOM = null;
|
|
153
|
-
for (const childNode of viewDescRef.current.contentDOM.childNodes){
|
|
154
|
-
if (children.every((child)=>child.dom !== childNode)) {
|
|
155
|
-
compositionTopDOM = childNode;
|
|
156
|
-
break;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
if (!compositionTopDOM) {
|
|
160
|
-
// Otherwise the IME extended an existing tracked text node. Take it over.
|
|
161
|
-
const reactView = view;
|
|
162
|
-
const imeTextNode = reactView.input.compositionNode;
|
|
163
|
-
if (!imeTextNode || !viewDescRef.current.contentDOM.contains(imeTextNode.parentNode)) {
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
const claimedDesc = imeTextNode.pmViewDesc;
|
|
167
|
-
if (!(claimedDesc instanceof _viewdesc.TextViewDesc)) return;
|
|
168
|
-
if (claimedDesc.node.text === imeTextNode.nodeValue) return; // not extended
|
|
169
|
-
// Walk up to the direct child of contentDOM that contains the IME text node
|
|
170
|
-
// (could be the text node itself, could be wrapped in a mark span).
|
|
171
|
-
let topDOM = imeTextNode;
|
|
172
|
-
while(topDOM.parentNode !== viewDescRef.current.contentDOM){
|
|
173
|
-
const next = topDOM.parentNode;
|
|
174
|
-
if (!next) return;
|
|
175
|
-
topDOM = next;
|
|
176
|
-
}
|
|
177
|
-
// Detach the displaced TextViewDesc from the sibling list so sibling-size
|
|
178
|
-
// accounting (used by posBeforeChild) doesn't double-count this text node.
|
|
179
|
-
const displacedIdx = children.indexOf(claimedDesc);
|
|
180
|
-
if (displacedIdx >= 0) children.splice(displacedIdx, 1);
|
|
181
|
-
reactView.displacedNodes.push(claimedDesc);
|
|
182
|
-
compositionViewDesc.dom = topDOM;
|
|
183
|
-
compositionViewDesc.textDOM = imeTextNode;
|
|
184
|
-
compositionViewDesc.text = imeTextNode.data;
|
|
185
|
-
imeTextNode.pmViewDesc = compositionViewDesc;
|
|
186
|
-
compositionViewDesc._displacedDesc = claimedDesc;
|
|
187
|
-
reactView.input.compositionNodes.push(compositionViewDesc);
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
let textDOM = compositionTopDOM;
|
|
191
|
-
while(textDOM.firstChild){
|
|
192
|
-
textDOM = textDOM.firstChild;
|
|
193
|
-
}
|
|
194
|
-
if (!textDOM || !(textDOM instanceof Text)) {
|
|
195
|
-
console.error(compositionTopDOM, textDOM);
|
|
196
|
-
throw new Error(`Started a composition but couldn't find the text node it belongs to.`);
|
|
197
|
-
}
|
|
198
|
-
compositionViewDesc.dom = compositionTopDOM;
|
|
199
|
-
compositionViewDesc.textDOM = textDOM;
|
|
200
|
-
compositionViewDesc.text = textDOM.data;
|
|
201
|
-
compositionViewDesc.textDOM.pmViewDesc = compositionViewDesc;
|
|
202
|
-
view.input.compositionNodes.push(compositionViewDesc);
|
|
203
|
-
}, [
|
|
204
|
-
props.node.isTextblock,
|
|
205
|
-
view
|
|
206
|
-
]);
|
|
207
146
|
const childContextValue = (0, _react.useMemo)(()=>({
|
|
208
147
|
parentRef: viewDescRef,
|
|
209
|
-
siblingsRef: childrenRef
|
|
210
|
-
|
|
211
|
-
}), [
|
|
212
|
-
findCompositionDOM
|
|
213
|
-
]);
|
|
148
|
+
siblingsRef: childrenRef
|
|
149
|
+
}), []);
|
|
214
150
|
return {
|
|
215
151
|
childContextValue,
|
|
216
152
|
contentDOM: contentDOMRef.current,
|
|
@@ -57,36 +57,66 @@ function handleGapCursorComposition(view) {
|
|
|
57
57
|
tr.setSelection(_prosemirrorstate.TextSelection.near(tr.doc.resolve($from.pos + 1)));
|
|
58
58
|
view.dispatch(tr);
|
|
59
59
|
}
|
|
60
|
+
const observeOptions = {
|
|
61
|
+
childList: true,
|
|
62
|
+
characterData: true,
|
|
63
|
+
characterDataOldValue: true,
|
|
64
|
+
attributes: true,
|
|
65
|
+
attributeOldValue: true,
|
|
66
|
+
subtree: true
|
|
67
|
+
};
|
|
60
68
|
function beforeInputPlugin() {
|
|
61
|
-
let
|
|
69
|
+
let observer = null;
|
|
70
|
+
let preCompositionSnapshot = null;
|
|
71
|
+
function teardownComposition(view, endedAt) {
|
|
72
|
+
view.input.composing = false;
|
|
73
|
+
if (observer) {
|
|
74
|
+
if (view.input.compositionNode && view.dom.contains(view.input.compositionNode)) {
|
|
75
|
+
view.domObserver.queue.push(...observer.takeRecords());
|
|
76
|
+
view.domObserver.flush();
|
|
77
|
+
} else {
|
|
78
|
+
const freezeFrom = _reactKeys.reactKeysPluginKey.getState(view.state)?.freezeFrom;
|
|
79
|
+
const frozenNode = freezeFrom == null ? null : view.state.doc.nodeAt(freezeFrom);
|
|
80
|
+
if (freezeFrom != null && frozenNode != null && preCompositionSnapshot) {
|
|
81
|
+
// This is a little hacky — it only works because we always abort
|
|
82
|
+
// compositions if the node after freezeFrom changes, so we can
|
|
83
|
+
// be sure that if a composition was canceled by the user/browser,
|
|
84
|
+
// the content hasn't changed since the composition started
|
|
85
|
+
view.dispatch(view.state.tr.replaceWith(freezeFrom + 1, freezeFrom + 1 + frozenNode.content.size, preCompositionSnapshot));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
observer.disconnect();
|
|
89
|
+
observer = null;
|
|
90
|
+
}
|
|
91
|
+
view.input.compositionEndedAt = endedAt;
|
|
92
|
+
view.input.compositionNode = null;
|
|
93
|
+
view.input.compositionNodes = [];
|
|
94
|
+
view.input.compositionID++;
|
|
95
|
+
}
|
|
62
96
|
return new _prosemirrorstate.Plugin({
|
|
97
|
+
view () {
|
|
98
|
+
return {
|
|
99
|
+
update (view) {
|
|
100
|
+
if (!(view instanceof _ReactEditorView.ReactEditorView)) return;
|
|
101
|
+
const frozen = _reactKeys.reactKeysPluginKey.getState(view.state)?.freezeFrom != null;
|
|
102
|
+
if (observer && view.composing && !frozen) {
|
|
103
|
+
teardownComposition(view, Date.now());
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
},
|
|
63
108
|
props: {
|
|
64
109
|
handleDOMEvents: {
|
|
65
110
|
compositionstart (view) {
|
|
66
111
|
if (!(view instanceof _ReactEditorView.ReactEditorView)) return false;
|
|
67
|
-
view.
|
|
68
|
-
|
|
69
|
-
const { selection } = state;
|
|
70
|
-
const isEmptyTr = state.tr.delete(selection.from, selection.to);
|
|
71
|
-
const $from = isEmptyTr.doc.resolve(isEmptyTr.mapping.map(selection.from));
|
|
72
|
-
const isEmptyTextblock = $from.parent.isTextblock && $from.parent.childCount === 0;
|
|
73
|
-
compositionMarks = view.state.storedMarks;
|
|
74
|
-
// Render a CursorWrapper with empty marks if starting a composition in an
|
|
75
|
-
// empty textblock with no marks. This prevents the browser from adding a
|
|
76
|
-
// <br> to the text block when it becomes empty (either via canceling the
|
|
77
|
-
// composition with the escape key or deleting all composition text when
|
|
78
|
-
// the composition node is the only text node in the text block)
|
|
79
|
-
if (compositionMarks === null && isEmptyTextblock) {
|
|
80
|
-
compositionMarks = [];
|
|
81
|
-
}
|
|
82
|
-
const tr = view.state.tr.setStoredMarks(null);
|
|
83
|
-
view.dispatch(tr);
|
|
112
|
+
const storedMarks = view.state.selection.empty ? view.state.storedMarks : view.state.storedMarks ?? (view.state.selection instanceof _prosemirrorstate.TextSelection ? view.state.selection.$from.marksAcross(view.state.selection.$to) : null);
|
|
113
|
+
view.dispatch(view.state.tr.deleteSelection().setStoredMarks(storedMarks));
|
|
84
114
|
handleGapCursorComposition(view);
|
|
85
|
-
if (
|
|
115
|
+
if (storedMarks) {
|
|
86
116
|
view.dispatch(view.state.tr.setMeta(_reactKeys.reactKeysPluginKey, {
|
|
87
|
-
cursorWrapper: (0, _ReactWidgetType.widget)(state.selection.from, _CursorWrapper.CursorWrapper, {
|
|
117
|
+
cursorWrapper: (0, _ReactWidgetType.widget)(view.state.selection.from, _CursorWrapper.CursorWrapper, {
|
|
88
118
|
key: "cursor-wrapper",
|
|
89
|
-
marks:
|
|
119
|
+
marks: storedMarks,
|
|
90
120
|
side: 0,
|
|
91
121
|
raw: true
|
|
92
122
|
})
|
|
@@ -97,24 +127,38 @@ function beforeInputPlugin() {
|
|
|
97
127
|
// node depending on the user's last navigation direction, and the
|
|
98
128
|
// IME composes into whichever one it found.
|
|
99
129
|
} else if (view.state.selection.empty) {
|
|
100
|
-
// @ts-expect-error internal method
|
|
101
130
|
view.domObserver.disconnectSelection();
|
|
102
131
|
try {
|
|
103
132
|
view.docView.setSelection(view.state.selection.anchor, view.state.selection.head, view, true // force — skip the isEquivalentPosition early-return
|
|
104
133
|
);
|
|
105
134
|
} finally{
|
|
106
|
-
// @ts-expect-error internal method
|
|
107
135
|
view.domObserver.setCurSelection();
|
|
108
|
-
// @ts-expect-error internal method
|
|
109
136
|
view.domObserver.connectSelection();
|
|
110
137
|
}
|
|
111
138
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
139
|
+
const freezeFrom = view.state.selection.$from.before();
|
|
140
|
+
view.dispatch(view.state.tr.setMeta(_reactKeys.reactKeysPluginKey, {
|
|
141
|
+
freezeFrom
|
|
142
|
+
}));
|
|
143
|
+
const frozenDom = view.nodeDOM(freezeFrom);
|
|
144
|
+
if (!frozenDom) {
|
|
145
|
+
view.dispatch(view.state.tr.setMeta(_reactKeys.reactKeysPluginKey, {
|
|
146
|
+
cursorWrapper: null,
|
|
147
|
+
freezeFrom: null
|
|
148
|
+
}));
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
preCompositionSnapshot = view.state.doc.nodeAt(freezeFrom)?.content ?? null;
|
|
117
152
|
view.input.composing = true;
|
|
153
|
+
observer = new MutationObserver((records)=>{
|
|
154
|
+
if (_reactKeys.reactKeysPluginKey.getState(view.state)?.freezeFrom == null) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
view.domObserver.queue.push(...records);
|
|
158
|
+
view.domObserver.flush();
|
|
159
|
+
syncCompositionViewDescs(view);
|
|
160
|
+
});
|
|
161
|
+
observer.observe(frozenDom, observeOptions);
|
|
118
162
|
return true;
|
|
119
163
|
},
|
|
120
164
|
compositionupdate () {
|
|
@@ -123,36 +167,11 @@ function beforeInputPlugin() {
|
|
|
123
167
|
compositionend (view, event) {
|
|
124
168
|
if (!(view instanceof _ReactEditorView.ReactEditorView)) return false;
|
|
125
169
|
if (!view.composing) return false;
|
|
126
|
-
view.
|
|
127
|
-
compositionMarks = null;
|
|
128
|
-
for (const displaced of view.displacedNodes){
|
|
129
|
-
// Put the displaced TextViewDesc back into its parent's child list.
|
|
130
|
-
const parent = displaced.parent;
|
|
131
|
-
if (parent && !parent.children.includes(displaced)) {
|
|
132
|
-
parent.children.push(displaced);
|
|
133
|
-
parent.children.sort(_viewdesc.sortViewDescs);
|
|
134
|
-
}
|
|
135
|
-
// Restore pmViewDesc claim on the text node.
|
|
136
|
-
displaced.dom.pmViewDesc = displaced;
|
|
137
|
-
// Truncate the IME text node back to what the displaced PM node says it
|
|
138
|
-
// is. The composed content lives in PM state; the next React render will
|
|
139
|
-
// mount a sibling TextNodeView that inserts its own DOM (e.g.
|
|
140
|
-
// `<span class="word">k</span>`) right after this node.
|
|
141
|
-
const claimedText = displaced.node.text ?? "";
|
|
142
|
-
if (displaced.nodeDOM.nodeValue !== claimedText) {
|
|
143
|
-
displaced.nodeDOM.nodeValue = claimedText;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
170
|
+
teardownComposition(view, event.timeStamp);
|
|
146
171
|
view.dispatch(view.state.tr.setMeta(_reactKeys.reactKeysPluginKey, {
|
|
147
|
-
cursorWrapper: null
|
|
172
|
+
cursorWrapper: null,
|
|
173
|
+
freezeFrom: null
|
|
148
174
|
}));
|
|
149
|
-
if (view.input.compositionNode && isCompositionNodeOrphaned(view.input.compositionNode)) {
|
|
150
|
-
view.input.compositionNode.remove();
|
|
151
|
-
}
|
|
152
|
-
view.input.compositionEndedAt = event.timeStamp;
|
|
153
|
-
view.input.compositionNode = null;
|
|
154
|
-
view.input.compositionNodes = [];
|
|
155
|
-
view.input.compositionID++;
|
|
156
175
|
return true;
|
|
157
176
|
},
|
|
158
177
|
beforeinput (view, event) {
|
|
@@ -203,63 +222,6 @@ function beforeInputPlugin() {
|
|
|
203
222
|
insertText(view, event.data);
|
|
204
223
|
break;
|
|
205
224
|
}
|
|
206
|
-
case "insertCompositionText":
|
|
207
|
-
case "deleteCompositionText":
|
|
208
|
-
case "insertFromComposition":
|
|
209
|
-
{
|
|
210
|
-
if (!(view instanceof _ReactEditorView.ReactEditorView)) break;
|
|
211
|
-
const { tr } = view.state;
|
|
212
|
-
// There's always a range on insertCompositionText beforeinput events
|
|
213
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
214
|
-
const range = event.getTargetRanges()[0];
|
|
215
|
-
const start = view.posAtDOM(range.startContainer, range.startOffset);
|
|
216
|
-
const end = view.posAtDOM(range.endContainer, range.endOffset, 1);
|
|
217
|
-
if (view.state.doc.textBetween(start, end, "**", "*") === event.data) {
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
if (event.data) {
|
|
221
|
-
if (compositionMarks) tr.ensureMarks(compositionMarks);
|
|
222
|
-
tr.insertText(event.data, start, end);
|
|
223
|
-
} else {
|
|
224
|
-
tr.delete(start, end);
|
|
225
|
-
}
|
|
226
|
-
// When updating a composition within an existing text node,
|
|
227
|
-
// we need to avoid remounting it. If the composition is at
|
|
228
|
-
// the very beginning of the text node, the start position of
|
|
229
|
-
// that node will either be mapped forward (if inserting new
|
|
230
|
-
// content) or deleted (if replacing existing content).
|
|
231
|
-
//
|
|
232
|
-
// This will cause the reactKeys plugin to mint a new key for
|
|
233
|
-
// that node, which triggers a remount. So we check to see whether
|
|
234
|
-
// we're working on a composition at the very beginning of a text
|
|
235
|
-
// node, and if so, tell the react keys plugin not to change the
|
|
236
|
-
// key for that node.
|
|
237
|
-
//
|
|
238
|
-
// We need to check that the marks are the same — if they're not,
|
|
239
|
-
// then we're inserting text _before_ this text node, not at the
|
|
240
|
-
// start of it, so we actually _do_ want to map the exsting node
|
|
241
|
-
// forward.
|
|
242
|
-
const $start = view.state.doc.resolve(start);
|
|
243
|
-
const $end = view.state.doc.resolve(end);
|
|
244
|
-
const marks = compositionMarks ?? $start.marksAcross($end) ?? [];
|
|
245
|
-
if ($start.textOffset === 0 && $end.nodeAfter?.marks.every((m)=>m.isInSet(marks))) {
|
|
246
|
-
tr.setMeta(_reactKeys.reactKeysPluginKey, {
|
|
247
|
-
overrides: {
|
|
248
|
-
[start]: start
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
view.dom.addEventListener("input", ()=>{
|
|
253
|
-
const sel = view.domSelectionRange();
|
|
254
|
-
if (sel.focusNode && sel.focusNode.nodeType === 3) {
|
|
255
|
-
view.input.compositionNode = sel.focusNode;
|
|
256
|
-
}
|
|
257
|
-
view.dispatch(tr);
|
|
258
|
-
}, {
|
|
259
|
-
once: true
|
|
260
|
-
});
|
|
261
|
-
break;
|
|
262
|
-
}
|
|
263
225
|
case "deleteWordBackward":
|
|
264
226
|
case "deleteHardLineBackward":
|
|
265
227
|
case "deleteSoftLineBackward":
|
|
@@ -293,11 +255,59 @@ function beforeInputPlugin() {
|
|
|
293
255
|
}
|
|
294
256
|
});
|
|
295
257
|
}
|
|
296
|
-
function
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
258
|
+
function syncCompositionViewDescs(view) {
|
|
259
|
+
const compositionNode = view.domObserver.lastChangedTextNode;
|
|
260
|
+
if (!compositionNode) return;
|
|
261
|
+
const freezeFrom = _reactKeys.reactKeysPluginKey.getState(view.state)?.freezeFrom;
|
|
262
|
+
if (freezeFrom == null) return;
|
|
263
|
+
const compositionBlock = view.state.doc.nodeAt(freezeFrom);
|
|
264
|
+
if (!compositionBlock) return;
|
|
265
|
+
const compositionBlockDesc = view.docView.descAt(freezeFrom);
|
|
266
|
+
if (!compositionBlockDesc) return;
|
|
267
|
+
const desc = view.docView.nearestDesc(compositionNode);
|
|
268
|
+
compositionBlockDesc.node = compositionBlock;
|
|
269
|
+
if (desc instanceof _viewdesc.TextViewDesc) {
|
|
270
|
+
if (compositionNode.nodeValue && desc.node.text !== compositionNode.nodeValue) {
|
|
271
|
+
desc.node = view.state.schema.text(compositionNode.nodeValue, desc.node.marks);
|
|
272
|
+
desc.nodeDOM = compositionNode;
|
|
273
|
+
compositionNode.pmViewDesc = desc;
|
|
274
|
+
}
|
|
275
|
+
return;
|
|
301
276
|
}
|
|
302
|
-
|
|
277
|
+
if (desc instanceof _viewdesc.CompositionViewDesc) {
|
|
278
|
+
if (compositionNode.nodeValue != null && desc.text !== compositionNode.nodeValue) {
|
|
279
|
+
desc.dom = compositionNode;
|
|
280
|
+
desc.textDOM = compositionNode;
|
|
281
|
+
desc.text = compositionNode.nodeValue;
|
|
282
|
+
compositionNode.pmViewDesc = desc;
|
|
283
|
+
}
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const parentDesc = desc?.contentDOM ? desc : compositionBlockDesc;
|
|
287
|
+
const children = parentDesc.children;
|
|
288
|
+
// Drop any text or composition desc in this container whose DOM the
|
|
289
|
+
// IME has detached. This covers two cases: a TextViewDesc the IME subsumed
|
|
290
|
+
// into the composition node, and (on Safari, which replaces the whole text
|
|
291
|
+
// node on each composition update) any orphaned composition view
|
|
292
|
+
// desc(s) left over from the previous composition steps.
|
|
293
|
+
for(let i = children.length - 1; i >= 0; i--){
|
|
294
|
+
const c = children[i];
|
|
295
|
+
if (!(c instanceof _viewdesc.TextViewDesc) && !(c instanceof _viewdesc.CompositionViewDesc)) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
const dom = c.dom;
|
|
299
|
+
if (view.dom.contains(dom)) continue;
|
|
300
|
+
children.splice(i, 1);
|
|
301
|
+
}
|
|
302
|
+
const contentStart = freezeFrom + 1;
|
|
303
|
+
const { from, to } = view.state.selection;
|
|
304
|
+
const textPos = (0, _viewdesc.findTextInFragment)(compositionBlock.content, compositionNode.nodeValue ?? "", from - contentStart, to - contentStart);
|
|
305
|
+
if (textPos < 0) return;
|
|
306
|
+
const startPos = contentStart + textPos;
|
|
307
|
+
let topDOM = compositionNode;
|
|
308
|
+
while(topDOM.parentNode && topDOM.parentNode !== parentDesc.contentDOM){
|
|
309
|
+
topDOM = topDOM.parentNode;
|
|
310
|
+
}
|
|
311
|
+
const insertIndex = children.findLastIndex((c)=>c.posBefore <= startPos) + 1;
|
|
312
|
+
children.splice(insertIndex, 0, new _viewdesc.CompositionViewDesc(parentDesc, ()=>startPos, topDOM, compositionNode, compositionNode.nodeValue ?? ""));
|
|
303
313
|
}
|
|
@@ -35,7 +35,8 @@ function reactKeys() {
|
|
|
35
35
|
const next = {
|
|
36
36
|
posToKey: new Map(),
|
|
37
37
|
keyToPos: new Map(),
|
|
38
|
-
cursorWrapper: null
|
|
38
|
+
cursorWrapper: null,
|
|
39
|
+
freezeFrom: null
|
|
39
40
|
};
|
|
40
41
|
state.doc.descendants((_, pos)=>{
|
|
41
42
|
const key = createNodeKey();
|
|
@@ -52,19 +53,30 @@ function reactKeys() {
|
|
|
52
53
|
* through the transaction to identify its current position,
|
|
53
54
|
* and assign its key to that new position, dropping it if the
|
|
54
55
|
* node was deleted.
|
|
55
|
-
*/ apply (tr, value,
|
|
56
|
+
*/ apply (tr, value, oldState, newState) {
|
|
56
57
|
const meta = tr.getMeta(reactKeysPluginKey);
|
|
57
58
|
const overrides = meta && "overrides" in meta ? meta.overrides : {};
|
|
58
59
|
const cursorWrapper = meta && "cursorWrapper" in meta ? meta.cursorWrapper : undefined;
|
|
60
|
+
const freezeFrom = meta && "freezeFrom" in meta ? meta.freezeFrom : undefined;
|
|
59
61
|
const next = {
|
|
60
62
|
posToKey: new Map(),
|
|
61
63
|
keyToPos: new Map(),
|
|
62
|
-
cursorWrapper: cursorWrapper === undefined ? value.cursorWrapper ? (0, _ReactWidgetType.widget)(tr.mapping.map(value.cursorWrapper.from, -1), value.cursorWrapper.type.Component, value.cursorWrapper.spec) : null : cursorWrapper
|
|
64
|
+
cursorWrapper: cursorWrapper === undefined ? value.cursorWrapper ? (0, _ReactWidgetType.widget)(tr.mapping.map(value.cursorWrapper.from, -1), value.cursorWrapper.type.Component, value.cursorWrapper.spec) : null : cursorWrapper,
|
|
65
|
+
freezeFrom: freezeFrom === undefined ? value.freezeFrom !== null ? tr.mapping.map(value.freezeFrom, -1) : null : freezeFrom
|
|
63
66
|
};
|
|
67
|
+
if (value.freezeFrom !== null && next.freezeFrom !== null && tr.getMeta("composition") == null) {
|
|
68
|
+
const oldBlock = oldState.doc.nodeAt(value.freezeFrom);
|
|
69
|
+
const newBlock = newState.doc.nodeAt(next.freezeFrom);
|
|
70
|
+
if (newBlock && !oldBlock?.eq(newBlock)) {
|
|
71
|
+
next.freezeFrom = null;
|
|
72
|
+
next.cursorWrapper = null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
64
75
|
if (!tr.docChanged) {
|
|
65
76
|
return {
|
|
66
77
|
...value,
|
|
67
|
-
cursorWrapper: next.cursorWrapper
|
|
78
|
+
cursorWrapper: next.cursorWrapper,
|
|
79
|
+
freezeFrom: next.freezeFrom
|
|
68
80
|
};
|
|
69
81
|
}
|
|
70
82
|
const posToKeyEntries = Array.from(value.posToKey.entries()).sort((param, param1)=>{
|
|
@@ -116,15 +116,6 @@ function tiptapNodeView(param) {
|
|
|
116
116
|
return result;
|
|
117
117
|
});
|
|
118
118
|
(0, _useIgnoreMutation.useIgnoreMutation)(function(_, mutation) {
|
|
119
|
-
if (ignoreMutation) {
|
|
120
|
-
return ignoreMutation.call({
|
|
121
|
-
name: extension.name,
|
|
122
|
-
editor,
|
|
123
|
-
type: node.type
|
|
124
|
-
}, {
|
|
125
|
-
mutation
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
119
|
if (!editor || !(this.dom instanceof HTMLElement)) return false;
|
|
129
120
|
const nodeView = new _ReactProseMirrorNodeView.ReactProseMirrorNodeView(WrappedComponent, {
|
|
130
121
|
extension,
|
|
@@ -136,6 +127,16 @@ function tiptapNodeView(param) {
|
|
|
136
127
|
node,
|
|
137
128
|
view: editor.view
|
|
138
129
|
}, this.dom, this.contentDOM);
|
|
130
|
+
if (ignoreMutation) {
|
|
131
|
+
return ignoreMutation.call({
|
|
132
|
+
name: extension.name,
|
|
133
|
+
editor,
|
|
134
|
+
type: node.type
|
|
135
|
+
}, {
|
|
136
|
+
mutation,
|
|
137
|
+
defaultIgnoreMutation: nodeView.ignoreMutation.bind(nodeView)
|
|
138
|
+
});
|
|
139
|
+
}
|
|
139
140
|
return nodeView.ignoreMutation(mutation) ?? false;
|
|
140
141
|
});
|
|
141
142
|
const { extraClassName, htmlProps } = (0, _react1.useMemo)(()=>{
|
package/dist/cjs/viewdesc.js
CHANGED
|
@@ -416,7 +416,7 @@ let ViewDesc = class ViewDesc {
|
|
|
416
416
|
const after = selRange.focusNode.childNodes[selRange.focusOffset];
|
|
417
417
|
if (after && after.contentEditable == "false") force = true;
|
|
418
418
|
}
|
|
419
|
-
if (view.
|
|
419
|
+
if (view.cursorWrapped || !(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)) {
|
|
420
420
|
return;
|
|
421
421
|
}
|
|
422
422
|
// Selection.extend can be used to create an 'inverted' selection
|
|
@@ -715,6 +715,9 @@ let TextViewDesc = class TextViewDesc extends NodeViewDesc {
|
|
|
715
715
|
isText(text) {
|
|
716
716
|
return this.node.text == text;
|
|
717
717
|
}
|
|
718
|
+
ignoreMutation(mutation) {
|
|
719
|
+
return mutation.type !== "characterData" && mutation.type !== "selection";
|
|
720
|
+
}
|
|
718
721
|
};
|
|
719
722
|
let TrailingHackViewDesc = class TrailingHackViewDesc extends ViewDesc {
|
|
720
723
|
parseRule() {
|
|
@@ -33,10 +33,7 @@ function changedNodeViews(a, b) {
|
|
|
33
33
|
nextProps;
|
|
34
34
|
prevState;
|
|
35
35
|
_destroyed;
|
|
36
|
-
|
|
37
|
-
// whether it was mounted during a compositionstart event handler
|
|
38
|
-
compositionStarting;
|
|
39
|
-
displacedNodes;
|
|
36
|
+
cursorWrapped;
|
|
40
37
|
constructor(place, props){
|
|
41
38
|
// Prevent the base class from destroying the React-managed nodes.
|
|
42
39
|
// Restore them below after invoking the base class constructor.
|
|
@@ -89,8 +86,7 @@ function changedNodeViews(a, b) {
|
|
|
89
86
|
// @ts-expect-error this violates the typing but class does it, too.
|
|
90
87
|
this.docView = null;
|
|
91
88
|
this._destroyed = false;
|
|
92
|
-
this.
|
|
93
|
-
this.displacedNodes = [];
|
|
89
|
+
this.cursorWrapped = false;
|
|
94
90
|
}
|
|
95
91
|
get props() {
|
|
96
92
|
return this.nextProps;
|
|
@@ -191,7 +187,22 @@ function changedNodeViews(a, b) {
|
|
|
191
187
|
// this ensures that the base class validates the DOM selection and invokes
|
|
192
188
|
// node view selection callbacks.
|
|
193
189
|
this.docView.markDirty(-1, -1);
|
|
194
|
-
|
|
190
|
+
const selectionChanged = !this.state.selection.eq(this.prevState.selection);
|
|
191
|
+
if (selectionChanged) {
|
|
192
|
+
super.update(this.nextProps);
|
|
193
|
+
} else {
|
|
194
|
+
// If the selection hasn't changed between renders, force prosemirror-view to
|
|
195
|
+
// skip the selectionToDOM call. If a render happens after a DOM selection change
|
|
196
|
+
// but before the "selectionchange" event fired, calling selectionToDOM will cause
|
|
197
|
+
// the selection to be reset its the previous position.
|
|
198
|
+
this.domObserver.setCurSelection();
|
|
199
|
+
this.input.mouseDown = {
|
|
200
|
+
allowDefault: false,
|
|
201
|
+
delayedSelectionSync: false
|
|
202
|
+
};
|
|
203
|
+
super.update(this.nextProps);
|
|
204
|
+
this.input.mouseDown = null;
|
|
205
|
+
}
|
|
195
206
|
// Store the new previous state.
|
|
196
207
|
this.prevState = this.state;
|
|
197
208
|
}
|
|
@@ -7,11 +7,11 @@ import { htmlAttrsToReactProps, mergeReactProps } from "../props.js";
|
|
|
7
7
|
import { sameOuterDeco } from "../viewdesc.js";
|
|
8
8
|
import { NativeWidgetView } from "./NativeWidgetView.js";
|
|
9
9
|
import { SeparatorHackView } from "./SeparatorHackView.js";
|
|
10
|
-
import {
|
|
10
|
+
import { TextNodeView } from "./TextNodeView.js";
|
|
11
11
|
import { TrailingHackView } from "./TrailingHackView.js";
|
|
12
12
|
import { WidgetView } from "./WidgetView.js";
|
|
13
13
|
import { MarkView } from "./marks/MarkView.js";
|
|
14
|
-
import {
|
|
14
|
+
import { RemountableNodeView } from "./nodes/NodeView.js";
|
|
15
15
|
export function wrapInDeco(reactNode, deco) {
|
|
16
16
|
const { nodeName, ...attrs } = deco.type.attrs;
|
|
17
17
|
const props = htmlAttrsToReactProps(attrs);
|
|
@@ -50,22 +50,16 @@ const ChildView = /*#__PURE__*/ memo(function ChildView(param) {
|
|
|
50
50
|
}) : child.node.isText ? /*#__PURE__*/ React.createElement(ChildDescriptionsContext.Consumer, {
|
|
51
51
|
key: child.key
|
|
52
52
|
}, (param)=>{
|
|
53
|
-
let { siblingsRef, parentRef
|
|
54
|
-
return /*#__PURE__*/ React.createElement(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
parentRef: parentRef,
|
|
62
|
-
findCompositionDOM: findCompositionDOM,
|
|
63
|
-
decorations: child.outerDeco,
|
|
64
|
-
registerEventListener: registerEventListener,
|
|
65
|
-
unregisterEventListener: unregisterEventListener
|
|
66
|
-
});
|
|
53
|
+
let { siblingsRef, parentRef } = param;
|
|
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
|
|
67
61
|
});
|
|
68
|
-
}) : /*#__PURE__*/ React.createElement(
|
|
62
|
+
}) : /*#__PURE__*/ React.createElement(RemountableNodeView, {
|
|
69
63
|
key: child.key,
|
|
70
64
|
node: child.node,
|
|
71
65
|
getPos: getPos,
|
|
@@ -213,7 +207,7 @@ const ChildElement = /*#__PURE__*/ memo(function ChildElement(param) {
|
|
|
213
207
|
mark: mark,
|
|
214
208
|
getPos: getPos,
|
|
215
209
|
inline: false
|
|
216
|
-
}, element), /*#__PURE__*/ React.createElement(
|
|
210
|
+
}, element), /*#__PURE__*/ React.createElement(RemountableNodeView, {
|
|
217
211
|
key: child.key,
|
|
218
212
|
outerDeco: child.outerDeco,
|
|
219
213
|
node: child.node,
|