prosemirror-view 1.41.4 → 1.41.6
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 +16 -0
- package/dist/index.cjs +83 -34
- package/dist/index.js +76 -31
- package/package.json +1 -1
- package/src/domchange.ts +1 -1
- package/src/domobserver.ts +54 -16
- package/src/index.ts +1 -1
- package/src/input.ts +3 -1
- package/src/viewdesc.ts +18 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
## 1.41.6 (2026-02-06)
|
|
2
|
+
|
|
3
|
+
### Bug fixes
|
|
4
|
+
|
|
5
|
+
Fix an issue where backspacing content before a flexbox or grid styled widget would insert a bogus line break on Firefox.
|
|
6
|
+
|
|
7
|
+
## 1.41.5 (2026-01-14)
|
|
8
|
+
|
|
9
|
+
### Bug fixes
|
|
10
|
+
|
|
11
|
+
Work around an issue where, after some kinds of changes, Chrome misreports the cursor position and breaks composition.
|
|
12
|
+
|
|
13
|
+
Improve DOM update behavior when deleting three or more nodes right before a mark, which would unnecessarily re-render the entire mark.
|
|
14
|
+
|
|
15
|
+
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.
|
|
16
|
+
|
|
1
17
|
## 1.41.4 (2025-12-01)
|
|
2
18
|
|
|
3
19
|
### 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
|
-
|
|
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
|
-
}))
|
|
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) {
|
|
@@ -4391,7 +4403,25 @@ var DOMObserver = function () {
|
|
|
4391
4403
|
}
|
|
4392
4404
|
}
|
|
4393
4405
|
}
|
|
4394
|
-
if (
|
|
4406
|
+
if (added.some(function (n) {
|
|
4407
|
+
return n.nodeName == "BR";
|
|
4408
|
+
}) && (view.input.lastKeyCode == 8 || view.input.lastKeyCode == 46)) {
|
|
4409
|
+
var _iterator3 = _createForOfIteratorHelper(added),
|
|
4410
|
+
_step3;
|
|
4411
|
+
try {
|
|
4412
|
+
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
4413
|
+
var node = _step3.value;
|
|
4414
|
+
if (node.nodeName == "BR" && node.parentNode) {
|
|
4415
|
+
var after = node.nextSibling;
|
|
4416
|
+
if (after && after.nodeType == 1 && after.contentEditable == "false") node.parentNode.removeChild(node);
|
|
4417
|
+
}
|
|
4418
|
+
}
|
|
4419
|
+
} catch (err) {
|
|
4420
|
+
_iterator3.e(err);
|
|
4421
|
+
} finally {
|
|
4422
|
+
_iterator3.f();
|
|
4423
|
+
}
|
|
4424
|
+
} else if (gecko && added.length) {
|
|
4395
4425
|
var brs = added.filter(function (n) {
|
|
4396
4426
|
return n.nodeName == "BR";
|
|
4397
4427
|
});
|
|
@@ -4402,38 +4432,20 @@ var DOMObserver = function () {
|
|
|
4402
4432
|
if (a.parentNode && a.parentNode.parentNode == b.parentNode) b.remove();else a.remove();
|
|
4403
4433
|
} else {
|
|
4404
4434
|
var focusNode = this.currentSelection.focusNode;
|
|
4405
|
-
var
|
|
4406
|
-
|
|
4435
|
+
var _iterator4 = _createForOfIteratorHelper(brs),
|
|
4436
|
+
_step4;
|
|
4407
4437
|
try {
|
|
4408
|
-
for (
|
|
4409
|
-
var br =
|
|
4438
|
+
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
|
|
4439
|
+
var br = _step4.value;
|
|
4410
4440
|
var parent = br.parentNode;
|
|
4411
4441
|
if (parent && parent.nodeName == "LI" && (!focusNode || blockParent(view, focusNode) != parent)) br.remove();
|
|
4412
4442
|
}
|
|
4413
4443
|
} catch (err) {
|
|
4414
|
-
|
|
4444
|
+
_iterator4.e(err);
|
|
4415
4445
|
} finally {
|
|
4416
|
-
|
|
4446
|
+
_iterator4.f();
|
|
4417
4447
|
}
|
|
4418
4448
|
}
|
|
4419
|
-
} else if ((chrome || safari) && added.some(function (n) {
|
|
4420
|
-
return n.nodeName == "BR";
|
|
4421
|
-
}) && (view.input.lastKeyCode == 8 || view.input.lastKeyCode == 46)) {
|
|
4422
|
-
var _iterator4 = _createForOfIteratorHelper(added),
|
|
4423
|
-
_step4;
|
|
4424
|
-
try {
|
|
4425
|
-
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
|
|
4426
|
-
var node = _step4.value;
|
|
4427
|
-
if (node.nodeName == "BR" && node.parentNode) {
|
|
4428
|
-
var after = node.nextSibling;
|
|
4429
|
-
if (after && after.nodeType == 1 && after.contentEditable == "false") node.parentNode.removeChild(node);
|
|
4430
|
-
}
|
|
4431
|
-
}
|
|
4432
|
-
} catch (err) {
|
|
4433
|
-
_iterator4.e(err);
|
|
4434
|
-
} finally {
|
|
4435
|
-
_iterator4.f();
|
|
4436
|
-
}
|
|
4437
4449
|
}
|
|
4438
4450
|
var readSel = null;
|
|
4439
4451
|
if (from < 0 && newSel && view.input.lastFocus > Date.now() - 200 && Math.max(view.input.lastTouch, view.input.lastClick.time) < Date.now() - 300 && selectionCollapsed(sel) && (readSel = selectionFromDOM(view)) && readSel.eq(prosemirrorState.Selection.near(view.state.doc.resolve(0), 1))) {
|
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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 => {
|
|
@@ -4713,7 +4724,17 @@ class DOMObserver {
|
|
|
4713
4724
|
}
|
|
4714
4725
|
}
|
|
4715
4726
|
}
|
|
4716
|
-
if (
|
|
4727
|
+
if (added.some(n => n.nodeName == "BR") && (view.input.lastKeyCode == 8 || view.input.lastKeyCode == 46)) {
|
|
4728
|
+
// Browsers sometimes insert a bogus break node if you
|
|
4729
|
+
// backspace out the last bit of text before an inline-flex node (#1552)
|
|
4730
|
+
for (let node of added)
|
|
4731
|
+
if (node.nodeName == "BR" && node.parentNode) {
|
|
4732
|
+
let after = node.nextSibling;
|
|
4733
|
+
if (after && after.nodeType == 1 && after.contentEditable == "false")
|
|
4734
|
+
node.parentNode.removeChild(node);
|
|
4735
|
+
}
|
|
4736
|
+
}
|
|
4737
|
+
else if (gecko && added.length) {
|
|
4717
4738
|
let brs = added.filter(n => n.nodeName == "BR");
|
|
4718
4739
|
if (brs.length == 2) {
|
|
4719
4740
|
let [a, b] = brs;
|
|
@@ -4731,17 +4752,6 @@ class DOMObserver {
|
|
|
4731
4752
|
}
|
|
4732
4753
|
}
|
|
4733
4754
|
}
|
|
4734
|
-
else if ((chrome || safari) && added.some(n => n.nodeName == "BR") &&
|
|
4735
|
-
(view.input.lastKeyCode == 8 || view.input.lastKeyCode == 46)) {
|
|
4736
|
-
// Chrome/Safari sometimes insert a bogus break node if you
|
|
4737
|
-
// backspace out the last bit of text before an inline-flex node (#1552)
|
|
4738
|
-
for (let node of added)
|
|
4739
|
-
if (node.nodeName == "BR" && node.parentNode) {
|
|
4740
|
-
let after = node.nextSibling;
|
|
4741
|
-
if (after && after.nodeType == 1 && after.contentEditable == "false")
|
|
4742
|
-
node.parentNode.removeChild(node);
|
|
4743
|
-
}
|
|
4744
|
-
}
|
|
4745
4755
|
let readSel = null;
|
|
4746
4756
|
// If it looks like the browser has reset the selection to the
|
|
4747
4757
|
// start of the document after focus, restore the selection from
|
|
@@ -4760,6 +4770,10 @@ class DOMObserver {
|
|
|
4760
4770
|
view.docView.markDirty(from, to);
|
|
4761
4771
|
checkCSS(view);
|
|
4762
4772
|
}
|
|
4773
|
+
if (view.input.badSafariComposition) {
|
|
4774
|
+
view.input.badSafariComposition = false;
|
|
4775
|
+
fixUpBadSafariComposition(view, added);
|
|
4776
|
+
}
|
|
4763
4777
|
this.handleDOMChange(from, to, typeOver, added);
|
|
4764
4778
|
if (view.docView && view.docView.dirty)
|
|
4765
4779
|
view.updateState(view.state);
|
|
@@ -4883,6 +4897,37 @@ function blockParent(view, node) {
|
|
|
4883
4897
|
}
|
|
4884
4898
|
return null;
|
|
4885
4899
|
}
|
|
4900
|
+
// Kludge for a Safari bug where, on ending a composition in an
|
|
4901
|
+
// otherwise empty table cell, it randomly moves the composed text
|
|
4902
|
+
// into the table row around that cell, greatly confusing everything
|
|
4903
|
+
// (#188).
|
|
4904
|
+
function fixUpBadSafariComposition(view, addedNodes) {
|
|
4905
|
+
var _a;
|
|
4906
|
+
let { focusNode, focusOffset } = view.domSelectionRange();
|
|
4907
|
+
for (let node of addedNodes) {
|
|
4908
|
+
if (((_a = node.parentNode) === null || _a === void 0 ? void 0 : _a.nodeName) == "TR") {
|
|
4909
|
+
let nextCell = node.nextSibling;
|
|
4910
|
+
while (nextCell && (nextCell.nodeName != "TD" && nextCell.nodeName != "TH"))
|
|
4911
|
+
nextCell = nextCell.nextSibling;
|
|
4912
|
+
if (nextCell) {
|
|
4913
|
+
let parent = nextCell;
|
|
4914
|
+
for (;;) {
|
|
4915
|
+
let first = parent.firstChild;
|
|
4916
|
+
if (!first || first.nodeType != 1 || first.contentEditable == "false" ||
|
|
4917
|
+
/^(BR|IMG)$/.test(first.nodeName))
|
|
4918
|
+
break;
|
|
4919
|
+
parent = first;
|
|
4920
|
+
}
|
|
4921
|
+
parent.insertBefore(node, parent.firstChild);
|
|
4922
|
+
if (focusNode == node)
|
|
4923
|
+
view.domSelection().collapse(node, focusOffset);
|
|
4924
|
+
}
|
|
4925
|
+
else {
|
|
4926
|
+
node.parentNode.removeChild(node);
|
|
4927
|
+
}
|
|
4928
|
+
}
|
|
4929
|
+
}
|
|
4930
|
+
}
|
|
4886
4931
|
|
|
4887
4932
|
// Note that all referencing and parsing is done with the
|
|
4888
4933
|
// start-of-operation selection and document, since that's the one
|
|
@@ -5455,7 +5500,7 @@ class EditorView {
|
|
|
5455
5500
|
this.docView.destroy();
|
|
5456
5501
|
this.docView = docViewDesc(state.doc, outerDeco, innerDeco, this.dom, this);
|
|
5457
5502
|
}
|
|
5458
|
-
if (chromeKludge && !this.trackWrites)
|
|
5503
|
+
if (chromeKludge && (!this.trackWrites || !this.dom.contains(this.trackWrites)))
|
|
5459
5504
|
forceSelUpdate = true;
|
|
5460
5505
|
}
|
|
5461
5506
|
// Work around for an issue where an update arriving right between
|
package/package.json
CHANGED
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)
|
package/src/domobserver.ts
CHANGED
|
@@ -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 => {
|
|
@@ -184,7 +192,15 @@ export class DOMObserver {
|
|
|
184
192
|
}
|
|
185
193
|
}
|
|
186
194
|
|
|
187
|
-
if (
|
|
195
|
+
if (added.some(n => n.nodeName == "BR") && (view.input.lastKeyCode == 8 || view.input.lastKeyCode == 46)) {
|
|
196
|
+
// Browsers sometimes insert a bogus break node if you
|
|
197
|
+
// backspace out the last bit of text before an inline-flex node (#1552)
|
|
198
|
+
for (let node of added) if (node.nodeName == "BR" && node.parentNode) {
|
|
199
|
+
let after = node.nextSibling
|
|
200
|
+
if (after && after.nodeType == 1 && (after as HTMLElement).contentEditable == "false")
|
|
201
|
+
node.parentNode.removeChild(node)
|
|
202
|
+
}
|
|
203
|
+
} else if (browser.gecko && added.length) {
|
|
188
204
|
let brs = added.filter(n => n.nodeName == "BR") as HTMLElement[]
|
|
189
205
|
if (brs.length == 2) {
|
|
190
206
|
let [a, b] = brs
|
|
@@ -198,15 +214,6 @@ export class DOMObserver {
|
|
|
198
214
|
br.remove()
|
|
199
215
|
}
|
|
200
216
|
}
|
|
201
|
-
} else if ((browser.chrome || browser.safari) && added.some(n => n.nodeName == "BR") &&
|
|
202
|
-
(view.input.lastKeyCode == 8 || view.input.lastKeyCode == 46)) {
|
|
203
|
-
// Chrome/Safari sometimes insert a bogus break node if you
|
|
204
|
-
// backspace out the last bit of text before an inline-flex node (#1552)
|
|
205
|
-
for (let node of added) if (node.nodeName == "BR" && node.parentNode) {
|
|
206
|
-
let after = node.nextSibling
|
|
207
|
-
if (after && after.nodeType == 1 && (after as HTMLElement).contentEditable == "false")
|
|
208
|
-
node.parentNode.removeChild(node)
|
|
209
|
-
}
|
|
210
217
|
}
|
|
211
218
|
|
|
212
219
|
let readSel: Selection | null = null
|
|
@@ -226,6 +233,10 @@ export class DOMObserver {
|
|
|
226
233
|
view.docView.markDirty(from, to)
|
|
227
234
|
checkCSS(view)
|
|
228
235
|
}
|
|
236
|
+
if (view.input.badSafariComposition) {
|
|
237
|
+
view.input.badSafariComposition = false
|
|
238
|
+
fixUpBadSafariComposition(view, added)
|
|
239
|
+
}
|
|
229
240
|
this.handleDOMChange(from, to, typeOver, added)
|
|
230
241
|
if (view.docView && view.docView.dirty) view.updateState(view.state)
|
|
231
242
|
else if (!this.currentSelection.eq(sel)) selectionToDOM(view)
|
|
@@ -347,3 +358,30 @@ function blockParent(view: EditorView, node: DOMNode): Node | null {
|
|
|
347
358
|
}
|
|
348
359
|
return null
|
|
349
360
|
}
|
|
361
|
+
|
|
362
|
+
// Kludge for a Safari bug where, on ending a composition in an
|
|
363
|
+
// otherwise empty table cell, it randomly moves the composed text
|
|
364
|
+
// into the table row around that cell, greatly confusing everything
|
|
365
|
+
// (#188).
|
|
366
|
+
function fixUpBadSafariComposition(view: EditorView, addedNodes: readonly DOMNode[]) {
|
|
367
|
+
let {focusNode, focusOffset} = view.domSelectionRange()
|
|
368
|
+
for (let node of addedNodes) {
|
|
369
|
+
if (node.parentNode?.nodeName == "TR") {
|
|
370
|
+
let nextCell = node.nextSibling
|
|
371
|
+
while (nextCell && (nextCell.nodeName != "TD" && nextCell.nodeName != "TH")) nextCell = nextCell.nextSibling
|
|
372
|
+
if (nextCell) {
|
|
373
|
+
let parent = nextCell
|
|
374
|
+
for (;;) {
|
|
375
|
+
let first = parent.firstChild
|
|
376
|
+
if (!first || first.nodeType != 1 || (first as HTMLElement).contentEditable == "false" ||
|
|
377
|
+
/^(BR|IMG)$/.test(first.nodeName)) break
|
|
378
|
+
parent = first
|
|
379
|
+
}
|
|
380
|
+
parent.insertBefore(node, parent.firstChild)
|
|
381
|
+
if (focusNode == node) view.domSelection()!.collapse(node, focusOffset)
|
|
382
|
+
} else {
|
|
383
|
+
node.parentNode.removeChild(node)
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
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.
|
|
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
|
-
|
|
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.
|
|
1408
|
-
|
|
1409
|
-
// fragment
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
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
|
}
|