prosemirror-view 1.41.4 → 1.41.5

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,13 @@
1
+ ## 1.41.5 (2026-01-14)
2
+
3
+ ### Bug fixes
4
+
5
+ Work around an issue where, after some kinds of changes, Chrome misreports the cursor position and breaks composition.
6
+
7
+ Improve DOM update behavior when deleting three or more nodes right before a mark, which would unnecessarily re-render the entire mark.
8
+
9
+ Work around a newly introduced bug in Safari where composition in an empty table cell causes it to move the text out of the cell into the row element.
10
+
1
11
  ## 1.41.4 (2025-12-01)
2
12
 
3
13
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -1338,17 +1338,17 @@ var NodeViewDesc = function (_ViewDesc4) {
1338
1338
  var compositionInChild = composition && composition.pos < 0;
1339
1339
  var updater = new ViewTreeUpdater(this, localComposition && localComposition.node, view);
1340
1340
  iterDeco(this.node, this.innerDeco, function (widget, i, insideNode) {
1341
- if (widget.spec.marks) updater.syncToMarks(widget.spec.marks, inline, view);else if (widget.type.side >= 0 && !insideNode) updater.syncToMarks(i == _this6.node.childCount ? prosemirrorModel.Mark.none : _this6.node.child(i).marks, inline, view);
1341
+ if (widget.spec.marks) updater.syncToMarks(widget.spec.marks, inline, view, i);else if (widget.type.side >= 0 && !insideNode) updater.syncToMarks(i == _this6.node.childCount ? prosemirrorModel.Mark.none : _this6.node.child(i).marks, inline, view, i);
1342
1342
  updater.placeWidget(widget, view, off);
1343
1343
  }, function (child, outerDeco, innerDeco, i) {
1344
- updater.syncToMarks(child.marks, inline, view);
1344
+ updater.syncToMarks(child.marks, inline, view, i);
1345
1345
  var compIndex;
1346
1346
  if (updater.findNodeMatch(child, outerDeco, innerDeco, i)) ;else if (compositionInChild && view.state.selection.from > off && view.state.selection.to < off + child.nodeSize && (compIndex = updater.findIndexWithChild(composition.node)) > -1 && updater.updateNodeAt(child, outerDeco, innerDeco, compIndex, view)) ;else if (updater.updateNextNode(child, outerDeco, innerDeco, view, i, off)) ;else {
1347
1347
  updater.addNode(child, outerDeco, innerDeco, view, off);
1348
1348
  }
1349
1349
  off += child.nodeSize;
1350
1350
  });
1351
- updater.syncToMarks([], inline, view);
1351
+ updater.syncToMarks([], inline, view, 0);
1352
1352
  if (this.node.isTextblock) updater.addTextblockHacks();
1353
1353
  updater.destroyRest();
1354
1354
  if (updater.changed || this.dirty == CONTENT_DIRTY) {
@@ -1786,7 +1786,7 @@ var ViewTreeUpdater = function () {
1786
1786
  }
1787
1787
  }, {
1788
1788
  key: "syncToMarks",
1789
- value: function syncToMarks(marks, inline, view) {
1789
+ value: function syncToMarks(marks, inline, view, parentIndex) {
1790
1790
  var keep = 0,
1791
1791
  depth = this.stack.length >> 1;
1792
1792
  var maxKeep = Math.min(depth, marks.length);
@@ -1800,8 +1800,10 @@ var ViewTreeUpdater = function () {
1800
1800
  }
1801
1801
  while (depth < marks.length) {
1802
1802
  this.stack.push(this.top, this.index + 1);
1803
- var found = -1;
1804
- for (var i = this.index; i < Math.min(this.index + 3, this.top.children.length); i++) {
1803
+ var found = -1,
1804
+ scanTo = this.top.children.length;
1805
+ if (parentIndex < this.preMatch.index) scanTo = Math.min(this.index + 3, scanTo);
1806
+ for (var i = this.index; i < scanTo; i++) {
1805
1807
  var next = this.top.children[i];
1806
1808
  if (next.matchesMark(marks[depth]) && !this.isLocked(next.dom)) {
1807
1809
  found = i;
@@ -2919,6 +2921,7 @@ var InputState = _createClass(function InputState() {
2919
2921
  this.compositionNodes = [];
2920
2922
  this.compositionEndedAt = -2e8;
2921
2923
  this.compositionID = 1;
2924
+ this.badSafariComposition = false;
2922
2925
  this.compositionPendingChanges = 0;
2923
2926
  this.domChangeCount = 0;
2924
2927
  this.eventHandlers = Object.create(null);
@@ -3317,7 +3320,7 @@ editHandlers.compositionend = function (view, event) {
3317
3320
  view.input.compositionEndedAt = event.timeStamp;
3318
3321
  view.input.compositionPendingChanges = view.domObserver.pendingRecords().length ? view.input.compositionID : 0;
3319
3322
  view.input.compositionNode = null;
3320
- if (view.input.compositionPendingChanges) Promise.resolve().then(function () {
3323
+ if (view.input.badSafariComposition) view.domObserver.forceFlush();else if (view.input.compositionPendingChanges) Promise.resolve().then(function () {
3321
3324
  return view.domObserver.flush();
3322
3325
  });
3323
3326
  view.input.compositionID++;
@@ -4235,7 +4238,16 @@ var DOMObserver = function () {
4235
4238
  for (var i = 0; i < mutations.length; i++) _this11.queue.push(mutations[i]);
4236
4239
  if (ie && ie_version <= 11 && mutations.some(function (m) {
4237
4240
  return m.type == "childList" && m.removedNodes.length || m.type == "characterData" && m.oldValue.length > m.target.nodeValue.length;
4238
- })) _this11.flushSoon();else _this11.flush();
4241
+ })) {
4242
+ _this11.flushSoon();
4243
+ } else if (safari && view.composing && mutations.some(function (m) {
4244
+ return m.type == "childList" && m.target.nodeName == "TR";
4245
+ })) {
4246
+ view.input.badSafariComposition = true;
4247
+ _this11.flushSoon();
4248
+ } else {
4249
+ _this11.flush();
4250
+ }
4239
4251
  });
4240
4252
  if (useCharData) {
4241
4253
  this.onCharData = function (e) {
@@ -4446,6 +4458,10 @@ var DOMObserver = function () {
4446
4458
  view.docView.markDirty(from, to);
4447
4459
  checkCSS(view);
4448
4460
  }
4461
+ if (view.input.badSafariComposition) {
4462
+ view.input.badSafariComposition = false;
4463
+ fixUpBadSafariComposition(view, added);
4464
+ }
4449
4465
  this.handleDOMChange(from, to, typeOver, added);
4450
4466
  if (view.docView && view.docView.dirty) view.updateState(view.state);else if (!this.currentSelection.eq(sel)) selectionToDOM(view);
4451
4467
  this.currentSelection.set(sel);
@@ -4559,6 +4575,39 @@ function blockParent(view, node) {
4559
4575
  }
4560
4576
  return null;
4561
4577
  }
4578
+ function fixUpBadSafariComposition(view, addedNodes) {
4579
+ var _a;
4580
+ var _view$domSelectionRan5 = view.domSelectionRange(),
4581
+ focusNode = _view$domSelectionRan5.focusNode,
4582
+ focusOffset = _view$domSelectionRan5.focusOffset;
4583
+ var _iterator5 = _createForOfIteratorHelper(addedNodes),
4584
+ _step5;
4585
+ try {
4586
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
4587
+ var node = _step5.value;
4588
+ if (((_a = node.parentNode) === null || _a === void 0 ? void 0 : _a.nodeName) == "TR") {
4589
+ var nextCell = node.nextSibling;
4590
+ while (nextCell && nextCell.nodeName != "TD" && nextCell.nodeName != "TH") nextCell = nextCell.nextSibling;
4591
+ if (nextCell) {
4592
+ var parent = nextCell;
4593
+ for (;;) {
4594
+ var first = parent.firstChild;
4595
+ if (!first || first.nodeType != 1 || first.contentEditable == "false" || /^(BR|IMG)$/.test(first.nodeName)) break;
4596
+ parent = first;
4597
+ }
4598
+ parent.insertBefore(node, parent.firstChild);
4599
+ if (focusNode == node) view.domSelection().collapse(node, focusOffset);
4600
+ } else {
4601
+ node.parentNode.removeChild(node);
4602
+ }
4603
+ }
4604
+ }
4605
+ } catch (err) {
4606
+ _iterator5.e(err);
4607
+ } finally {
4608
+ _iterator5.f();
4609
+ }
4610
+ }
4562
4611
  function parseBetween(view, from_, to_) {
4563
4612
  var _view$docView$parseRa = view.docView.parseRange(from_, to_),
4564
4613
  parent = _view$docView$parseRa.node,
@@ -5014,7 +5063,7 @@ var EditorView = function () {
5014
5063
  this.docView.destroy();
5015
5064
  this.docView = docViewDesc(state.doc, outerDeco, innerDeco, this.dom, this);
5016
5065
  }
5017
- if (chromeKludge && !this.trackWrites) forceSelUpdate = true;
5066
+ if (chromeKludge && (!this.trackWrites || !this.dom.contains(this.trackWrites))) forceSelUpdate = true;
5018
5067
  }
5019
5068
  if (forceSelUpdate || !(this.input.mouseDown && this.domObserver.currentSelection.eq(this.domSelectionRange()) && anchorInRightPlace(this))) {
5020
5069
  selectionToDOM(this, forceSelUpdate);
package/dist/index.js CHANGED
@@ -1376,15 +1376,15 @@ class NodeViewDesc extends ViewDesc {
1376
1376
  let updater = new ViewTreeUpdater(this, localComposition && localComposition.node, view);
1377
1377
  iterDeco(this.node, this.innerDeco, (widget, i, insideNode) => {
1378
1378
  if (widget.spec.marks)
1379
- updater.syncToMarks(widget.spec.marks, inline, view);
1379
+ updater.syncToMarks(widget.spec.marks, inline, view, i);
1380
1380
  else if (widget.type.side >= 0 && !insideNode)
1381
- updater.syncToMarks(i == this.node.childCount ? Mark.none : this.node.child(i).marks, inline, view);
1381
+ updater.syncToMarks(i == this.node.childCount ? Mark.none : this.node.child(i).marks, inline, view, i);
1382
1382
  // If the next node is a desc matching this widget, reuse it,
1383
1383
  // otherwise insert the widget as a new view desc.
1384
1384
  updater.placeWidget(widget, view, off);
1385
1385
  }, (child, outerDeco, innerDeco, i) => {
1386
1386
  // Make sure the wrapping mark descs match the node's marks.
1387
- updater.syncToMarks(child.marks, inline, view);
1387
+ updater.syncToMarks(child.marks, inline, view, i);
1388
1388
  // Try several strategies for drawing this node
1389
1389
  let compIndex;
1390
1390
  if (updater.findNodeMatch(child, outerDeco, innerDeco, i)) ;
@@ -1400,7 +1400,7 @@ class NodeViewDesc extends ViewDesc {
1400
1400
  off += child.nodeSize;
1401
1401
  });
1402
1402
  // Drop all remaining descs after the current position.
1403
- updater.syncToMarks([], inline, view);
1403
+ updater.syncToMarks([], inline, view, 0);
1404
1404
  if (this.node.isTextblock)
1405
1405
  updater.addTextblockHacks();
1406
1406
  updater.destroyRest();
@@ -1790,7 +1790,7 @@ class ViewTreeUpdater {
1790
1790
  }
1791
1791
  // Sync the current stack of mark descs with the given array of
1792
1792
  // marks, reusing existing mark descs when possible.
1793
- syncToMarks(marks, inline, view) {
1793
+ syncToMarks(marks, inline, view, parentIndex) {
1794
1794
  let keep = 0, depth = this.stack.length >> 1;
1795
1795
  let maxKeep = Math.min(depth, marks.length);
1796
1796
  while (keep < maxKeep &&
@@ -1806,8 +1806,10 @@ class ViewTreeUpdater {
1806
1806
  }
1807
1807
  while (depth < marks.length) {
1808
1808
  this.stack.push(this.top, this.index + 1);
1809
- let found = -1;
1810
- for (let i = this.index; i < Math.min(this.index + 3, this.top.children.length); i++) {
1809
+ let found = -1, scanTo = this.top.children.length;
1810
+ if (parentIndex < this.preMatch.index)
1811
+ scanTo = Math.min(this.index + 3, scanTo);
1812
+ for (let i = this.index; i < scanTo; i++) {
1811
1813
  let next = this.top.children[i];
1812
1814
  if (next.matchesMark(marks[depth]) && !this.isLocked(next.dom)) {
1813
1815
  found = i;
@@ -2003,9 +2005,7 @@ class ViewTreeUpdater {
2003
2005
  }
2004
2006
  // Iterate from the end of the fragment and array of descs to find
2005
2007
  // directly matching ones, in order to avoid overeagerly reusing those
2006
- // for other nodes. Returns the fragment index of the first node that
2007
- // is part of the sequence of matched nodes at the end of the
2008
- // fragment.
2008
+ // for other nodes.
2009
2009
  function preMatch(frag, parentDesc) {
2010
2010
  let curDesc = parentDesc, descI = curDesc.children.length;
2011
2011
  let fI = frag.childCount, matched = new Map, matches = [];
@@ -2028,7 +2028,6 @@ function preMatch(frag, parentDesc) {
2028
2028
  break outer;
2029
2029
  }
2030
2030
  else {
2031
- // FIXME
2032
2031
  descI = curDesc.parent.children.indexOf(curDesc);
2033
2032
  curDesc = curDesc.parent;
2034
2033
  }
@@ -3075,6 +3074,7 @@ class InputState {
3075
3074
  this.compositionNodes = [];
3076
3075
  this.compositionEndedAt = -2e8;
3077
3076
  this.compositionID = 1;
3077
+ this.badSafariComposition = false;
3078
3078
  // Set to a composition ID when there are pending changes at compositionend
3079
3079
  this.compositionPendingChanges = 0;
3080
3080
  this.domChangeCount = 0;
@@ -3522,7 +3522,9 @@ editHandlers.compositionend = (view, event) => {
3522
3522
  view.input.compositionEndedAt = event.timeStamp;
3523
3523
  view.input.compositionPendingChanges = view.domObserver.pendingRecords().length ? view.input.compositionID : 0;
3524
3524
  view.input.compositionNode = null;
3525
- if (view.input.compositionPendingChanges)
3525
+ if (view.input.badSafariComposition)
3526
+ view.domObserver.forceFlush();
3527
+ else if (view.input.compositionPendingChanges)
3526
3528
  Promise.resolve().then(() => view.domObserver.flush());
3527
3529
  view.input.compositionID++;
3528
3530
  scheduleComposeEnd(view, 20);
@@ -4585,15 +4587,24 @@ class DOMObserver {
4585
4587
  new window.MutationObserver(mutations => {
4586
4588
  for (let i = 0; i < mutations.length; i++)
4587
4589
  this.queue.push(mutations[i]);
4588
- // IE11 will sometimes (on backspacing out a single character
4589
- // text node after a BR node) call the observer callback
4590
- // before actually updating the DOM, which will cause
4591
- // ProseMirror to miss the change (see #930)
4592
4590
  if (ie && ie_version <= 11 && mutations.some(m => m.type == "childList" && m.removedNodes.length ||
4593
- m.type == "characterData" && m.oldValue.length > m.target.nodeValue.length))
4591
+ m.type == "characterData" && m.oldValue.length > m.target.nodeValue.length)) {
4592
+ // IE11 will sometimes (on backspacing out a single character
4593
+ // text node after a BR node) call the observer callback
4594
+ // before actually updating the DOM, which will cause
4595
+ // ProseMirror to miss the change (see #930)
4594
4596
  this.flushSoon();
4595
- else
4597
+ }
4598
+ else if (safari && view.composing && mutations.some(m => m.type == "childList" && m.target.nodeName == "TR")) {
4599
+ // Safari does weird stuff when finishing a composition in a
4600
+ // table cell, which tends to involve inserting inappropriate
4601
+ // nodes in the table row.
4602
+ view.input.badSafariComposition = true;
4603
+ this.flushSoon();
4604
+ }
4605
+ else {
4596
4606
  this.flush();
4607
+ }
4597
4608
  });
4598
4609
  if (useCharData) {
4599
4610
  this.onCharData = e => {
@@ -4760,6 +4771,10 @@ class DOMObserver {
4760
4771
  view.docView.markDirty(from, to);
4761
4772
  checkCSS(view);
4762
4773
  }
4774
+ if (view.input.badSafariComposition) {
4775
+ view.input.badSafariComposition = false;
4776
+ fixUpBadSafariComposition(view, added);
4777
+ }
4763
4778
  this.handleDOMChange(from, to, typeOver, added);
4764
4779
  if (view.docView && view.docView.dirty)
4765
4780
  view.updateState(view.state);
@@ -4883,6 +4898,37 @@ function blockParent(view, node) {
4883
4898
  }
4884
4899
  return null;
4885
4900
  }
4901
+ // Kludge for a Safari bug where, on ending a composition in an
4902
+ // otherwise empty table cell, it randomly moves the composed text
4903
+ // into the table row around that cell, greatly confusing everything
4904
+ // (#188).
4905
+ function fixUpBadSafariComposition(view, addedNodes) {
4906
+ var _a;
4907
+ let { focusNode, focusOffset } = view.domSelectionRange();
4908
+ for (let node of addedNodes) {
4909
+ if (((_a = node.parentNode) === null || _a === void 0 ? void 0 : _a.nodeName) == "TR") {
4910
+ let nextCell = node.nextSibling;
4911
+ while (nextCell && (nextCell.nodeName != "TD" && nextCell.nodeName != "TH"))
4912
+ nextCell = nextCell.nextSibling;
4913
+ if (nextCell) {
4914
+ let parent = nextCell;
4915
+ for (;;) {
4916
+ let first = parent.firstChild;
4917
+ if (!first || first.nodeType != 1 || first.contentEditable == "false" ||
4918
+ /^(BR|IMG)$/.test(first.nodeName))
4919
+ break;
4920
+ parent = first;
4921
+ }
4922
+ parent.insertBefore(node, parent.firstChild);
4923
+ if (focusNode == node)
4924
+ view.domSelection().collapse(node, focusOffset);
4925
+ }
4926
+ else {
4927
+ node.parentNode.removeChild(node);
4928
+ }
4929
+ }
4930
+ }
4931
+ }
4886
4932
 
4887
4933
  // Note that all referencing and parsing is done with the
4888
4934
  // start-of-operation selection and document, since that's the one
@@ -5455,7 +5501,7 @@ class EditorView {
5455
5501
  this.docView.destroy();
5456
5502
  this.docView = docViewDesc(state.doc, outerDeco, innerDeco, this.dom, this);
5457
5503
  }
5458
- if (chromeKludge && !this.trackWrites)
5504
+ if (chromeKludge && (!this.trackWrites || !this.dom.contains(this.trackWrites)))
5459
5505
  forceSelUpdate = true;
5460
5506
  }
5461
5507
  // Work around for an issue where an update arriving right between
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prosemirror-view",
3
- "version": "1.41.4",
3
+ "version": "1.41.5",
4
4
  "description": "ProseMirror's view component",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
package/src/domchange.ts CHANGED
@@ -81,7 +81,7 @@ const isInline = /^(a|abbr|acronym|b|bd[io]|big|br|button|cite|code|data(list)?|
81
81
  export function readDOMChange(view: EditorView, from: number, to: number, typeOver: boolean, addedNodes: readonly DOMNode[]) {
82
82
  let compositionID = view.input.compositionPendingChanges || (view.composing ? view.input.compositionID : 0)
83
83
  view.input.compositionPendingChanges = 0
84
-
84
+
85
85
  if (from < 0) {
86
86
  let origin = view.input.lastSelectionTime > Date.now() - 50 ? view.input.lastSelectionOrigin : null
87
87
  let newSel = selectionFromDOM(view, origin)
@@ -52,16 +52,24 @@ export class DOMObserver {
52
52
  this.observer = window.MutationObserver &&
53
53
  new window.MutationObserver(mutations => {
54
54
  for (let i = 0; i < mutations.length; i++) this.queue.push(mutations[i])
55
- // IE11 will sometimes (on backspacing out a single character
56
- // text node after a BR node) call the observer callback
57
- // before actually updating the DOM, which will cause
58
- // ProseMirror to miss the change (see #930)
59
55
  if (browser.ie && browser.ie_version <= 11 && mutations.some(
60
56
  m => m.type == "childList" && m.removedNodes.length ||
61
- m.type == "characterData" && m.oldValue!.length > m.target.nodeValue!.length))
57
+ m.type == "characterData" && m.oldValue!.length > m.target.nodeValue!.length)) {
58
+ // IE11 will sometimes (on backspacing out a single character
59
+ // text node after a BR node) call the observer callback
60
+ // before actually updating the DOM, which will cause
61
+ // ProseMirror to miss the change (see #930)
62
62
  this.flushSoon()
63
- else
63
+ } else if (browser.safari && view.composing && mutations.some(
64
+ m => m.type == "childList" && m.target.nodeName == "TR")) {
65
+ // Safari does weird stuff when finishing a composition in a
66
+ // table cell, which tends to involve inserting inappropriate
67
+ // nodes in the table row.
68
+ view.input.badSafariComposition = true
69
+ this.flushSoon()
70
+ } else {
64
71
  this.flush()
72
+ }
65
73
  })
66
74
  if (useCharData) {
67
75
  this.onCharData = e => {
@@ -226,6 +234,10 @@ export class DOMObserver {
226
234
  view.docView.markDirty(from, to)
227
235
  checkCSS(view)
228
236
  }
237
+ if (view.input.badSafariComposition) {
238
+ view.input.badSafariComposition = false
239
+ fixUpBadSafariComposition(view, added)
240
+ }
229
241
  this.handleDOMChange(from, to, typeOver, added)
230
242
  if (view.docView && view.docView.dirty) view.updateState(view.state)
231
243
  else if (!this.currentSelection.eq(sel)) selectionToDOM(view)
@@ -347,3 +359,30 @@ function blockParent(view: EditorView, node: DOMNode): Node | null {
347
359
  }
348
360
  return null
349
361
  }
362
+
363
+ // Kludge for a Safari bug where, on ending a composition in an
364
+ // otherwise empty table cell, it randomly moves the composed text
365
+ // into the table row around that cell, greatly confusing everything
366
+ // (#188).
367
+ function fixUpBadSafariComposition(view: EditorView, addedNodes: readonly DOMNode[]) {
368
+ let {focusNode, focusOffset} = view.domSelectionRange()
369
+ for (let node of addedNodes) {
370
+ if (node.parentNode?.nodeName == "TR") {
371
+ let nextCell = node.nextSibling
372
+ while (nextCell && (nextCell.nodeName != "TD" && nextCell.nodeName != "TH")) nextCell = nextCell.nextSibling
373
+ if (nextCell) {
374
+ let parent = nextCell
375
+ for (;;) {
376
+ let first = parent.firstChild
377
+ if (!first || first.nodeType != 1 || (first as HTMLElement).contentEditable == "false" ||
378
+ /^(BR|IMG)$/.test(first.nodeName)) break
379
+ parent = first
380
+ }
381
+ parent.insertBefore(node, parent.firstChild)
382
+ if (focusNode == node) view.domSelection()!.collapse(node, focusOffset)
383
+ } else {
384
+ node.parentNode.removeChild(node)
385
+ }
386
+ }
387
+ }
388
+ }
package/src/index.ts CHANGED
@@ -202,7 +202,7 @@ export class EditorView {
202
202
  this.docView.destroy()
203
203
  this.docView = docViewDesc(state.doc, outerDeco, innerDeco, this.dom, this)
204
204
  }
205
- if (chromeKludge && !this.trackWrites) forceSelUpdate = true
205
+ if (chromeKludge && (!this.trackWrites || !this.dom.contains(this.trackWrites))) forceSelUpdate = true
206
206
  }
207
207
  // Work around for an issue where an update arriving right between
208
208
  // a DOM selection change and the "selectionchange" event for it
package/src/input.ts CHANGED
@@ -35,6 +35,7 @@ export class InputState {
35
35
  compositionNodes: ViewDesc[] = []
36
36
  compositionEndedAt = -2e8
37
37
  compositionID = 1
38
+ badSafariComposition = false
38
39
  // Set to a composition ID when there are pending changes at compositionend
39
40
  compositionPendingChanges = 0
40
41
  domChangeCount = 0
@@ -504,7 +505,8 @@ editHandlers.compositionend = (view, event) => {
504
505
  view.input.compositionEndedAt = event.timeStamp
505
506
  view.input.compositionPendingChanges = view.domObserver.pendingRecords().length ? view.input.compositionID : 0
506
507
  view.input.compositionNode = null
507
- if (view.input.compositionPendingChanges) Promise.resolve().then(() => view.domObserver.flush())
508
+ if (view.input.badSafariComposition) view.domObserver.forceFlush()
509
+ else if (view.input.compositionPendingChanges) Promise.resolve().then(() => view.domObserver.flush())
508
510
  view.input.compositionID++
509
511
  scheduleComposeEnd(view, 20)
510
512
  }
package/src/viewdesc.ts CHANGED
@@ -772,15 +772,15 @@ export class NodeViewDesc extends ViewDesc {
772
772
  let updater = new ViewTreeUpdater(this, localComposition && localComposition.node, view)
773
773
  iterDeco(this.node, this.innerDeco, (widget, i, insideNode) => {
774
774
  if (widget.spec.marks)
775
- updater.syncToMarks(widget.spec.marks, inline, view)
775
+ updater.syncToMarks(widget.spec.marks, inline, view, i)
776
776
  else if ((widget.type as WidgetType).side >= 0 && !insideNode)
777
- updater.syncToMarks(i == this.node.childCount ? Mark.none : this.node.child(i).marks, inline, view)
777
+ updater.syncToMarks(i == this.node.childCount ? Mark.none : this.node.child(i).marks, inline, view, i)
778
778
  // If the next node is a desc matching this widget, reuse it,
779
779
  // otherwise insert the widget as a new view desc.
780
780
  updater.placeWidget(widget, view, off)
781
781
  }, (child, outerDeco, innerDeco, i) => {
782
782
  // Make sure the wrapping mark descs match the node's marks.
783
- updater.syncToMarks(child.marks, inline, view)
783
+ updater.syncToMarks(child.marks, inline, view, i)
784
784
  // Try several strategies for drawing this node
785
785
  let compIndex
786
786
  if (updater.findNodeMatch(child, outerDeco, innerDeco, i)) {
@@ -799,7 +799,7 @@ export class NodeViewDesc extends ViewDesc {
799
799
  off += child.nodeSize
800
800
  })
801
801
  // Drop all remaining descs after the current position.
802
- updater.syncToMarks([], inline, view)
802
+ updater.syncToMarks([], inline, view, 0)
803
803
  if (this.node.isTextblock) updater.addTextblockHacks()
804
804
  updater.destroyRest()
805
805
 
@@ -1197,7 +1197,7 @@ class ViewTreeUpdater {
1197
1197
 
1198
1198
  // Sync the current stack of mark descs with the given array of
1199
1199
  // marks, reusing existing mark descs when possible.
1200
- syncToMarks(marks: readonly Mark[], inline: boolean, view: EditorView) {
1200
+ syncToMarks(marks: readonly Mark[], inline: boolean, view: EditorView, parentIndex: number) {
1201
1201
  let keep = 0, depth = this.stack.length >> 1
1202
1202
  let maxKeep = Math.min(depth, marks.length)
1203
1203
  while (keep < maxKeep &&
@@ -1214,8 +1214,9 @@ class ViewTreeUpdater {
1214
1214
  }
1215
1215
  while (depth < marks.length) {
1216
1216
  this.stack.push(this.top, this.index + 1)
1217
- let found = -1
1218
- for (let i = this.index; i < Math.min(this.index + 3, this.top.children.length); i++) {
1217
+ let found = -1, scanTo = this.top.children.length
1218
+ if (parentIndex < this.preMatch.index) scanTo = Math.min(this.index + 3, scanTo)
1219
+ for (let i = this.index; i < scanTo; i++) {
1219
1220
  let next = this.top.children[i]
1220
1221
  if (next.matchesMark(marks[depth]) && !this.isLocked(next.dom)) { found = i; break }
1221
1222
  }
@@ -1404,12 +1405,16 @@ class ViewTreeUpdater {
1404
1405
 
1405
1406
  // Iterate from the end of the fragment and array of descs to find
1406
1407
  // directly matching ones, in order to avoid overeagerly reusing those
1407
- // for other nodes. Returns the fragment index of the first node that
1408
- // is part of the sequence of matched nodes at the end of the
1409
- // fragment.
1410
- function preMatch(
1411
- frag: Fragment, parentDesc: ViewDesc
1412
- ): {index: number, matched: Map<ViewDesc, number>, matches: readonly ViewDesc[]} {
1408
+ // for other nodes.
1409
+ function preMatch(frag: Fragment, parentDesc: ViewDesc): {
1410
+ // The fragment index of the first node that is part of the sequence
1411
+ // of matched nodes at the end of the fragment.
1412
+ index: number,
1413
+ // A map from matched descs to fragment indices.
1414
+ matched: Map<ViewDesc, number>,
1415
+ // The matched descs.
1416
+ matches: readonly ViewDesc[]
1417
+ } {
1413
1418
  let curDesc = parentDesc, descI = curDesc.children.length
1414
1419
  let fI = frag.childCount, matched = new Map, matches = []
1415
1420
  outer: while (fI > 0) {
@@ -1428,7 +1433,6 @@ function preMatch(
1428
1433
  } else if (curDesc == parentDesc) {
1429
1434
  break outer
1430
1435
  } else {
1431
- // FIXME
1432
1436
  descI = curDesc.parent!.children.indexOf(curDesc)
1433
1437
  curDesc = curDesc.parent!
1434
1438
  }