prosemirror-view 1.30.2 → 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,19 @@
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
+
1
17
  ## 1.30.2 (2023-03-13)
2
18
 
3
19
  ### 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,
@@ -440,25 +480,11 @@ function _posAtCoords(view, coords) {
440
480
  var doc = view.dom.ownerDocument,
441
481
  node,
442
482
  offset = 0;
483
+ var caret = caretFromPoint(doc, coords.left, coords.top);
443
484
 
444
- if (doc.caretPositionFromPoint) {
445
- try {
446
- var _pos = doc.caretPositionFromPoint(coords.left, coords.top);
447
-
448
- if (_pos) {
449
- node = _pos.offsetNode;
450
- offset = _pos.offset;
451
- }
452
- } catch (_) {}
453
- }
454
-
455
- if (!node && doc.caretRangeFromPoint) {
456
- var range = doc.caretRangeFromPoint(coords.left, coords.top);
457
-
458
- if (range) {
459
- node = range.startContainer;
460
- offset = range.startOffset;
461
- }
485
+ if (caret) {
486
+ node = caret.node;
487
+ offset = caret.offset;
462
488
  }
463
489
 
464
490
  var elt = (view.root.elementFromPoint ? view.root : doc).elementFromPoint(coords.left, coords.top);
@@ -502,9 +528,19 @@ function _posAtCoords(view, coords) {
502
528
  };
503
529
  }
504
530
 
531
+ function nonZero(rect) {
532
+ return rect.top < rect.bottom || rect.left < rect.right;
533
+ }
534
+
505
535
  function singleRect(target, bias) {
506
536
  var rects = target.getClientRects();
507
- 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();
508
544
  }
509
545
 
510
546
  var BIDI = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
@@ -548,7 +584,7 @@ function _coordsAtPos(view, pos, side) {
548
584
  to++;
549
585
  }
550
586
 
551
- return flattenV(singleRect(textRange(node, from, to), 1), takeSide < 0);
587
+ return flattenV(singleRect(textRange(node, from, to), takeSide), takeSide < 0);
552
588
  }
553
589
  }
554
590
 
@@ -1425,7 +1461,6 @@ var NodeViewDesc = function (_ViewDesc4) {
1425
1461
  _this4.outerDeco = outerDeco;
1426
1462
  _this4.innerDeco = innerDeco;
1427
1463
  _this4.nodeDOM = nodeDOM;
1428
- if (contentDOM) _this4.updateChildren(view, pos);
1429
1464
  return _this4;
1430
1465
  }
1431
1466
 
