prosemirror-view 1.42.0 → 1.42.2
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 +14 -0
- package/dist/index.cjs +36 -28
- package/dist/index.js +21 -12
- package/package.json +1 -1
- package/src/domchange.ts +5 -5
- package/src/domcoords.ts +4 -1
- package/src/input.ts +2 -2
- package/src/viewdesc.ts +10 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## 1.42.2 (2026-07-24)
|
|
2
|
+
|
|
3
|
+
### Bug fixes
|
|
4
|
+
|
|
5
|
+
Enable a workaround for backspace not taking effect on Android for Firefox as well as Chrome (for which it already kicked in).
|
|
6
|
+
|
|
7
|
+
Work around the situation where Chrome/Webkit replace a node's content DOM with a completely different element.
|
|
8
|
+
|
|
9
|
+
## 1.42.1 (2026-07-11)
|
|
10
|
+
|
|
11
|
+
### Bug fixes
|
|
12
|
+
|
|
13
|
+
Don't scroll to a bogus position when trying to scroll an element that has no layout into view.
|
|
14
|
+
|
|
1
15
|
## 1.42.0 (2026-07-01)
|
|
2
16
|
|
|
3
17
|
### Bug fixes
|
package/dist/index.cjs
CHANGED
|
@@ -202,6 +202,7 @@ function clientRect(node) {
|
|
|
202
202
|
};
|
|
203
203
|
}
|
|
204
204
|
function scrollRectIntoView(view, rect, startDOM) {
|
|
205
|
+
if (!nonZero(rect) && rect.left == 0) return;
|
|
205
206
|
var scrollThreshold = view.someProp("scrollThreshold") || 0,
|
|
206
207
|
scrollMargin = view.someProp("scrollMargin") || 5;
|
|
207
208
|
var doc = view.dom.ownerDocument;
|
|
@@ -686,7 +687,7 @@ var ViewDesc = function () {
|
|
|
686
687
|
}
|
|
687
688
|
}, {
|
|
688
689
|
key: "parseRule",
|
|
689
|
-
value: function parseRule() {
|
|
690
|
+
value: function parseRule(addedNodes) {
|
|
690
691
|
return null;
|
|
691
692
|
}
|
|
692
693
|
}, {
|
|
@@ -1284,7 +1285,7 @@ var NodeViewDesc = function (_ViewDesc4) {
|
|
|
1284
1285
|
}
|
|
1285
1286
|
_createClass(NodeViewDesc, [{
|
|
1286
1287
|
key: "parseRule",
|
|
1287
|
-
value: function parseRule() {
|
|
1288
|
+
value: function parseRule(addedNodes) {
|
|
1288
1289
|
var _this5 = this;
|
|
1289
1290
|
if (this.node.type.spec.reparseInView) return null;
|
|
1290
1291
|
var rule = {
|
|
@@ -1306,9 +1307,14 @@ var NodeViewDesc = function (_ViewDesc4) {
|
|
|
1306
1307
|
break;
|
|
1307
1308
|
}
|
|
1308
1309
|
}
|
|
1309
|
-
if (!rule.contentElement)
|
|
1310
|
-
|
|
1311
|
-
|
|
1310
|
+
if (!rule.contentElement) {
|
|
1311
|
+
var found = addedNodes && addedNodes.find(function (n) {
|
|
1312
|
+
return n.nodeType == 1 && addedNodes.indexOf(n.parentNode) < 0 && _this5.dom.contains(n);
|
|
1313
|
+
});
|
|
1314
|
+
if (found) rule.contentElement = found;else rule.getContent = function () {
|
|
1315
|
+
return prosemirrorModel.Fragment.empty;
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1312
1318
|
}
|
|
1313
1319
|
return rule;
|
|
1314
1320
|
}
|
|
@@ -3668,7 +3674,7 @@ handlers.blur = function (view, _event) {
|
|
|
3668
3674
|
};
|
|
3669
3675
|
handlers.beforeinput = function (view, _event) {
|
|
3670
3676
|
var event = _event;
|
|
3671
|
-
if (
|
|
3677
|
+
if (android && event.inputType == "deleteContentBackward") {
|
|
3672
3678
|
view.domObserver.flushSoon();
|
|
3673
3679
|
var domChangeCount = view.input.domChangeCount;
|
|
3674
3680
|
setTimeout(function () {
|
|
@@ -4705,7 +4711,7 @@ function fixUpBadSafariComposition(view, addedNodes) {
|
|
|
4705
4711
|
_iterator5.f();
|
|
4706
4712
|
}
|
|
4707
4713
|
}
|
|
4708
|
-
function parseBetween(view, from_, to_) {
|
|
4714
|
+
function parseBetween(view, from_, to_, addedNodes) {
|
|
4709
4715
|
var _view$docView$parseRa = view.docView.parseRange(from_, to_),
|
|
4710
4716
|
parent = _view$docView$parseRa.node,
|
|
4711
4717
|
fromOffset = _view$docView$parseRa.fromOffset,
|
|
@@ -4748,7 +4754,7 @@ function parseBetween(view, from_, to_) {
|
|
|
4748
4754
|
to: toOffset,
|
|
4749
4755
|
preserveWhitespace: $from.parent.type.whitespace == "pre" ? "full" : true,
|
|
4750
4756
|
findPositions: find,
|
|
4751
|
-
ruleFromNode: ruleFromNode,
|
|
4757
|
+
ruleFromNode: ruleFromNode(addedNodes),
|
|
4752
4758
|
context: $from
|
|
4753
4759
|
});
|
|
4754
4760
|
if (find && find[0].pos != null) {
|
|
@@ -4767,29 +4773,31 @@ function parseBetween(view, from_, to_) {
|
|
|
4767
4773
|
to: to
|
|
4768
4774
|
};
|
|
4769
4775
|
}
|
|
4770
|
-
function ruleFromNode(
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
if (
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4776
|
+
var ruleFromNode = function ruleFromNode(added) {
|
|
4777
|
+
return function (dom) {
|
|
4778
|
+
var desc = dom.pmViewDesc;
|
|
4779
|
+
if (desc) {
|
|
4780
|
+
return desc.parseRule(added);
|
|
4781
|
+
} else if (dom.nodeName == "BR" && dom.parentNode) {
|
|
4782
|
+
if (safari && /^(ul|ol)$/i.test(dom.parentNode.nodeName)) {
|
|
4783
|
+
var skip = document.createElement("div");
|
|
4784
|
+
skip.appendChild(document.createElement("li"));
|
|
4785
|
+
return {
|
|
4786
|
+
skip: skip
|
|
4787
|
+
};
|
|
4788
|
+
} else if (dom.parentNode.lastChild == dom || safari && /^(tr|table)$/i.test(dom.parentNode.nodeName)) {
|
|
4789
|
+
return {
|
|
4790
|
+
ignore: true
|
|
4791
|
+
};
|
|
4792
|
+
}
|
|
4793
|
+
} else if (dom.nodeName == "IMG" && dom.getAttribute("mark-placeholder")) {
|
|
4782
4794
|
return {
|
|
4783
4795
|
ignore: true
|
|
4784
4796
|
};
|
|
4785
4797
|
}
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
};
|
|
4790
|
-
}
|
|
4791
|
-
return null;
|
|
4792
|
-
}
|
|
4798
|
+
return null;
|
|
4799
|
+
};
|
|
4800
|
+
};
|
|
4793
4801
|
var isInline = /^(a|abbr|acronym|b|bd[io]|big|br|button|cite|code|data(list)?|del|dfn|em|i|img|ins|kbd|label|map|mark|meter|output|q|ruby|s|samp|small|span|strong|su[bp]|time|u|tt|var)$/i;
|
|
4794
4802
|
function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
4795
4803
|
var compositionID = view.input.compositionPendingChanges || (view.composing ? view.input.compositionID : 0);
|
|
@@ -4813,7 +4821,7 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
|
4813
4821
|
from = $before.before(shared + 1);
|
|
4814
4822
|
to = view.state.doc.resolve(to).after(shared + 1);
|
|
4815
4823
|
var sel = view.state.selection;
|
|
4816
|
-
var parse = parseBetween(view, from, to);
|
|
4824
|
+
var parse = parseBetween(view, from, to, addedNodes);
|
|
4817
4825
|
var doc = view.state.doc,
|
|
4818
4826
|
compare = doc.slice(parse.from, parse.to);
|
|
4819
4827
|
var preferredPos, preferredSide;
|
package/dist/index.js
CHANGED
|
@@ -207,6 +207,9 @@ function clientRect(node) {
|
|
|
207
207
|
top: rect.top, bottom: rect.top + node.clientHeight * scaleY };
|
|
208
208
|
}
|
|
209
209
|
function scrollRectIntoView(view, rect, startDOM) {
|
|
210
|
+
// Skip empty rects with all sides at 0, for example, when the element has no CSS box (display: none)
|
|
211
|
+
if (!nonZero(rect) && rect.left == 0)
|
|
212
|
+
return;
|
|
210
213
|
let scrollThreshold = view.someProp("scrollThreshold") || 0, scrollMargin = view.someProp("scrollMargin") || 5;
|
|
211
214
|
let doc = view.dom.ownerDocument;
|
|
212
215
|
for (let parent = startDOM || view.dom;;) {
|
|
@@ -761,7 +764,7 @@ class ViewDesc {
|
|
|
761
764
|
// When parsing in-editor content (in domchange.js), we allow
|
|
762
765
|
// descriptions to determine the parse rules that should be used to
|
|
763
766
|
// parse them.
|
|
764
|
-
parseRule() { return null; }
|
|
767
|
+
parseRule(addedNodes) { return null; }
|
|
765
768
|
// Used by the editor's event handler to ignore events that come
|
|
766
769
|
// from certain descs.
|
|
767
770
|
stopEvent(event) { return false; }
|
|
@@ -1325,7 +1328,7 @@ class NodeViewDesc extends ViewDesc {
|
|
|
1325
1328
|
else
|
|
1326
1329
|
return new NodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM || null, nodeDOM);
|
|
1327
1330
|
}
|
|
1328
|
-
parseRule() {
|
|
1331
|
+
parseRule(addedNodes) {
|
|
1329
1332
|
// Experimental kludge to allow opt-in re-parsing of nodes
|
|
1330
1333
|
if (this.node.type.spec.reparseInView)
|
|
1331
1334
|
return null;
|
|
@@ -1353,8 +1356,14 @@ class NodeViewDesc extends ViewDesc {
|
|
|
1353
1356
|
break;
|
|
1354
1357
|
}
|
|
1355
1358
|
}
|
|
1356
|
-
if (!rule.contentElement)
|
|
1357
|
-
|
|
1359
|
+
if (!rule.contentElement) {
|
|
1360
|
+
let found = addedNodes &&
|
|
1361
|
+
addedNodes.find(n => n.nodeType == 1 && addedNodes.indexOf(n.parentNode) < 0 && this.dom.contains(n));
|
|
1362
|
+
if (found)
|
|
1363
|
+
rule.contentElement = found;
|
|
1364
|
+
else
|
|
1365
|
+
rule.getContent = () => Fragment.empty;
|
|
1366
|
+
}
|
|
1358
1367
|
}
|
|
1359
1368
|
return rule;
|
|
1360
1369
|
}
|
|
@@ -3908,8 +3917,8 @@ handlers.beforeinput = (view, _event) => {
|
|
|
3908
3917
|
// We should probably do more with beforeinput events, but support
|
|
3909
3918
|
// is so spotty that I'm still waiting to see where they are going.
|
|
3910
3919
|
// Very specific hack to deal with backspace sometimes failing on
|
|
3911
|
-
// Chrome Android when after an uneditable node.
|
|
3912
|
-
if (
|
|
3920
|
+
// Chrome and Firefox Android when after an uneditable node.
|
|
3921
|
+
if (android && event.inputType == "deleteContentBackward") {
|
|
3913
3922
|
view.domObserver.flushSoon();
|
|
3914
3923
|
let { domChangeCount } = view.input;
|
|
3915
3924
|
setTimeout(() => {
|
|
@@ -5013,7 +5022,7 @@ function fixUpBadSafariComposition(view, addedNodes) {
|
|
|
5013
5022
|
// that the DOM represents. If any changes came in in the meantime,
|
|
5014
5023
|
// the modification is mapped over those before it is applied, in
|
|
5015
5024
|
// readDOMChange.
|
|
5016
|
-
function parseBetween(view, from_, to_) {
|
|
5025
|
+
function parseBetween(view, from_, to_, addedNodes) {
|
|
5017
5026
|
let { node: parent, fromOffset, toOffset, from, to } = view.docView.parseRange(from_, to_);
|
|
5018
5027
|
let domSel = view.domSelectionRange();
|
|
5019
5028
|
let find;
|
|
@@ -5047,7 +5056,7 @@ function parseBetween(view, from_, to_) {
|
|
|
5047
5056
|
to: toOffset,
|
|
5048
5057
|
preserveWhitespace: $from.parent.type.whitespace == "pre" ? "full" : true,
|
|
5049
5058
|
findPositions: find,
|
|
5050
|
-
ruleFromNode,
|
|
5059
|
+
ruleFromNode: ruleFromNode(addedNodes),
|
|
5051
5060
|
context: $from
|
|
5052
5061
|
});
|
|
5053
5062
|
if (find && find[0].pos != null) {
|
|
@@ -5058,10 +5067,10 @@ function parseBetween(view, from_, to_) {
|
|
|
5058
5067
|
}
|
|
5059
5068
|
return { doc, sel, from, to };
|
|
5060
5069
|
}
|
|
5061
|
-
|
|
5070
|
+
const ruleFromNode = (added) => (dom) => {
|
|
5062
5071
|
let desc = dom.pmViewDesc;
|
|
5063
5072
|
if (desc) {
|
|
5064
|
-
return desc.parseRule();
|
|
5073
|
+
return desc.parseRule(added);
|
|
5065
5074
|
}
|
|
5066
5075
|
else if (dom.nodeName == "BR" && dom.parentNode) {
|
|
5067
5076
|
// Safari replaces the list item or table cell with a BR
|
|
@@ -5080,7 +5089,7 @@ function ruleFromNode(dom) {
|
|
|
5080
5089
|
return { ignore: true };
|
|
5081
5090
|
}
|
|
5082
5091
|
return null;
|
|
5083
|
-
}
|
|
5092
|
+
};
|
|
5084
5093
|
const isInline = /^(a|abbr|acronym|b|bd[io]|big|br|button|cite|code|data(list)?|del|dfn|em|i|img|ins|kbd|label|map|mark|meter|output|q|ruby|s|samp|small|span|strong|su[bp]|time|u|tt|var)$/i;
|
|
5085
5094
|
function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
5086
5095
|
let compositionID = view.input.compositionPendingChanges || (view.composing ? view.input.compositionID : 0);
|
|
@@ -5109,7 +5118,7 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
|
5109
5118
|
from = $before.before(shared + 1);
|
|
5110
5119
|
to = view.state.doc.resolve(to).after(shared + 1);
|
|
5111
5120
|
let sel = view.state.selection;
|
|
5112
|
-
let parse = parseBetween(view, from, to);
|
|
5121
|
+
let parse = parseBetween(view, from, to, addedNodes);
|
|
5113
5122
|
let doc = view.state.doc, compare = doc.slice(parse.from, parse.to);
|
|
5114
5123
|
let preferredPos, preferredSide;
|
|
5115
5124
|
// Prefer anchoring to end when Backspace is pressed
|
package/package.json
CHANGED
package/src/domchange.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {EditorView} from "./index"
|
|
|
12
12
|
// the modification is mapped over those before it is applied, in
|
|
13
13
|
// readDOMChange.
|
|
14
14
|
|
|
15
|
-
function parseBetween(view: EditorView, from_: number, to_: number) {
|
|
15
|
+
function parseBetween(view: EditorView, from_: number, to_: number, addedNodes: readonly DOMNode[]) {
|
|
16
16
|
let {node: parent, fromOffset, toOffset, from, to} = view.docView.parseRange(from_, to_)
|
|
17
17
|
|
|
18
18
|
let domSel = view.domSelectionRange()
|
|
@@ -44,7 +44,7 @@ function parseBetween(view: EditorView, from_: number, to_: number) {
|
|
|
44
44
|
to: toOffset,
|
|
45
45
|
preserveWhitespace: $from.parent.type.whitespace == "pre" ? "full" : true,
|
|
46
46
|
findPositions: find,
|
|
47
|
-
ruleFromNode,
|
|
47
|
+
ruleFromNode: ruleFromNode(addedNodes),
|
|
48
48
|
context: $from
|
|
49
49
|
})
|
|
50
50
|
if (find && find[0].pos != null) {
|
|
@@ -55,10 +55,10 @@ function parseBetween(view: EditorView, from_: number, to_: number) {
|
|
|
55
55
|
return {doc, sel, from, to}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
const ruleFromNode = (added: readonly DOMNode[]) => (dom: DOMNode): Omit<TagParseRule, "tag"> | null => {
|
|
59
59
|
let desc = dom.pmViewDesc
|
|
60
60
|
if (desc) {
|
|
61
|
-
return desc.parseRule()
|
|
61
|
+
return desc.parseRule(added)
|
|
62
62
|
} else if (dom.nodeName == "BR" && dom.parentNode) {
|
|
63
63
|
// Safari replaces the list item or table cell with a BR
|
|
64
64
|
// directly in the list node (?!) if you delete the last
|
|
@@ -105,7 +105,7 @@ export function readDOMChange(view: EditorView, from: number, to: number, typeOv
|
|
|
105
105
|
to = view.state.doc.resolve(to).after(shared + 1)
|
|
106
106
|
|
|
107
107
|
let sel = view.state.selection
|
|
108
|
-
let parse = parseBetween(view, from, to)
|
|
108
|
+
let parse = parseBetween(view, from, to, addedNodes)
|
|
109
109
|
|
|
110
110
|
let doc = view.state.doc, compare = doc.slice(parse.from, parse.to)
|
|
111
111
|
let preferredPos, preferredSide: "start" | "end"
|
package/src/domcoords.ts
CHANGED
|
@@ -30,6 +30,9 @@ function clientRect(node: HTMLElement): Rect {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export function scrollRectIntoView(view: EditorView, rect: Rect, startDOM: Node) {
|
|
33
|
+
// Skip empty rects with all sides at 0, for example, when the element has no CSS box (display: none)
|
|
34
|
+
if (!nonZero(rect) && rect.left == 0) return
|
|
35
|
+
|
|
33
36
|
let scrollThreshold = view.someProp("scrollThreshold") || 0, scrollMargin = view.someProp("scrollMargin") || 5
|
|
34
37
|
let doc = view.dom.ownerDocument
|
|
35
38
|
for (let parent: Node | null = startDOM || view.dom;;) {
|
|
@@ -328,7 +331,7 @@ export function posAtCoords(view: EditorView, coords: {top: number, left: number
|
|
|
328
331
|
return {pos, inside: desc ? desc.posAtStart - desc.border : -1}
|
|
329
332
|
}
|
|
330
333
|
|
|
331
|
-
function nonZero(rect:
|
|
334
|
+
function nonZero(rect: Rect) {
|
|
332
335
|
return rect.top < rect.bottom || rect.left < rect.right
|
|
333
336
|
}
|
|
334
337
|
|
package/src/input.ts
CHANGED
|
@@ -864,8 +864,8 @@ handlers.beforeinput = (view, _event: Event) => {
|
|
|
864
864
|
// is so spotty that I'm still waiting to see where they are going.
|
|
865
865
|
|
|
866
866
|
// Very specific hack to deal with backspace sometimes failing on
|
|
867
|
-
// Chrome Android when after an uneditable node.
|
|
868
|
-
if (browser.
|
|
867
|
+
// Chrome and Firefox Android when after an uneditable node.
|
|
868
|
+
if (browser.android && event.inputType == "deleteContentBackward") {
|
|
869
869
|
view.domObserver.flushSoon()
|
|
870
870
|
let {domChangeCount} = view.input
|
|
871
871
|
setTimeout(() => {
|
package/src/viewdesc.ts
CHANGED
|
@@ -167,7 +167,7 @@ export class ViewDesc {
|
|
|
167
167
|
// When parsing in-editor content (in domchange.js), we allow
|
|
168
168
|
// descriptions to determine the parse rules that should be used to
|
|
169
169
|
// parse them.
|
|
170
|
-
parseRule(): Omit<TagParseRule, "tag"> | null { return null }
|
|
170
|
+
parseRule(addedNodes?: readonly DOMNode[]): Omit<TagParseRule, "tag"> | null { return null }
|
|
171
171
|
|
|
172
172
|
// Used by the editor's event handler to ignore events that come
|
|
173
173
|
// from certain descs.
|
|
@@ -727,7 +727,7 @@ export class NodeViewDesc extends ViewDesc {
|
|
|
727
727
|
return new NodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM || null, nodeDOM)
|
|
728
728
|
}
|
|
729
729
|
|
|
730
|
-
parseRule() {
|
|
730
|
+
parseRule(addedNodes?: readonly DOMNode[]) {
|
|
731
731
|
// Experimental kludge to allow opt-in re-parsing of nodes
|
|
732
732
|
if (this.node.type.spec.reparseInView) return null
|
|
733
733
|
// FIXME the assumption that this can always return the current
|
|
@@ -751,7 +751,14 @@ export class NodeViewDesc extends ViewDesc {
|
|
|
751
751
|
break
|
|
752
752
|
}
|
|
753
753
|
}
|
|
754
|
-
if (!rule.contentElement)
|
|
754
|
+
if (!rule.contentElement) {
|
|
755
|
+
let found = addedNodes &&
|
|
756
|
+
addedNodes.find(n => n.nodeType == 1 && addedNodes.indexOf(n.parentNode!) < 0 && this.dom.contains(n))
|
|
757
|
+
if (found)
|
|
758
|
+
rule.contentElement = found as HTMLElement
|
|
759
|
+
else
|
|
760
|
+
rule.getContent = () => Fragment.empty
|
|
761
|
+
}
|
|
755
762
|
}
|
|
756
763
|
return rule
|
|
757
764
|
}
|