prosemirror-view 1.41.9 → 1.42.1

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,19 @@
1
+ ## 1.42.1 (2026-07-11)
2
+
3
+ ### Bug fixes
4
+
5
+ Don't scroll to a bogus position when trying to scroll an element that has no layout into view.
6
+
7
+ ## 1.42.0 (2026-07-01)
8
+
9
+ ### Bug fixes
10
+
11
+ The clipboard content parser now inlines simple styles defined in the pasted HTML before parsing it, since some applications output their styling that way.
12
+
13
+ ### New features
14
+
15
+ Mark views now also support an `update` method.
16
+
1
17
  ## 1.41.9 (2026-06-10)
2
18
 
3
19
  ### 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;
@@ -1272,7 +1273,7 @@ var MarkViewDesc = function (_ViewDesc3) {
1272
1273
  var NodeViewDesc = function (_ViewDesc4) {
1273
1274
  _inherits(NodeViewDesc, _ViewDesc4);
1274
1275
  var _super4 = _createSuper(NodeViewDesc);
1275
- function NodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, view, pos) {
1276
+ function NodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM) {
1276
1277
  var _this4;
1277
1278
  _classCallCheck(this, NodeViewDesc);
1278
1279
  _this4 = _super4.call(this, parent, [], dom, contentDOM);
@@ -1474,23 +1475,23 @@ var NodeViewDesc = function (_ViewDesc4) {
1474
1475
  }
1475
1476
  var nodeDOM = dom;
1476
1477
  dom = applyOuterDeco(dom, outerDeco, node);
1477
- if (spec) return descObj = new CustomNodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM || null, nodeDOM, spec, view, pos + 1);else if (node.isText) return new TextViewDesc(parent, node, outerDeco, innerDeco, dom, nodeDOM, view);else return new NodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM || null, nodeDOM, view, pos + 1);
1478
+ if (spec) return descObj = new CustomNodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM || null, nodeDOM, spec);else if (node.isText) return new TextViewDesc(parent, node, outerDeco, innerDeco, dom, nodeDOM);else return new NodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM || null, nodeDOM);
1478
1479
  }
1479
1480
  }]);
1480
1481
  return NodeViewDesc;
1481
1482
  }(ViewDesc);
1482
1483
  function docViewDesc(doc, outerDeco, innerDeco, dom, view) {
1483
1484
  applyOuterDeco(dom, outerDeco, doc);
1484
- var docView = new NodeViewDesc(undefined, doc, outerDeco, innerDeco, dom, dom, dom, view, 0);
1485
+ var docView = new NodeViewDesc(undefined, doc, outerDeco, innerDeco, dom, dom, dom);
1485
1486
  if (docView.contentDOM) docView.updateChildren(view, 0);
1486
1487
  return docView;
1487
1488
  }
