prosemirror-view 1.30.1 → 1.31.0

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,29 @@
1
+ ## 1.31.0 (2023-04-26)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix an issue where, when the browser failed to return a cursor for a set of coordinates (as in a shadow root in Chrome) the library's own algorithm worked poorly for coordinates between lines in a textblock.
6
+
7
+ Improve performance of changing a parent node (by type or attributes) without modifying its children by reusing the DOM of the child nodes.
8
+
9
+ Fix an issue where, in right-to-left context, the left and right arrow keys could get stuck on a selectable leaf node because the editor was moving the selection in the wrong direction.
10
+
11
+ Fix a regression in `coordsAtPos` when querying the position of a line wrap.
12
+
13
+ ### New features
14
+
15
+ Attach a `"composition"` meta field to transactions generated from DOM changes.
16
+
17
+ ## 1.30.2 (2023-03-13)
18
+
19
+ ### Bug fixes
20
+
21
+ Fix an issue where `posAtCoords` could incorrectly return positions outside of a table when querying a coordinate inside it.
22
+
23
+ Update the type of the `getPos` callback to include undefined in its return type. Update broken link (ViewProps -> EditorProps) Also detect iOS enter when the new block type is a less common node
24
+
25
+ Fix an issue where Enter on iOS would sometimes not be handled by the library.
26
+
1
27
  ## 1.30.1 (2023-02-04)
2
28
 
3
29
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
4
4
 
5
+ function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }
6
+
7
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
8
+
9
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
10
+
5
11
  function _get() { if (typeof Reflect !== "undefined" && Reflect.get) { _get = Reflect.get; } else { _get = function _get(target, property, receiver) { var base = _superPropBase(target, property); if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property); if (desc.get) { return desc.get.call(arguments.length < 3 ? target : receiver); } return desc.value; }; } return _get.apply(this, arguments); }
6
12
 
7
13
  function _superPropBase(object, property) { while (!Object.prototype.hasOwnProperty.call(object, property)) { object = _getPrototypeOf(object); if (object === null) break; } return object; }
@@ -129,6 +135,26 @@ function deepActiveElement(doc) {
129
135
  return elt;
130
136
  }
131
137
 
138
+ function caretFromPoint(doc, x, y) {
139
+ if (doc.caretPositionFromPoint) {
140
+ try {
141
+ var pos = doc.caretPositionFromPoint(x, y);
142
+ if (pos) return {
143
+ node: pos.offsetNode,
144
+ offset: pos.offset
145
+ };
146
+ } catch (_) {}
147
+ }
148
+
149
+ if (doc.caretRangeFromPoint) {
150
+ var range = doc.caretRangeFromPoint(x, y);
151
+ if (range) return {
152
+ node: range.startContainer,
153
+ offset: range.startOffset
154
+ };
155
+ }
156
+ }
157
+
132
158
  var nav = typeof navigator != "undefined" ? navigator : null;
133
159
  var doc = typeof document != "undefined" ? document : null;
134
160
  var agent = nav && nav.userAgent || "";
@@ -147,6 +173,7 @@ var chrome_version = _chrome ? +_chrome[1] : 0;
147
173
  var safari = !ie && !!nav && /Apple Computer/.test(nav.vendor);
148
174
  var ios = safari && (/Mobile\/\w+/.test(agent) || !!nav && nav.maxTouchPoints > 2);
149
175
  var mac = ios || (nav ? /Mac/.test(nav.platform) : false);
176
+ var windows = nav ? /Win/.test(nav.platform) : false;
150
177
  var android = /Android \d/.test(agent);
151
178
  var webkit = !!doc && "webkitFontSmoothing" in doc.documentElement.style;
152
179
  var webkit_version = webkit ? +(/\bAppleWebKit\/(\d+)/.exec(navigator.userAgent) || [0, 0])[1] : 0;
@@ -303,6 +330,7 @@ function findOffsetInNode(node, coords) {
303
330
  offset = 0;
304
331
  var rowBot = coords.top,
305
332
  rowTop = coords.top;
333
+ var firstBelow, coordsBelow;
306
334
 
307
335
  for (var child = node.firstChild, childIndex = 0; child; child = child.nextSibling, childIndex++) {
308
336
  var rects = void 0;
@@ -326,12 +354,24 @@ function findOffsetInNode(node, coords) {
326
354
  if (child.nodeType == 1 && dx) offset = childIndex + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0);
327
355
  continue;
328
356
  }
357
+ } else if (rect.top > coords.top && !firstBelow && rect.left <= coords.left && rect.right >= coords.left) {
358
+ firstBelow = child;
359
+ coordsBelow = {
360
+ left: Math.max(rect.left, Math.min(rect.right, coords.left)),
361
+ top: rect.top
362
+ };
329
363
  }
330
364
 
331
365
  if (!closest && (coords.left >= rect.right && coords.top >= rect.top || coords.left >= rect.left && coords.top >= rect.bottom)) offset = childIndex + 1;
332
366
  }
333
367
  }
334
368
 
369
+ if (!closest && firstBelow) {
370
+ closest = firstBelow;
371
+ coordsClosest = coordsBelow;
372
+ dxClosest = 0;
373
+ }
374
+
335
375
  if (closest && closest.nodeType == 3) return findOffsetInText(closest, coordsClosest);
