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 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) rule.getContent = function () {
1310
- return prosemirrorModel.Fragment.empty;
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 (chrome && android && event.inputType == "deleteContentBackward") {
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(dom) {
4771
- var desc = dom.pmViewDesc;
4772
- if (desc) {
4773
- return desc.parseRule();
4774
- } else if (dom.nodeName == "BR" && dom.parentNode) {
4775
- if (safari && /^(ul|ol)$/i.test(dom.parentNode.nodeName)) {
4776
- var skip = document.createElement("div");
4777
- skip.appendChild(document.createElement("li"));
4778
- return {
4779
- skip: skip
4780
- };
4781
- } else if (dom.parentNode.lastChild == dom || safari && /^(tr|table)$/i.test(dom.parentNode.nodeName)) {
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
- } else if (dom.nodeName == "IMG" && dom.getAttribute("mark-placeholder")) {
4787
- return {
4788
- ignore: true
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
- rule.getContent = () => Fragment.empty;
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 (chrome && android && event.inputType == "deleteContentBackward") {
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
- function ruleFromNode(dom) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prosemirror-view",
3
- "version": "1.42.0",
3
+ "version": "1.42.2",
4
4
  "description": "ProseMirror's view component",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
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
- function ruleFromNode(dom: DOMNode): Omit<TagParseRule, "tag"> | null {
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: DOMRect) {
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.chrome && browser.android && event.inputType == "deleteContentBackward") {
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) rule.getContent = () => Fragment.empty
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
  }