@@ -1496,7 +1531,7 @@ var NodeViewDesc = function (_ViewDesc4) {
1496
1531
  }, function (child, outerDeco, innerDeco, i) {
1497
1532
  updater.syncToMarks(child.marks, inline, view);
1498
1533
  var compIndex;
1499
- 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 {
1500
1535
  updater.addNode(child, outerDeco, innerDeco, view, off);
1501
1536
  }
1502
1537
  off += child.nodeSize;
@@ -1650,7 +1685,9 @@ var NodeViewDesc = function (_ViewDesc4) {
1650
1685
 
1651
1686
  function docViewDesc(doc, outerDeco, innerDeco, dom, view) {
1652
1687
  applyOuterDeco(dom, outerDeco, doc);
1653
- 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;
1654
1691
  }
1655
1692
 
1656
1693
  var TextViewDesc = function (_NodeViewDesc) {
@@ -2134,7 +2171,7 @@ var ViewTreeUpdater = function () {
2134
2171
  }
2135
2172
  }, {
2136
2173
  key: "updateNextNode",
2137
- value: function updateNextNode(node, outerDeco, innerDeco, view, index) {
2174
+ value: function updateNextNode(node, outerDeco, innerDeco, view, index, pos) {
2138
2175
  for (var i = this.index; i < this.top.children.length; i++) {
2139
2176
  var next = this.top.children[i];
2140
2177
 
@@ -2142,7 +2179,8 @@ var ViewTreeUpdater = function () {
2142
2179
  var _preMatch = this.preMatch.matched.get(next);
2143
2180
 
2144
2181
  if (_preMatch != null && _preMatch != index) return false;
2145
- var nextDOM = next.dom;
2182
+ var nextDOM = next.dom,
2183
+ updated = void 0;
2146
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));
2147
2185
 
2148
2186
  if (!locked && next.update(node, outerDeco, innerDeco, view)) {
@@ -2150,6 +2188,11 @@ var ViewTreeUpdater = function () {
2150
2188
  if (next.dom != nextDOM) this.changed = true;
2151
2189
  this.index++;
2152
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;
2153
2196
  }
2154
2197
 
2155
2198
  break;
@@ -2158,10 +2201,40 @@ var ViewTreeUpdater = function () {
2158
2201
 
2159
2202
  return false;
2160
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
+ }
2161
2232
  }, {
2162
2233
  key: "addNode",
2163
2234
  value: function addNode(node, outerDeco, innerDeco, view, pos) {
2164
- 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);
2165
2238
  this.changed = true;
2166
2239
  }
2167
2240
  }, {
@@ -2665,7 +2738,7 @@ function selectHorizontally(view, dir, mods) {
2665
2738
  if (sel instanceof prosemirrorState.TextSelection) {
2666
2739
  if (!sel.empty || mods.indexOf("s") > -1) {
2667
2740
  return false;
2668
- } else if (view.endOfTextblock(dir > 0 ? "right" : "left")) {
2741
+ } else if (view.endOfTextblock(dir > 0 ? "forward" : "backward")) {
2669
2742
  var next = moveSelectionBlock(view.state, dir);
2670
2743
  if (next && next instanceof prosemirrorState.NodeSelection) return apply(view, next);
2671
2744
  return false;
@@ -2704,7 +2777,11 @@ function isIgnorable(dom) {
2704
2777
  return desc && desc.size == 0 && (dom.nextSibling || dom.nodeName != "BR");
2705
2778
  }
2706
2779
 
2707
- function skipIgnoredNodesLeft(view) {
2780
+ function skipIgnoredNodes(view, dir) {
2781
+ return dir < 0 ? skipIgnoredNodesBefore(view) : skipIgnoredNodesAfter(view);
2782
+ }
2783
+
2784
+ function skipIgnoredNodesBefore(view) {
2708
2785
  var sel = view.domSelectionRange();
2709
2786
  var node = sel.focusNode,
2710
2787
  offset = sel.focusOffset;
@@ -2754,7 +2831,7 @@ function skipIgnoredNodesLeft(view) {
2754
2831
  if (force) setSelFocus(view, node, offset);else if (moveNode) setSelFocus(view, moveNode, moveOffset);
2755
2832
  }
2756
2833
 
2757
- function skipIgnoredNodesRight(view) {
2834
+ function skipIgnoredNodesAfter(view) {
2758
2835
  var sel = view.domSelectionRange();
2759
2836
  var node = sel.focusNode,
2760
2837
  offset = sel.focusOffset;
@@ -2822,6 +2899,31 @@ function setSelFocus(view, node, offset) {
2822
2899
  }, 50);
2823
2900
  }
2824
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
+
2825
2927
  function selectVertically(view, dir, mods) {
2826
2928
  var sel = view.state.selection;
2827
2929
  if (sel instanceof prosemirrorState.TextSelection && !sel.empty || mods.indexOf("s") > -1) return false;
@@ -2902,19 +3004,22 @@ function captureKeyDown(view, event) {
2902
3004
  mods = getMods(event);
2903
3005
 
2904
3006
  if (code == 8 || mac && code == 72 && mods == "c") {
2905
- return stopNativeHorizontalDelete(view, -1) || skipIgnoredNodesLeft(view);
3007
+ return stopNativeHorizontalDelete(view, -1) || skipIgnoredNodes(view, -1);
2906
3008
  } else if (code == 46 || mac && code == 68 && mods == "c") {
2907
- return stopNativeHorizontalDelete(view, 1) || skipIgnoredNodesRight(view);
3009
+ return stopNativeHorizontalDelete(view, 1) || skipIgnoredNodes(view, 1);
2908
3010
  } else if (code == 13 || code == 27) {
2909
3011
  return true;
2910
3012
  } else if (code == 37 || mac && code == 66 && mods == "c") {
2911
- 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);
2912
3015
  } else if (code == 39 || mac && code == 70 && mods == "c") {
2913
- 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);
2914
3019
  } else if (code == 38 || mac && code == 80 && mods == "c") {
2915
- return selectVertically(view, -1, mods) || skipIgnoredNodesLeft(view);
3020
+ return selectVertically(view, -1, mods) || skipIgnoredNodes(view, -1);
2916
3021
  } else if (code == 40 || mac && code == 78 && mods == "c") {
2917
- return safariDownArrowBug(view) || selectVertically(view, 1, mods) || skipIgnoredNodesRight(view);
3022
+ return safariDownArrowBug(view) || selectVertically(view, 1, mods) || skipIgnoredNodesAfter(view);
2918
3023
  } else if (mods == (mac ? "m" : "c") && (code == 66 || code == 73 || code == 89 || code == 90)) {
2919
3024
  return true;
2920
3025
  }
@@ -3241,6 +3346,7 @@ var InputState = _createClass(function InputState() {
3241
3346
  this.composingTimeout = -1;
3242
3347
  this.compositionNodes = [];
3243
3348
  this.compositionEndedAt = -2e8;
3349
+ this.compositionID = 1;
3244
3350
  this.domChangeCount = 0;
3245
3351
  this.eventHandlers = Object.create(null);
3246
3352
  this.hideSelectionGuard = null;
@@ -3704,6 +3810,7 @@ editHandlers.compositionend = function (view, event) {
3704
3810
  if (view.composing) {
3705
3811
  view.input.composing = false;
3706
3812
  view.input.compositionEndedAt = event.timeStamp;
3813
+ view.input.compositionID++;
3707
3814
  scheduleComposeEnd(view, 20);
3708
3815
  }
3709
3816
  };
@@ -5185,6 +5292,7 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
5185
5292
  var _tr = view.state.tr.setSelection(newSel);
5186
5293
 
5187
5294
  if (origin == "pointer") _tr.setMeta("pointer", true);else if (origin == "key") _tr.scrollIntoView();
5295
+ if (view.composing) _tr.setMeta("composition", view.input.compositionID);
5188
5296
  view.dispatch(_tr);
5189
5297
  }
5190
5298
 
@@ -5232,7 +5340,12 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
5232
5340
  if (parse.sel) {
5233
5341
  var _sel = resolveSelection(view, view.state.doc, parse.sel);
5234
5342
 
5235
- 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
+ }
5236
5349
  }
5237
5350
 
5238
5351
  return;
@@ -5334,6 +5447,7 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
5334
5447
  }
5335
5448
 
5336
5449
  if (storedMarks) tr.ensureMarks(storedMarks);
5450
+ if (view.composing) tr.setMeta("composition", view.input.compositionID);
5337
5451
  view.dispatch(tr.scrollIntoView());
5338
5452
  }
5339
5453
 
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))
@@ -363,19 +389,9 @@ function elementFromPoint(element, coords, box) {
363
389
  // Given an x,y position on the editor, get the position in the document.
364
390
  function posAtCoords(view, coords) {
365
391
  let doc = view.dom.ownerDocument, node, offset = 0;
366
- if (doc.caretPositionFromPoint) {
367
- try { // Firefox throws for this call in hard-to-predict circumstances (#994)
368
- let pos = doc.caretPositionFromPoint(coords.left, coords.top);
369
- if (pos)
370
- ({ offsetNode: node, offset } = pos);
371
- }
372
- catch (_) { }
373
- }
374
- if (!node && doc.caretRangeFromPoint) {
375
- let range = doc.caretRangeFromPoint(coords.left, coords.top);
376
- if (range)
377
- ({ startContainer: node, startOffset: offset } = range);
378
- }
392
+ let caret = caretFromPoint(doc, coords.left, coords.top);
393
+ if (caret)
394
+ ({ node, offset } = caret);
379
395
  let elt = (view.root.elementFromPoint ? view.root : doc)
380
396
  .elementFromPoint(coords.left, coords.top);
381
397
  let pos;
@@ -424,9 +440,17 @@ function posAtCoords(view, coords) {
424
440
  let desc = view.docView.nearestDesc(elt, true);
425
441
  return { pos, inside: desc ? desc.posAtStart - desc.border : -1 };
426
442
  }
443
+ function nonZero(rect) {
444
+ return rect.top < rect.bottom || rect.left < rect.right;
445
+ }
427
446
  function singleRect(target, bias) {
428
447
  let rects = target.getClientRects();
429
- 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();
430
454
  }
431
455
  const BIDI = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
432
456
  // Given a position in the document model, get a bounding box of the
@@ -468,7 +492,7 @@ function coordsAtPos(view, pos, side) {
468
492
  else {
469
493
  to++;
470
494
  }
471
- return flattenV(singleRect(textRange(node, from, to), 1), takeSide < 0);
495
+ return flattenV(singleRect(textRange(node, from, to), takeSide), takeSide < 0);
472
496
  }
473
497
  }
474
498
  let $dom = view.state.doc.resolve(pos - (atom || 0));
@@ -1159,8 +1183,6 @@ class NodeViewDesc extends ViewDesc {
1159
1183
  this.outerDeco = outerDeco;
1160
1184
  this.innerDeco = innerDeco;
1161
1185
  this.nodeDOM = nodeDOM;
1162
- if (contentDOM)
1163
- this.updateChildren(view, pos);
1164
1186
  }
1165
1187
  // By default, a node is rendered using the `toDOM` method from the
1166
1188
  // node type spec. But client code can use the `nodeViews` spec to
@@ -1273,7 +1295,7 @@ class NodeViewDesc extends ViewDesc {
1273
1295
  view.state.selection.to < off + child.nodeSize &&
1274
1296
  (compIndex = updater.findIndexWithChild(composition.node)) > -1 &&
1275
1297
  updater.updateNodeAt(child, outerDeco, innerDeco, compIndex, view)) ;
1276
- else if (updater.updateNextNode(child, outerDeco, innerDeco, view, i)) ;
1298
+ else if (updater.updateNextNode(child, outerDeco, innerDeco, view, i, off)) ;
1277
1299
  else {
1278
1300
  // Add it as a new view
1279
1301
  updater.addNode(child, outerDeco, innerDeco, view, off);
@@ -1387,7 +1409,10 @@ class NodeViewDesc extends ViewDesc {
1387
1409
  // and used by the view class.
1388
1410
  function docViewDesc(doc, outerDeco, innerDeco, dom, view) {
1389
1411
  applyOuterDeco(dom, outerDeco, doc);
1390
- 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;
1391
1416
  }
1392
1417
  class TextViewDesc extends NodeViewDesc {
1393
1418
  constructor(parent, node, outerDeco, innerDeco, dom, nodeDOM, view) {
@@ -1760,14 +1785,14 @@ class ViewTreeUpdater {
1760
1785
  }
1761
1786
  // Try to update the next node, if any, to the given data. Checks
1762
1787
  // pre-matches to avoid overwriting nodes that could still be used.
1763
- updateNextNode(node, outerDeco, innerDeco, view, index) {
1788
+ updateNextNode(node, outerDeco, innerDeco, view, index, pos) {
1764
1789
  for (let i = this.index; i < this.top.children.length; i++) {
1765
1790
  let next = this.top.children[i];
1766
1791
  if (next instanceof NodeViewDesc) {
1767
1792
  let preMatch = this.preMatch.matched.get(next);
1768
1793
  if (preMatch != null && preMatch != index)
1769
1794
  return false;
1770
- let nextDOM = next.dom;
1795
+ let nextDOM = next.dom, updated;
1771
1796
  // Can't update if nextDOM is or contains this.lock, except if
1772
1797
  // it's a text node whose content already matches the new text
1773
1798
  // and whose decorations match the new ones.
@@ -1781,14 +1806,41 @@ class ViewTreeUpdater {
1781
1806
  this.index++;
1782
1807
  return true;
1783
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
+ }
1784
1815
  break;
1785
1816
  }
1786
1817
  }
1787
1818
  return false;
1788
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
+ }
1789
1838
  // Insert the node as a newly created node desc.
1790
1839
  addNode(node, outerDeco, innerDeco, view, pos) {
1791
- 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);
1792
1844
  this.changed = true;
1793
1845
  }
1794
1846
  placeWidget(widget, view, pos) {
@@ -2260,7 +2312,7 @@ function selectHorizontally(view, dir, mods) {
2260
2312
  if (!sel.empty || mods.indexOf("s") > -1) {
2261
2313
  return false;
2262
2314
  }
2263
- else if (view.endOfTextblock(dir > 0 ? "right" : "left")) {
2315
+ else if (view.endOfTextblock(dir > 0 ? "forward" : "backward")) {
2264
2316
  let next = moveSelectionBlock(view.state, dir);
2265
2317
  if (next && (next instanceof NodeSelection))
2266
2318
  return apply(view, next);
@@ -2304,9 +2356,12 @@ function isIgnorable(dom) {
2304
2356
  let desc = dom.pmViewDesc;
2305
2357
  return desc && desc.size == 0 && (dom.nextSibling || dom.nodeName != "BR");
2306
2358
  }
2359
+ function skipIgnoredNodes(view, dir) {
2360
+ return dir < 0 ? skipIgnoredNodesBefore(view) : skipIgnoredNodesAfter(view);
2361
+ }
2307
2362
  // Make sure the cursor isn't directly after one or more ignored
2308
2363
  // nodes, which will confuse the browser's cursor motion logic.
2309
- function skipIgnoredNodesLeft(view) {
2364
+ function skipIgnoredNodesBefore(view) {
2310
2365
  let sel = view.domSelectionRange();
2311
2366
  let node = sel.focusNode, offset = sel.focusOffset;
2312
2367
  if (!node)
@@ -2365,7 +2420,7 @@ function skipIgnoredNodesLeft(view) {
2365
2420
  }
2366
2421
  // Make sure the cursor isn't directly before one or more ignored
2367
2422
  // nodes.
2368
- function skipIgnoredNodesRight(view) {
2423
+ function skipIgnoredNodesAfter(view) {
2369
2424
  let sel = view.domSelectionRange();
2370
2425
  let node = sel.focusNode, offset = sel.focusOffset;
2371
2426
  if (!node)
@@ -2434,6 +2489,26 @@ function setSelFocus(view, node, offset) {
2434
2489
  selectionToDOM(view);
2435
2490
  }, 50);
2436
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
+ }
2437
2512
  // Check whether vertical selection motion would involve node
2438
2513
  // selections. If so, apply it (if not, the result is left to the
2439
2514
  // browser)
@@ -2521,25 +2596,27 @@ function getMods(event) {
2521
2596
  function captureKeyDown(view, event) {
2522
2597
  let code = event.keyCode, mods = getMods(event);
2523
2598
  if (code == 8 || (mac && code == 72 && mods == "c")) { // Backspace, Ctrl-h on Mac
2524
- return stopNativeHorizontalDelete(view, -1) || skipIgnoredNodesLeft(view);
2599
+ return stopNativeHorizontalDelete(view, -1) || skipIgnoredNodes(view, -1);
2525
2600
  }
2526
2601
  else if (code == 46 || (mac && code == 68 && mods == "c")) { // Delete, Ctrl-d on Mac
2527
- return stopNativeHorizontalDelete(view, 1) || skipIgnoredNodesRight(view);
2602
+ return stopNativeHorizontalDelete(view, 1) || skipIgnoredNodes(view, 1);
2528
2603
  }
2529
2604
  else if (code == 13 || code == 27) { // Enter, Esc
2530
2605
  return true;
2531
2606
  }
2532
2607
  else if (code == 37 || (mac && code == 66 && mods == "c")) { // Left arrow, Ctrl-b on Mac
2533
- 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);
2534
2610
  }
2535
2611
  else if (code == 39 || (mac && code == 70 && mods == "c")) { // Right arrow, Ctrl-f on Mac
2536
- 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);
2537
2614
  }
2538
2615
  else if (code == 38 || (mac && code == 80 && mods == "c")) { // Up arrow, Ctrl-p on Mac
2539
- return selectVertically(view, -1, mods) || skipIgnoredNodesLeft(view);
2616
+ return selectVertically(view, -1, mods) || skipIgnoredNodes(view, -1);
2540
2617
  }
2541
2618
  else if (code == 40 || (mac && code == 78 && mods == "c")) { // Down arrow, Ctrl-n on Mac
2542
- return safariDownArrowBug(view) || selectVertically(view, 1, mods) || skipIgnoredNodesRight(view);
2619
+ return safariDownArrowBug(view) || selectVertically(view, 1, mods) || skipIgnoredNodesAfter(view);
2543
2620
  }
2544
2621
  else if (mods == (mac ? "m" : "c") &&
2545
2622
  (code == 66 || code == 73 || code == 89 || code == 90)) { // Mod-[biyz]
@@ -2818,6 +2895,7 @@ class InputState {
2818
2895
  this.composingTimeout = -1;
2819
2896
  this.compositionNodes = [];
2820
2897
  this.compositionEndedAt = -2e8;
2898
+ this.compositionID = 1;
2821
2899
  this.domChangeCount = 0;
2822
2900
  this.eventHandlers = Object.create(null);
2823
2901
  this.hideSelectionGuard = null;
@@ -3247,6 +3325,7 @@ editHandlers.compositionend = (view, event) => {
3247
3325
  if (view.composing) {
3248
3326
  view.input.composing = false;
3249
3327
  view.input.compositionEndedAt = event.timeStamp;
3328
+ view.input.compositionID++;
3250
3329
  scheduleComposeEnd(view, 20);
3251
3330
  }
3252
3331
  };
@@ -4590,6 +4669,8 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
4590
4669
  tr.setMeta("pointer", true);
4591
4670
  else if (origin == "key")
4592
4671
  tr.scrollIntoView();
4672
+ if (view.composing)
4673
+ tr.setMeta("composition", view.input.compositionID);
4593
4674
  view.dispatch(tr);
4594
4675
  }
4595
4676
  return;
@@ -4628,8 +4709,12 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
4628
4709
  else {
4629
4710
  if (parse.sel) {
4630
4711
  let sel = resolveSelection(view, view.state.doc, parse.sel);
4631
- if (sel && !sel.eq(view.state.selection))
4632
- 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
+ }
4633
4718
  }
4634
4719
  return;
4635
4720
  }
@@ -4760,6 +4845,8 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
4760
4845
  }
4761
4846
  if (storedMarks)
4762
4847
  tr.ensureMarks(storedMarks);
4848
+ if (view.composing)
4849
+ tr.setMeta("composition", view.input.compositionID);
4763
4850
  view.dispatch(tr.scrollIntoView());
4764
4851
  }
4765
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.2",
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
@@ -90,6 +90,7 @@ export function readDOMChange(view: EditorView, from: number, to: number, typeOv
90
90
  let tr = view.state.tr.setSelection(newSel)
91
91
  if (origin == "pointer") tr.setMeta("pointer", true)
92
92
  else if (origin == "key") tr.scrollIntoView()
93
+ if (view.composing) tr.setMeta("composition", view.input.compositionID)
93
94
  view.dispatch(tr)
94
95
  }
95
96
  return
@@ -130,7 +131,11 @@ export function readDOMChange(view: EditorView, from: number, to: number, typeOv
130
131
  } else {
131
132
  if (parse.sel) {
132
133
  let sel = resolveSelection(view, view.state.doc, parse.sel)
133
- 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
+ }
134
139
  }
135
140
  return
136
141
  }
@@ -263,6 +268,7 @@ export function readDOMChange(view: EditorView, from: number, to: number, typeOv
263
268
  tr.setSelection(sel)
264
269
  }
265
270
  if (storedMarks) tr.ensureMarks(storedMarks)
271
+ if (view.composing) tr.setMeta("composition", view.input.compositionID)
266
272
  view.dispatch(tr.scrollIntoView())
267
273
  }
268
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!)
@@ -255,16 +260,8 @@ function elementFromPoint(element: HTMLElement, coords: {top: number, left: numb
255
260
  // Given an x,y position on the editor, get the position in the document.
256
261
  export function posAtCoords(view: EditorView, coords: {top: number, left: number}) {
257
262
  let doc = view.dom.ownerDocument, node: Node | undefined, offset = 0
258
- if ((doc as any).caretPositionFromPoint) {
259
- try { // Firefox throws for this call in hard-to-predict circumstances (#994)
260
- let pos = (doc as any).caretPositionFromPoint(coords.left, coords.top)
261
- if (pos) ({offsetNode: node, offset} = pos)
262
- } catch (_) {}
263
- }
264
- if (!node && doc.caretRangeFromPoint) {
265
- let range = doc.caretRangeFromPoint(coords.left, coords.top)
266
- if (range) ({startContainer: node, startOffset: offset} = range)
267
- }
263
+ let caret = caretFromPoint(doc, coords.left, coords.top)
264
+ if (caret) ({node, offset} = caret)
268
265
 
269
266
  let elt = ((view.root as any).elementFromPoint ? view.root : doc)
270
267
  .elementFromPoint(coords.left, coords.top) as HTMLElement
@@ -312,9 +309,17 @@ export function posAtCoords(view: EditorView, coords: {top: number, left: number
312
309
  return {pos, inside: desc ? desc.posAtStart - desc.border : -1}
313
310
  }
314
311
 
312
+ function nonZero(rect: DOMRect) {
313
+ return rect.top < rect.bottom || rect.left < rect.right
314
+ }
315
+
315
316
  function singleRect(target: HTMLElement | Range, bias: number): DOMRect {
316
317
  let rects = target.getClientRects()
317
- 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()
318
323
  }
319
324
 
320
325
  const BIDI = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/
@@ -348,7 +353,7 @@ export function coordsAtPos(view: EditorView, pos: number, side: number): Rect {
348
353
  else if (side >= 0 && offset == node.nodeValue!.length) { from--; takeSide = 1 }
349
354
  else if (side < 0) { from-- }
350
355
  else { to ++ }
351
- 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)
352
357
  }
353
358
  }
354
359
 
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