1488
1489
  var TextViewDesc = function (_NodeViewDesc) {
1489
1490
  _inherits(TextViewDesc, _NodeViewDesc);
1490
1491
  var _super5 = _createSuper(TextViewDesc);
1491
- function TextViewDesc(parent, node, outerDeco, innerDeco, dom, nodeDOM, view) {
1492
+ function TextViewDesc(parent, node, outerDeco, innerDeco, dom, nodeDOM) {
1492
1493
  _classCallCheck(this, TextViewDesc);
1493
- return _super5.call(this, parent, node, outerDeco, innerDeco, dom, null, nodeDOM, view, 0);
1494
+ return _super5.call(this, parent, node, outerDeco, innerDeco, dom, null, nodeDOM);
1494
1495
  }
1495
1496
  _createClass(TextViewDesc, [{
1496
1497
  key: "parseRule",
@@ -1542,10 +1543,10 @@ var TextViewDesc = function (_NodeViewDesc) {
1542
1543
  }
1543
1544
  }, {
1544
1545
  key: "slice",
1545
- value: function slice(from, to, view) {
1546
+ value: function slice(from, to, _view) {
1546
1547
  var node = this.node.cut(from, to),
1547
1548
  dom = document.createTextNode(node.text);
1548
- return new TextViewDesc(this.parent, node, this.outerDeco, this.innerDeco, dom, dom, view);
1549
+ return new TextViewDesc(this.parent, node, this.outerDeco, this.innerDeco, dom, dom);
1549
1550
  }
1550
1551
  }, {
1551
1552
  key: "markDirty",
@@ -1601,10 +1602,10 @@ var TrailingHackViewDesc = function (_ViewDesc5) {
1601
1602
  var CustomNodeViewDesc = function (_NodeViewDesc2) {
1602
1603
  _inherits(CustomNodeViewDesc, _NodeViewDesc2);
1603
1604
  var _super7 = _createSuper(CustomNodeViewDesc);
1604
- function CustomNodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, spec, view, pos) {
1605
+ function CustomNodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, spec) {
1605
1606
  var _this7;
1606
1607
  _classCallCheck(this, CustomNodeViewDesc);
1607
- _this7 = _super7.call(this, parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, view, pos);
1608
+ _this7 = _super7.call(this, parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM);
1608
1609
  _this7.spec = spec;
1609
1610
  return _this7;
1610
1611
  }
@@ -1810,6 +1811,14 @@ var ViewTreeUpdater = function () {
1810
1811
  break;
1811
1812
  }
1812
1813
  }
1814
+ if (found < 0 && this.index < this.top.children.length) {
1815
+ var cur = this.top.children[this.index];
1816
+ if (cur instanceof MarkViewDesc && cur.dirty != NODE_DIRTY && cur.mark.type == marks[depth].type && cur.spec.update && !this.isLocked(cur.dom) && cur.spec.update(marks[depth])) {
1817
+ cur.mark = marks[depth];
1818
+ found = this.index;
1819
+ this.changed = true;
1820
+ }
1821
+ }
1813
1822
  if (found > -1) {
1814
1823
  if (found > this.index) {
1815
1824
  this.changed = true;
@@ -2831,9 +2840,8 @@ var wrapMap = {
2831
2840
  td: ["table", "tbody", "tr"],
2832
2841
  th: ["table", "tbody", "tr"]
2833
2842
  };
2834
- var _detachedDoc = null;
2835
2843
  function detachedDoc() {
2836
- return _detachedDoc || (_detachedDoc = document.implementation.createHTMLDocument("title"));
2844
+ return document.implementation.createHTMLDocument("title");
2837
2845
  }
2838
2846
  var _policy = null;
2839
2847
  function maybeWrapTrusted(html) {
@@ -2849,7 +2857,8 @@ function maybeWrapTrusted(html) {
2849
2857
  function readHTML(html) {
2850
2858
  var metas = /^(\s*<meta [^>]*>)*/.exec(html);
2851
2859
  if (metas) html = html.slice(metas[0].length);
2852
- var elt = detachedDoc().createElement("div");
2860
+ var doc = detachedDoc(),
2861
+ elt = doc.body;
2853
2862
  var firstTag = /<([a-z][^>\s]+)/i.exec(html),
2854
2863
  wrap;
2855
2864
  if (wrap = firstTag && wrapMap[firstTag[1].toLowerCase()]) html = wrap.map(function (n) {
@@ -2859,6 +2868,16 @@ function readHTML(html) {
2859
2868
  }).reverse().join("");
2860
2869
  elt.innerHTML = maybeWrapTrusted(html);
2861
2870
  if (wrap) for (var i = 0; i < wrap.length; i++) elt = elt.querySelector(wrap[i]) || elt;
2871
+ for (var _i6 = 0; _i6 < doc.styleSheets.length; _i6++) {
2872
+ var style = doc.styleSheets[_i6];
2873
+ for (var j = 0; j < style.rules.length; j++) {
2874
+ var rule = style.rules[j];
2875
+ if (rule instanceof CSSStyleRule) {
2876
+ var matches = elt.querySelectorAll(rule.selectorText);
2877
+ for (var k = 0; k < matches.length; k++) matches[k].style.cssText += rule.style.cssText;
2878
+ }
2879
+ }
2880
+ }
2862
2881
  return elt;
2863
2882
  }
2864
2883
  function restoreReplacedSpaces(dom) {
@@ -3852,10 +3871,10 @@ var DecorationSet = function () {
3852
3871
  var span = this.local[i];
3853
3872
  if (span.from <= end && span.to >= start && (!predicate || predicate(span.spec))) result.push(span.copy(span.from + offset, span.to + offset));
3854
3873
  }
3855
- for (var _i6 = 0; _i6 < this.children.length; _i6 += 3) {
3856
- if (this.children[_i6] < end && this.children[_i6 + 1] > start) {
3857
- var childOff = this.children[_i6] + 1;
3858
- this.children[_i6 + 2].findInner(start - childOff, end - childOff, result, offset + childOff, predicate);
3874
+ for (var _i7 = 0; _i7 < this.children.length; _i7 += 3) {
3875
+ if (this.children[_i7] < end && this.children[_i7 + 1] > start) {
3876
+ var childOff = this.children[_i7] + 1;
3877
+ this.children[_i7 + 2].findInner(start - childOff, end - childOff, result, offset + childOff, predicate);
3859
3878
  }
3860
3879
  }
3861
3880
  }
@@ -3932,7 +3951,7 @@ var DecorationSet = function () {
3932
3951
  i -= 3;
3933
3952
  }
3934
3953
  }
3935
- if (local.length) for (var _i7 = 0, _span; _i7 < decorations.length; _i7++) if (_span = decorations[_i7]) {
3954
+ if (local.length) for (var _i8 = 0, _span; _i8 < decorations.length; _i8++) if (_span = decorations[_i8]) {
3936
3955
  for (var _j2 = 0; _j2 < local.length; _j2++) if (local[_j2].eq(_span, offset)) {
3937
3956
  if (local == this.local) local = this.local.slice();
3938
3957
  local.splice(_j2--, 1);
@@ -3953,8 +3972,8 @@ var DecorationSet = function () {
3953
3972
  }
3954
3973
  var start = offset + 1,
3955
3974
  end = start + node.content.size;
3956
- for (var _i8 = 0; _i8 < this.local.length; _i8++) {
3957
- var dec = this.local[_i8];
3975
+ for (var _i9 = 0; _i9 < this.local.length; _i9++) {
3976
+ var dec = this.local[_i9];
3958
3977
  if (dec.from < end && dec.to > start && dec.type instanceof InlineType) {
3959
3978
  var from = Math.max(start, dec.from) - start,
3960
3979
  to = Math.min(end, dec.to) - start;
@@ -3973,7 +3992,7 @@ var DecorationSet = function () {
3973
3992
  if (this == other) return true;
3974
3993
  if (!(other instanceof DecorationSet) || this.local.length != other.local.length || this.children.length != other.children.length) return false;
3975
3994
  for (var i = 0; i < this.local.length; i++) if (!this.local[i].eq(other.local[i])) return false;
3976
- for (var _i9 = 0; _i9 < this.children.length; _i9 += 3) if (this.children[_i9] != other.children[_i9] || this.children[_i9 + 1] != other.children[_i9 + 1] || !this.children[_i9 + 2].eq(other.children[_i9 + 2])) return false;
3995
+ for (var _i10 = 0; _i10 < this.children.length; _i10 += 3) if (this.children[_i10] != other.children[_i10] || this.children[_i10 + 1] != other.children[_i10 + 1] || !this.children[_i10 + 2].eq(other.children[_i10 + 2])) return false;
3977
3996
  return true;
3978
3997
  }
3979
3998
  }, {
@@ -4090,15 +4109,15 @@ function mapChildren(oldChildren, newLocal, mapping, node, offset, oldOffset, op
4090
4109
  var moved = 0;
4091
4110
  mapping.maps[i].forEach(function (oldStart, oldEnd, newStart, newEnd) {
4092
4111
  var dSize = newEnd - newStart - (oldEnd - oldStart);
4093
- for (var _i13 = 0; _i13 < children.length; _i13 += 3) {
4094
- var end = children[_i13 + 1];
4112
+ for (var _i14 = 0; _i14 < children.length; _i14 += 3) {
4113
+ var end = children[_i14 + 1];
4095
4114
  if (end < 0 || oldStart > end + _baseOffset - moved) continue;
4096
- var start = children[_i13] + _baseOffset - moved;
4115
+ var start = children[_i14] + _baseOffset - moved;
4097
4116
  if (oldEnd >= start) {
4098
- children[_i13 + 1] = oldStart <= start ? -2 : -1;
4117
+ children[_i14 + 1] = oldStart <= start ? -2 : -1;
4099
4118
  } else if (oldStart >= _baseOffset && dSize) {
4100
- children[_i13] += dSize;
4101
- children[_i13 + 1] += dSize;
4119
+ children[_i14] += dSize;
4120
+ children[_i14 + 1] += dSize;
4102
4121
  }
4103
4122
  }
4104
4123
  moved += dSize;
@@ -4110,32 +4129,32 @@ function mapChildren(oldChildren, newLocal, mapping, node, offset, oldOffset, op
4110
4129
  _loop4(baseOffset);
4111
4130
  }
4112
4131
  var mustRebuild = false;
4113
- for (var _i10 = 0; _i10 < children.length; _i10 += 3) if (children[_i10 + 1] < 0) {
4114
- if (children[_i10 + 1] == -2) {
4132
+ for (var _i11 = 0; _i11 < children.length; _i11 += 3) if (children[_i11 + 1] < 0) {
4133
+ if (children[_i11 + 1] == -2) {
4115
4134
  mustRebuild = true;
4116
- children[_i10 + 1] = -1;
4135
+ children[_i11 + 1] = -1;
4117
4136
  continue;
4118
4137
  }
4119
- var from = mapping.map(oldChildren[_i10] + oldOffset),
4138
+ var from = mapping.map(oldChildren[_i11] + oldOffset),
4120
4139
  fromLocal = from - offset;
4121
4140
  if (fromLocal < 0 || fromLocal >= node.content.size) {
4122
4141
  mustRebuild = true;
4123
4142
  continue;
4124
4143
  }
4125
- var to = mapping.map(oldChildren[_i10 + 1] + oldOffset, -1),
4144
+ var to = mapping.map(oldChildren[_i11 + 1] + oldOffset, -1),
4126
4145
  toLocal = to - offset;
4127
4146
  var _node$content$findInd2 = node.content.findIndex(fromLocal),
4128
4147
  index = _node$content$findInd2.index,
4129
4148
  childOffset = _node$content$findInd2.offset;
4130
4149
  var childNode = node.maybeChild(index);
4131
4150
  if (childNode && childOffset == fromLocal && childOffset + childNode.nodeSize == toLocal) {
4132
- var mapped = children[_i10 + 2].mapInner(mapping, childNode, from + 1, oldChildren[_i10] + oldOffset + 1, options);
4151
+ var mapped = children[_i11 + 2].mapInner(mapping, childNode, from + 1, oldChildren[_i11] + oldOffset + 1, options);
4133
4152
  if (mapped != empty) {
4134
- children[_i10] = fromLocal;
4135
- children[_i10 + 1] = toLocal;
4136
- children[_i10 + 2] = mapped;
4153
+ children[_i11] = fromLocal;
4154
+ children[_i11 + 1] = toLocal;
4155
+ children[_i11 + 2] = mapped;
4137
4156
  } else {
4138
- children[_i10 + 1] = -2;
4157
+ children[_i11 + 1] = -2;
4139
4158
  mustRebuild = true;
4140
4159
  }
4141
4160
  } else {
@@ -4146,14 +4165,14 @@ function mapChildren(oldChildren, newLocal, mapping, node, offset, oldOffset, op
4146
4165
  var decorations = mapAndGatherRemainingDecorations(children, oldChildren, newLocal, mapping, offset, oldOffset, options);
4147
4166
  var built = buildTree(decorations, node, 0, options);
4148
4167
  newLocal = built.local;
4149
- for (var _i11 = 0; _i11 < children.length; _i11 += 3) if (children[_i11 + 1] < 0) {
4150
- children.splice(_i11, 3);
4151
- _i11 -= 3;
4168
+ for (var _i12 = 0; _i12 < children.length; _i12 += 3) if (children[_i12 + 1] < 0) {
4169
+ children.splice(_i12, 3);
4170
+ _i12 -= 3;
4152
4171
  }
4153
- for (var _i12 = 0, j = 0; _i12 < built.children.length; _i12 += 3) {
4154
- var _from2 = built.children[_i12];
4172
+ for (var _i13 = 0, j = 0; _i13 < built.children.length; _i13 += 3) {
4173
+ var _from2 = built.children[_i13];
4155
4174
  while (j < children.length && children[j] < _from2) j += 3;
4156
- children.splice(j, 0, built.children[_i12], built.children[_i12 + 1], built.children[_i12 + 2]);
4175
+ children.splice(j, 0, built.children[_i13], built.children[_i13 + 1], built.children[_i13 + 2]);
4157
4176
  }
4158
4177
  }
4159
4178
  return new DecorationSet(newLocal.sort(byPos), children);
@@ -4173,7 +4192,7 @@ function mapAndGatherRemainingDecorations(children, oldChildren, decorations, ma
4173
4192
  var mapped = set.local[i].map(mapping, offset, oldOffset);
4174
4193
  if (mapped) decorations.push(mapped);else if (options.onRemove) options.onRemove(set.local[i].spec);
4175
4194
  }
4176
- for (var _i14 = 0; _i14 < set.children.length; _i14 += 3) gather(set.children[_i14 + 2], set.children[_i14] + oldOffset + 1);
4195
+ for (var _i15 = 0; _i15 < set.children.length; _i15 += 3) gather(set.children[_i15 + 2], set.children[_i15] + oldOffset + 1);
4177
4196
  }
4178
4197
  for (var i = 0; i < children.length; i += 3) if (children[i + 1] == -1) gather(children[i + 2], oldChildren[i] + oldOffset + 1);
4179
4198
  return decorations;
@@ -4566,8 +4585,8 @@ var DOMObserver = function () {
4566
4585
  var prev = mut.previousSibling,
4567
4586
  next = mut.nextSibling;
4568
4587
  if (ie && ie_version <= 11 && mut.addedNodes.length) {
4569
- for (var _i15 = 0; _i15 < mut.addedNodes.length; _i15++) {
4570
- var _mut$addedNodes$_i = mut.addedNodes[_i15],
4588
+ for (var _i16 = 0; _i16 < mut.addedNodes.length; _i16++) {
4589
+ var _mut$addedNodes$_i = mut.addedNodes[_i16],
4571
4590
  previousSibling = _mut$addedNodes$_i.previousSibling,
4572
4591
  nextSibling = _mut$addedNodes$_i.nextSibling;
4573
4592
  if (!previousSibling || Array.prototype.indexOf.call(mut.addedNodes, previousSibling) < 0) prev = previousSibling;
@@ -4933,7 +4952,7 @@ function isMarkChange(cur, prev) {
4933
4952
  mark,
4934
4953
  update;
4935
4954
  for (var i = 0; i < prevMarks.length; i++) added = prevMarks[i].removeFromSet(added);
4936
- for (var _i16 = 0; _i16 < curMarks.length; _i16++) removed = curMarks[_i16].removeFromSet(removed);
4955
+ for (var _i17 = 0; _i17 < curMarks.length; _i17++) removed = curMarks[_i17].removeFromSet(removed);
4937
4956
  if (added.length == 1 && removed.length == 0) {
4938
4957
  mark = added[0];
4939
4958
  type = "add";
@@ -4950,7 +4969,7 @@ function isMarkChange(cur, prev) {
4950
4969
  return null;
4951
4970
  }
4952
4971
  var updated = [];
4953
- for (var _i17 = 0; _i17 < prev.childCount; _i17++) updated.push(update(prev.child(_i17)));
4972
+ for (var _i18 = 0; _i18 < prev.childCount; _i18++) updated.push(update(prev.child(_i18)));
4954
4973
  if (prosemirrorModel.Fragment.from(updated).eq(cur)) return {
4955
4974
  mark: mark,
4956
4975
  type: type
@@ -5187,13 +5206,13 @@ var EditorView = function () {
5187
5206
  var plugin = this.directPlugins[i];
5188
5207
  if (plugin.spec.view) this.pluginViews.push(plugin.spec.view(this));
5189
5208
  }
5190
- for (var _i18 = 0; _i18 < this.state.plugins.length; _i18++) {
5191
- var _plugin = this.state.plugins[_i18];
5209
+ for (var _i19 = 0; _i19 < this.state.plugins.length; _i19++) {
5210
+ var _plugin = this.state.plugins[_i19];
5192
5211
  if (_plugin.spec.view) this.pluginViews.push(_plugin.spec.view(this));
5193
5212
  }
5194
5213
  } else {
5195
- for (var _i19 = 0; _i19 < this.pluginViews.length; _i19++) {
5196
- var pluginView = this.pluginViews[_i19];
5214
+ for (var _i20 = 0; _i20 < this.pluginViews.length; _i20++) {
5215
+ var pluginView = this.pluginViews[_i20];
5197
5216
  if (pluginView.update) pluginView.update(this, prevState);
5198
5217
  }
5199
5218
  }
@@ -5223,8 +5242,8 @@ var EditorView = function () {
5223
5242
  if (_prop != null && (value = f ? f(_prop) : _prop)) return value;
5224
5243
  }
5225
5244
  var plugins = this.state.plugins;
5226
- if (plugins) for (var _i20 = 0; _i20 < plugins.length; _i20++) {
5227
- var _prop2 = plugins[_i20].props[propName];
5245
+ if (plugins) for (var _i21 = 0; _i21 < plugins.length; _i21++) {
5246
+ var _prop2 = plugins[_i21].props[propName];
5228
5247
  if (_prop2 != null && (value = f ? f(_prop2) : _prop2)) return value;
5229
5248
  }
5230
5249
  }
package/dist/index.d.cts CHANGED
@@ -363,6 +363,14 @@ interface MarkView {
363
363
  */
364
364
  contentDOM?: HTMLElement | null;
365
365
  /**
366
+ When given, this is called when the view is updating itself. It
367
+ will be given a mark (of the same type as the current one). When it
368
+ returns true, the existing DOM is kept and reused (its content is
369
+ still managed by ProseMirror); when it returns false, the mark view
370
+ is rebuilt.
371
+ */
372
+ update?: (mark: Mark) => boolean;
373
+ /**
366
374
  Called when a [mutation](https://prosemirror.net/docs/ref/#view.ViewMutationRecord) happens within the
367
375
  view. Return false if the editor should re-read the selection or re-parse
368
376
  the range around the mutation, true if it can safely be ignored.
package/dist/index.d.ts CHANGED
@@ -363,6 +363,14 @@ interface MarkView {
363
363
  */
364
364
  contentDOM?: HTMLElement | null;
365
365
  /**
366
+ When given, this is called when the view is updating itself. It
367
+ will be given a mark (of the same type as the current one). When it
368
+ returns true, the existing DOM is kept and reused (its content is
369
+ still managed by ProseMirror); when it returns false, the mark view
370
+ is rebuilt.
371
+ */
372
+ update?: (mark: Mark) => boolean;
373
+ /**
366
374
  Called when a [mutation](https://prosemirror.net/docs/ref/#view.ViewMutationRecord) happens within the
367
375
  view. Return false if the editor should re-read the selection or re-parse
368
376
  the range around the mutation, true if it can safely be ignored.
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;;) {
@@ -1273,7 +1276,7 @@ class MarkViewDesc extends ViewDesc {
1273
1276
  // correspond to an actual node in the document. Unlike mark descs,
1274
1277
  // they populate their child array themselves.
1275
1278
  class NodeViewDesc extends ViewDesc {
1276
- constructor(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, view, pos) {
1279
+ constructor(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM) {
1277
1280
  super(parent, [], dom, contentDOM);
1278
1281
  this.node = node;
1279
1282
  this.outerDeco = outerDeco;
@@ -1319,11 +1322,11 @@ class NodeViewDesc extends ViewDesc {
1319
1322
  let nodeDOM = dom;
1320
1323
  dom = applyOuterDeco(dom, outerDeco, node);
1321
1324
  if (spec)
1322
- return descObj = new CustomNodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM || null, nodeDOM, spec, view, pos + 1);
1325
+ return descObj = new CustomNodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM || null, nodeDOM, spec);
1323
1326
  else if (node.isText)
1324
- return new TextViewDesc(parent, node, outerDeco, innerDeco, dom, nodeDOM, view);
1327
+ return new TextViewDesc(parent, node, outerDeco, innerDeco, dom, nodeDOM);
1325
1328
  else
1326
- return new NodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM || null, nodeDOM, view, pos + 1);
1329
+ return new NodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM || null, nodeDOM);
1327
1330
  }
1328
1331
  parseRule() {
1329
1332
  // Experimental kludge to allow opt-in re-parsing of nodes
@@ -1507,14 +1510,14 @@ class NodeViewDesc extends ViewDesc {
1507
1510
  // and used by the view class.
1508
1511
  function docViewDesc(doc, outerDeco, innerDeco, dom, view) {
1509
1512
  applyOuterDeco(dom, outerDeco, doc);
1510
- let docView = new NodeViewDesc(undefined, doc, outerDeco, innerDeco, dom, dom, dom, view, 0);
1513
+ let docView = new NodeViewDesc(undefined, doc, outerDeco, innerDeco, dom, dom, dom);
1511
1514
  if (docView.contentDOM)
1512
1515
  docView.updateChildren(view, 0);
1513
1516
  return docView;
1514
1517
  }
1515
1518
  class TextViewDesc extends NodeViewDesc {
1516
- constructor(parent, node, outerDeco, innerDeco, dom, nodeDOM, view) {
1517
- super(parent, node, outerDeco, innerDeco, dom, null, nodeDOM, view, 0);
1519
+ constructor(parent, node, outerDeco, innerDeco, dom, nodeDOM) {
1520
+ super(parent, node, outerDeco, innerDeco, dom, null, nodeDOM);
1518
1521
  }
1519
1522
  parseRule() {
1520
1523
  let skip = this.nodeDOM.parentNode;
@@ -1554,9 +1557,9 @@ class TextViewDesc extends NodeViewDesc {
1554
1557
  ignoreMutation(mutation) {
1555
1558
  return mutation.type != "characterData" && mutation.type != "selection";
1556
1559
  }
1557
- slice(from, to, view) {
1560
+ slice(from, to, _view) {
1558
1561
  let node = this.node.cut(from, to), dom = document.createTextNode(node.text);
1559
- return new TextViewDesc(this.parent, node, this.outerDeco, this.innerDeco, dom, dom, view);
1562
+ return new TextViewDesc(this.parent, node, this.outerDeco, this.innerDeco, dom, dom);
1560
1563
  }
1561
1564
  markDirty(from, to) {
1562
1565
  super.markDirty(from, to);
@@ -1578,8 +1581,8 @@ class TrailingHackViewDesc extends ViewDesc {
1578
1581
  // extra checks only have to be made for nodes that are actually
1579
1582
  // customized.
1580
1583
  class CustomNodeViewDesc extends NodeViewDesc {
1581
- constructor(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, spec, view, pos) {
1582
- super(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, view, pos);
1584
+ constructor(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, spec) {
1585
+ super(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM);
1583
1586
  this.spec = spec;
1584
1587
  }
1585
1588
  // A custom `update` method gets to decide whether the update goes
@@ -1816,6 +1819,19 @@ class ViewTreeUpdater {
1816
1819
  break;
1817
1820
  }
1818
1821
  }
1822
+ // When nothing matches, try to update the mark view at this position
1823
+ // in place, so a custom mark view can adapt to a changed mark without
1824
+ // re-creating its DOM.
1825
+ if (found < 0 && this.index < this.top.children.length) {
1826
+ let cur = this.top.children[this.index];
1827
+ if (cur instanceof MarkViewDesc && cur.dirty != NODE_DIRTY &&
1828
+ cur.mark.type == marks[depth].type && cur.spec.update &&
1829
+ !this.isLocked(cur.dom) && cur.spec.update(marks[depth])) {
1830
+ cur.mark = marks[depth];
1831
+ found = this.index;
1832
+ this.changed = true;
1833
+ }
1834
+ }
1819
1835
  if (found > -1) {
1820
1836
  if (found > this.index) {
1821
1837
  this.changed = true;
@@ -2984,9 +3000,8 @@ const wrapMap = {
2984
3000
  td: ["table", "tbody", "tr"],
2985
3001
  th: ["table", "tbody", "tr"]
2986
3002
  };
2987
- let _detachedDoc = null;
2988
3003
  function detachedDoc() {
2989
- return _detachedDoc || (_detachedDoc = document.implementation.createHTMLDocument("title"));
3004
+ return document.implementation.createHTMLDocument("title");
2990
3005
  }
2991
3006
  let _policy = null;
2992
3007
  function maybeWrapTrusted(html) {
@@ -3004,7 +3019,7 @@ function readHTML(html) {
3004
3019
  let metas = /^(\s*<meta [^>]*>)*/.exec(html);
3005
3020
  if (metas)
3006
3021
  html = html.slice(metas[0].length);
3007
- let elt = detachedDoc().createElement("div");
3022
+ let doc = detachedDoc(), elt = doc.body;
3008
3023
  let firstTag = /<([a-z][^>\s]+)/i.exec(html), wrap;
3009
3024
  if (wrap = firstTag && wrapMap[firstTag[1].toLowerCase()])
3010
3025
  html = wrap.map(n => "<" + n + ">").join("") + html + wrap.map(n => "</" + n + ">").reverse().join("");
@@ -3012,6 +3027,18 @@ function readHTML(html) {
3012
3027
  if (wrap)
3013
3028
  for (let i = 0; i < wrap.length; i++)
3014
3029
  elt = elt.querySelector(wrap[i]) || elt;
3030
+ // Inline styles defined in the pasted content, so that parse rules pick them up
3031
+ for (let i = 0; i < doc.styleSheets.length; i++) {
3032
+ let style = doc.styleSheets[i];
3033
+ for (let j = 0; j < style.rules.length; j++) {
3034
+ let rule = style.rules[j];
3035
+ if (rule instanceof CSSStyleRule) {
3036
+ let matches = elt.querySelectorAll(rule.selectorText);
3037
+ for (let k = 0; k < matches.length; k++)
3038
+ matches[k].style.cssText += rule.style.cssText;
3039
+ }
3040
+ }
3041
+ }
3015
3042
  return elt;
3016
3043
  }
3017
3044
  // Webkit browsers do some hard-to-predict replacement of regular
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prosemirror-view",
3
- "version": "1.41.9",
3
+ "version": "1.42.1",
4
4
  "description": "ProseMirror's view component",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -0,0 +1,275 @@
1
+ import {Slice, Fragment, DOMParser, DOMSerializer, ResolvedPos, NodeType, Node} from "prosemirror-model"
2
+ import * as browser from "./browser"
3
+ import {EditorView} from "./index"
4
+
5
+ export function serializeForClipboard(view: EditorView, slice: Slice) {
6
+ view.someProp("transformCopied", f => { slice = f(slice!, view) })
7
+
8
+ let context = [], {content, openStart, openEnd} = slice
9
+ while (openStart > 1 && openEnd > 1 && content.childCount == 1 && content.firstChild!.childCount == 1) {
10
+ openStart--
11
+ openEnd--
12
+ let node = content.firstChild!
13
+ context.push(node.type.name, node.attrs != node.type.defaultAttrs ? node.attrs : null)
14
+ content = node.content
15
+ }
16
+
17
+ let serializer = view.someProp("clipboardSerializer") || DOMSerializer.fromSchema(view.state.schema)
18
+ let doc = detachedDoc(), wrap = doc.createElement("div")
19
+ wrap.appendChild(serializer.serializeFragment(content, {document: doc}))
20
+
21
+ let firstChild = wrap.firstChild, needsWrap, wrappers = 0
22
+ while (firstChild && firstChild.nodeType == 1 && (needsWrap = wrapMap[firstChild.nodeName.toLowerCase()])) {
23
+ for (let i = needsWrap.length - 1; i >= 0; i--) {
24
+ let wrapper = doc.createElement(needsWrap[i])
25
+ while (wrap.firstChild) wrapper.appendChild(wrap.firstChild)
26
+ wrap.appendChild(wrapper)
27
+ wrappers++
28
+ }
29
+ firstChild = wrap.firstChild
30
+ }
31
+
32
+ if (firstChild && firstChild.nodeType == 1)
33
+ (firstChild as HTMLElement).setAttribute(
34
+ "data-pm-slice", `${openStart} ${openEnd}${wrappers ? ` -${wrappers}` : ""} ${JSON.stringify(context)}`)
35
+
36
+ let text = view.someProp("clipboardTextSerializer", f => f(slice, view)) ||
37
+ slice.content.textBetween(0, slice.content.size, "\n\n")
38
+
39
+ return {dom: wrap, text, slice}
40
+ }
41
+
42
+ // Read a slice of content from the clipboard (or drop data).
43
+ export function parseFromClipboard(view: EditorView, text: string, html: string | null, plainText: boolean, $context: ResolvedPos) {
44
+ let inCode = $context.parent.type.spec.code
45
+ let dom: HTMLElement | undefined, slice: Slice | undefined
46
+ if (!html && !text) return null
47
+ let asText = !!text && (plainText || inCode || !html)
48
+ if (asText) {
49
+ view.someProp("transformPastedText", f => { text = f(text, inCode || plainText, view) })
50
+ if (inCode) {
51
+ slice = new Slice(Fragment.from(view.state.schema.text(text.replace(/\r\n?/g, "\n"))), 0, 0)
52
+ view.someProp("transformPasted", f => { slice = f(slice!, view, true) })
53
+ return slice
54
+ }
55
+ let parsed = view.someProp("clipboardTextParser", f => f(text, $context, plainText, view))
56
+ if (parsed) {
57
+ slice = parsed
58
+ } else {
59
+ let marks = $context.marks()
60
+ let {schema} = view.state, serializer = DOMSerializer.fromSchema(schema)
61
+ dom = document.createElement("div")
62
+ text.split(/(?:\r\n?|\n)+/).forEach(block => {
63
+ let p = dom!.appendChild(document.createElement("p"))
64
+ if (block) p.appendChild(serializer.serializeNode(schema.text(block, marks)))
65
+ })
66
+ }
67
+ } else {
68
+ view.someProp("transformPastedHTML", f => { html = f(html!, view) })
69
+ dom = readHTML(html!)
70
+ if (browser.webkit) restoreReplacedSpaces(dom)
71
+ }
72
+
73
+ let contextNode = dom && dom.querySelector("[data-pm-slice]")
74
+ let sliceData = contextNode && /^(\d+) (\d+)(?: -(\d+))? (.*)/.exec(contextNode.getAttribute("data-pm-slice") || "")
75
+ if (sliceData && sliceData[3]) for (let i = +sliceData[3]; i > 0; i--) {
76
+ let child = dom!.firstChild
77
+ while (child && child.nodeType != 1) child = child.nextSibling
78
+ if (!child) break
79
+ dom = child as HTMLElement
80
+ }
81
+
82
+ if (!slice) {
83
+ let parser = view.someProp("clipboardParser") || view.someProp("domParser") || DOMParser.fromSchema(view.state.schema)
84
+ slice = parser.parseSlice(dom!, {
85
+ preserveWhitespace: !!(asText || sliceData),
86
+ context: $context,
87
+ ruleFromNode(dom) {
88
+ if (dom.nodeName == "BR" && !dom.nextSibling &&
89
+ dom.parentNode && !inlineParents.test(dom.parentNode.nodeName)) return {ignore: true}
90
+ return null
91
+ }
92
+ })
93
+ }
94
+ if (sliceData) {
95
+ slice = addContext(closeSlice(slice, +sliceData[1], +sliceData[2]), sliceData[4])
96
+ } else { // HTML wasn't created by ProseMirror. Make sure top-level siblings are coherent
97
+ slice = Slice.maxOpen(normalizeSiblings(slice.content, $context), true)
98
+ if (slice.openStart || slice.openEnd) {
99
+ let openStart = 0, openEnd = 0
100
+ for (let node = slice.content.firstChild; openStart < slice.openStart && !node!.type.spec.isolating;
101
+ openStart++, node = node!.firstChild) {}
102
+ for (let node = slice.content.lastChild; openEnd < slice.openEnd && !node!.type.spec.isolating;
103
+ openEnd++, node = node!.lastChild) {}
104
+ slice = closeSlice(slice, openStart, openEnd)
105
+ }
106
+ }
107
+
108
+ view.someProp("transformPasted", f => { slice = f(slice!, view, asText) })
109
+ return slice
110
+ }
111
+
112
+ const inlineParents = /^(a|abbr|acronym|b|cite|code|del|em|i|ins|kbd|label|output|q|ruby|s|samp|span|strong|sub|sup|time|u|tt|var)$/i
113
+
114
+ // Takes a slice parsed with parseSlice, which means there hasn't been
115
+ // any content-expression checking done on the top nodes, tries to
116
+ // find a parent node in the current context that might fit the nodes,
117
+ // and if successful, rebuilds the slice so that it fits into that parent.
118
+ //
119
+ // This addresses the problem that Transform.replace expects a
120
+ // coherent slice, and will fail to place a set of siblings that don't
121
+ // fit anywhere in the schema.
122
+ function normalizeSiblings(fragment: Fragment, $context: ResolvedPos) {
123
+ if (fragment.childCount < 2) return fragment
124
+ for (let d = $context.depth; d >= 0; d--) {
125
+ let parent = $context.node(d)
126
+ let match = parent.contentMatchAt($context.index(d))
127
+ let lastWrap: readonly NodeType[] | undefined, result: Node[] | null = []
128
+ fragment.forEach(node => {
129
+ if (!result) return
130
+ let wrap = match.findWrapping(node.type), inLast
131
+ if (!wrap) return result = null
132
+ if (inLast = result.length && lastWrap!.length && addToSibling(wrap, lastWrap!, node, result[result.length - 1], 0)) {
133
+ result[result.length - 1] = inLast
134
+ } else {
135
+ if (result.length) result[result.length - 1] = closeRight(result[result.length - 1], lastWrap!.length)
136
+ let wrapped = withWrappers(node, wrap)
137
+ result.push(wrapped)
138
+ match = match.matchType(wrapped.type)!
139
+ lastWrap = wrap
140
+ }
141
+ })
142
+ if (result) return Fragment.from(result)
143
+ }
144
+ return fragment
145
+ }
146
+
147
+ function withWrappers(node: Node, wrap: readonly NodeType[], from = 0) {
148
+ for (let i = wrap.length - 1; i >= from; i--)
149
+ node = wrap[i].create(null, Fragment.from(node))
150
+ return node
151
+ }
152
+
153
+ // Used to group adjacent nodes wrapped in similar parents by
154
+ // normalizeSiblings into the same parent node
155
+ function addToSibling(wrap: readonly NodeType[], lastWrap: readonly NodeType[],
156
+ node: Node, sibling: Node, depth: number): Node | undefined {
157
+ if (depth < wrap.length && depth < lastWrap.length && wrap[depth] == lastWrap[depth]) {
158
+ let inner = addToSibling(wrap, lastWrap, node, sibling.lastChild!, depth + 1)
159
+ if (inner) return sibling.copy(sibling.content.replaceChild(sibling.childCount - 1, inner))
160
+ let match = sibling.contentMatchAt(sibling.childCount)
161
+ if (match.matchType(depth == wrap.length - 1 ? node.type : wrap[depth + 1]))
162
+ return sibling.copy(sibling.content.append(Fragment.from(withWrappers(node, wrap, depth + 1))))
163
+ }
164
+ }
165
+
166
+ function closeRight(node: Node, depth: number) {
167
+ if (depth == 0) return node
168
+ let fragment = node.content.replaceChild(node.childCount - 1, closeRight(node.lastChild!, depth - 1))
169
+ let fill = node.contentMatchAt(node.childCount).fillBefore(Fragment.empty, true)!
170
+ return node.copy(fragment.append(fill))
171
+ }
172
+
173
+ function closeRange(fragment: Fragment, side: number, from: number, to: number, depth: number, openEnd: number) {
174
+ let node = side < 0 ? fragment.firstChild! : fragment.lastChild!, inner = node.content
175
+ if (fragment.childCount > 1) openEnd = 0
176
+ if (depth < to - 1) inner = closeRange(inner, side, from, to, depth + 1, openEnd)
177
+ if (depth >= from)
178
+ inner = side < 0 ? node.contentMatchAt(0)!.fillBefore(inner, openEnd <= depth)!.append(inner)
179
+ : inner.append(node.contentMatchAt(node.childCount)!.fillBefore(Fragment.empty, true)!)
180
+ return fragment.replaceChild(side < 0 ? 0 : fragment.childCount - 1, node.copy(inner))
181
+ }
182
+
183
+ function closeSlice(slice: Slice, openStart: number, openEnd: number) {
184
+ if (openStart < slice.openStart)
185
+ slice = new Slice(closeRange(slice.content, -1, openStart, slice.openStart, 0, slice.openEnd), openStart, slice.openEnd)
186
+ if (openEnd < slice.openEnd)
187
+ slice = new Slice(closeRange(slice.content, 1, openEnd, slice.openEnd, 0, 0), slice.openStart, openEnd)
188
+ return slice
189
+ }
190
+
191
+ // Trick from jQuery -- some elements must be wrapped in other
192
+ // elements for innerHTML to work. I.e. if you do `div.innerHTML =
193
+ // "<td>..</td>"` the table cells are ignored.
194
+ const wrapMap: {[node: string]: string[]} = {
195
+ thead: ["table"],
196
+ tbody: ["table"],
197
+ tfoot: ["table"],
198
+ caption: ["table"],
199
+ colgroup: ["table"],
200
+ col: ["table", "colgroup"],
201
+ tr: ["table", "tbody"],
202
+ td: ["table", "tbody", "tr"],
203
+ th: ["table", "tbody", "tr"]
204
+ }
205
+
206
+ function detachedDoc() {
207
+ return document.implementation.createHTMLDocument("title")
208
+ }
209
+
210
+ let _policy: any = null
211
+
212
+ function maybeWrapTrusted(html: string): string {
213
+ let trustedTypes = (window as any).trustedTypes
214
+ if (!trustedTypes) return html
215
+ // With the require-trusted-types-for CSP, Chrome will block
216
+ // innerHTML, even on a detached document. This wraps the string in
217
+ // a way that makes the browser allow us to use its parser again.
218
+ if (!_policy)
219
+ _policy = trustedTypes.defaultPolicy || trustedTypes.createPolicy("ProseMirrorClipboard", {createHTML: (s: string) => s})
220
+ return _policy.createHTML(html)
221
+ }
222
+
223
+ function readHTML(html: string) {
224
+ html = "\n\n" + html + "\n\n"
225
+ let metas = /^(\s*<meta [^>]*>)*/.exec(html)
226
+ if (metas) html = html.slice(metas[0].length)
227
+ let doc = detachedDoc(), elt = doc.createElement("div")
228
+ let firstTag = /<([a-z][^>\s]+)/i.exec(html), wrap
229
+ if (wrap = firstTag && wrapMap[firstTag[1].toLowerCase()])
230
+ html = wrap.map(n => "<" + n + ">").join("") + html + wrap.map(n => "</" + n + ">").reverse().join("")
231
+ elt.innerHTML = maybeWrapTrusted(html)
232
+ if (wrap) for (let i = 0; i < wrap.length; i++) elt = elt.querySelector(wrap[i]) || elt
233
+ // Inline styles defined in the pasted content, so that parse rules pick them up
234
+ for (let i = 0; i < doc.styleSheets.length; i++) {
235
+ let style = doc.styleSheets[i]
236
+ for (let j = 0; j < style.rules.length; j++) {
237
+ let rule = style.rules[j]
238
+ if (rule instanceof CSSStyleRule) {
239
+ let matches = elt.querySelectorAll(rule.selectorText)
240
+ for (let k = 0; k < matches.length; k++)
241
+ (matches[k] as HTMLElement).style.cssText += rule.style.cssText
242
+ }
243
+ }
244
+ }
245
+ return elt
246
+ }
247
+
248
+ // Webkit browsers do some hard-to-predict replacement of regular
249
+ // spaces with non-breaking spaces when putting content on the
250
+ // clipboard. This tries to convert such non-breaking spaces (which
251
+ // will be wrapped in a plain span on Chrome, a span with class
252
+ // Apple-converted-space on Safari) back to regular spaces.
253
+ function restoreReplacedSpaces(dom: HTMLElement) {
254
+ let nodes = dom.querySelectorAll(browser.chrome ? "span:not([class]):not([style])" : "span.Apple-converted-space")
255
+ for (let i = 0; i < nodes.length; i++) {
256
+ let node = nodes[i]
257
+ if (node.childNodes.length == 1 && node.textContent == "\u00a0" && node.parentNode)
258
+ node.parentNode.replaceChild(dom.ownerDocument.createTextNode(" "), node)
259
+ }
260
+ }
261
+
262
+ function addContext(slice: Slice, context: string) {
263
+ if (!slice.size) return slice
264
+ let schema = slice.content.firstChild!.type.schema, array
265
+ try { array = JSON.parse(context) }
266
+ catch(e) { return slice }
267
+ let {content, openStart, openEnd} = slice
268
+ for (let i = array.length - 2; i >= 0; i -= 2) {
269
+ let type = schema.nodes[array[i]]
270
+ if (!type || type.hasRequiredAttrs()) break
271
+ content = Fragment.from(type.create(array[i + 1], content))
272
+ openStart++; openEnd++
273
+ }
274
+ return new Slice(content, openStart, openEnd)
275
+ }
package/src/clipboard.ts CHANGED
@@ -203,9 +203,8 @@ const wrapMap: {[node: string]: string[]} = {
203
203
  th: ["table", "tbody", "tr"]
204
204
  }
205
205
 
206
- let _detachedDoc: Document | null = null
207
206
  function detachedDoc() {
208
- return _detachedDoc || (_detachedDoc = document.implementation.createHTMLDocument("title"))
207
+ return document.implementation.createHTMLDocument("title")
209
208
  }
210
209
 
211
210
  let _policy: any = null
@@ -224,12 +223,24 @@ function maybeWrapTrusted(html: string): string {
224
223
  function readHTML(html: string) {
225
224
  let metas = /^(\s*<meta [^>]*>)*/.exec(html)
226
225
  if (metas) html = html.slice(metas[0].length)
227
- let elt = detachedDoc().createElement("div")
226
+ let doc = detachedDoc(), elt = doc.body
228
227
  let firstTag = /<([a-z][^>\s]+)/i.exec(html), wrap
229
228
  if (wrap = firstTag && wrapMap[firstTag[1].toLowerCase()])
230
229
  html = wrap.map(n => "<" + n + ">").join("") + html + wrap.map(n => "</" + n + ">").reverse().join("")
231
230
  elt.innerHTML = maybeWrapTrusted(html)
232
231
  if (wrap) for (let i = 0; i < wrap.length; i++) elt = elt.querySelector(wrap[i]) || elt
232
+ // Inline styles defined in the pasted content, so that parse rules pick them up
233
+ for (let i = 0; i < doc.styleSheets.length; i++) {
234
+ let style = doc.styleSheets[i]
235
+ for (let j = 0; j < style.rules.length; j++) {
236
+ let rule = style.rules[j]
237
+ if (rule instanceof CSSStyleRule) {
238
+ let matches = elt.querySelectorAll(rule.selectorText)
239
+ for (let k = 0; k < matches.length; k++)
240
+ (matches[k] as HTMLElement).style.cssText += rule.style.cssText
241
+ }
242
+ }
243
+ }
233
244
  return elt
234
245
  }
235
246
 
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/viewdesc.ts CHANGED
@@ -105,6 +105,13 @@ export interface MarkView {
105
105
  /// present, the `dom` property is used as the content DOM.
106
106
  contentDOM?: HTMLElement | null
107
107
 
108
+ /// When given, this is called when the view is updating itself. It
109
+ /// will be given a mark (of the same type as the current one). When it
110
+ /// returns true, the existing DOM is kept and reused (its content is
111
+ /// still managed by ProseMirror); when it returns false, the mark view
112
+ /// is rebuilt.
113
+ update?: (mark: Mark) => boolean
114
+
108
115
  /// Called when a [mutation](#view.ViewMutationRecord) happens within the
109
116
  /// view. Return false if the editor should re-read the selection or re-parse
110
117
  /// the range around the mutation, true if it can safely be ignored.
@@ -610,7 +617,7 @@ class CompositionViewDesc extends ViewDesc {
610
617
  // some cases they will be split more often than would appear
611
618
  // necessary.
612
619
  class MarkViewDesc extends ViewDesc {
613
- constructor(parent: ViewDesc, readonly mark: Mark, dom: HTMLElement, contentDOM: HTMLElement, readonly spec: MarkView) {
620
+ constructor(parent: ViewDesc, public mark: Mark, dom: HTMLElement, contentDOM: HTMLElement, readonly spec: MarkView) {
614
621
  super(parent, [], dom, contentDOM)
615
622
  }
616
623
 
@@ -672,8 +679,6 @@ export class NodeViewDesc extends ViewDesc {
672
679
  dom: DOMNode,
673
680
  contentDOM: HTMLElement | null,
674
681
  readonly nodeDOM: DOMNode,
675
- view: EditorView,
676
- pos: number
677
682
  ) {
678
683
  super(parent, [], dom, contentDOM)
679
684
  }
@@ -715,11 +720,11 @@ export class NodeViewDesc extends ViewDesc {
715
720
 
716
721
  if (spec)
717
722
  return descObj = new CustomNodeViewDesc(parent, node, outerDeco, innerDeco, dom as HTMLElement, contentDOM || null,
718
- nodeDOM as HTMLElement, spec, view, pos + 1)
723
+ nodeDOM as HTMLElement, spec)
719
724
  else if (node.isText)
720
- return new TextViewDesc(parent, node, outerDeco, innerDeco, dom, nodeDOM, view)
725
+ return new TextViewDesc(parent, node, outerDeco, innerDeco, dom, nodeDOM)
721
726
  else
722
- return new NodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM || null, nodeDOM, view, pos + 1)
727
+ return new NodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM || null, nodeDOM)
723
728
  }
724
729
 
725
730
  parseRule() {
@@ -906,15 +911,15 @@ export class NodeViewDesc extends ViewDesc {
906
911
  export function docViewDesc(doc: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource,
907
912
  dom: HTMLElement, view: EditorView): NodeViewDesc {
908
913
  applyOuterDeco(dom, outerDeco, doc)
909
- let docView = new NodeViewDesc(undefined, doc, outerDeco, innerDeco, dom, dom, dom, view, 0)
914
+ let docView = new NodeViewDesc(undefined, doc, outerDeco, innerDeco, dom, dom, dom)
910
915
  if (docView.contentDOM) docView.updateChildren(view, 0)
911
916
  return docView
912
917
  }
913
918
 
914
919
  class TextViewDesc extends NodeViewDesc {
915
920
  constructor(parent: ViewDesc | undefined, node: Node, outerDeco: readonly Decoration[],
916
- innerDeco: DecorationSource, dom: DOMNode, nodeDOM: DOMNode, view: EditorView) {
917
- super(parent, node, outerDeco, innerDeco, dom, null, nodeDOM, view, 0)
921
+ innerDeco: DecorationSource, dom: DOMNode, nodeDOM: DOMNode) {
922
+ super(parent, node, outerDeco, innerDeco, dom, null, nodeDOM)
918
923
  }
919
924
 
920
925
  parseRule() {
@@ -955,9 +960,9 @@ class TextViewDesc extends NodeViewDesc {
955
960
  return mutation.type != "characterData" && mutation.type != "selection"
956
961
  }
957
962
 
958
- slice(from: number, to: number, view: EditorView) {
963
+ slice(from: number, to: number, _view?: EditorView) {
959
964
  let node = this.node.cut(from, to), dom = document.createTextNode(node.text!)
960
- return new TextViewDesc(this.parent, node, this.outerDeco, this.innerDeco, dom, dom, view)
965
+ return new TextViewDesc(this.parent, node, this.outerDeco, this.innerDeco, dom, dom)
961
966
  }
962
967
 
963
968
  markDirty(from: number, to: number) {
@@ -985,9 +990,8 @@ class TrailingHackViewDesc extends ViewDesc {
985
990
  // customized.
986
991
  class CustomNodeViewDesc extends NodeViewDesc {
987
992
  constructor(parent: ViewDesc | undefined, node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource,
988
- dom: HTMLElement, contentDOM: HTMLElement | null, nodeDOM: HTMLElement, readonly spec: NodeView,
989
- view: EditorView, pos: number) {
990
- super(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, view, pos)
993
+ dom: HTMLElement, contentDOM: HTMLElement | null, nodeDOM: HTMLElement, readonly spec: NodeView) {
994
+ super(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM)
991
995
  }
992
996
 
993
997
  // A custom `update` method gets to decide whether the update goes
@@ -1220,6 +1224,19 @@ class ViewTreeUpdater {
1220
1224
  let next = this.top.children[i]
1221
1225
  if (next.matchesMark(marks[depth]) && !this.isLocked(next.dom)) { found = i; break }
1222
1226
  }
1227
+ // When nothing matches, try to update the mark view at this position
1228
+ // in place, so a custom mark view can adapt to a changed mark without
1229
+ // re-creating its DOM.
1230
+ if (found < 0 && this.index < this.top.children.length) {
1231
+ let cur = this.top.children[this.index]
1232
+ if (cur instanceof MarkViewDesc && cur.dirty != NODE_DIRTY &&
1233
+ cur.mark.type == marks[depth].type && cur.spec.update &&
1234
+ !this.isLocked(cur.dom) && cur.spec.update(marks[depth])) {
1235
+ cur.mark = marks[depth]
1236
+ found = this.index
1237
+ this.changed = true
1238
+ }
1239
+ }
1223
1240
  if (found > -1) {
1224
1241
  if (found > this.index) {
1225
1242
  this.changed = true