prosemirror-view 1.39.3 → 1.40.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/CHANGELOG.md +12 -0
- package/dist/index.cjs +59 -35
- package/dist/index.d.cts +16 -3
- package/dist/index.d.ts +16 -3
- package/dist/index.js +59 -51
- package/package.json +1 -1
- package/src/decoration.ts +13 -0
- package/src/dom.ts +8 -3
- package/src/domchange.ts +33 -26
- package/src/index.ts +10 -7
- package/src/input.ts +3 -2
- package/src/viewdesc.ts +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## 1.40.0 (2025-06-02)
|
|
2
|
+
|
|
3
|
+
### Bug fixes
|
|
4
|
+
|
|
5
|
+
Suppress lint warnings about dereferencing methods by making `EditorView.dispatch` a property instead of a method.
|
|
6
|
+
|
|
7
|
+
### New features
|
|
8
|
+
|
|
9
|
+
The `"handleTextInput"` prop now takes an additional argument that can be used to generate the default transaction for that text change.
|
|
10
|
+
|
|
11
|
+
Widget decorations now support a `relaxedSide` option which can be used to allow the DOM cursor to appear at either side.
|
|
12
|
+
|
|
1
13
|
## 1.39.3 (2025-05-14)
|
|
2
14
|
|
|
3
15
|
### Bug fixes
|
package/dist/index.cjs
CHANGED
|
@@ -50,6 +50,7 @@ var isEquivalentPosition = function isEquivalentPosition(node, off, targetNode,
|
|
|
50
50
|
};
|
|
51
51
|
var atomElements = /^(img|br|input|textarea|hr)$/i;
|
|
52
52
|
function scanFor(node, off, targetNode, targetOff, dir) {
|
|
53
|
+
var _a;
|
|
53
54
|
for (;;) {
|
|
54
55
|
if (node == targetNode && off == targetOff) return true;
|
|
55
56
|
if (off == (dir < 0 ? 0 : nodeSize(node))) {
|
|
@@ -58,9 +59,13 @@ function scanFor(node, off, targetNode, targetOff, dir) {
|
|
|
58
59
|
off = domIndex(node) + (dir < 0 ? 0 : 1);
|
|
59
60
|
node = parent;
|
|
60
61
|
} else if (node.nodeType == 1) {
|
|
61
|
-
|
|
62
|
-
if (
|
|
63
|
-
|
|
62
|
+
var child = node.childNodes[off + (dir < 0 ? -1 : 0)];
|
|
63
|
+
if (child.nodeType == 1 && child.contentEditable == "false") {
|
|
64
|
+
if ((_a = child.pmViewDesc) === null || _a === void 0 ? void 0 : _a.ignoreForSelection) off += dir;else return false;
|
|
65
|
+
} else {
|
|
66
|
+
node = child;
|
|
67
|
+
off = dir < 0 ? nodeSize(node) : 0;
|
|
68
|
+
}
|
|
64
69
|
} else {
|
|
65
70
|
return false;
|
|
66
71
|
}
|
|
@@ -1063,6 +1068,11 @@ var ViewDesc = function () {
|
|
|
1063
1068
|
get: function get() {
|
|
1064
1069
|
return false;
|
|
1065
1070
|
}
|
|
1071
|
+
}, {
|
|
1072
|
+
key: "ignoreForSelection",
|
|
1073
|
+
get: function get() {
|
|
1074
|
+
return false;
|
|
1075
|
+
}
|
|
1066
1076
|
}, {
|
|
1067
1077
|
key: "isText",
|
|
1068
1078
|
value: function isText(text) {
|
|
@@ -1132,6 +1142,11 @@ var WidgetViewDesc = function (_ViewDesc) {
|
|
|
1132
1142
|
get: function get() {
|
|
1133
1143
|
return true;
|
|
1134
1144
|
}
|
|
1145
|
+
}, {
|
|
1146
|
+
key: "ignoreForSelection",
|
|
1147
|
+
get: function get() {
|
|
1148
|
+
return !!this.widget.type.spec.relaxedSide;
|
|
1149
|
+
}
|
|
1135
1150
|
}, {
|
|
1136
1151
|
key: "side",
|
|
1137
1152
|
get: function get() {
|
|
@@ -2991,9 +3006,12 @@ editHandlers.keypress = function (view, _event) {
|
|
|
2991
3006
|
var sel = view.state.selection;
|
|
2992
3007
|
if (!(sel instanceof prosemirrorState.TextSelection) || !sel.$from.sameParent(sel.$to)) {
|
|
2993
3008
|
var text = String.fromCharCode(event.charCode);
|
|
3009
|
+
var deflt = function deflt() {
|
|
3010
|
+
return view.state.tr.insertText(text).scrollIntoView();
|
|
3011
|
+
};
|
|
2994
3012
|
if (!/[\r\n]/.test(text) && !view.someProp("handleTextInput", function (f) {
|
|
2995
|
-
return f(view, sel.$from.pos, sel.$to.pos, text);
|
|
2996
|
-
})) view.dispatch(
|
|
3013
|
+
return f(view, sel.$from.pos, sel.$to.pos, text, deflt);
|
|
3014
|
+
})) view.dispatch(deflt());
|
|
2997
3015
|
event.preventDefault();
|
|
2998
3016
|
}
|
|
2999
3017
|
};
|
|
@@ -4598,10 +4616,10 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
|
4598
4616
|
if (chrome && android && view.input.lastKeyCode === 13 && Date.now() - 100 < view.input.lastKeyCodeTime && view.someProp("handleKeyDown", function (f) {
|
|
4599
4617
|
return f(view, keyEvent(13, "Enter"));
|
|
4600
4618
|
})) return;
|
|
4601
|
-
var
|
|
4602
|
-
if (origin == "pointer")
|
|
4603
|
-
if (compositionID)
|
|
4604
|
-
view.dispatch(
|
|
4619
|
+
var tr = view.state.tr.setSelection(newSel);
|
|
4620
|
+
if (origin == "pointer") tr.setMeta("pointer", true);else if (origin == "key") tr.scrollIntoView();
|
|
4621
|
+
if (compositionID) tr.setMeta("composition", compositionID);
|
|
4622
|
+
view.dispatch(tr);
|
|
4605
4623
|
}
|
|
4606
4624
|
return;
|
|
4607
4625
|
}
|
|
@@ -4643,9 +4661,9 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
|
4643
4661
|
if (parse.sel) {
|
|
4644
4662
|
var _sel2 = resolveSelection(view, view.state.doc, parse.sel);
|
|
4645
4663
|
if (_sel2 && !_sel2.eq(view.state.selection)) {
|
|
4646
|
-
var
|
|
4647
|
-
if (compositionID)
|
|
4648
|
-
view.dispatch(
|
|
4664
|
+
var _tr = view.state.tr.setSelection(_sel2);
|
|
4665
|
+
if (compositionID) _tr.setMeta("composition", compositionID);
|
|
4666
|
+
view.dispatch(_tr);
|
|
4649
4667
|
}
|
|
4650
4668
|
}
|
|
4651
4669
|
return;
|
|
@@ -4695,7 +4713,16 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
|
4695
4713
|
}
|
|
4696
4714
|
var chFrom = change.start,
|
|
4697
4715
|
chTo = change.endA;
|
|
4698
|
-
var
|
|
4716
|
+
var mkTr = function mkTr(base) {
|
|
4717
|
+
var tr = base || view.state.tr.replace(chFrom, chTo, parse.doc.slice(change.start - parse.from, change.endB - parse.from));
|
|
4718
|
+
if (parse.sel) {
|
|
4719
|
+
var _sel3 = resolveSelection(view, tr.doc, parse.sel);
|
|
4720
|
+
if (_sel3 && !(chrome && view.composing && _sel3.empty && (change.start != change.endB || view.input.lastChromeDelete < Date.now() - 100) && (_sel3.head == chFrom || _sel3.head == tr.mapping.map(chTo) - 1) || ie && _sel3.empty && _sel3.head == chFrom)) tr.setSelection(_sel3);
|
|
4721
|
+
}
|
|
4722
|
+
if (compositionID) tr.setMeta("composition", compositionID);
|
|
4723
|
+
return tr.scrollIntoView();
|
|
4724
|
+
};
|
|
4725
|
+
var markChange;
|
|
4699
4726
|
if (inlineChange) {
|
|
4700
4727
|
if ($from.pos == $to.pos) {
|
|
4701
4728
|
if (ie && ie_version <= 11 && $from.parentOffset == 0) {
|
|
@@ -4704,27 +4731,26 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
|
4704
4731
|
return selectionToDOM(view);
|
|
4705
4732
|
}, 20);
|
|
4706
4733
|
}
|
|
4707
|
-
|
|
4708
|
-
|
|
4734
|
+
var _tr2 = mkTr(view.state.tr["delete"](chFrom, chTo));
|
|
4735
|
+
var marks = doc.resolve(change.start).marksAcross(doc.resolve(change.endA));
|
|
4736
|
+
if (marks) _tr2.ensureMarks(marks);
|
|
4737
|
+
view.dispatch(_tr2);
|
|
4709
4738
|
} else if (change.endA == change.endB && (markChange = isMarkChange($from.parent.content.cut($from.parentOffset, $to.parentOffset), $fromA.parent.content.cut($fromA.parentOffset, change.endA - $fromA.start())))) {
|
|
4710
|
-
|
|
4711
|
-
if (markChange.type == "add")
|
|
4739
|
+
var _tr3 = mkTr(view.state.tr);
|
|
4740
|
+
if (markChange.type == "add") _tr3.addMark(chFrom, chTo, markChange.mark);else _tr3.removeMark(chFrom, chTo, markChange.mark);
|
|
4741
|
+
view.dispatch(_tr3);
|
|
4712
4742
|
} else if ($from.parent.child($from.index()).isText && $from.index() == $to.index() - ($to.textOffset ? 0 : 1)) {
|
|
4713
4743
|
var text = $from.parent.textBetween($from.parentOffset, $to.parentOffset);
|
|
4714
|
-
|
|
4715
|
-
return
|
|
4716
|
-
}
|
|
4717
|
-
|
|
4744
|
+
var deflt = function deflt() {
|
|
4745
|
+
return mkTr(view.state.tr.insertText(text, chFrom, chTo));
|
|
4746
|
+
};
|
|
4747
|
+
if (!view.someProp("handleTextInput", function (f) {
|
|
4748
|
+
return f(view, chFrom, chTo, text, deflt);
|
|
4749
|
+
})) view.dispatch(deflt());
|
|
4718
4750
|
}
|
|
4751
|
+
} else {
|
|
4752
|
+
view.dispatch(mkTr());
|
|
4719
4753
|
}
|
|
4720
|
-
if (!tr) tr = view.state.tr.replace(chFrom, chTo, parse.doc.slice(change.start - parse.from, change.endB - parse.from));
|
|
4721
|
-
if (parse.sel) {
|
|
4722
|
-
var _sel3 = resolveSelection(view, tr.doc, parse.sel);
|
|
4723
|
-
if (_sel3 && !(chrome && view.composing && _sel3.empty && (change.start != change.endB || view.input.lastChromeDelete < Date.now() - 100) && (_sel3.head == chFrom || _sel3.head == tr.mapping.map(chTo) - 1) || ie && _sel3.empty && _sel3.head == chFrom)) tr.setSelection(_sel3);
|
|
4724
|
-
}
|
|
4725
|
-
if (storedMarks) tr.ensureMarks(storedMarks);
|
|
4726
|
-
if (compositionID) tr.setMeta("composition", compositionID);
|
|
4727
|
-
view.dispatch(tr.scrollIntoView());
|
|
4728
4754
|
}
|
|
4729
4755
|
function resolveSelection(view, doc, parsedSel) {
|
|
4730
4756
|
if (Math.max(parsedSel.anchor, parsedSel.head) > doc.content.size) return null;
|
|
@@ -5168,12 +5194,6 @@ var EditorView = function () {
|
|
|
5168
5194
|
value: function dispatchEvent(event) {
|
|
5169
5195
|
return _dispatchEvent(this, event);
|
|
5170
5196
|
}
|
|
5171
|
-
}, {
|
|
5172
|
-
key: "dispatch",
|
|
5173
|
-
value: function dispatch(tr) {
|
|
5174
|
-
var dispatchTransaction = this._props.dispatchTransaction;
|
|
5175
|
-
if (dispatchTransaction) dispatchTransaction.call(this, tr);else this.updateState(this.state.apply(tr));
|
|
5176
|
-
}
|
|
5177
5197
|
}, {
|
|
5178
5198
|
key: "domSelectionRange",
|
|
5179
5199
|
value: function domSelectionRange() {
|
|
@@ -5194,6 +5214,10 @@ var EditorView = function () {
|
|
|
5194
5214
|
}]);
|
|
5195
5215
|
return EditorView;
|
|
5196
5216
|
}();
|
|
5217
|
+
EditorView.prototype.dispatch = function (tr) {
|
|
5218
|
+
var dispatchTransaction = this._props.dispatchTransaction;
|
|
5219
|
+
if (dispatchTransaction) dispatchTransaction.call(this, tr);else this.updateState(this.state.apply(tr));
|
|
5220
|
+
};
|
|
5197
5221
|
function computeDocDeco(view) {
|
|
5198
5222
|
var attrs = Object.create(null);
|
|
5199
5223
|
attrs["class"] = "ProseMirror";
|
package/dist/index.d.cts
CHANGED
|
@@ -48,6 +48,20 @@ declare class Decoration {
|
|
|
48
48
|
*/
|
|
49
49
|
side?: number;
|
|
50
50
|
/**
|
|
51
|
+
By default, the cursor, when at the position of the widget,
|
|
52
|
+
will be strictly kept on the side indicated by
|
|
53
|
+
[`side`](https://prosemirror.net/docs/ref/#view.Decoration^widget^spec.side). Set this to true
|
|
54
|
+
to allow the DOM selection to stay on the other side if the
|
|
55
|
+
client sets it there.
|
|
56
|
+
|
|
57
|
+
**Note**: Mapping of this decoration, which decides on which
|
|
58
|
+
side insertions at its position appear, will still happen
|
|
59
|
+
according to `side`, and keyboard cursor motion will not,
|
|
60
|
+
without further custom handling, visit both sides of the
|
|
61
|
+
widget.
|
|
62
|
+
*/
|
|
63
|
+
relaxedSide?: boolean;
|
|
64
|
+
/**
|
|
51
65
|
The precise set of marks to draw around the widget.
|
|
52
66
|
*/
|
|
53
67
|
marks?: readonly Mark[];
|
|
@@ -367,7 +381,6 @@ editable document. Its state and behavior are determined by its
|
|
|
367
381
|
[props](https://prosemirror.net/docs/ref/#view.DirectEditorProps).
|
|
368
382
|
*/
|
|
369
383
|
declare class EditorView {
|
|
370
|
-
private _props;
|
|
371
384
|
private directPlugins;
|
|
372
385
|
private _root;
|
|
373
386
|
private mounted;
|
|
@@ -587,7 +600,7 @@ declare class EditorView {
|
|
|
587
600
|
This method is bound to the view instance, so that it can be
|
|
588
601
|
easily passed around.
|
|
589
602
|
*/
|
|
590
|
-
dispatch(tr: Transaction)
|
|
603
|
+
dispatch: (tr: Transaction) => void;
|
|
591
604
|
}
|
|
592
605
|
/**
|
|
593
606
|
The type of function [provided](https://prosemirror.net/docs/ref/#view.EditorProps.nodeViews) to
|
|
@@ -652,7 +665,7 @@ interface EditorProps<P = any> {
|
|
|
652
665
|
before the input is applied. If it returns `true`, the default
|
|
653
666
|
behavior of actually inserting the text is suppressed.
|
|
654
667
|
*/
|
|
655
|
-
handleTextInput?: (this: P, view: EditorView, from: number, to: number, text: string) => boolean | void;
|
|
668
|
+
handleTextInput?: (this: P, view: EditorView, from: number, to: number, text: string, deflt: () => Transaction) => boolean | void;
|
|
656
669
|
/**
|
|
657
670
|
Called for each node around a click, from the inside out. The
|
|
658
671
|
`direct` flag will be true for the inner node.
|
package/dist/index.d.ts
CHANGED
|
@@ -48,6 +48,20 @@ declare class Decoration {
|
|
|
48
48
|
*/
|
|
49
49
|
side?: number;
|
|
50
50
|
/**
|
|
51
|
+
By default, the cursor, when at the position of the widget,
|
|
52
|
+
will be strictly kept on the side indicated by
|
|
53
|
+
[`side`](https://prosemirror.net/docs/ref/#view.Decoration^widget^spec.side). Set this to true
|
|
54
|
+
to allow the DOM selection to stay on the other side if the
|
|
55
|
+
client sets it there.
|
|
56
|
+
|
|
57
|
+
**Note**: Mapping of this decoration, which decides on which
|
|
58
|
+
side insertions at its position appear, will still happen
|
|
59
|
+
according to `side`, and keyboard cursor motion will not,
|
|
60
|
+
without further custom handling, visit both sides of the
|
|
61
|
+
widget.
|
|
62
|
+
*/
|
|
63
|
+
relaxedSide?: boolean;
|
|
64
|
+
/**
|
|
51
65
|
The precise set of marks to draw around the widget.
|
|
52
66
|
*/
|
|
53
67
|
marks?: readonly Mark[];
|
|
@@ -367,7 +381,6 @@ editable document. Its state and behavior are determined by its
|
|
|
367
381
|
[props](https://prosemirror.net/docs/ref/#view.DirectEditorProps).
|
|
368
382
|
*/
|
|
369
383
|
declare class EditorView {
|
|
370
|
-
private _props;
|
|
371
384
|
private directPlugins;
|
|
372
385
|
private _root;
|
|
373
386
|
private mounted;
|
|
@@ -587,7 +600,7 @@ declare class EditorView {
|
|
|
587
600
|
This method is bound to the view instance, so that it can be
|
|
588
601
|
easily passed around.
|
|
589
602
|
*/
|
|
590
|
-
dispatch(tr: Transaction)
|
|
603
|
+
dispatch: (tr: Transaction) => void;
|
|
591
604
|
}
|
|
592
605
|
/**
|
|
593
606
|
The type of function [provided](https://prosemirror.net/docs/ref/#view.EditorProps.nodeViews) to
|
|
@@ -652,7 +665,7 @@ interface EditorProps<P = any> {
|
|
|
652
665
|
before the input is applied. If it returns `true`, the default
|
|
653
666
|
behavior of actually inserting the text is suppressed.
|
|
654
667
|
*/
|
|
655
|
-
handleTextInput?: (this: P, view: EditorView, from: number, to: number, text: string) => boolean | void;
|
|
668
|
+
handleTextInput?: (this: P, view: EditorView, from: number, to: number, text: string, deflt: () => Transaction) => boolean | void;
|
|
656
669
|
/**
|
|
657
670
|
Called for each node around a click, from the inside out. The
|
|
658
671
|
`direct` flag will be true for the inner node.
|
package/dist/index.js
CHANGED
|
@@ -35,6 +35,7 @@ const isEquivalentPosition = function (node, off, targetNode, targetOff) {
|
|
|
35
35
|
};
|
|
36
36
|
const atomElements = /^(img|br|input|textarea|hr)$/i;
|
|
37
37
|
function scanFor(node, off, targetNode, targetOff, dir) {
|
|
38
|
+
var _a;
|
|
38
39
|
for (;;) {
|
|
39
40
|
if (node == targetNode && off == targetOff)
|
|
40
41
|
return true;
|
|
@@ -47,10 +48,17 @@ function scanFor(node, off, targetNode, targetOff, dir) {
|
|
|
47
48
|
node = parent;
|
|
48
49
|
}
|
|
49
50
|
else if (node.nodeType == 1) {
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
let child = node.childNodes[off + (dir < 0 ? -1 : 0)];
|
|
52
|
+
if (child.nodeType == 1 && child.contentEditable == "false") {
|
|
53
|
+
if ((_a = child.pmViewDesc) === null || _a === void 0 ? void 0 : _a.ignoreForSelection)
|
|
54
|
+
off += dir;
|
|
55
|
+
else
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
node = child;
|
|
60
|
+
off = dir < 0 ? nodeSize(node) : 0;
|
|
61
|
+
}
|
|
54
62
|
}
|
|
55
63
|
else {
|
|
56
64
|
return false;
|
|
@@ -1134,6 +1142,7 @@ class ViewDesc {
|
|
|
1134
1142
|
}
|
|
1135
1143
|
get domAtom() { return false; }
|
|
1136
1144
|
get ignoreForCoords() { return false; }
|
|
1145
|
+
get ignoreForSelection() { return false; }
|
|
1137
1146
|
isText(text) { return false; }
|
|
1138
1147
|
}
|
|
1139
1148
|
// A widget desc represents a widget decoration, which is a DOM node
|
|
@@ -1178,6 +1187,7 @@ class WidgetViewDesc extends ViewDesc {
|
|
|
1178
1187
|
super.destroy();
|
|
1179
1188
|
}
|
|
1180
1189
|
get domAtom() { return true; }
|
|
1190
|
+
get ignoreForSelection() { return !!this.widget.type.spec.relaxedSide; }
|
|
1181
1191
|
get side() { return this.widget.type.side; }
|
|
1182
1192
|
}
|
|
1183
1193
|
class CompositionViewDesc extends ViewDesc {
|
|
@@ -3175,8 +3185,9 @@ editHandlers.keypress = (view, _event) => {
|
|
|
3175
3185
|
let sel = view.state.selection;
|
|
3176
3186
|
if (!(sel instanceof TextSelection) || !sel.$from.sameParent(sel.$to)) {
|
|
3177
3187
|
let text = String.fromCharCode(event.charCode);
|
|
3178
|
-
|
|
3179
|
-
|
|
3188
|
+
let deflt = () => view.state.tr.insertText(text).scrollIntoView();
|
|
3189
|
+
if (!/[\r\n]/.test(text) && !view.someProp("handleTextInput", f => f(view, sel.$from.pos, sel.$to.pos, text, deflt)))
|
|
3190
|
+
view.dispatch(deflt());
|
|
3180
3191
|
event.preventDefault();
|
|
3181
3192
|
}
|
|
3182
3193
|
};
|
|
@@ -5061,7 +5072,26 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
|
5061
5072
|
}, 20);
|
|
5062
5073
|
}
|
|
5063
5074
|
let chFrom = change.start, chTo = change.endA;
|
|
5064
|
-
let
|
|
5075
|
+
let mkTr = (base) => {
|
|
5076
|
+
let tr = base || view.state.tr.replace(chFrom, chTo, parse.doc.slice(change.start - parse.from, change.endB - parse.from));
|
|
5077
|
+
if (parse.sel) {
|
|
5078
|
+
let sel = resolveSelection(view, tr.doc, parse.sel);
|
|
5079
|
+
// Chrome will sometimes, during composition, report the
|
|
5080
|
+
// selection in the wrong place. If it looks like that is
|
|
5081
|
+
// happening, don't update the selection.
|
|
5082
|
+
// Edge just doesn't move the cursor forward when you start typing
|
|
5083
|
+
// in an empty block or between br nodes.
|
|
5084
|
+
if (sel && !(chrome && view.composing && sel.empty &&
|
|
5085
|
+
(change.start != change.endB || view.input.lastChromeDelete < Date.now() - 100) &&
|
|
5086
|
+
(sel.head == chFrom || sel.head == tr.mapping.map(chTo) - 1) ||
|
|
5087
|
+
ie && sel.empty && sel.head == chFrom))
|
|
5088
|
+
tr.setSelection(sel);
|
|
5089
|
+
}
|
|
5090
|
+
if (compositionID)
|
|
5091
|
+
tr.setMeta("composition", compositionID);
|
|
5092
|
+
return tr.scrollIntoView();
|
|
5093
|
+
};
|
|
5094
|
+
let markChange;
|
|
5065
5095
|
if (inlineChange) {
|
|
5066
5096
|
if ($from.pos == $to.pos) { // Deletion
|
|
5067
5097
|
// IE11 sometimes weirdly moves the DOM selection around after
|
|
@@ -5070,46 +5100,33 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
|
5070
5100
|
view.domObserver.suppressSelectionUpdates();
|
|
5071
5101
|
setTimeout(() => selectionToDOM(view), 20);
|
|
5072
5102
|
}
|
|
5073
|
-
tr = view.state.tr.delete(chFrom, chTo);
|
|
5074
|
-
|
|
5103
|
+
let tr = mkTr(view.state.tr.delete(chFrom, chTo));
|
|
5104
|
+
let marks = doc.resolve(change.start).marksAcross(doc.resolve(change.endA));
|
|
5105
|
+
if (marks)
|
|
5106
|
+
tr.ensureMarks(marks);
|
|
5107
|
+
view.dispatch(tr);
|
|
5075
5108
|
}
|
|
5076
5109
|
else if ( // Adding or removing a mark
|
|
5077
5110
|
change.endA == change.endB &&
|
|
5078
5111
|
(markChange = isMarkChange($from.parent.content.cut($from.parentOffset, $to.parentOffset), $fromA.parent.content.cut($fromA.parentOffset, change.endA - $fromA.start())))) {
|
|
5079
|
-
tr = view.state.tr;
|
|
5112
|
+
let tr = mkTr(view.state.tr);
|
|
5080
5113
|
if (markChange.type == "add")
|
|
5081
5114
|
tr.addMark(chFrom, chTo, markChange.mark);
|
|
5082
5115
|
else
|
|
5083
5116
|
tr.removeMark(chFrom, chTo, markChange.mark);
|
|
5117
|
+
view.dispatch(tr);
|
|
5084
5118
|
}
|
|
5085
5119
|
else if ($from.parent.child($from.index()).isText && $from.index() == $to.index() - ($to.textOffset ? 0 : 1)) {
|
|
5086
5120
|
// Both positions in the same text node -- simply insert text
|
|
5087
5121
|
let text = $from.parent.textBetween($from.parentOffset, $to.parentOffset);
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
}
|
|
5092
|
-
}
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
let sel = resolveSelection(view, tr.doc, parse.sel);
|
|
5097
|
-
// Chrome will sometimes, during composition, report the
|
|
5098
|
-
// selection in the wrong place. If it looks like that is
|
|
5099
|
-
// happening, don't update the selection.
|
|
5100
|
-
// Edge just doesn't move the cursor forward when you start typing
|
|
5101
|
-
// in an empty block or between br nodes.
|
|
5102
|
-
if (sel && !(chrome && view.composing && sel.empty &&
|
|
5103
|
-
(change.start != change.endB || view.input.lastChromeDelete < Date.now() - 100) &&
|
|
5104
|
-
(sel.head == chFrom || sel.head == tr.mapping.map(chTo) - 1) ||
|
|
5105
|
-
ie && sel.empty && sel.head == chFrom))
|
|
5106
|
-
tr.setSelection(sel);
|
|
5107
|
-
}
|
|
5108
|
-
if (storedMarks)
|
|
5109
|
-
tr.ensureMarks(storedMarks);
|
|
5110
|
-
if (compositionID)
|
|
5111
|
-
tr.setMeta("composition", compositionID);
|
|
5112
|
-
view.dispatch(tr.scrollIntoView());
|
|
5122
|
+
let deflt = () => mkTr(view.state.tr.insertText(text, chFrom, chTo));
|
|
5123
|
+
if (!view.someProp("handleTextInput", f => f(view, chFrom, chTo, text, deflt)))
|
|
5124
|
+
view.dispatch(deflt());
|
|
5125
|
+
}
|
|
5126
|
+
}
|
|
5127
|
+
else {
|
|
5128
|
+
view.dispatch(mkTr());
|
|
5129
|
+
}
|
|
5113
5130
|
}
|
|
5114
5131
|
function resolveSelection(view, doc, parsedSel) {
|
|
5115
5132
|
if (Math.max(parsedSel.anchor, parsedSel.head) > doc.content.size)
|
|
@@ -5712,22 +5729,6 @@ class EditorView {
|
|
|
5712
5729
|
return dispatchEvent(this, event);
|
|
5713
5730
|
}
|
|
5714
5731
|
/**
|
|
5715
|
-
Dispatch a transaction. Will call
|
|
5716
|
-
[`dispatchTransaction`](https://prosemirror.net/docs/ref/#view.DirectEditorProps.dispatchTransaction)
|
|
5717
|
-
when given, and otherwise defaults to applying the transaction to
|
|
5718
|
-
the current state and calling
|
|
5719
|
-
[`updateState`](https://prosemirror.net/docs/ref/#view.EditorView.updateState) with the result.
|
|
5720
|
-
This method is bound to the view instance, so that it can be
|
|
5721
|
-
easily passed around.
|
|
5722
|
-
*/
|
|
5723
|
-
dispatch(tr) {
|
|
5724
|
-
let dispatchTransaction = this._props.dispatchTransaction;
|
|
5725
|
-
if (dispatchTransaction)
|
|
5726
|
-
dispatchTransaction.call(this, tr);
|
|
5727
|
-
else
|
|
5728
|
-
this.updateState(this.state.apply(tr));
|
|
5729
|
-
}
|
|
5730
|
-
/**
|
|
5731
5732
|
@internal
|
|
5732
5733
|
*/
|
|
5733
5734
|
domSelectionRange() {
|
|
@@ -5744,6 +5745,13 @@ class EditorView {
|
|
|
5744
5745
|
return this.root.getSelection();
|
|
5745
5746
|
}
|
|
5746
5747
|
}
|
|
5748
|
+
EditorView.prototype.dispatch = function (tr) {
|
|
5749
|
+
let dispatchTransaction = this._props.dispatchTransaction;
|
|
5750
|
+
if (dispatchTransaction)
|
|
5751
|
+
dispatchTransaction.call(this, tr);
|
|
5752
|
+
else
|
|
5753
|
+
this.updateState(this.state.apply(tr));
|
|
5754
|
+
};
|
|
5747
5755
|
function computeDocDeco(view) {
|
|
5748
5756
|
let attrs = Object.create(null);
|
|
5749
5757
|
attrs.class = "ProseMirror";
|
package/package.json
CHANGED
package/src/decoration.ts
CHANGED
|
@@ -156,6 +156,19 @@ export class Decoration {
|
|
|
156
156
|
/// negative, those of the node after when positive.
|
|
157
157
|
side?: number
|
|
158
158
|
|
|
159
|
+
/// By default, the cursor, when at the position of the widget,
|
|
160
|
+
/// will be strictly kept on the side indicated by
|
|
161
|
+
/// [`side`](#view.Decoration^widget^spec.side). Set this to true
|
|
162
|
+
/// to allow the DOM selection to stay on the other side if the
|
|
163
|
+
/// client sets it there.
|
|
164
|
+
///
|
|
165
|
+
/// **Note**: Mapping of this decoration, which decides on which
|
|
166
|
+
/// side insertions at its position appear, will still happen
|
|
167
|
+
/// according to `side`, and keyboard cursor motion will not,
|
|
168
|
+
/// without further custom handling, visit both sides of the
|
|
169
|
+
/// widget.
|
|
170
|
+
relaxedSide?: boolean
|
|
171
|
+
|
|
159
172
|
/// The precise set of marks to draw around the widget.
|
|
160
173
|
marks?: readonly Mark[]
|
|
161
174
|
|
package/src/dom.ts
CHANGED
|
@@ -54,9 +54,14 @@ function scanFor(node: Node, off: number, targetNode: Node, targetOff: number, d
|
|
|
54
54
|
off = domIndex(node) + (dir < 0 ? 0 : 1)
|
|
55
55
|
node = parent
|
|
56
56
|
} else if (node.nodeType == 1) {
|
|
57
|
-
|
|
58
|
-
if ((
|
|
59
|
-
|
|
57
|
+
let child = node.childNodes[off + (dir < 0 ? -1 : 0)]
|
|
58
|
+
if (child.nodeType == 1 && (child as HTMLElement).contentEditable == "false") {
|
|
59
|
+
if (child.pmViewDesc?.ignoreForSelection) off += dir
|
|
60
|
+
else return false
|
|
61
|
+
} else {
|
|
62
|
+
node = child
|
|
63
|
+
off = dir < 0 ? nodeSize(node) : 0
|
|
64
|
+
}
|
|
60
65
|
} else {
|
|
61
66
|
return false
|
|
62
67
|
}
|
package/src/domchange.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {Fragment, DOMParser, TagParseRule, Node, Mark, ResolvedPos} from "prosemirror-model"
|
|
2
|
-
import {Selection, TextSelection} from "prosemirror-state"
|
|
2
|
+
import {Selection, TextSelection, Transaction} from "prosemirror-state"
|
|
3
3
|
|
|
4
4
|
import {selectionBetween, selectionFromDOM, selectionToDOM} from "./selection"
|
|
5
5
|
import {selectionCollapsed, keyEvent, DOMNode} from "./dom"
|
|
@@ -223,7 +223,27 @@ export function readDOMChange(view: EditorView, from: number, to: number, typeOv
|
|
|
223
223
|
|
|
224
224
|
let chFrom = change.start, chTo = change.endA
|
|
225
225
|
|
|
226
|
-
let
|
|
226
|
+
let mkTr = (base?: Transaction) => {
|
|
227
|
+
let tr = base || view.state.tr.replace(chFrom, chTo, parse.doc.slice(change!.start - parse.from,
|
|
228
|
+
change!.endB - parse.from))
|
|
229
|
+
if (parse.sel) {
|
|
230
|
+
let sel = resolveSelection(view, tr.doc, parse.sel)
|
|
231
|
+
// Chrome will sometimes, during composition, report the
|
|
232
|
+
// selection in the wrong place. If it looks like that is
|
|
233
|
+
// happening, don't update the selection.
|
|
234
|
+
// Edge just doesn't move the cursor forward when you start typing
|
|
235
|
+
// in an empty block or between br nodes.
|
|
236
|
+
if (sel && !(browser.chrome && view.composing && sel.empty &&
|
|
237
|
+
(change!.start != change!.endB || view.input.lastChromeDelete < Date.now() - 100) &&
|
|
238
|
+
(sel.head == chFrom || sel.head == tr.mapping.map(chTo) - 1) ||
|
|
239
|
+
browser.ie && sel.empty && sel.head == chFrom))
|
|
240
|
+
tr.setSelection(sel)
|
|
241
|
+
}
|
|
242
|
+
if (compositionID) tr.setMeta("composition", compositionID)
|
|
243
|
+
return tr.scrollIntoView()
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let markChange
|
|
227
247
|
if (inlineChange) {
|
|
228
248
|
if ($from.pos == $to.pos) { // Deletion
|
|
229
249
|
// IE11 sometimes weirdly moves the DOM selection around after
|
|
@@ -232,42 +252,29 @@ export function readDOMChange(view: EditorView, from: number, to: number, typeOv
|
|
|
232
252
|
view.domObserver.suppressSelectionUpdates()
|
|
233
253
|
setTimeout(() => selectionToDOM(view), 20)
|
|
234
254
|
}
|
|
235
|
-
tr = view.state.tr.delete(chFrom, chTo)
|
|
236
|
-
|
|
255
|
+
let tr = mkTr(view.state.tr.delete(chFrom, chTo))
|
|
256
|
+
let marks = doc.resolve(change.start).marksAcross(doc.resolve(change.endA))
|
|
257
|
+
if (marks) tr.ensureMarks(marks)
|
|
258
|
+
view.dispatch(tr)
|
|
237
259
|
} else if ( // Adding or removing a mark
|
|
238
260
|
change.endA == change.endB &&
|
|
239
261
|
(markChange = isMarkChange($from.parent.content.cut($from.parentOffset, $to.parentOffset),
|
|
240
262
|
$fromA.parent.content.cut($fromA.parentOffset, change.endA - $fromA.start())))
|
|
241
263
|
) {
|
|
242
|
-
tr = view.state.tr
|
|
264
|
+
let tr = mkTr(view.state.tr)
|
|
243
265
|
if (markChange.type == "add") tr.addMark(chFrom, chTo, markChange.mark)
|
|
244
266
|
else tr.removeMark(chFrom, chTo, markChange.mark)
|
|
267
|
+
view.dispatch(tr)
|
|
245
268
|
} else if ($from.parent.child($from.index()).isText && $from.index() == $to.index() - ($to.textOffset ? 0 : 1)) {
|
|
246
269
|
// Both positions in the same text node -- simply insert text
|
|
247
270
|
let text = $from.parent.textBetween($from.parentOffset, $to.parentOffset)
|
|
248
|
-
|
|
249
|
-
|
|
271
|
+
let deflt = () => mkTr(view.state.tr.insertText(text, chFrom, chTo))
|
|
272
|
+
if (!view.someProp("handleTextInput", f => f(view, chFrom, chTo, text, deflt)))
|
|
273
|
+
view.dispatch(deflt())
|
|
250
274
|
}
|
|
275
|
+
} else {
|
|
276
|
+
view.dispatch(mkTr())
|
|
251
277
|
}
|
|
252
|
-
|
|
253
|
-
if (!tr)
|
|
254
|
-
tr = view.state.tr.replace(chFrom, chTo, parse.doc.slice(change.start - parse.from, change.endB - parse.from))
|
|
255
|
-
if (parse.sel) {
|
|
256
|
-
let sel = resolveSelection(view, tr.doc, parse.sel)
|
|
257
|
-
// Chrome will sometimes, during composition, report the
|
|
258
|
-
// selection in the wrong place. If it looks like that is
|
|
259
|
-
// happening, don't update the selection.
|
|
260
|
-
// Edge just doesn't move the cursor forward when you start typing
|
|
261
|
-
// in an empty block or between br nodes.
|
|
262
|
-
if (sel && !(browser.chrome && view.composing && sel.empty &&
|
|
263
|
-
(change.start != change.endB || view.input.lastChromeDelete < Date.now() - 100) &&
|
|
264
|
-
(sel.head == chFrom || sel.head == tr.mapping.map(chTo) - 1) ||
|
|
265
|
-
browser.ie && sel.empty && sel.head == chFrom))
|
|
266
|
-
tr.setSelection(sel)
|
|
267
|
-
}
|
|
268
|
-
if (storedMarks) tr.ensureMarks(storedMarks)
|
|
269
|
-
if (compositionID) tr.setMeta("composition", compositionID)
|
|
270
|
-
view.dispatch(tr.scrollIntoView())
|
|
271
278
|
}
|
|
272
279
|
|
|
273
280
|
function resolveSelection(view: EditorView, doc: Node, parsedSel: {anchor: number, head: number}) {
|
package/src/index.ts
CHANGED
|
@@ -28,7 +28,8 @@ export const __endComposition = endComposition
|
|
|
28
28
|
/// editable document. Its state and behavior are determined by its
|
|
29
29
|
/// [props](#view.DirectEditorProps).
|
|
30
30
|
export class EditorView {
|
|
31
|
-
|
|
31
|
+
/// @internal
|
|
32
|
+
_props: DirectEditorProps
|
|
32
33
|
private directPlugins: readonly Plugin[]
|
|
33
34
|
private _root: Document | ShadowRoot | null = null
|
|
34
35
|
/// @internal
|
|
@@ -490,11 +491,7 @@ export class EditorView {
|
|
|
490
491
|
/// [`updateState`](#view.EditorView.updateState) with the result.
|
|
491
492
|
/// This method is bound to the view instance, so that it can be
|
|
492
493
|
/// easily passed around.
|
|
493
|
-
dispatch(tr: Transaction)
|
|
494
|
-
let dispatchTransaction = this._props.dispatchTransaction
|
|
495
|
-
if (dispatchTransaction) dispatchTransaction.call(this, tr)
|
|
496
|
-
else this.updateState(this.state.apply(tr))
|
|
497
|
-
}
|
|
494
|
+
declare dispatch: (tr: Transaction) => void
|
|
498
495
|
|
|
499
496
|
/// @internal
|
|
500
497
|
domSelectionRange(): DOMSelectionRange {
|
|
@@ -510,6 +507,12 @@ export class EditorView {
|
|
|
510
507
|
}
|
|
511
508
|
}
|
|
512
509
|
|
|
510
|
+
EditorView.prototype.dispatch = function(tr: Transaction) {
|
|
511
|
+
let dispatchTransaction = this._props.dispatchTransaction
|
|
512
|
+
if (dispatchTransaction) dispatchTransaction.call(this, tr)
|
|
513
|
+
else this.updateState(this.state.apply(tr))
|
|
514
|
+
}
|
|
515
|
+
|
|
513
516
|
function computeDocDeco(view: EditorView) {
|
|
514
517
|
let attrs = Object.create(null)
|
|
515
518
|
attrs.class = "ProseMirror"
|
|
@@ -635,7 +638,7 @@ export interface EditorProps<P = any> {
|
|
|
635
638
|
/// Whenever the user directly input text, this handler is called
|
|
636
639
|
/// before the input is applied. If it returns `true`, the default
|
|
637
640
|
/// behavior of actually inserting the text is suppressed.
|
|
638
|
-
handleTextInput?: (this: P, view: EditorView, from: number, to: number, text: string) => boolean | void
|
|
641
|
+
handleTextInput?: (this: P, view: EditorView, from: number, to: number, text: string, deflt: () => Transaction) => boolean | void
|
|
639
642
|
|
|
640
643
|
/// Called for each node around a click, from the inside out. The
|
|
641
644
|
/// `direct` flag will be true for the inner node.
|
package/src/input.ts
CHANGED
|
@@ -151,8 +151,9 @@ editHandlers.keypress = (view, _event) => {
|
|
|
151
151
|
let sel = view.state.selection
|
|
152
152
|
if (!(sel instanceof TextSelection) || !sel.$from.sameParent(sel.$to)) {
|
|
153
153
|
let text = String.fromCharCode(event.charCode)
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
let deflt = () => view.state.tr.insertText(text).scrollIntoView()
|
|
155
|
+
if (!/[\r\n]/.test(text) && !view.someProp("handleTextInput", f => f(view, sel.$from.pos, sel.$to.pos, text, deflt)))
|
|
156
|
+
view.dispatch(deflt())
|
|
156
157
|
event.preventDefault()
|
|
157
158
|
}
|
|
158
159
|
}
|
package/src/viewdesc.ts
CHANGED
|
@@ -528,6 +528,8 @@ export class ViewDesc {
|
|
|
528
528
|
|
|
529
529
|
get ignoreForCoords() { return false }
|
|
530
530
|
|
|
531
|
+
get ignoreForSelection() { return false }
|
|
532
|
+
|
|
531
533
|
isText(text: string) { return false }
|
|
532
534
|
}
|
|
533
535
|
|
|
@@ -576,6 +578,8 @@ class WidgetViewDesc extends ViewDesc {
|
|
|
576
578
|
|
|
577
579
|
get domAtom() { return true }
|
|
578
580
|
|
|
581
|
+
get ignoreForSelection() { return !!this.widget.type.spec.relaxedSide }
|
|
582
|
+
|
|
579
583
|
get side() { return (this.widget.type as any).side as number }
|
|
580
584
|
}
|
|
581
585
|
|