336
376
  if (!closest || dxClosest && closest.nodeType == 1) return {
337
377
  node: node,
@@ -388,15 +428,16 @@ function posFromElement(view, elt, coords) {
388
428
  function posFromCaret(view, node, offset, coords) {
389
429
  var outsideBlock = -1;
390
430
 
391
- for (var cur = node;;) {
431
+ for (var cur = node, sawBlock = false;;) {
392
432
  if (cur == view.dom) break;
393
433
  var desc = view.docView.nearestDesc(cur, true);
394
434
  if (!desc) return null;
395
435
 
396
- if (desc.dom.nodeType == 1 && (desc.node.isBlock && desc.parent || !desc.contentDOM)) {
436
+ if (desc.dom.nodeType == 1 && (desc.node.isBlock && desc.parent && !sawBlock || !desc.contentDOM)) {
397
437
  var rect = desc.dom.getBoundingClientRect();
398
438
 
399
- if (desc.node.isBlock && desc.parent) {
439
+ if (desc.node.isBlock && desc.parent && !sawBlock) {
440
+ sawBlock = true;
400
441
  if (rect.left > coords.left || rect.top > coords.top) outsideBlock = desc.posBefore;else if (rect.right < coords.left || rect.bottom < coords.top) outsideBlock = desc.posAfter;
401
442
  }
402
443
 
@@ -439,25 +480,11 @@ function _posAtCoords(view, coords) {
439
480
  var doc = view.dom.ownerDocument,
440
481
  node,
441
482
  offset = 0;
483
+ var caret = caretFromPoint(doc, coords.left, coords.top);
442
484
 
443
- if (doc.caretPositionFromPoint) {
444
- try {
445
- var _pos = doc.caretPositionFromPoint(coords.left, coords.top);
446
-
447
- if (_pos) {
448
- node = _pos.offsetNode;
449
- offset = _pos.offset;
450
- }
451
- } catch (_) {}
452
- }
453
-
454
- if (!node && doc.caretRangeFromPoint) {
455
- var range = doc.caretRangeFromPoint(coords.left, coords.top);
456
-
457
- if (range) {
458
- node = range.startContainer;
459
- offset = range.startOffset;
460
- }
485
+ if (caret) {
486
+ node = caret.node;
487
+ offset = caret.offset;
461
488
  }
462
489
 
463
490
  var elt = (view.root.elementFromPoint ? view.root : doc).elementFromPoint(coords.left, coords.top);
@@ -501,9 +528,19 @@ function _posAtCoords(view, coords) {
501
528
  };
502
529
  }
503
530
 
531
+ function nonZero(rect) {
532
+ return rect.top < rect.bottom || rect.left < rect.right;
533
+ }
534
+
504
535
  function singleRect(target, bias) {
505
536
  var rects = target.getClientRects();
506
- return !rects.length ? target.getBoundingClientRect() : rects[bias < 0 ? 0 : rects.length - 1];
537
+
538
+ if (rects.length) {
539
+ var first = rects[bias < 0 ? 0 : rects.length - 1];
540
+ if (nonZero(first)) return first;
541
+ }
542
+
543
+ return Array.prototype.find.call(rects, nonZero) || target.getBoundingClientRect();
507
544
  }
508
545
 
509
546
  var BIDI = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
@@ -547,7 +584,7 @@ function _coordsAtPos(view, pos, side) {
547
584
  to++;
548
585
  }
549
586
 
550
- return flattenV(singleRect(textRange(node, from, to), 1), takeSide < 0);
587
+ return flattenV(singleRect(textRange(node, from, to), takeSide), takeSide < 0);
551
588
  }
552
589
  }
553
590
 
@@ -1424,7 +1461,6 @@ var NodeViewDesc = function (_ViewDesc4) {
1424
1461
  _this4.outerDeco = outerDeco;
1425
1462
  _this4.innerDeco = innerDeco;
1426
1463
  _this4.nodeDOM = nodeDOM;
1427
- if (contentDOM) _this4.updateChildren(view, pos);
1428
1464
  return _this4;
1429
1465
  }
1430
1466
 
@@ -1495,7 +1531,7 @@ var NodeViewDesc = function (_ViewDesc4) {
1495
1531
  }, function (child, outerDeco, innerDeco, i) {
1496
1532
  updater.syncToMarks(child.marks, inline, view);
1497
1533
  var compIndex;
1498
- 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)) ;else {
1534
+ 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 {
1499
1535
  updater.addNode(child, outerDeco, innerDeco, view, off);
1500
1536
  }
1501
1537
  off += child.nodeSize;
@@ -1649,7 +1685,9 @@ var NodeViewDesc = function (_ViewDesc4) {
1649
1685
 
1650
1686
  function docViewDesc(doc, outerDeco, innerDeco, dom, view) {
1651
1687
  applyOuterDeco(dom, outerDeco, doc);
1652
- return new NodeViewDesc(undefined, doc, outerDeco, innerDeco, dom, dom, dom, view, 0);
1688
+ var docView = new NodeViewDesc(undefined, doc, outerDeco, innerDeco, dom, dom, dom, view, 0);
1689
+ if (docView.contentDOM) docView.updateChildren(view, 0);
1690
+ return docView;
1653
1691
  }
1654
1692
 
1655
1693
  var TextViewDesc = function (_NodeViewDesc) {
@@ -2133,7 +2171,7 @@ var ViewTreeUpdater = function () {
2133
2171
  }
2134
2172
  }, {
2135
2173
  key: "updateNextNode",
2136
- value: function updateNextNode(node, outerDeco, innerDeco, view, index) {
2174
+ value: function updateNextNode(node, outerDeco, innerDeco, view, index, pos) {
2137
2175
  for (var i = this.index; i < this.top.children.length; i++) {
2138
2176
  var next = this.top.children[i];
2139
2177
 
@@ -2141,7 +2179,8 @@ var ViewTreeUpdater = function () {
2141
2179
  var _preMatch = this.preMatch.matched.get(next);
2142
2180
 
2143
2181
  if (_preMatch != null && _preMatch != index) return false;
2144
- var nextDOM = next.dom;
2182
+ var nextDOM = next.dom,
2183
+ updated = void 0;
2145
2184
  var locked = this.isLocked(nextDOM) && !(node.isText && next.node && next.node.isText && next.nodeDOM.nodeValue == node.text && next.dirty != NODE_DIRTY && sameOuterDeco(outerDeco, next.outerDeco));
2146
2185
 
2147
2186
  if (!locked && next.update(node, outerDeco, innerDeco, view)) {
@@ -2149,6 +2188,11 @@ var ViewTreeUpdater = function () {
2149
2188
  if (next.dom != nextDOM) this.changed = true;
2150
2189
  this.index++;
2151
2190
  return true;
2191
+ } else if (!locked && (updated = this.recreateWrapper(next, node, outerDeco, innerDeco, view, pos))) {
2192
+ this.top.children[this.index] = updated;
2193
+ this.changed = true;
2194
+ this.index++;
2195
+ return true;
2152
2196
  }
2153
2197
 
2154
2198
  break;
@@ -2157,10 +2201,40 @@ var ViewTreeUpdater = function () {
2157
2201
 
2158
2202
  return false;
2159
2203
  }
2204
+ }, {
2205
+ key: "recreateWrapper",
2206
+ value: function recreateWrapper(next, node, outerDeco, innerDeco, view, pos) {
2207
+ if (next.dirty || node.isAtom || !next.children.length || !next.node.content.eq(node.content)) return null;
2208
+ var wrapper = NodeViewDesc.create(this.top, node, outerDeco, innerDeco, view, pos);
2209
+ if (!wrapper.contentDOM) return null;
2210
+ wrapper.children = next.children;
2211
+ next.children = [];
2212
+
2213
+ var _iterator = _createForOfIteratorHelper(wrapper.children),
2214
+ _step;
2215
+
2216
+ try {
2217
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
2218
+ var ch = _step.value;
2219
+ ch.parent = wrapper;
2220
+ }
2221
+ } catch (err) {
2222
+ _iterator.e(err);
2223
+ } finally {
2224
+ _iterator.f();
2225
+ }
2226
+
2227
+ wrapper.dirty = CONTENT_DIRTY;
2228
+ wrapper.updateChildren(view, pos + 1);
2229
+ wrapper.dirty = NOT_DIRTY;
2230
+ return wrapper;
2231
+ }
2160
2232
  }, {
2161
2233
  key: "addNode",
2162
2234
  value: function addNode(node, outerDeco, innerDeco, view, pos) {
2163
- this.top.children.splice(this.index++, 0, NodeViewDesc.create(this.top, node, outerDeco, innerDeco, view, pos));
2235
+ var desc = NodeViewDesc.create(this.top, node, outerDeco, innerDeco, view, pos);
2236
+ if (desc.contentDOM) desc.updateChildren(view, pos + 1);
2237
+ this.top.children.splice(this.index++, 0, desc);
2164
2238
  this.changed = true;
2165
2239
  }
2166
2240
  }, {
@@ -2664,7 +2738,7 @@ function selectHorizontally(view, dir, mods) {
2664
2738
  if (sel instanceof prosemirrorState.TextSelection) {
2665
2739
  if (!sel.empty || mods.indexOf("s") > -1) {
2666
2740
  return false;
2667
- } else if (view.endOfTextblock(dir > 0 ? "right" : "left")) {
2741
+ } else if (view.endOfTextblock(dir > 0 ? "forward" : "backward")) {
2668
2742
  var next = moveSelectionBlock(view.state, dir);
2669
2743
  if (next && next instanceof prosemirrorState.NodeSelection) return apply(view, next);
2670
2744
  return false;
@@ -2703,7 +2777,11 @@ function isIgnorable(dom) {
2703
2777
  return desc && desc.size == 0 && (dom.nextSibling || dom.nodeName != "BR");
2704
2778
  }
2705
2779
 
2706
- function skipIgnoredNodesLeft(view) {
2780
+ function skipIgnoredNodes(view, dir) {
2781
+ return dir < 0 ? skipIgnoredNodesBefore(view) : skipIgnoredNodesAfter(view);
2782
+ }
2783
+
2784
+ function skipIgnoredNodesBefore(view) {
2707
2785
  var sel = view.domSelectionRange();
2708
2786
  var node = sel.focusNode,
2709
2787
  offset = sel.focusOffset;
@@ -2753,7 +2831,7 @@ function skipIgnoredNodesLeft(view) {
2753
2831
  if (force) setSelFocus(view, node, offset);else if (moveNode) setSelFocus(view, moveNode, moveOffset);
2754
2832
  }
2755
2833
 
2756
- function skipIgnoredNodesRight(view) {
2834
+ function skipIgnoredNodesAfter(view) {
2757
2835
  var sel = view.domSelectionRange();
2758
2836
  var node = sel.focusNode,
2759
2837
  offset = sel.focusOffset;
@@ -2821,6 +2899,31 @@ function setSelFocus(view, node, offset) {
2821
2899
  }, 50);
2822
2900
  }
2823
2901
 
2902
+ function findDirection(view, pos) {
2903
+ var $pos = view.state.doc.resolve(pos);
2904
+
2905
+ if (!(chrome || windows) && $pos.parent.inlineContent) {
2906
+ var coords = view.coordsAtPos(pos);
2907
+
2908
+ if (pos > $pos.start()) {
2909
+ var before = view.coordsAtPos(pos - 1);
2910
+ var mid = (before.top + before.bottom) / 2;
2911
+ if (mid > coords.top && mid < coords.bottom && Math.abs(before.left - coords.left) > 1) return before.left < coords.left ? "ltr" : "rtl";
2912
+ }
2913
+
2914
+ if (pos < $pos.end()) {
2915
+ var after = view.coordsAtPos(pos + 1);
2916
+
2917
+ var _mid = (after.top + after.bottom) / 2;
2918
+
2919
+ if (_mid > coords.top && _mid < coords.bottom && Math.abs(after.left - coords.left) > 1) return after.left > coords.left ? "ltr" : "rtl";
2920
+ }
2921
+ }
2922
+
2923
+ var computed = getComputedStyle(view.dom).direction;
2924
+ return computed == "rtl" ? "rtl" : "ltr";
2925
+ }
2926
+
2824
2927
  function selectVertically(view, dir, mods) {
2825
2928
  var sel = view.state.selection;
2826
2929
  if (sel instanceof prosemirrorState.TextSelection && !sel.empty || mods.indexOf("s") > -1) return false;
@@ -2901,19 +3004,22 @@ function captureKeyDown(view, event) {
2901
3004
  mods = getMods(event);
2902
3005
 
2903
3006
  if (code == 8 || mac && code == 72 && mods == "c") {
2904
- return stopNativeHorizontalDelete(view, -1) || skipIgnoredNodesLeft(view);
3007
+ return stopNativeHorizontalDelete(view, -1) || skipIgnoredNodes(view, -1);
2905
3008
  } else if (code == 46 || mac && code == 68 && mods == "c") {
2906
- return stopNativeHorizontalDelete(view, 1) || skipIgnoredNodesRight(view);
3009
+ return stopNativeHorizontalDelete(view, 1) || skipIgnoredNodes(view, 1);
2907
3010
  } else if (code == 13 || code == 27) {
2908
3011
  return true;
2909
3012
  } else if (code == 37 || mac && code == 66 && mods == "c") {
2910
- return selectHorizontally(view, -1, mods) || skipIgnoredNodesLeft(view);
3013
+ var dir = code == 37 ? findDirection(view, view.state.selection.from) == "ltr" ? -1 : 1 : -1;
3014
+ return selectHorizontally(view, dir, mods) || skipIgnoredNodes(view, dir);
2911
3015
  } else if (code == 39 || mac && code == 70 && mods == "c") {
2912
- return selectHorizontally(view, 1, mods) || skipIgnoredNodesRight(view);
3016
+ var _dir = code == 39 ? findDirection(view, view.state.selection.from) == "ltr" ? 1 : -1 : 1;
3017
+
3018
+ return selectHorizontally(view, _dir, mods) || skipIgnoredNodes(view, _dir);
2913
3019
  } else if (code == 38 || mac && code == 80 && mods == "c") {
2914
- return selectVertically(view, -1, mods) || skipIgnoredNodesLeft(view);
3020
+ return selectVertically(view, -1, mods) || skipIgnoredNodes(view, -1);
2915
3021
  } else if (code == 40 || mac && code == 78 && mods == "c") {
2916
- return safariDownArrowBug(view) || selectVertically(view, 1, mods) || skipIgnoredNodesRight(view);
3022
+ return safariDownArrowBug(view) || selectVertically(view, 1, mods) || skipIgnoredNodesAfter(view);
2917
3023
  } else if (mods == (mac ? "m" : "c") && (code == 66 || code == 73 || code == 89 || code == 90)) {
2918
3024
  return true;
2919
3025
  }
@@ -3240,6 +3346,7 @@ var InputState = _createClass(function InputState() {
3240
3346
  this.composingTimeout = -1;
3241
3347
  this.compositionNodes = [];
3242
3348
  this.compositionEndedAt = -2e8;
3349
+ this.compositionID = 1;
3243
3350
  this.domChangeCount = 0;
3244
3351
  this.eventHandlers = Object.create(null);
3245
3352
  this.hideSelectionGuard = null;
@@ -3703,6 +3810,7 @@ editHandlers.compositionend = function (view, event) {
3703
3810
  if (view.composing) {
3704
3811
  view.input.composing = false;
3705
3812
  view.input.compositionEndedAt = event.timeStamp;
3813
+ view.input.compositionID++;
3706
3814
  scheduleComposeEnd(view, 20);
3707
3815
  }
3708
3816
  };
@@ -5169,6 +5277,8 @@ function ruleFromNode(dom) {
5169
5277
  return null;
5170
5278
  }
5171
5279
 
5280
+ var isInline = /^(a|abbr|acronym|b|bd[io]|big|br|button|cite|code|data(list)?|del|dfn|em|i|ins|kbd|label|map|mark|meter|output|q|ruby|s|samp|small|span|strong|su[bp]|time|u|tt|var)$/i;
5281
+
5172
5282
  function readDOMChange(view, from, to, typeOver, addedNodes) {
5173
5283
  if (from < 0) {
5174
5284
  var origin = view.input.lastSelectionTime > Date.now() - 50 ? view.input.lastSelectionOrigin : null;
@@ -5182,6 +5292,7 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
5182
5292
  var _tr = view.state.tr.setSelection(newSel);
5183
5293
 
5184
5294
  if (origin == "pointer") _tr.setMeta("pointer", true);else if (origin == "key") _tr.scrollIntoView();
5295
+ if (view.composing) _tr.setMeta("composition", view.input.compositionID);
5185
5296
  view.dispatch(_tr);
5186
5297
  }
5187
5298
 
@@ -5210,7 +5321,7 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
5210
5321
  var change = findDiff(compare.content, parse.doc.content, parse.from, preferredPos, preferredSide);
5211
5322
 
5212
5323
  if ((ios && view.input.lastIOSEnter > Date.now() - 225 || android) && addedNodes.some(function (n) {
5213
- return n.nodeName == "DIV" || n.nodeName == "P" || n.nodeName == "LI";
5324
+ return n.nodeType == 1 && !isInline.test(n.nodeName);
5214
5325
  }) && (!change || change.endA >= change.endB) && view.someProp("handleKeyDown", function (f) {
5215
5326
  return f(view, keyEvent(13, "Enter"));
5216
5327
  })) {
@@ -5229,7 +5340,12 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
5229
5340
  if (parse.sel) {
5230
5341
  var _sel = resolveSelection(view, view.state.doc, parse.sel);
5231
5342
 
5232
- if (_sel && !_sel.eq(view.state.selection)) view.dispatch(view.state.tr.setSelection(_sel));
5343
+ if (_sel && !_sel.eq(view.state.selection)) {
5344
+ var _tr2 = view.state.tr.setSelection(_sel);
5345
+
5346
+ if (view.composing) _tr2.setMeta("composition", view.input.compositionID);
5347
+ view.dispatch(_tr2);
5348
+ }
5233
5349
  }
5234
5350
 
5235
5351
  return;
@@ -5331,6 +5447,7 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
5331
5447
  }
5332
5448
 
5333
5449
  if (storedMarks) tr.ensureMarks(storedMarks);
5450
+ if (view.composing) tr.setMeta("composition", view.input.compositionID);
5334
5451
  view.dispatch(tr.scrollIntoView());
5335
5452
  }
5336
5453
 
package/dist/index.d.ts CHANGED
@@ -601,12 +601,12 @@ declare class EditorView {
601
601
  dispatch(tr: Transaction): void;
602
602
  }
603
603
  /**
604
- The type of function [provided](https://prosemirror.net/docs/ref/#view.ViewProps.nodeViews) to
604
+ The type of function [provided](https://prosemirror.net/docs/ref/#view.EditorProps.nodeViews) to
605
605
  create [node views](https://prosemirror.net/docs/ref/#view.NodeView).
606
606
  */
607
- declare type NodeViewConstructor = (node: Node, view: EditorView, getPos: () => number, decorations: readonly Decoration[], innerDecorations: DecorationSource) => NodeView;
607
+ declare type NodeViewConstructor = (node: Node, view: EditorView, getPos: () => number | undefined, decorations: readonly Decoration[], innerDecorations: DecorationSource) => NodeView;
608
608
  /**
609
- The function types [used](https://prosemirror.net/docs/ref/#view.ViewProps.markViews) to create
609
+ The function types [used](https://prosemirror.net/docs/ref/#view.EditorProps.markViews) to create
610
610
  mark views.
611
611
  */
612
612
  declare type MarkViewConstructor = (mark: Mark, view: EditorView, inline: boolean) => {
@@ -767,6 +767,8 @@ interface EditorProps<P = any> {
767
767
  node's display behavior. The third argument `getPos` is a
768
768
  function that can be called to get the node's current position,
769
769
  which can be useful when creating transactions to update it.
770
+ Note that if the node is not in the document, the position
771
+ returned by this function will be `undefined`.
770
772
 
771
773
  `decorations` is an array of node or inline decorations that are
772
774
  active around the node. They are automatically drawn in the
@@ -782,7 +784,7 @@ interface EditorProps<P = any> {
782
784
  inner decorations.
783
785
 
784
786
  (For backwards compatibility reasons, [mark
785
- views](https://prosemirror.net/docs/ref/#view.ViewProps.markViews) can also be included in this
787
+ views](https://prosemirror.net/docs/ref/#view.EditorProps.markViews) can also be included in this
786
788
  object.)
787
789
  */
788
790
  nodeViews?: {
package/dist/index.js CHANGED
@@ -94,6 +94,21 @@ function deepActiveElement(doc) {
94
94
  elt = elt.shadowRoot.activeElement;
95
95
  return elt;
96
96
  }
97
+ function caretFromPoint(doc, x, y) {
98
+ if (doc.caretPositionFromPoint) {
99
+ try { // Firefox throws for this call in hard-to-predict circumstances (#994)
100
+ let pos = doc.caretPositionFromPoint(x, y);
101
+ if (pos)
102
+ return { node: pos.offsetNode, offset: pos.offset };
103
+ }
104
+ catch (_) { }
105
+ }
106
+ if (doc.caretRangeFromPoint) {
107
+ let range = doc.caretRangeFromPoint(x, y);
108
+ if (range)
109
+ return { node: range.startContainer, offset: range.startOffset };
110
+ }
111
+ }
97
112
 
98
113
  const nav = typeof navigator != "undefined" ? navigator : null;
99
114
  const doc = typeof document != "undefined" ? document : null;
@@ -112,6 +127,7 @@ const safari = !ie && !!nav && /Apple Computer/.test(nav.vendor);
112
127
  // Is true for both iOS and iPadOS for convenience
113
128
  const ios = safari && (/Mobile\/\w+/.test(agent) || !!nav && nav.maxTouchPoints > 2);
114
129
  const mac = ios || (nav ? /Mac/.test(nav.platform) : false);
130
+ const windows = nav ? /Win/.test(nav.platform) : false;
115
131
  const android = /Android \d/.test(agent);
116
132
  const webkit = !!doc && "webkitFontSmoothing" in doc.documentElement.style;
117
133
  const webkit_version = webkit ? +(/\bAppleWebKit\/(\d+)/.exec(navigator.userAgent) || [0, 0])[1] : 0;
@@ -237,6 +253,7 @@ function focusPreventScroll(dom) {
237
253
  function findOffsetInNode(node, coords) {
238
254
  let closest, dxClosest = 2e8, coordsClosest, offset = 0;
239
255
  let rowBot = coords.top, rowTop = coords.top;
256
+ let firstBelow, coordsBelow;
240
257
  for (let child = node.firstChild, childIndex = 0; child; child = child.nextSibling, childIndex++) {
241
258
  let rects;
242
259
  if (child.nodeType == 1)
@@ -264,11 +281,20 @@ function findOffsetInNode(node, coords) {
264
281
  continue;
265
282
  }
266
283
  }
284
+ else if (rect.top > coords.top && !firstBelow && rect.left <= coords.left && rect.right >= coords.left) {
285
+ firstBelow = child;
286
+ coordsBelow = { left: Math.max(rect.left, Math.min(rect.right, coords.left)), top: rect.top };
287
+ }
267
288
  if (!closest && (coords.left >= rect.right && coords.top >= rect.top ||
268
289
  coords.left >= rect.left && coords.top >= rect.bottom))
269
290
  offset = childIndex + 1;
270
291
  }
271
292
  }
293
+ if (!closest && firstBelow) {
294
+ closest = firstBelow;
295
+ coordsClosest = coordsBelow;
296
+ dxClosest = 0;
297
+ }
272
298
  if (closest && closest.nodeType == 3)
273
299
  return findOffsetInText(closest, coordsClosest);
274
300
  if (!closest || (dxClosest && closest.nodeType == 1))
@@ -315,15 +341,16 @@ function posFromCaret(view, node, offset, coords) {
315
341
  // fall outside of. If so, we take the position before/after that
316
342
  // block. If not, we call `posFromDOM` on the raw node/offset.
317
343
  let outsideBlock = -1;
318
- for (let cur = node;;) {
344
+ for (let cur = node, sawBlock = false;;) {
319
345
  if (cur == view.dom)
320
346
  break;
321
347
  let desc = view.docView.nearestDesc(cur, true);
322
348
  if (!desc)
323
349
  return null;
324
- if (desc.dom.nodeType == 1 && (desc.node.isBlock && desc.parent || !desc.contentDOM)) {
350
+ if (desc.dom.nodeType == 1 && (desc.node.isBlock && desc.parent && !sawBlock || !desc.contentDOM)) {
325
351
  let rect = desc.dom.getBoundingClientRect();
326
- if (desc.node.isBlock && desc.parent) {
352
+ if (desc.node.isBlock && desc.parent && !sawBlock) {
353
+ sawBlock = true;
327
354
  if (rect.left > coords.left || rect.top > coords.top)
328
355
  outsideBlock = desc.posBefore;
329
356
  else if (rect.right < coords.left || rect.bottom < coords.top)
@@ -362,19 +389,9 @@ function elementFromPoint(element, coords, box) {
362
389
  // Given an x,y position on the editor, get the position in the document.
363
390
  function posAtCoords(view, coords) {
364
391
  let doc = view.dom.ownerDocument, node, offset = 0;
365
- if (doc.caretPositionFromPoint) {
366
- try { // Firefox throws for this call in hard-to-predict circumstances (#994)
367
- let pos = doc.caretPositionFromPoint(coords.left, coords.top);
368
- if (pos)
369
- ({ offsetNode: node, offset } = pos);
370
- }
371
- catch (_) { }
372
- }
373
- if (!node && doc.caretRangeFromPoint) {
374
- let range = doc.caretRangeFromPoint(coords.left, coords.top);
375
- if (range)
376
- ({ startContainer: node, startOffset: offset } = range);
377
- }
392
+ let caret = caretFromPoint(doc, coords.left, coords.top);
393
+ if (caret)
394
+ ({ node, offset } = caret);
378
395
  let elt = (view.root.elementFromPoint ? view.root : doc)
379
396
  .elementFromPoint(coords.left, coords.top);
380
397
  let pos;
@@ -423,9 +440,17 @@ function posAtCoords(view, coords) {
423
440
  let desc = view.docView.nearestDesc(elt, true);
424
441
  return { pos, inside: desc ? desc.posAtStart - desc.border : -1 };
425
442
  }
443
+ function nonZero(rect) {
444
+ return rect.top < rect.bottom || rect.left < rect.right;
445
+ }
426
446
  function singleRect(target, bias) {
427
447
  let rects = target.getClientRects();
428
- return !rects.length ? target.getBoundingClientRect() : rects[bias < 0 ? 0 : rects.length - 1];
448
+ if (rects.length) {
449
+ let first = rects[bias < 0 ? 0 : rects.length - 1];
450
+ if (nonZero(first))
451
+ return first;
452
+ }
453
+ return Array.prototype.find.call(rects, nonZero) || target.getBoundingClientRect();
429
454
  }
430
455
  const BIDI = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
431
456
  // Given a position in the document model, get a bounding box of the
@@ -467,7 +492,7 @@ function coordsAtPos(view, pos, side) {
467
492
  else {
468
493
  to++;
469
494
  }
470
- return flattenV(singleRect(textRange(node, from, to), 1), takeSide < 0);
495
+ return flattenV(singleRect(textRange(node, from, to), takeSide), takeSide < 0);
471
496
  }
472
497
  }
473
498
  let $dom = view.state.doc.resolve(pos - (atom || 0));
@@ -1158,8 +1183,6 @@ class NodeViewDesc extends ViewDesc {
1158
1183
  this.outerDeco = outerDeco;
1159
1184
  this.innerDeco = innerDeco;
1160
1185
  this.nodeDOM = nodeDOM;
1161
- if (contentDOM)
1162
- this.updateChildren(view, pos);
1163
1186
  }
1164
1187
  // By default, a node is rendered using the `toDOM` method from the
1165
1188
  // node type spec. But client code can use the `nodeViews` spec to
@@ -1272,7 +1295,7 @@ class NodeViewDesc extends ViewDesc {
1272
1295
  view.state.selection.to < off + child.nodeSize &&
1273
1296
  (compIndex = updater.findIndexWithChild(composition.node)) > -1 &&
1274
1297
  updater.updateNodeAt(child, outerDeco, innerDeco, compIndex, view)) ;
1275
- else if (updater.updateNextNode(child, outerDeco, innerDeco, view, i)) ;
1298
+ else if (updater.updateNextNode(child, outerDeco, innerDeco, view, i, off)) ;
1276
1299
  else {
1277
1300
  // Add it as a new view
1278
1301
  updater.addNode(child, outerDeco, innerDeco, view, off);
@@ -1386,7 +1409,10 @@ class NodeViewDesc extends ViewDesc {
1386
1409
  // and used by the view class.
1387
1410
  function docViewDesc(doc, outerDeco, innerDeco, dom, view) {
1388
1411
  applyOuterDeco(dom, outerDeco, doc);
1389
- return new NodeViewDesc(undefined, doc, outerDeco, innerDeco, dom, dom, dom, view, 0);
1412
+ let docView = new NodeViewDesc(undefined, doc, outerDeco, innerDeco, dom, dom, dom, view, 0);
1413
+ if (docView.contentDOM)
1414
+ docView.updateChildren(view, 0);
1415
+ return docView;
1390
1416
  }
1391
1417
  class TextViewDesc extends NodeViewDesc {
1392
1418
  constructor(parent, node, outerDeco, innerDeco, dom, nodeDOM, view) {
@@ -1759,14 +1785,14 @@ class ViewTreeUpdater {
1759
1785
  }
1760
1786
  // Try to update the next node, if any, to the given data. Checks
1761
1787
  // pre-matches to avoid overwriting nodes that could still be used.
1762
- updateNextNode(node, outerDeco, innerDeco, view, index) {
1788
+ updateNextNode(node, outerDeco, innerDeco, view, index, pos) {
1763
1789
  for (let i = this.index; i < this.top.children.length; i++) {
1764
1790
  let next = this.top.children[i];
1765
1791
  if (next instanceof NodeViewDesc) {
1766
1792
  let preMatch = this.preMatch.matched.get(next);
1767
1793
  if (preMatch != null && preMatch != index)
1768
1794
  return false;
1769
- let nextDOM = next.dom;
1795
+ let nextDOM = next.dom, updated;
1770
1796
  // Can't update if nextDOM is or contains this.lock, except if
1771
1797
  // it's a text node whose content already matches the new text
1772
1798
  // and whose decorations match the new ones.
@@ -1780,14 +1806,41 @@ class ViewTreeUpdater {
1780
1806
  this.index++;
1781
1807
  return true;
1782
1808
  }
1809
+ else if (!locked && (updated = this.recreateWrapper(next, node, outerDeco, innerDeco, view, pos))) {
1810
+ this.top.children[this.index] = updated;
1811
+ this.changed = true;
1812
+ this.index++;
1813
+ return true;
1814
+ }
1783
1815
  break;
1784
1816
  }
1785
1817
  }
1786
1818
  return false;
1787
1819
  }
1820
+ // When a node with content is replaced by a different node with
1821
+ // identical content, move over its children.
1822
+ recreateWrapper(next, node, outerDeco, innerDeco, view, pos) {
1823
+ if (next.dirty || node.isAtom || !next.children.length ||
1824
+ !next.node.content.eq(node.content))
1825
+ return null;
1826
+ let wrapper = NodeViewDesc.create(this.top, node, outerDeco, innerDeco, view, pos);
1827
+ if (!wrapper.contentDOM)
1828
+ return null;
1829
+ wrapper.children = next.children;
1830
+ next.children = [];
1831
+ for (let ch of wrapper.children)
1832
+ ch.parent = wrapper;
1833
+ wrapper.dirty = CONTENT_DIRTY;
1834
+ wrapper.updateChildren(view, pos + 1);
1835
+ wrapper.dirty = NOT_DIRTY;
1836
+ return wrapper;
1837
+ }
1788
1838
  // Insert the node as a newly created node desc.
1789
1839
  addNode(node, outerDeco, innerDeco, view, pos) {
1790
- this.top.children.splice(this.index++, 0, NodeViewDesc.create(this.top, node, outerDeco, innerDeco, view, pos));
1840
+ let desc = NodeViewDesc.create(this.top, node, outerDeco, innerDeco, view, pos);
1841
+ if (desc.contentDOM)
1842
+ desc.updateChildren(view, pos + 1);
1843
+ this.top.children.splice(this.index++, 0, desc);
1791
1844
  this.changed = true;
1792
1845
  }
1793
1846
  placeWidget(widget, view, pos) {
@@ -2259,7 +2312,7 @@ function selectHorizontally(view, dir, mods) {
2259
2312
  if (!sel.empty || mods.indexOf("s") > -1) {
2260
2313
  return false;
2261
2314
  }
2262
- else if (view.endOfTextblock(dir > 0 ? "right" : "left")) {
2315
+ else if (view.endOfTextblock(dir > 0 ? "forward" : "backward")) {
2263
2316
  let next = moveSelectionBlock(view.state, dir);
2264
2317
  if (next && (next instanceof NodeSelection))
2265
2318
  return apply(view, next);
@@ -2303,9 +2356,12 @@ function isIgnorable(dom) {
2303
2356
  let desc = dom.pmViewDesc;
2304
2357
  return desc && desc.size == 0 && (dom.nextSibling || dom.nodeName != "BR");
2305
2358
  }
2359
+ function skipIgnoredNodes(view, dir) {
2360
+ return dir < 0 ? skipIgnoredNodesBefore(view) : skipIgnoredNodesAfter(view);
2361
+ }
2306
2362
  // Make sure the cursor isn't directly after one or more ignored
2307
2363
  // nodes, which will confuse the browser's cursor motion logic.
2308
- function skipIgnoredNodesLeft(view) {
2364
+ function skipIgnoredNodesBefore(view) {
2309
2365
  let sel = view.domSelectionRange();
2310
2366
  let node = sel.focusNode, offset = sel.focusOffset;
2311
2367
  if (!node)
@@ -2364,7 +2420,7 @@ function skipIgnoredNodesLeft(view) {
2364
2420
  }
2365
2421
  // Make sure the cursor isn't directly before one or more ignored
2366
2422
  // nodes.
2367
- function skipIgnoredNodesRight(view) {
2423
+ function skipIgnoredNodesAfter(view) {
2368
2424
  let sel = view.domSelectionRange();
2369
2425
  let node = sel.focusNode, offset = sel.focusOffset;
2370
2426
  if (!node)
@@ -2433,6 +2489,26 @@ function setSelFocus(view, node, offset) {
2433
2489
  selectionToDOM(view);
2434
2490
  }, 50);
2435
2491
  }
2492
+ function findDirection(view, pos) {
2493
+ let $pos = view.state.doc.resolve(pos);
2494
+ if (!(chrome || windows) && $pos.parent.inlineContent) {
2495
+ let coords = view.coordsAtPos(pos);
2496
+ if (pos > $pos.start()) {
2497
+ let before = view.coordsAtPos(pos - 1);
2498
+ let mid = (before.top + before.bottom) / 2;
2499
+ if (mid > coords.top && mid < coords.bottom && Math.abs(before.left - coords.left) > 1)
2500
+ return before.left < coords.left ? "ltr" : "rtl";
2501
+ }
2502
+ if (pos < $pos.end()) {
2503
+ let after = view.coordsAtPos(pos + 1);
2504
+ let mid = (after.top + after.bottom) / 2;
2505
+ if (mid > coords.top && mid < coords.bottom && Math.abs(after.left - coords.left) > 1)
2506
+ return after.left > coords.left ? "ltr" : "rtl";
2507
+ }
2508
+ }
2509
+ let computed = getComputedStyle(view.dom).direction;
2510
+ return computed == "rtl" ? "rtl" : "ltr";
2511
+ }
2436
2512
  // Check whether vertical selection motion would involve node
2437
2513
  // selections. If so, apply it (if not, the result is left to the
2438
2514
  // browser)
@@ -2520,25 +2596,27 @@ function getMods(event) {
2520
2596
  function captureKeyDown(view, event) {
2521
2597
  let code = event.keyCode, mods = getMods(event);
2522
2598
  if (code == 8 || (mac && code == 72 && mods == "c")) { // Backspace, Ctrl-h on Mac
2523
- return stopNativeHorizontalDelete(view, -1) || skipIgnoredNodesLeft(view);
2599
+ return stopNativeHorizontalDelete(view, -1) || skipIgnoredNodes(view, -1);
2524
2600
  }
2525
2601
  else if (code == 46 || (mac && code == 68 && mods == "c")) { // Delete, Ctrl-d on Mac
2526
- return stopNativeHorizontalDelete(view, 1) || skipIgnoredNodesRight(view);
2602
+ return stopNativeHorizontalDelete(view, 1) || skipIgnoredNodes(view, 1);
2527
2603
  }
2528
2604
  else if (code == 13 || code == 27) { // Enter, Esc
2529
2605
  return true;
2530
2606
  }
2531
2607
  else if (code == 37 || (mac && code == 66 && mods == "c")) { // Left arrow, Ctrl-b on Mac
2532
- return selectHorizontally(view, -1, mods) || skipIgnoredNodesLeft(view);
2608
+ let dir = code == 37 ? (findDirection(view, view.state.selection.from) == "ltr" ? -1 : 1) : -1;
2609
+ return selectHorizontally(view, dir, mods) || skipIgnoredNodes(view, dir);
2533
2610
  }
2534
2611
  else if (code == 39 || (mac && code == 70 && mods == "c")) { // Right arrow, Ctrl-f on Mac
2535
- return selectHorizontally(view, 1, mods) || skipIgnoredNodesRight(view);
2612
+ let dir = code == 39 ? (findDirection(view, view.state.selection.from) == "ltr" ? 1 : -1) : 1;
2613
+ return selectHorizontally(view, dir, mods) || skipIgnoredNodes(view, dir);
2536
2614
  }
2537
2615
  else if (code == 38 || (mac && code == 80 && mods == "c")) { // Up arrow, Ctrl-p on Mac
2538
- return selectVertically(view, -1, mods) || skipIgnoredNodesLeft(view);
2616
+ return selectVertically(view, -1, mods) || skipIgnoredNodes(view, -1);
2539
2617
  }
2540
2618
  else if (code == 40 || (mac && code == 78 && mods == "c")) { // Down arrow, Ctrl-n on Mac
2541
- return safariDownArrowBug(view) || selectVertically(view, 1, mods) || skipIgnoredNodesRight(view);
2619
+ return safariDownArrowBug(view) || selectVertically(view, 1, mods) || skipIgnoredNodesAfter(view);
2542
2620
  }
2543
2621
  else if (mods == (mac ? "m" : "c") &&
2544
2622
  (code == 66 || code == 73 || code == 89 || code == 90)) { // Mod-[biyz]
@@ -2817,6 +2895,7 @@ class InputState {
2817
2895
  this.composingTimeout = -1;
2818
2896
  this.compositionNodes = [];
2819
2897
  this.compositionEndedAt = -2e8;
2898
+ this.compositionID = 1;
2820
2899
  this.domChangeCount = 0;
2821
2900
  this.eventHandlers = Object.create(null);
2822
2901
  this.hideSelectionGuard = null;
@@ -3246,6 +3325,7 @@ editHandlers.compositionend = (view, event) => {
3246
3325
  if (view.composing) {
3247
3326
  view.input.composing = false;
3248
3327
  view.input.compositionEndedAt = event.timeStamp;
3328
+ view.input.compositionID++;
3249
3329
  scheduleComposeEnd(view, 20);
3250
3330
  }
3251
3331
  };
@@ -4574,6 +4654,7 @@ function ruleFromNode(dom) {
4574
4654
  }
4575
4655
  return null;
4576
4656
  }
4657
+ const isInline = /^(a|abbr|acronym|b|bd[io]|big|br|button|cite|code|data(list)?|del|dfn|em|i|ins|kbd|label|map|mark|meter|output|q|ruby|s|samp|small|span|strong|su[bp]|time|u|tt|var)$/i;
4577
4658
  function readDOMChange(view, from, to, typeOver, addedNodes) {
4578
4659
  if (from < 0) {
4579
4660
  let origin = view.input.lastSelectionTime > Date.now() - 50 ? view.input.lastSelectionOrigin : null;
@@ -4588,6 +4669,8 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
4588
4669
  tr.setMeta("pointer", true);
4589
4670
  else if (origin == "key")
4590
4671
  tr.scrollIntoView();
4672
+ if (view.composing)
4673
+ tr.setMeta("composition", view.input.compositionID);
4591
4674
  view.dispatch(tr);
4592
4675
  }
4593
4676
  return;
@@ -4612,7 +4695,7 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
4612
4695
  view.input.lastKeyCode = null;
4613
4696
  let change = findDiff(compare.content, parse.doc.content, parse.from, preferredPos, preferredSide);
4614
4697
  if ((ios && view.input.lastIOSEnter > Date.now() - 225 || android) &&
4615
- addedNodes.some(n => n.nodeName == "DIV" || n.nodeName == "P" || n.nodeName == "LI") &&
4698
+ addedNodes.some(n => n.nodeType == 1 && !isInline.test(n.nodeName)) &&
4616
4699
  (!change || change.endA >= change.endB) &&
4617
4700
  view.someProp("handleKeyDown", f => f(view, keyEvent(13, "Enter")))) {
4618
4701
  view.input.lastIOSEnter = 0;
@@ -4626,8 +4709,12 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
4626
4709
  else {
4627
4710
  if (parse.sel) {
4628
4711
  let sel = resolveSelection(view, view.state.doc, parse.sel);
4629
- if (sel && !sel.eq(view.state.selection))
4630
- view.dispatch(view.state.tr.setSelection(sel));
4712
+ if (sel && !sel.eq(view.state.selection)) {
4713
+ let tr = view.state.tr.setSelection(sel);
4714
+ if (view.composing)
4715
+ tr.setMeta("composition", view.input.compositionID);
4716
+ view.dispatch(tr);
4717
+ }
4631
4718
  }
4632
4719
  return;
4633
4720
  }
@@ -4758,6 +4845,8 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
4758
4845
  }
4759
4846
  if (storedMarks)
4760
4847
  tr.ensureMarks(storedMarks);
4848
+ if (view.composing)
4849
+ tr.setMeta("composition", view.input.compositionID);
4761
4850
  view.dispatch(tr.scrollIntoView());
4762
4851
  }
4763
4852
  function resolveSelection(view, doc, parsedSel) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prosemirror-view",
3
- "version": "1.30.1",
3
+ "version": "1.31.0",
4
4
  "description": "ProseMirror's view component",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
package/src/browser.ts CHANGED
@@ -18,6 +18,7 @@ export const safari = !ie && !!nav && /Apple Computer/.test(nav.vendor)
18
18
  // Is true for both iOS and iPadOS for convenience
19
19
  export const ios = safari && (/Mobile\/\w+/.test(agent) || !!nav && nav.maxTouchPoints > 2)
20
20
  export const mac = ios || (nav ? /Mac/.test(nav.platform) : false)
21
+ export const windows = nav ? /Win/.test(nav.platform) : false
21
22
  export const android = /Android \d/.test(agent)
22
23
  export const webkit = !!doc && "webkitFontSmoothing" in doc.documentElement.style
23
24
  export const webkit_version = webkit ? +(/\bAppleWebKit\/(\d+)/.exec(navigator.userAgent) || [0, 0])[1] : 0
@@ -21,7 +21,7 @@ function selectHorizontally(view: EditorView, dir: number, mods: string) {
21
21
  if (sel instanceof TextSelection) {
22
22
  if (!sel.empty || mods.indexOf("s") > -1) {
23
23
  return false
24
- } else if (view.endOfTextblock(dir > 0 ? "right" : "left")) {
24
+ } else if (view.endOfTextblock(dir > 0 ? "forward" : "backward")) {
25
25
  let next = moveSelectionBlock(view.state, dir)
26
26
  if (next && (next instanceof NodeSelection)) return apply(view, next)
27
27
  return false
@@ -59,9 +59,13 @@ function isIgnorable(dom: Node) {
59
59
  return desc && desc.size == 0 && (dom.nextSibling || dom.nodeName != "BR")
60
60
  }
61
61
 
62
+ function skipIgnoredNodes(view: EditorView, dir: number) {
63
+ return dir < 0 ? skipIgnoredNodesBefore(view) : skipIgnoredNodesAfter(view)
64
+ }
65
+
62
66
  // Make sure the cursor isn't directly after one or more ignored
63
67
  // nodes, which will confuse the browser's cursor motion logic.
64
- function skipIgnoredNodesLeft(view: EditorView) {
68
+ function skipIgnoredNodesBefore(view: EditorView) {
65
69
  let sel = view.domSelectionRange()
66
70
  let node = sel.focusNode!, offset = sel.focusOffset
67
71
  if (!node) return
@@ -109,7 +113,7 @@ function skipIgnoredNodesLeft(view: EditorView) {
109
113
 
110
114
  // Make sure the cursor isn't directly before one or more ignored
111
115
  // nodes.
112
- function skipIgnoredNodesRight(view: EditorView) {
116
+ function skipIgnoredNodesAfter(view: EditorView) {
113
117
  let sel = view.domSelectionRange()
114
118
  let node = sel.focusNode!, offset = sel.focusOffset
115
119
  if (!node) return
@@ -171,6 +175,27 @@ function setSelFocus(view: EditorView, node: Node, offset: number) {
171
175
  }, 50)
172
176
  }
173
177
 
178
+ function findDirection(view: EditorView, pos: number): "rtl" | "ltr" {
179
+ let $pos = view.state.doc.resolve(pos)
180
+ if (!(browser.chrome || browser.windows) && $pos.parent.inlineContent) {
181
+ let coords = view.coordsAtPos(pos)
182
+ if (pos > $pos.start()) {
183
+ let before = view.coordsAtPos(pos - 1)
184
+ let mid = (before.top + before.bottom) / 2
185
+ if (mid > coords.top && mid < coords.bottom && Math.abs(before.left - coords.left) > 1)
186
+ return before.left < coords.left ? "ltr" : "rtl"
187
+ }
188
+ if (pos < $pos.end()) {
189
+ let after = view.coordsAtPos(pos + 1)
190
+ let mid = (after.top + after.bottom) / 2
191
+ if (mid > coords.top && mid < coords.bottom && Math.abs(after.left - coords.left) > 1)
192
+ return after.left > coords.left ? "ltr" : "rtl"
193
+ }
194
+ }
195
+ let computed = getComputedStyle(view.dom).direction
196
+ return computed == "rtl" ? "rtl" : "ltr"
197
+ }
198
+
174
199
  // Check whether vertical selection motion would involve node
175
200
  // selections. If so, apply it (if not, the result is left to the
176
201
  // browser)
@@ -252,19 +277,21 @@ function getMods(event: KeyboardEvent) {
252
277
  export function captureKeyDown(view: EditorView, event: KeyboardEvent) {
253
278
  let code = event.keyCode, mods = getMods(event)
254
279
  if (code == 8 || (browser.mac && code == 72 && mods == "c")) { // Backspace, Ctrl-h on Mac
255
- return stopNativeHorizontalDelete(view, -1) || skipIgnoredNodesLeft(view)
280
+ return stopNativeHorizontalDelete(view, -1) || skipIgnoredNodes(view, -1)
256
281
  } else if (code == 46 || (browser.mac && code == 68 && mods == "c")) { // Delete, Ctrl-d on Mac
257
- return stopNativeHorizontalDelete(view, 1) || skipIgnoredNodesRight(view)
282
+ return stopNativeHorizontalDelete(view, 1) || skipIgnoredNodes(view, 1)
258
283
  } else if (code == 13 || code == 27) { // Enter, Esc
259
284
  return true
260
285
  } else if (code == 37 || (browser.mac && code == 66 && mods == "c")) { // Left arrow, Ctrl-b on Mac
261
- return selectHorizontally(view, -1, mods) || skipIgnoredNodesLeft(view)
286
+ let dir = code == 37 ? (findDirection(view, view.state.selection.from) == "ltr" ? -1 : 1) : -1
287
+ return selectHorizontally(view, dir, mods) || skipIgnoredNodes(view, dir)
262
288
  } else if (code == 39 || (browser.mac && code == 70 && mods == "c")) { // Right arrow, Ctrl-f on Mac
263
- return selectHorizontally(view, 1, mods) || skipIgnoredNodesRight(view)
289
+ let dir = code == 39 ? (findDirection(view, view.state.selection.from) == "ltr" ? 1 : -1) : 1
290
+ return selectHorizontally(view, dir, mods) || skipIgnoredNodes(view, dir)
264
291
  } else if (code == 38 || (browser.mac && code == 80 && mods == "c")) { // Up arrow, Ctrl-p on Mac
265
- return selectVertically(view, -1, mods) || skipIgnoredNodesLeft(view)
292
+ return selectVertically(view, -1, mods) || skipIgnoredNodes(view, -1)
266
293
  } else if (code == 40 || (browser.mac && code == 78 && mods == "c")) { // Down arrow, Ctrl-n on Mac
267
- return safariDownArrowBug(view) || selectVertically(view, 1, mods) || skipIgnoredNodesRight(view)
294
+ return safariDownArrowBug(view) || selectVertically(view, 1, mods) || skipIgnoredNodesAfter(view)
268
295
  } else if (mods == (browser.mac ? "m" : "c") &&
269
296
  (code == 66 || code == 73 || code == 89 || code == 90)) { // Mod-[biyz]
270
297
  return true
package/src/dom.ts CHANGED
@@ -100,3 +100,16 @@ export function deepActiveElement(doc: Document) {
100
100
  while (elt && elt.shadowRoot) elt = elt.shadowRoot.activeElement
101
101
  return elt
102
102
  }
103
+
104
+ export function caretFromPoint(doc: Document, x: number, y: number): {node: Node, offset: number} | undefined {
105
+ if ((doc as any).caretPositionFromPoint) {
106
+ try { // Firefox throws for this call in hard-to-predict circumstances (#994)
107
+ let pos = (doc as any).caretPositionFromPoint(x, y)
108
+ if (pos) return {node: pos.offsetNode, offset: pos.offset}
109
+ } catch (_) {}
110
+ }
111
+ if (doc.caretRangeFromPoint) {
112
+ let range = doc.caretRangeFromPoint(x, y)
113
+ if (range) return {node: range.startContainer, offset: range.startOffset}
114
+ }
115
+ }
package/src/domchange.ts CHANGED
@@ -76,6 +76,8 @@ function ruleFromNode(dom: DOMNode): ParseRule | null {
76
76
  return null
77
77
  }
78
78
 
79
+ const isInline = /^(a|abbr|acronym|b|bd[io]|big|br|button|cite|code|data(list)?|del|dfn|em|i|ins|kbd|label|map|mark|meter|output|q|ruby|s|samp|small|span|strong|su[bp]|time|u|tt|var)$/i
80
+
79
81
  export function readDOMChange(view: EditorView, from: number, to: number, typeOver: boolean, addedNodes: readonly DOMNode[]) {
80
82
  if (from < 0) {
81
83
  let origin = view.input.lastSelectionTime > Date.now() - 50 ? view.input.lastSelectionOrigin : null
@@ -88,6 +90,7 @@ export function readDOMChange(view: EditorView, from: number, to: number, typeOv
88
90
  let tr = view.state.tr.setSelection(newSel)
89
91
  if (origin == "pointer") tr.setMeta("pointer", true)
90
92
  else if (origin == "key") tr.scrollIntoView()
93
+ if (view.composing) tr.setMeta("composition", view.input.compositionID)
91
94
  view.dispatch(tr)
92
95
  }
93
96
  return
@@ -115,7 +118,7 @@ export function readDOMChange(view: EditorView, from: number, to: number, typeOv
115
118
 
116
119
  let change = findDiff(compare.content, parse.doc.content, parse.from, preferredPos, preferredSide)
117
120
  if ((browser.ios && view.input.lastIOSEnter > Date.now() - 225 || browser.android) &&
118
- addedNodes.some(n => n.nodeName == "DIV" || n.nodeName == "P" || n.nodeName == "LI") &&
121
+ addedNodes.some(n => n.nodeType == 1 && !isInline.test(n.nodeName)) &&
119
122
  (!change || change.endA >= change.endB) &&
120
123
  view.someProp("handleKeyDown", f => f(view, keyEvent(13, "Enter")))) {
121
124
  view.input.lastIOSEnter = 0
@@ -128,7 +131,11 @@ export function readDOMChange(view: EditorView, from: number, to: number, typeOv
128
131
  } else {
129
132
  if (parse.sel) {
130
133
  let sel = resolveSelection(view, view.state.doc, parse.sel)
131
- if (sel && !sel.eq(view.state.selection)) view.dispatch(view.state.tr.setSelection(sel))
134
+ if (sel && !sel.eq(view.state.selection)) {
135
+ let tr = view.state.tr.setSelection(sel)
136
+ if (view.composing) tr.setMeta("composition", view.input.compositionID)
137
+ view.dispatch(tr)
138
+ }
132
139
  }
133
140
  return
134
141
  }
@@ -261,6 +268,7 @@ export function readDOMChange(view: EditorView, from: number, to: number, typeOv
261
268
  tr.setSelection(sel)
262
269
  }
263
270
  if (storedMarks) tr.ensureMarks(storedMarks)
271
+ if (view.composing) tr.setMeta("composition", view.input.compositionID)
264
272
  view.dispatch(tr.scrollIntoView())
265
273
  }
266
274
 
package/src/domcoords.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import {EditorState} from "prosemirror-state"
2
- import {nodeSize, textRange, parentNode} from "./dom"
2
+ import {nodeSize, textRange, parentNode, caretFromPoint} from "./dom"
3
3
  import * as browser from "./browser"
4
4
  import {EditorView} from "./index"
5
5
 
@@ -133,6 +133,7 @@ export function focusPreventScroll(dom: HTMLElement) {
133
133
  function findOffsetInNode(node: HTMLElement, coords: {top: number, left: number}): {node: Node, offset: number} {
134
134
  let closest, dxClosest = 2e8, coordsClosest: {left: number, top: number} | undefined, offset = 0
135
135
  let rowBot = coords.top, rowTop = coords.top
136
+ let firstBelow: Node | undefined, coordsBelow: {left: number, top: number} | undefined
136
137
  for (let child = node.firstChild, childIndex = 0; child; child = child.nextSibling, childIndex++) {
137
138
  let rects
138
139
  if (child.nodeType == 1) rects = (child as HTMLElement).getClientRects()
@@ -157,12 +158,16 @@ function findOffsetInNode(node: HTMLElement, coords: {top: number, left: number}
157
158
  offset = childIndex + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0)
158
159
  continue
159
160
  }
161
+ } else if (rect.top > coords.top && !firstBelow && rect.left <= coords.left && rect.right >= coords.left) {
162
+ firstBelow = child
163
+ coordsBelow = {left: Math.max(rect.left, Math.min(rect.right, coords.left)), top: rect.top}
160
164
  }
161
165
  if (!closest && (coords.left >= rect.right && coords.top >= rect.top ||
162
166
  coords.left >= rect.left && coords.top >= rect.bottom))
163
167
  offset = childIndex + 1
164
168
  }
165
169
  }
170
+ if (!closest && firstBelow) { closest = firstBelow; coordsClosest = coordsBelow; dxClosest = 0 }
166
171
  if (closest && closest.nodeType == 3) return findOffsetInText(closest as Text, coordsClosest!)
167
172
  if (!closest || (dxClosest && closest.nodeType == 1)) return {node, offset}
168
173
  return findOffsetInNode(closest as HTMLElement, coordsClosest!)
@@ -211,13 +216,14 @@ function posFromCaret(view: EditorView, node: Node, offset: number, coords: {top
211
216
  // fall outside of. If so, we take the position before/after that
212
217
  // block. If not, we call `posFromDOM` on the raw node/offset.
213
218
  let outsideBlock = -1
214
- for (let cur = node;;) {
219
+ for (let cur = node, sawBlock = false;;) {
215
220
  if (cur == view.dom) break
216
221
  let desc = view.docView.nearestDesc(cur, true)
217
222
  if (!desc) return null
218
- if (desc.dom.nodeType == 1 && (desc.node.isBlock && desc.parent || !desc.contentDOM)) {
223
+ if (desc.dom.nodeType == 1 && (desc.node.isBlock && desc.parent && !sawBlock || !desc.contentDOM)) {
219
224
  let rect = (desc.dom as HTMLElement).getBoundingClientRect()
220
- if (desc.node.isBlock && desc.parent) {
225
+ if (desc.node.isBlock && desc.parent && !sawBlock) {
226
+ sawBlock = true
221
227
  if (rect.left > coords.left || rect.top > coords.top) outsideBlock = desc.posBefore
222
228
  else if (rect.right < coords.left || rect.bottom < coords.top) outsideBlock = desc.posAfter
223
229
  }
@@ -254,16 +260,8 @@ function elementFromPoint(element: HTMLElement, coords: {top: number, left: numb
254
260
  // Given an x,y position on the editor, get the position in the document.
255
261
  export function posAtCoords(view: EditorView, coords: {top: number, left: number}) {
256
262
  let doc = view.dom.ownerDocument, node: Node | undefined, offset = 0
257
- if ((doc as any).caretPositionFromPoint) {
258
- try { // Firefox throws for this call in hard-to-predict circumstances (#994)
259
- let pos = (doc as any).caretPositionFromPoint(coords.left, coords.top)
260
- if (pos) ({offsetNode: node, offset} = pos)
261
- } catch (_) {}
262
- }
263
- if (!node && doc.caretRangeFromPoint) {
264
- let range = doc.caretRangeFromPoint(coords.left, coords.top)
265
- if (range) ({startContainer: node, startOffset: offset} = range)
266
- }
263
+ let caret = caretFromPoint(doc, coords.left, coords.top)
264
+ if (caret) ({node, offset} = caret)
267
265
 
268
266
  let elt = ((view.root as any).elementFromPoint ? view.root : doc)
269
267
  .elementFromPoint(coords.left, coords.top) as HTMLElement
@@ -311,9 +309,17 @@ export function posAtCoords(view: EditorView, coords: {top: number, left: number
311
309
  return {pos, inside: desc ? desc.posAtStart - desc.border : -1}
312
310
  }
313
311
 
312
+ function nonZero(rect: DOMRect) {
313
+ return rect.top < rect.bottom || rect.left < rect.right
314
+ }
315
+
314
316
  function singleRect(target: HTMLElement | Range, bias: number): DOMRect {
315
317
  let rects = target.getClientRects()
316
- return !rects.length ? target.getBoundingClientRect() : rects[bias < 0 ? 0 : rects.length - 1]
318
+ if (rects.length) {
319
+ let first = rects[bias < 0 ? 0 : rects.length - 1]
320
+ if (nonZero(first)) return first
321
+ }
322
+ return Array.prototype.find.call(rects, nonZero) || target.getBoundingClientRect()
317
323
  }
318
324
 
319
325
  const BIDI = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/
@@ -347,7 +353,7 @@ export function coordsAtPos(view: EditorView, pos: number, side: number): Rect {
347
353
  else if (side >= 0 && offset == node.nodeValue!.length) { from--; takeSide = 1 }
348
354
  else if (side < 0) { from-- }
349
355
  else { to ++ }
350
- return flattenV(singleRect(textRange(node as Text, from, to), 1), takeSide < 0)
356
+ return flattenV(singleRect(textRange(node as Text, from, to), takeSide), takeSide < 0)
351
357
  }
352
358
  }
353
359
 
package/src/index.ts CHANGED
@@ -544,12 +544,12 @@ function checkStateComponent(plugin: Plugin) {
544
544
  throw new RangeError("Plugins passed directly to the view must not have a state component")
545
545
  }
546
546
 
547
- /// The type of function [provided](#view.ViewProps.nodeViews) to
547
+ /// The type of function [provided](#view.EditorProps.nodeViews) to
548
548
  /// create [node views](#view.NodeView).
549
- export type NodeViewConstructor = (node: Node, view: EditorView, getPos: () => number,
549
+ export type NodeViewConstructor = (node: Node, view: EditorView, getPos: () => number | undefined,
550
550
  decorations: readonly Decoration[], innerDecorations: DecorationSource) => NodeView
551
551
 
552
- /// The function types [used](#view.ViewProps.markViews) to create
552
+ /// The function types [used](#view.EditorProps.markViews) to create
553
553
  /// mark views.
554
554
  export type MarkViewConstructor = (mark: Mark, view: EditorView, inline: boolean) => {dom: HTMLElement, contentDOM?: HTMLElement}
555
555
 
@@ -684,6 +684,8 @@ export interface EditorProps<P = any> {
684
684
  /// node's display behavior. The third argument `getPos` is a
685
685
  /// function that can be called to get the node's current position,
686
686
  /// which can be useful when creating transactions to update it.
687
+ /// Note that if the node is not in the document, the position
688
+ /// returned by this function will be `undefined`.
687
689
  ///
688
690
  /// `decorations` is an array of node or inline decorations that are
689
691
  /// active around the node. They are automatically drawn in the
@@ -699,7 +701,7 @@ export interface EditorProps<P = any> {
699
701
  /// inner decorations.
700
702
  ///
701
703
  /// (For backwards compatibility reasons, [mark
702
- /// views](#view.ViewProps.markViews) can also be included in this
704
+ /// views](#view.EditorProps.markViews) can also be included in this
703
705
  /// object.)
704
706
  nodeViews?: {[node: string]: NodeViewConstructor}
705
707
 
package/src/input.ts CHANGED
@@ -33,6 +33,7 @@ export class InputState {
33
33
  composingTimeout = -1
34
34
  compositionNodes: ViewDesc[] = []
35
35
  compositionEndedAt = -2e8
36
+ compositionID = 1
36
37
  domChangeCount = 0
37
38
  eventHandlers: {[event: string]: (event: Event) => void} = Object.create(null)
38
39
  hideSelectionGuard: (() => void) | null = null
@@ -486,6 +487,7 @@ editHandlers.compositionend = (view, event) => {
486
487
  if (view.composing) {
487
488
  view.input.composing = false
488
489
  view.input.compositionEndedAt = event.timeStamp
490
+ view.input.compositionID++
489
491
  scheduleComposeEnd(view, 20)
490
492
  }
491
493
  }
package/src/viewdesc.ts CHANGED
@@ -619,7 +619,6 @@ export class NodeViewDesc extends ViewDesc {
619
619
  pos: number
620
620
  ) {
621
621
  super(parent, [], dom, contentDOM)
622
- if (contentDOM) this.updateChildren(view, pos)
623
622
  }
624
623
 
625
624
  // By default, a node is rendered using the `toDOM` method from the
@@ -733,7 +732,7 @@ export class NodeViewDesc extends ViewDesc {
733
732
  (compIndex = updater.findIndexWithChild(composition!.node)) > -1 &&
734
733
  updater.updateNodeAt(child, outerDeco, innerDeco, compIndex, view)) {
735
734
  // Updated the specific node that holds the composition
736
- } else if (updater.updateNextNode(child, outerDeco, innerDeco, view, i)) {
735
+ } else if (updater.updateNextNode(child, outerDeco, innerDeco, view, i, off)) {
737
736
  // Could update an existing node to reflect this node
738
737
  } else {
739
738
  // Add it as a new view
@@ -846,7 +845,9 @@ export class NodeViewDesc extends ViewDesc {
846
845
  export function docViewDesc(doc: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource,
847
846
  dom: HTMLElement, view: EditorView): NodeViewDesc {
848
847
  applyOuterDeco(dom, outerDeco, doc)
849
- return new NodeViewDesc(undefined, doc, outerDeco, innerDeco, dom, dom, dom, view, 0)
848
+ let docView = new NodeViewDesc(undefined, doc, outerDeco, innerDeco, dom, dom, dom, view, 0)
849
+ if (docView.contentDOM) docView.updateChildren(view, 0)
850
+ return docView
850
851
  }
851
852
 
852
853
  class TextViewDesc extends NodeViewDesc {
@@ -1222,13 +1223,13 @@ class ViewTreeUpdater {
1222
1223
  // Try to update the next node, if any, to the given data. Checks
1223
1224
  // pre-matches to avoid overwriting nodes that could still be used.
1224
1225
  updateNextNode(node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource,
1225
- view: EditorView, index: number): boolean {
1226
+ view: EditorView, index: number, pos: number): boolean {
1226
1227
  for (let i = this.index; i < this.top.children.length; i++) {
1227
1228
  let next = this.top.children[i]
1228
1229
  if (next instanceof NodeViewDesc) {
1229
1230
  let preMatch = this.preMatch.matched.get(next)
1230
1231
  if (preMatch != null && preMatch != index) return false
1231
- let nextDOM = next.dom
1232
+ let nextDOM = next.dom, updated
1232
1233
 
1233
1234
  // Can't update if nextDOM is or contains this.lock, except if
1234
1235
  // it's a text node whose content already matches the new text
@@ -1241,6 +1242,11 @@ class ViewTreeUpdater {
1241
1242
  if (next.dom != nextDOM) this.changed = true
1242
1243
  this.index++
1243
1244
  return true
1245
+ } else if (!locked && (updated = this.recreateWrapper(next, node, outerDeco, innerDeco, view, pos))) {
1246
+ this.top.children[this.index] = updated
1247
+ this.changed = true
1248
+ this.index++
1249
+ return true
1244
1250
  }
1245
1251
  break
1246
1252
  }
@@ -1248,9 +1254,28 @@ class ViewTreeUpdater {
1248
1254
  return false
1249
1255
  }
1250
1256
 
1257
+ // When a node with content is replaced by a different node with
1258
+ // identical content, move over its children.
1259
+ recreateWrapper(next: NodeViewDesc, node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource,
1260
+ view: EditorView, pos: number) {
1261
+ if (next.dirty || node.isAtom || !next.children.length ||
1262
+ !next.node.content.eq(node.content)) return null
1263
+ let wrapper = NodeViewDesc.create(this.top, node, outerDeco, innerDeco, view, pos)
1264
+ if (!wrapper.contentDOM) return null
1265
+ wrapper.children = next.children
1266
+ next.children = []
1267
+ for (let ch of wrapper.children) ch.parent = wrapper
1268
+ wrapper.dirty = CONTENT_DIRTY
1269
+ wrapper.updateChildren(view, pos + 1)
1270
+ wrapper.dirty = NOT_DIRTY
1271
+ return wrapper
1272
+ }
1273
+
1251
1274
  // Insert the node as a newly created node desc.
1252
1275
  addNode(node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource, view: EditorView, pos: number) {
1253
- this.top.children.splice(this.index++, 0, NodeViewDesc.create(this.top, node, outerDeco, innerDeco, view, pos))
1276
+ let desc = NodeViewDesc.create(this.top, node, outerDeco, innerDeco, view, pos)
1277
+ if (desc.contentDOM) desc.updateChildren(view, pos + 1)
1278
+ this.top.children.splice(this.index++, 0, desc)
1254
1279
  this.changed = true
1255
1280
  }
1256
1281