@handlewithcare/react-prosemirror 3.1.0-tiptap.52 → 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 -1
- package/dist/cjs/components/ChildNodeViews.js +4 -4
- package/dist/cjs/components/CursorWrapper.js +9 -11
- package/dist/cjs/components/ProseMirror.js +13 -3
- package/dist/cjs/components/TextNodeView.js +54 -50
- package/dist/cjs/components/WidgetView.js +3 -1
- package/dist/cjs/components/nodes/NodeView.js +40 -4
- package/dist/cjs/contexts/CompositionContext.js +14 -0
- package/dist/cjs/decorations/viewDecorations.js +1 -6
- package/dist/cjs/hooks/useEditor.js +2 -10
- package/dist/cjs/hooks/useMarkViewDescription.js +1 -4
- package/dist/cjs/hooks/useNodeViewDescription.js +1 -22
- package/dist/cjs/plugins/beforeInputPlugin.js +162 -50
- package/dist/cjs/plugins/reactKeys.js +34 -15
- package/dist/cjs/tiptap/tiptapNodeView.js +10 -9
- package/dist/cjs/viewdesc.js +55 -4
- package/dist/esm/ReactEditorView.js +18 -1
- package/dist/esm/components/ChildNodeViews.js +5 -5
- package/dist/esm/components/CursorWrapper.js +9 -11
- package/dist/esm/components/ProseMirror.js +13 -3
- package/dist/esm/components/TextNodeView.js +56 -52
- package/dist/esm/components/WidgetView.js +3 -1
- package/dist/esm/components/nodes/NodeView.js +38 -5
- package/dist/esm/contexts/CompositionContext.js +4 -0
- package/dist/esm/decorations/viewDecorations.js +1 -6
- package/dist/esm/hooks/useEditor.js +2 -10
- package/dist/esm/hooks/useIsEditorStatic.js +4 -1
- package/dist/esm/hooks/useMarkViewDescription.js +1 -4
- package/dist/esm/hooks/useNodeViewDescription.js +2 -23
- package/dist/esm/plugins/beforeInputPlugin.js +162 -50
- package/dist/esm/plugins/reactKeys.js +34 -15
- 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 +54 -4
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/ReactEditorView.d.ts +10 -1
- 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 +7 -4
- package/dist/types/components/__tests__/ProseMirror.composition.test.d.ts +17 -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 +3 -2
- package/dist/types/contexts/CompositionContext.d.ts +4 -0
- package/dist/types/decorations/viewDecorations.d.ts +2 -2
- package/dist/types/hooks/useEditor.d.ts +3 -4
- package/dist/types/hooks/useIsEditorStatic.d.ts +4 -0
- package/dist/types/hooks/useReactKeys.d.ts +2 -5
- package/dist/types/plugins/beforeInputPlugin.d.ts +1 -2
- package/dist/types/plugins/reactKeys.d.ts +10 -9
- 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 +5 -3
- package/package.json +22 -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
|
@@ -10,8 +10,11 @@ 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");
|
|
16
|
+
const _viewdesc = require("../viewdesc.js");
|
|
17
|
+
const _reactKeys = require("./reactKeys.js");
|
|
15
18
|
function insertText(view, eventData) {
|
|
16
19
|
let options = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {};
|
|
17
20
|
if (eventData === null) return false;
|
|
@@ -54,74 +57,127 @@ function handleGapCursorComposition(view) {
|
|
|
54
57
|
tr.setSelection(_prosemirrorstate.TextSelection.near(tr.doc.resolve($from.pos + 1)));
|
|
55
58
|
view.dispatch(tr);
|
|
56
59
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
const observeOptions = {
|
|
61
|
+
childList: true,
|
|
62
|
+
characterData: true,
|
|
63
|
+
characterDataOldValue: true,
|
|
64
|
+
attributes: true,
|
|
65
|
+
attributeOldValue: true,
|
|
66
|
+
subtree: true
|
|
67
|
+
};
|
|
68
|
+
function beforeInputPlugin() {
|
|
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
|
+
}
|
|
60
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
|
+
},
|
|
61
108
|
props: {
|
|
62
109
|
handleDOMEvents: {
|
|
63
110
|
compositionstart (view) {
|
|
64
|
-
|
|
65
|
-
view.
|
|
111
|
+
if (!(view instanceof _ReactEditorView.ReactEditorView)) return false;
|
|
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));
|
|
66
114
|
handleGapCursorComposition(view);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
115
|
+
if (storedMarks) {
|
|
116
|
+
view.dispatch(view.state.tr.setMeta(_reactKeys.reactKeysPluginKey, {
|
|
117
|
+
cursorWrapper: (0, _ReactWidgetType.widget)(view.state.selection.from, _CursorWrapper.CursorWrapper, {
|
|
118
|
+
key: "cursor-wrapper",
|
|
119
|
+
marks: storedMarks,
|
|
120
|
+
side: 0,
|
|
121
|
+
raw: true
|
|
122
|
+
})
|
|
73
123
|
}));
|
|
124
|
+
// Pin the DOM cursor to PM's canonical position before the IME
|
|
125
|
+
// captures wherever the browser happened to leave it. Without this,
|
|
126
|
+
// a cursor at a mark boundary lands in either the left or right text
|
|
127
|
+
// node depending on the user's last navigation direction, and the
|
|
128
|
+
// IME composes into whichever one it found.
|
|
129
|
+
} else if (view.state.selection.empty) {
|
|
130
|
+
view.domObserver.disconnectSelection();
|
|
131
|
+
try {
|
|
132
|
+
view.docView.setSelection(view.state.selection.anchor, view.state.selection.head, view, true // force — skip the isEquivalentPosition early-return
|
|
133
|
+
);
|
|
134
|
+
} finally{
|
|
135
|
+
view.domObserver.setCurSelection();
|
|
136
|
+
view.domObserver.connectSelection();
|
|
137
|
+
}
|
|
74
138
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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;
|
|
82
150
|
}
|
|
83
|
-
|
|
151
|
+
preCompositionSnapshot = view.state.doc.nodeAt(freezeFrom)?.content ?? null;
|
|
84
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);
|
|
85
162
|
return true;
|
|
86
163
|
},
|
|
87
164
|
compositionupdate () {
|
|
88
165
|
return true;
|
|
89
166
|
},
|
|
90
167
|
compositionend (view, event) {
|
|
91
|
-
|
|
92
|
-
view.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
compositionMarks = null;
|
|
119
|
-
precompositionSnapshot = null;
|
|
120
|
-
setCursorWrapper(null);
|
|
168
|
+
if (!(view instanceof _ReactEditorView.ReactEditorView)) return false;
|
|
169
|
+
if (!view.composing) return false;
|
|
170
|
+
teardownComposition(view, event.timeStamp);
|
|
171
|
+
view.dispatch(view.state.tr.setMeta(_reactKeys.reactKeysPluginKey, {
|
|
172
|
+
cursorWrapper: null,
|
|
173
|
+
freezeFrom: null
|
|
174
|
+
}));
|
|
121
175
|
return true;
|
|
122
176
|
},
|
|
123
177
|
beforeinput (view, event) {
|
|
124
|
-
event.
|
|
178
|
+
if (event.inputType !== "insertFromComposition") {
|
|
179
|
+
event.preventDefault();
|
|
180
|
+
}
|
|
125
181
|
switch(event.inputType){
|
|
126
182
|
case "insertParagraph":
|
|
127
183
|
case "insertLineBreak":
|
|
@@ -199,3 +255,59 @@ function beforeInputPlugin(setCursorWrapper) {
|
|
|
199
255
|
}
|
|
200
256
|
});
|
|
201
257
|
}
|
|
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;
|
|
276
|
+
}
|
|
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 ?? ""));
|
|
313
|
+
}
|
|
@@ -20,20 +20,23 @@ _export(exports, {
|
|
|
20
20
|
}
|
|
21
21
|
});
|
|
22
22
|
const _prosemirrorstate = require("prosemirror-state");
|
|
23
|
+
const _prosemirrorview = require("prosemirror-view");
|
|
24
|
+
const _ReactWidgetType = require("../decorations/ReactWidgetType.js");
|
|
23
25
|
function createNodeKey() {
|
|
24
26
|
const key = Math.floor(Math.random() * 0xffffffffffff).toString(16);
|
|
25
27
|
return key;
|
|
26
28
|
}
|
|
27
29
|
const reactKeysPluginKey = new _prosemirrorstate.PluginKey("@handlewithcare/react-prosemirror/reactKeys");
|
|
28
30
|
function reactKeys() {
|
|
29
|
-
let composing = false;
|
|
30
31
|
return new _prosemirrorstate.Plugin({
|
|
31
32
|
key: reactKeysPluginKey,
|
|
32
33
|
state: {
|
|
33
34
|
init (_, state) {
|
|
34
35
|
const next = {
|
|
35
36
|
posToKey: new Map(),
|
|
36
|
-
keyToPos: new Map()
|
|
37
|
+
keyToPos: new Map(),
|
|
38
|
+
cursorWrapper: null,
|
|
39
|
+
freezeFrom: null
|
|
37
40
|
};
|
|
38
41
|
state.doc.descendants((_, pos)=>{
|
|
39
42
|
const key = createNodeKey();
|
|
@@ -50,15 +53,32 @@ function reactKeys() {
|
|
|
50
53
|
* through the transaction to identify its current position,
|
|
51
54
|
* and assign its key to that new position, dropping it if the
|
|
52
55
|
* node was deleted.
|
|
53
|
-
*/ apply (tr, value,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
56
|
+
*/ apply (tr, value, oldState, newState) {
|
|
57
|
+
const meta = tr.getMeta(reactKeysPluginKey);
|
|
58
|
+
const overrides = meta && "overrides" in meta ? meta.overrides : {};
|
|
59
|
+
const cursorWrapper = meta && "cursorWrapper" in meta ? meta.cursorWrapper : undefined;
|
|
60
|
+
const freezeFrom = meta && "freezeFrom" in meta ? meta.freezeFrom : undefined;
|
|
58
61
|
const next = {
|
|
59
62
|
posToKey: new Map(),
|
|
60
|
-
keyToPos: new Map()
|
|
63
|
+
keyToPos: new Map(),
|
|
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
|
|
61
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
|
+
}
|
|
75
|
+
if (!tr.docChanged) {
|
|
76
|
+
return {
|
|
77
|
+
...value,
|
|
78
|
+
cursorWrapper: next.cursorWrapper,
|
|
79
|
+
freezeFrom: next.freezeFrom
|
|
80
|
+
};
|
|
81
|
+
}
|
|
62
82
|
const posToKeyEntries = Array.from(value.posToKey.entries()).sort((param, param1)=>{
|
|
63
83
|
let [a] = param, [b] = param1;
|
|
64
84
|
return a - b;
|
|
@@ -84,13 +104,12 @@ function reactKeys() {
|
|
|
84
104
|
}
|
|
85
105
|
},
|
|
86
106
|
props: {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
107
|
+
decorations (state) {
|
|
108
|
+
const deco = reactKeysPluginKey.getState(state)?.cursorWrapper;
|
|
109
|
+
if (!deco) return _prosemirrorview.DecorationSet.empty;
|
|
110
|
+
return _prosemirrorview.DecorationSet.create(state.doc, [
|
|
111
|
+
deco
|
|
112
|
+
]);
|
|
94
113
|
}
|
|
95
114
|
}
|
|
96
115
|
});
|
|
@@ -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
|
@@ -36,6 +36,9 @@ _export(exports, {
|
|
|
36
36
|
WidgetViewDesc: function() {
|
|
37
37
|
return WidgetViewDesc;
|
|
38
38
|
},
|
|
39
|
+
findTextInFragment: function() {
|
|
40
|
+
return findTextInFragment;
|
|
41
|
+
},
|
|
39
42
|
sameOuterDeco: function() {
|
|
40
43
|
return sameOuterDeco;
|
|
41
44
|
},
|
|
@@ -49,7 +52,17 @@ const _dom = require("./dom.js");
|
|
|
49
52
|
function sortViewDescs(a, b) {
|
|
50
53
|
if (a instanceof TrailingHackViewDesc) return 1;
|
|
51
54
|
if (b instanceof TrailingHackViewDesc) return -1;
|
|
52
|
-
|
|
55
|
+
const posDiff = a.getPos() - b.getPos();
|
|
56
|
+
if (posDiff !== 0) return posDiff;
|
|
57
|
+
// When two descs share the same PM position (e.g. a zero-width widget
|
|
58
|
+
// and a text node that starts at the same position), fall back to DOM
|
|
59
|
+
// order so that the viewdesc children match the actual DOM layout.
|
|
60
|
+
// Without this, position computations like `posBeforeChild` can return
|
|
61
|
+
// the wrong PM position for the widget's container.
|
|
62
|
+
const cmp = a.dom.compareDocumentPosition(b.dom);
|
|
63
|
+
if (cmp & 4 /* DOCUMENT_POSITION_FOLLOWING */ ) return -1;
|
|
64
|
+
if (cmp & 2 /* DOCUMENT_POSITION_PRECEDING */ ) return 1;
|
|
65
|
+
return 0;
|
|
53
66
|
}
|
|
54
67
|
const NOT_DIRTY = 0, CHILD_DIRTY = 1, CONTENT_DIRTY = 2, NODE_DIRTY = 3;
|
|
55
68
|
let ViewDesc = class ViewDesc {
|
|
@@ -266,7 +279,9 @@ let ViewDesc = class ViewDesc {
|
|
|
266
279
|
prev = i ? this.children[i - 1] : null;
|
|
267
280
|
if (!prev || prev.dom.parentNode == this.contentDOM) break;
|
|
268
281
|
}
|
|
269
|
-
if (prev && side && enter && !prev.border && !prev.domAtom)
|
|
282
|
+
if (prev && side && enter && !prev.border && !prev.domAtom) {
|
|
283
|
+
return prev.domFromPos(prev.size, side);
|
|
284
|
+
}
|
|
270
285
|
return {
|
|
271
286
|
node: this.contentDOM,
|
|
272
287
|
offset: prev ? (0, _dom.domIndex)(prev.dom) + 1 : 0
|
|
@@ -401,7 +416,9 @@ let ViewDesc = class ViewDesc {
|
|
|
401
416
|
const after = selRange.focusNode.childNodes[selRange.focusOffset];
|
|
402
417
|
if (after && after.contentEditable == "false") force = true;
|
|
403
418
|
}
|
|
404
|
-
if (!(force || brKludge && _browser.browser.safari) && (0, _dom.isEquivalentPosition)(anchorDOM.node, anchorDOM.offset, selRange.anchorNode, selRange.anchorOffset) && (0, _dom.isEquivalentPosition)(headDOM.node, headDOM.offset, selRange.focusNode, selRange.focusOffset))
|
|
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
|
+
return;
|
|
421
|
+
}
|
|
405
422
|
// Selection.extend can be used to create an 'inverted' selection
|
|
406
423
|
// (one where the focus is before the anchor), but not all
|
|
407
424
|
// browsers support it yet.
|
|
@@ -666,7 +683,10 @@ let TextViewDesc = class TextViewDesc extends NodeViewDesc {
|
|
|
666
683
|
skip: skip || true
|
|
667
684
|
};
|
|
668
685
|
}
|
|
669
|
-
update(
|
|
686
|
+
update(node, outerDeco, _innerDeco, _view) {
|
|
687
|
+
if (this.dirty == NODE_DIRTY || this.dirty != NOT_DIRTY && !this.inParent() || !node.sameMarkup(this.node)) return false;
|
|
688
|
+
this.updateOuterDeco(outerDeco);
|
|
689
|
+
this.node = node;
|
|
670
690
|
this.dirty = NOT_DIRTY;
|
|
671
691
|
return true;
|
|
672
692
|
}
|
|
@@ -695,6 +715,9 @@ let TextViewDesc = class TextViewDesc extends NodeViewDesc {
|
|
|
695
715
|
isText(text) {
|
|
696
716
|
return this.node.text == text;
|
|
697
717
|
}
|
|
718
|
+
ignoreMutation(mutation) {
|
|
719
|
+
return mutation.type !== "characterData" && mutation.type !== "selection";
|
|
720
|
+
}
|
|
698
721
|
};
|
|
699
722
|
let TrailingHackViewDesc = class TrailingHackViewDesc extends ViewDesc {
|
|
700
723
|
parseRule() {
|
|
@@ -782,3 +805,31 @@ function sameOuterDeco(a, b) {
|
|
|
782
805
|
for(let i = 0; i < a.length; i++)if (!a[i].type.eq(b[i].type)) return false;
|
|
783
806
|
return true;
|
|
784
807
|
}
|
|
808
|
+
function findTextInFragment(frag, text, from, to) {
|
|
809
|
+
for(let i = 0, pos = 0; i < frag.childCount && pos <= to;){
|
|
810
|
+
const child = frag.child(i++);
|
|
811
|
+
const childStart = pos;
|
|
812
|
+
pos += child.nodeSize;
|
|
813
|
+
if (!child.isText) continue;
|
|
814
|
+
let str = child.text;
|
|
815
|
+
while(i < frag.childCount){
|
|
816
|
+
const next = frag.child(i++);
|
|
817
|
+
pos += next.nodeSize;
|
|
818
|
+
if (!next.isText) break;
|
|
819
|
+
str += next.text;
|
|
820
|
+
}
|
|
821
|
+
if (pos >= from) {
|
|
822
|
+
if (pos >= to && str.slice(to - text.length - childStart, to - childStart) === text) {
|
|
823
|
+
return to - text.length;
|
|
824
|
+
}
|
|
825
|
+
const found = childStart < to ? str.lastIndexOf(text, to - childStart - 1) : -1;
|
|
826
|
+
if (found >= 0 && found + text.length + childStart >= from) {
|
|
827
|
+
return childStart + found;
|
|
828
|
+
}
|
|
829
|
+
if (from === to && str.length >= to + text.length - childStart && str.slice(to - childStart, to - childStart + text.length) === text) {
|
|
830
|
+
return to;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
return -1;
|
|
835
|
+
}
|
|
@@ -33,6 +33,7 @@ function changedNodeViews(a, b) {
|
|
|
33
33
|
nextProps;
|
|
34
34
|
prevState;
|
|
35
35
|
_destroyed;
|
|
36
|
+
cursorWrapped;
|
|
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 @@ function changedNodeViews(a, b) {
|
|
|
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.cursorWrapped = false;
|
|
88
90
|
}
|
|
89
91
|
get props() {
|
|
90
92
|
return this.nextProps;
|
|
@@ -185,7 +187,22 @@ function changedNodeViews(a, b) {
|
|
|
185
187
|
// this ensures that the base class validates the DOM selection and invokes
|
|
186
188
|
// node view selection callbacks.
|
|
187
189
|
this.docView.markDirty(-1, -1);
|
|
188
|
-
|
|
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
|
+
}
|
|
189
206
|
// Store the new previous state.
|
|
190
207
|
this.prevState = this.state;
|
|
191
208
|
}
|
|
@@ -11,7 +11,7 @@ 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);
|
|
@@ -59,7 +59,7 @@ const ChildView = /*#__PURE__*/ memo(function ChildView(param) {
|
|
|
59
59
|
parentRef: parentRef,
|
|
60
60
|
decorations: child.outerDeco
|
|
61
61
|
});
|
|
62
|
-
}) : /*#__PURE__*/ React.createElement(
|
|
62
|
+
}) : /*#__PURE__*/ React.createElement(RemountableNodeView, {
|
|
63
63
|
key: child.key,
|
|
64
64
|
node: child.node,
|
|
65
65
|
getPos: getPos,
|
|
@@ -207,7 +207,7 @@ const ChildElement = /*#__PURE__*/ memo(function ChildElement(param) {
|
|
|
207
207
|
mark: mark,
|
|
208
208
|
getPos: getPos,
|
|
209
209
|
inline: false
|
|
210
|
-
}, element), /*#__PURE__*/ React.createElement(
|
|
210
|
+
}, element), /*#__PURE__*/ React.createElement(RemountableNodeView, {
|
|
211
211
|
key: child.key,
|
|
212
212
|
outerDeco: child.outerDeco,
|
|
213
213
|
node: child.node,
|
|
@@ -340,14 +340,14 @@ export const ChildNodeViews = /*#__PURE__*/ memo(function ChildNodeViews(param)
|
|
|
340
340
|
component: SeparatorHackView,
|
|
341
341
|
marks: [],
|
|
342
342
|
offset: lastChild?.offset ?? 0,
|
|
343
|
-
index: (lastChild?.index ?? 0) +
|
|
343
|
+
index: (lastChild?.index ?? 0) + 1,
|
|
344
344
|
key: "trailing-hack-img"
|
|
345
345
|
}, {
|
|
346
346
|
type: "hack",
|
|
347
347
|
component: TrailingHackView,
|
|
348
348
|
marks: [],
|
|
349
349
|
offset: lastChild?.offset ?? 0,
|
|
350
|
-
index: (lastChild?.index ?? 0) +
|
|
350
|
+
index: (lastChild?.index ?? 0) + 2,
|
|
351
351
|
key: "trailing-hack-br"
|
|
352
352
|
});
|
|
353
353
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { forwardRef, useImperativeHandle, useRef } from "react";
|
|
2
|
+
import { ReactEditorView } from "../ReactEditorView.js";
|
|
2
3
|
import { domIndex } from "../dom.js";
|
|
3
4
|
import { useEditorEffect } from "../hooks/useEditorEffect.js";
|
|
4
5
|
export const CursorWrapper = /*#__PURE__*/ forwardRef(function CursorWrapper(param, ref) {
|
|
@@ -8,21 +9,18 @@ export const CursorWrapper = /*#__PURE__*/ forwardRef(function CursorWrapper(par
|
|
|
8
9
|
return innerRef.current;
|
|
9
10
|
}, []);
|
|
10
11
|
useEditorEffect((view)=>{
|
|
11
|
-
if (!view || !innerRef.current) return;
|
|
12
|
-
// @ts-expect-error Internal property - domObserver
|
|
12
|
+
if (!(view instanceof ReactEditorView) || !innerRef.current) return;
|
|
13
13
|
view.domObserver.disconnectSelection();
|
|
14
|
-
// @ts-expect-error Internal property - domSelection
|
|
15
14
|
const domSel = view.domSelection();
|
|
15
|
+
if (!domSel.isCollapsed) return;
|
|
16
16
|
const node = innerRef.current;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
domSel.collapse(node.parentNode, domIndex(node) + 1);
|
|
21
|
-
} else {
|
|
22
|
-
domSel.collapse(node, 0);
|
|
23
|
-
}
|
|
24
|
-
// @ts-expect-error Internal property - domObserver
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
18
|
+
domSel.collapse(node.parentNode, domIndex(node) + 1);
|
|
19
|
+
view.cursorWrapped = true;
|
|
25
20
|
view.domObserver.connectSelection();
|
|
21
|
+
return ()=>{
|
|
22
|
+
view.cursorWrapped = false;
|
|
23
|
+
};
|
|
26
24
|
}, []);
|
|
27
25
|
return /*#__PURE__*/ React.createElement("img", {
|
|
28
26
|
ref: innerRef,
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import React, { useMemo, useState } from "react";
|
|
2
2
|
import { ChildDescriptionsContext } from "../contexts/ChildDescriptionsContext.js";
|
|
3
|
+
import { CompositionContext } from "../contexts/CompositionContext.js";
|
|
3
4
|
import { EditorContext } from "../contexts/EditorContext.js";
|
|
4
5
|
import { EditorStateContext } from "../contexts/EditorStateContext.js";
|
|
5
6
|
import { NodeViewContext } from "../contexts/NodeViewContext.js";
|
|
6
7
|
import { computeDocDeco } from "../decorations/computeDocDeco.js";
|
|
7
8
|
import { viewDecorations } from "../decorations/viewDecorations.js";
|
|
8
9
|
import { useEditor } from "../hooks/useEditor.js";
|
|
10
|
+
import { reactKeysPluginKey } from "../plugins/reactKeys.js";
|
|
9
11
|
import { LayoutGroup } from "./LayoutGroup.js";
|
|
10
12
|
import { DocNodeViewContext } from "./ProseMirrorDoc.js";
|
|
11
13
|
function getPos() {
|
|
@@ -22,7 +24,7 @@ const rootChildDescriptionsContextValue = {
|
|
|
22
24
|
function ProseMirrorInner(param) {
|
|
23
25
|
let { children, nodeViewComponents, markViewComponents, ...props } = param;
|
|
24
26
|
const [mount, setMount] = useState(null);
|
|
25
|
-
const { editor,
|
|
27
|
+
const { editor, state } = useEditor(mount, props);
|
|
26
28
|
const nodeViewConstructors = editor.view.nodeViews;
|
|
27
29
|
const nodeViewContextValue = useMemo(()=>{
|
|
28
30
|
return {
|
|
@@ -39,7 +41,7 @@ function ProseMirrorInner(param) {
|
|
|
39
41
|
]);
|
|
40
42
|
const node = state.doc;
|
|
41
43
|
const decorations = computeDocDeco(editor.view);
|
|
42
|
-
const innerDecorations = viewDecorations(editor.view
|
|
44
|
+
const innerDecorations = viewDecorations(editor.view);
|
|
43
45
|
const docNodeViewContextValue = useMemo(()=>({
|
|
44
46
|
setMount,
|
|
45
47
|
node,
|
|
@@ -51,6 +53,12 @@ function ProseMirrorInner(param) {
|
|
|
51
53
|
decorations,
|
|
52
54
|
innerDecorations
|
|
53
55
|
]);
|
|
56
|
+
const freezeFrom = reactKeysPluginKey.getState(state)?.freezeFrom ?? null;
|
|
57
|
+
const compositionContextValue = useMemo(()=>({
|
|
58
|
+
freezeFrom
|
|
59
|
+
}), [
|
|
60
|
+
freezeFrom
|
|
61
|
+
]);
|
|
54
62
|
return /*#__PURE__*/ React.createElement(EditorContext.Provider, {
|
|
55
63
|
value: editor
|
|
56
64
|
}, /*#__PURE__*/ React.createElement(EditorStateContext.Provider, {
|
|
@@ -59,9 +67,11 @@ function ProseMirrorInner(param) {
|
|
|
59
67
|
value: nodeViewContextValue
|
|
60
68
|
}, /*#__PURE__*/ React.createElement(ChildDescriptionsContext.Provider, {
|
|
61
69
|
value: rootChildDescriptionsContextValue
|
|
70
|
+
}, /*#__PURE__*/ React.createElement(CompositionContext.Provider, {
|
|
71
|
+
value: compositionContextValue
|
|
62
72
|
}, /*#__PURE__*/ React.createElement(DocNodeViewContext.Provider, {
|
|
63
73
|
value: docNodeViewContextValue
|
|
64
|
-
}, children)))));
|
|
74
|
+
}, children))))));
|
|
65
75
|
}
|
|
66
76
|
export function ProseMirror(props) {
|
|
67
77
|
return /*#__PURE__*/ React.createElement(LayoutGroup, null, /*#__PURE__*/ React.createElement(ProseMirrorInner, props));
|