@codemirror/view 0.19.32 → 0.19.36

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,45 @@
1
+ ## 0.19.36 (2021-12-22)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix a crash in `posAtCoords` when the position lies in a block widget that is rendered but scrolled out of view.
6
+
7
+ Adding block decorations from a plugin now raises an error. Replacing decorations that cross lines are ignored, when provided by a plugin.
8
+
9
+ Fix inverted interpretation of the `precise` argument to `posAtCoords`.
10
+
11
+ ## 0.19.35 (2021-12-20)
12
+
13
+ ### Bug fixes
14
+
15
+ The editor will now handle double-taps as if they are double-clicks, rather than letting the browser's native behavior happen (because the latter often does the wrong thing).
16
+
17
+ Fix an issue where backspacing out a selection on Chrome Android would sometimes only delete the last character due to event order issues.
18
+
19
+ `posAtCoords`, without second argument, will no longer return null for positions below or above the document.
20
+
21
+ ## 0.19.34 (2021-12-17)
22
+
23
+ ### Bug fixes
24
+
25
+ Fix a bug where content line elements would in some cases lose their `cm-line` class. Move test to scrollIntoView
26
+
27
+ ## 0.19.33 (2021-12-16)
28
+
29
+ ### Breaking changes
30
+
31
+ `EditorView.scrollTo` and `EditorView.centerOn` are deprecated in favor of `EditorView.scrollIntoView`, and will be removed in the next breaking release.
32
+
33
+ ### Bug fixes
34
+
35
+ Fix an issue that could cause the editor to unnecessarily interfere with composition (especially visible on macOS Chrome).
36
+
37
+ A composition started with multiple lines selected will no longer be interruptd by the editor.
38
+
39
+ ### New features
40
+
41
+ The new `EditorView.scrollIntoView` function allows you to do more fine-grained scrolling.
42
+
1
43
  ## 0.19.32 (2021-12-15)
2
44
 
3
45
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -101,8 +101,7 @@ function windowRect(win) {
101
101
  return { left: 0, right: win.innerWidth,
102
102
  top: 0, bottom: win.innerHeight };
103
103
  }
104
- const ScrollSpace = 5;
105
- function scrollRectIntoView(dom, rect, side, center) {
104
+ function scrollRectIntoView(dom, rect, side, x, y, xMargin, yMargin, ltr) {
106
105
  let doc = dom.ownerDocument, win = doc.defaultView;
107
106
  for (let cur = dom; cur;) {
108
107
  if (cur.nodeType == 1) { // Element
@@ -121,38 +120,42 @@ function scrollRectIntoView(dom, rect, side, center) {
121
120
  top: rect.top, bottom: rect.top + cur.clientHeight };
122
121
  }
123
122
  let moveX = 0, moveY = 0;
124
- if (center) {
123
+ if (y == "nearest") {
124
+ if (rect.top < bounding.top) {
125
+ moveY = -(bounding.top - rect.top + yMargin);
126
+ if (side > 0 && rect.bottom > bounding.bottom + moveY)
127
+ moveY = rect.bottom - bounding.bottom + moveY + yMargin;
128
+ }
129
+ else if (rect.bottom > bounding.bottom) {
130
+ moveY = rect.bottom - bounding.bottom + yMargin;
131
+ if (side < 0 && (rect.top - moveY) < bounding.top)
132
+ moveY = -(bounding.top + moveY - rect.top + yMargin);
133
+ }
134
+ }
135
+ else {
125
136
  let rectHeight = rect.bottom - rect.top, boundingHeight = bounding.bottom - bounding.top;
126
- let targetTop;
127
- if (rectHeight <= boundingHeight)
128
- targetTop = rect.top + rectHeight / 2 - boundingHeight / 2;
129
- else if (side < 0)
130
- targetTop = rect.top - ScrollSpace;
131
- else
132
- targetTop = rect.bottom + ScrollSpace - boundingHeight;
137
+ let targetTop = y == "center" && rectHeight <= boundingHeight ? rect.top + rectHeight / 2 - boundingHeight / 2 :
138
+ y == "start" || y == "center" && side < 0 ? rect.top - yMargin :
139
+ rect.bottom - boundingHeight + yMargin;
133
140
  moveY = targetTop - bounding.top;
134
- if (Math.abs(moveY) <= 1)
135
- moveY = 0;
136
- }
137
- else if (rect.top < bounding.top) {
138
- moveY = -(bounding.top - rect.top + ScrollSpace);
139
- if (side > 0 && rect.bottom > bounding.bottom + moveY)
140
- moveY = rect.bottom - bounding.bottom + moveY + ScrollSpace;
141
- }
142
- else if (rect.bottom > bounding.bottom) {
143
- moveY = rect.bottom - bounding.bottom + ScrollSpace;
144
- if (side < 0 && (rect.top - moveY) < bounding.top)
145
- moveY = -(bounding.top + moveY - rect.top + ScrollSpace);
146
141
  }
147
- if (rect.left < bounding.left) {
148
- moveX = -(bounding.left - rect.left + ScrollSpace);
149
- if (side > 0 && rect.right > bounding.right + moveX)
150
- moveX = rect.right - bounding.right + moveX + ScrollSpace;
142
+ if (x == "nearest") {
143
+ if (rect.left < bounding.left) {
144
+ moveX = -(bounding.left - rect.left + xMargin);
145
+ if (side > 0 && rect.right > bounding.right + moveX)
146
+ moveX = rect.right - bounding.right + moveX + xMargin;
147
+ }
148
+ else if (rect.right > bounding.right) {
149
+ moveX = rect.right - bounding.right + xMargin;
150
+ if (side < 0 && rect.left < bounding.left + moveX)
151
+ moveX = -(bounding.left + moveX - rect.left + xMargin);
152
+ }
151
153
  }
152
- else if (rect.right > bounding.right) {
153
- moveX = rect.right - bounding.right + ScrollSpace;
154
- if (side < 0 && rect.left < bounding.left + moveX)
155
- moveX = -(bounding.left + moveX - rect.left + ScrollSpace);
154
+ else {
155
+ let targetLeft = x == "center" ? rect.left + (rect.right - rect.left) / 2 - (bounding.right - bounding.left) / 2 :
156
+ (x == "start") == ltr ? rect.left - xMargin :
157
+ rect.right - (bounding.right - bounding.left) + xMargin;
158
+ moveX = targetLeft - bounding.left;
156
159
  }
157
160
  if (moveX || moveY) {
158
161
  if (top) {
@@ -176,7 +179,7 @@ function scrollRectIntoView(dom, rect, side, center) {
176
179
  if (top)
177
180
  break;
178
181
  cur = cur.assignedSlot || cur.parentNode;
179
- center = false;
182
+ x = y = "nearest";
180
183
  }
181
184
  else if (cur.nodeType == 11) { // A shadow root
182
185
  cur = cur.host;
@@ -263,6 +266,10 @@ function getRoot(node) {
263
266
  }
264
267
  return null;
265
268
  }
269
+ function clearAttributes(node) {
270
+ while (node.attributes.length)
271
+ node.removeAttributeNode(node.attributes[0]);
272
+ }
266
273
 
267
274
  class DOMPos {
268
275
  constructor(node, offset, precise = true) {
@@ -309,14 +316,16 @@ class ContentView {
309
316
  // given position.
310
317
  coordsAt(_pos, _side) { return null; }
311
318
  sync(track) {
312
- var _a;
313
319
  if (this.dirty & 2 /* Node */) {
314
320
  let parent = this.dom;
315
321
  let pos = parent.firstChild;
316
322
  for (let child of this.children) {
317
323
  if (child.dirty) {
318
- if (!child.dom && pos && !((_a = ContentView.get(pos)) === null || _a === void 0 ? void 0 : _a.parent))
319
- child.reuseDOM(pos);
324
+ if (!child.dom && pos) {
325
+ let contentView = ContentView.get(pos);
326
+ if (!contentView || !contentView.parent && contentView.constructor == child.constructor)
327
+ child.reuseDOM(pos);
328
+ }
320
329
  child.sync(track);
321
330
  child.dirty = 0 /* Not */;
322
331
  }
@@ -344,7 +353,7 @@ class ContentView {
344
353
  }
345
354
  }
346
355
  }
347
- reuseDOM(_dom) { return false; }
356
+ reuseDOM(_dom) { }
348
357
  localPosFromDOM(node, offset) {
349
358
  let after;
350
359
  if (node == this.dom) {
@@ -643,10 +652,8 @@ class TextView extends ContentView {
643
652
  }
644
653
  }
645
654
  reuseDOM(dom) {
646
- if (dom.nodeType != 3)
647
- return false;
648
- this.createDOM(dom);
649
- return true;
655
+ if (dom.nodeType == 3)
656
+ this.createDOM(dom);
650
657
  }
651
658
  merge(from, to, source) {
652
659
  if (source && (!(source instanceof TextView) || this.length - (to - from) + source.length > MaxJoinLen))
@@ -681,18 +688,26 @@ class MarkView extends ContentView {
681
688
  for (let ch of children)
682
689
  ch.setParent(this);
683
690
  }
684
- createDOM() {
685
- let dom = document.createElement(this.mark.tagName);
691
+ setAttrs(dom) {
692
+ clearAttributes(dom);
686
693
  if (this.mark.class)
687
694
  dom.className = this.mark.class;
688
695
  if (this.mark.attrs)
689
696
  for (let name in this.mark.attrs)
690
697
  dom.setAttribute(name, this.mark.attrs[name]);
691
- this.setDOM(dom);
698
+ return dom;
699
+ }
700
+ reuseDOM(node) {
701
+ if (node.nodeName == this.mark.tagName.toUpperCase()) {
702
+ this.setDOM(node);
703
+ this.dirty |= 4 /* Attrs */ | 2 /* Node */;
704
+ }
692
705
  }
693
706
  sync(track) {
694
- if (!this.dom || (this.dirty & 4 /* Attrs */))
695
- this.createDOM();
707
+ if (!this.dom)
708
+ this.setDOM(this.setAttrs(document.createElement(this.mark.tagName)));
709
+ else if (this.dirty & 4 /* Attrs */)
710
+ this.setAttrs(this.dom);
696
711
  super.sync(track);
697
712
  }
698
713
  merge(from, to, source, _hasStart, openStart, openEnd) {
@@ -836,8 +851,7 @@ class WidgetView extends ContentView {
836
851
  }
837
852
  class CompositionView extends WidgetView {
838
853
  domAtPos(pos) { return new DOMPos(this.widget.text, pos); }
839
- sync() { if (!this.dom)
840
- this.setDOM(this.widget.toDOM()); }
854
+ sync() { this.setDOM(this.widget.toDOM()); }
841
855
  localPosFromDOM(node, offset) {
842
856
  return !offset ? 0 : node.nodeType == 3 ? Math.min(offset, this.length) : this.length;
843
857
  }
@@ -1302,13 +1316,24 @@ class LineView extends ContentView {
1302
1316
  domAtPos(pos) {
1303
1317
  return inlineDOMAtPos(this.dom, this.children, pos);
1304
1318
  }
1319
+ reuseDOM(node) {
1320
+ if (node.nodeName == "DIV") {
1321
+ this.setDOM(node);
1322
+ this.dirty |= 4 /* Attrs */ | 2 /* Node */;
1323
+ }
1324
+ }
1305
1325
  sync(track) {
1306
1326
  var _a;
1307
- if (!this.dom || (this.dirty & 4 /* Attrs */)) {
1327
+ if (!this.dom) {
1308
1328
  this.setDOM(document.createElement("div"));
1309
1329
  this.dom.className = "cm-line";
1310
1330
  this.prevAttrs = this.attrs ? null : undefined;
1311
1331
  }
1332
+ else if (this.dirty & 4 /* Attrs */) {
1333
+ clearAttributes(this.dom);
1334
+ this.dom.className = "cm-line";
1335
+ this.prevAttrs = this.attrs ? null : undefined;
1336
+ }
1312
1337
  if (this.prevAttrs !== undefined) {
1313
1338
  updateAttrs(this.dom, this.prevAttrs, this.attrs);
1314
1339
  this.dom.classList.add("cm-line");
@@ -1347,16 +1372,17 @@ class LineView extends ContentView {
1347
1372
  become(_other) { return false; }
1348
1373
  get type() { return exports.BlockType.Text; }
1349
1374
  static find(docView, pos) {
1350
- for (let i = 0, off = 0;; i++) {
1375
+ for (let i = 0, off = 0; i < docView.children.length; i++) {
1351
1376
  let block = docView.children[i], end = off + block.length;
1352
1377
  if (end >= pos) {
1353
1378
  if (block instanceof LineView)
1354
1379
  return block;
1355
- if (block.length)
1356
- return null;
1380
+ if (end > pos)
1381
+ break;
1357
1382
  }
1358
1383
  off = end + block.breakAfter;
1359
1384
  }
1385
+ return null;
1360
1386
  }
1361
1387
  }
1362
1388
  class BlockWidgetView extends ContentView {
@@ -1417,10 +1443,11 @@ class BlockWidgetView extends ContentView {
1417
1443
  }
1418
1444
 
1419
1445
  class ContentBuilder {
1420
- constructor(doc, pos, end) {
1446
+ constructor(doc, pos, end, disallowBlockEffectsBelow) {
1421
1447
  this.doc = doc;
1422
1448
  this.pos = pos;
1423
1449
  this.end = end;
1450
+ this.disallowBlockEffectsBelow = disallowBlockEffectsBelow;
1424
1451
  this.content = [];
1425
1452
  this.curLine = null;
1426
1453
  this.breakAtStart = 0;
@@ -1549,8 +1576,15 @@ class ContentBuilder {
1549
1576
  if (this.openStart < 0)
1550
1577
  this.openStart = openStart;
1551
1578
  }
1552
- static build(text, from, to, decorations) {
1553
- let builder = new ContentBuilder(text, from, to);
1579
+ filterPoint(from, to, value, index) {
1580
+ if (index >= this.disallowBlockEffectsBelow || !(value instanceof PointDecoration))
1581
+ return true;
1582
+ if (value.block)
1583
+ throw new RangeError("Block decorations may not be specified via plugins");
1584
+ return to < this.doc.lineAt(this.pos).to;
1585
+ }
1586
+ static build(text, from, to, decorations, pluginDecorationLength) {
1587
+ let builder = new ContentBuilder(text, from, to, pluginDecorationLength);
1554
1588
  builder.openEnd = rangeset.RangeSet.spans(decorations, from, to, builder);
1555
1589
  if (builder.openStart < 0)
1556
1590
  builder.openStart = builder.openEnd;
@@ -1580,12 +1614,27 @@ const mouseSelectionStyle = state.Facet.define();
1580
1614
  const exceptionSink = state.Facet.define();
1581
1615
  const updateListener = state.Facet.define();
1582
1616
  const inputHandler = state.Facet.define();
1617
+ // FIXME remove
1583
1618
  const scrollTo = state.StateEffect.define({
1584
1619
  map: (range, changes) => range.map(changes)
1585
1620
  });
1621
+ // FIXME remove
1586
1622
  const centerOn = state.StateEffect.define({
1587
1623
  map: (range, changes) => range.map(changes)
1588
1624
  });
1625
+ class ScrollTarget {
1626
+ constructor(range, y = "nearest", x = "nearest", yMargin = 5, xMargin = 5) {
1627
+ this.range = range;
1628
+ this.y = y;
1629
+ this.x = x;
1630
+ this.yMargin = yMargin;
1631
+ this.xMargin = xMargin;
1632
+ }
1633
+ map(changes) {
1634
+ return changes.empty ? this : new ScrollTarget(this.range.map(changes), this.y, this.x, this.yMargin, this.xMargin);
1635
+ }
1636
+ }
1637
+ const scrollIntoView = state.StateEffect.define({ map: (t, ch) => t.map(ch) });
1589
1638
  /**
1590
1639
  Log or report an unhandled exception in client code. Should
1591
1640
  probably only be used by extension code that allows client code to
@@ -1658,11 +1707,13 @@ This field can be used by plugins to provide
1658
1707
  **Note**: For reasons of data flow (plugins are only updated
1659
1708
  after the viewport is computed), decorations produced by plugins
1660
1709
  are _not_ taken into account when predicting the vertical layout
1661
- structure of the editor. Thus, things like large widgets or big
1662
- replacements (i.e. code folding) should be provided through the
1663
- state-level [`decorations` facet](https://codemirror.net/6/docs/ref/#view.EditorView^decorations),
1664
- not this plugin field. Specifically, replacing decorations that
1665
- cross line boundaries will break if provided through a plugin.
1710
+ structure of the editor. They **must not** introduce block
1711
+ widgets (that will raise an error) or replacing decorations that
1712
+ cover line breaks (these will be ignored if they occur). Such
1713
+ decorations, or others that cause a large amount of vertical
1714
+ size shift compared to the undecorated content, should be
1715
+ provided through the state-level [`decorations`
1716
+ facet](https://codemirror.net/6/docs/ref/#view.EditorView^decorations) instead.
1666
1717
  */
1667
1718
  PluginField.decorations = PluginField.define();
1668
1719
  /**
@@ -1895,8 +1946,6 @@ class ViewUpdate {
1895
1946
  view.inputState.notifiedFocused = focus;
1896
1947
  this.flags |= 1 /* Focus */;
1897
1948
  }
1898
- if (this.docChanged)
1899
- this.flags |= 2 /* Height */;
1900
1949
  }
1901
1950
  /**
1902
1951
  Tells you whether the [viewport](https://codemirror.net/6/docs/ref/#view.EditorView.viewport) or
@@ -1907,14 +1956,15 @@ class ViewUpdate {
1907
1956
  return (this.flags & 4 /* Viewport */) > 0;
1908
1957
  }
1909
1958
  /**
1910
- Indicates whether the line height in the editor changed in this update.
1959
+ Indicates whether the height of an element in the editor changed
1960
+ in this update.
1911
1961
  */
1912
1962
  get heightChanged() {
1913
1963
  return (this.flags & 2 /* Height */) > 0;
1914
1964
  }
1915
1965
  /**
1916
- Returns true when the document changed or the size of the editor
1917
- or the lines or characters within it has changed.
1966
+ Returns true when the document was modified or the size of the
1967
+ editor, or elements within the editor, changed.
1918
1968
  */
1919
1969
  get geometryChanged() {
1920
1970
  return this.docChanged || (this.flags & (8 /* Geometry */ | 2 /* Height */)) > 0;
@@ -2333,6 +2383,7 @@ class DocView extends ContentView {
2333
2383
  this.view = view;
2334
2384
  this.compositionDeco = Decoration.none;
2335
2385
  this.decorations = [];
2386
+ this.pluginDecorationLength = 0;
2336
2387
  // Track a minimum width for the editor. When measuring sizes in
2337
2388
  // measureVisibleLineHeights, this is updated to point at the width
2338
2389
  // of a given element and its extent in the document. When a change
@@ -2354,7 +2405,8 @@ class DocView extends ContentView {
2354
2405
  this.setDOM(view.contentDOM);
2355
2406
  this.children = [new LineView];
2356
2407
  this.children[0].setParent(this);
2357
- this.updateInner([new ChangedRange(0, 0, 0, view.state.doc.length)], this.updateDeco(), 0);
2408
+ this.updateDeco();
2409
+ this.updateInner([new ChangedRange(0, 0, 0, view.state.doc.length)], 0);
2358
2410
  }
2359
2411
  get root() { return this.view.root; }
2360
2412
  get editorView() { return this.view; }
@@ -2393,7 +2445,7 @@ class DocView extends ContentView {
2393
2445
  return false;
2394
2446
  }
2395
2447
  else {
2396
- this.updateInner(changedRanges, deco, update.startState.doc.length);
2448
+ this.updateInner(changedRanges, update.startState.doc.length);
2397
2449
  if (update.transactions.length)
2398
2450
  this.lastUpdate = Date.now();
2399
2451
  return true;
@@ -2401,9 +2453,9 @@ class DocView extends ContentView {
2401
2453
  }
2402
2454
  // Used by update and the constructor do perform the actual DOM
2403
2455
  // update
2404
- updateInner(changes, deco, oldLength) {
2456
+ updateInner(changes, oldLength) {
2405
2457
  this.view.viewState.mustMeasureContent = true;
2406
- this.updateChildren(changes, deco, oldLength);
2458
+ this.updateChildren(changes, oldLength);
2407
2459
  let { observer } = this.view;
2408
2460
  observer.ignore(() => {
2409
2461
  // Lock the height during redrawing, since Chrome sometimes
@@ -2430,14 +2482,14 @@ class DocView extends ContentView {
2430
2482
  gaps.push(child.dom);
2431
2483
  observer.updateGaps(gaps);
2432
2484
  }
2433
- updateChildren(changes, deco, oldLength) {
2485
+ updateChildren(changes, oldLength) {
2434
2486
  let cursor = this.childCursor(oldLength);
2435
2487
  for (let i = changes.length - 1;; i--) {
2436
2488
  let next = i >= 0 ? changes[i] : null;
2437
2489
  if (!next)
2438
2490
  break;
2439
2491
  let { fromA, toA, fromB, toB } = next;
2440
- let { content, breakAtStart, openStart, openEnd } = ContentBuilder.build(this.view.state.doc, fromB, toB, deco);
2492
+ let { content, breakAtStart, openStart, openEnd } = ContentBuilder.build(this.view.state.doc, fromB, toB, this.decorations, this.pluginDecorationLength);
2441
2493
  let { i: toI, off: toOff } = cursor.findPos(toA, 1);
2442
2494
  let { i: fromI, off: fromOff } = cursor.findPos(fromA, -1);
2443
2495
  replaceRange(this, fromI, fromOff, toI, toOff, content, breakAtStart, openStart, openEnd);
@@ -2656,15 +2708,18 @@ class DocView extends ContentView {
2656
2708
  return Decoration.set(deco);
2657
2709
  }
2658
2710
  updateDeco() {
2711
+ let pluginDecorations = this.view.pluginField(PluginField.decorations);
2712
+ this.pluginDecorationLength = pluginDecorations.length;
2659
2713
  return this.decorations = [
2660
- ...this.view.pluginField(PluginField.decorations),
2714
+ ...pluginDecorations,
2661
2715
  ...this.view.state.facet(decorations),
2662
2716
  this.compositionDeco,
2663
2717
  this.computeBlockGapDeco(),
2664
2718
  this.view.viewState.lineGapDeco
2665
2719
  ];
2666
2720
  }
2667
- scrollIntoView({ range, center }) {
2721
+ scrollIntoView(target) {
2722
+ let { range } = target;
2668
2723
  let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
2669
2724
  if (!rect)
2670
2725
  return;
@@ -2684,10 +2739,11 @@ class DocView extends ContentView {
2684
2739
  if (bottom != null)
2685
2740
  mBottom = Math.max(mBottom, bottom);
2686
2741
  }
2687
- scrollRectIntoView(this.view.scrollDOM, {
2742
+ let targetRect = {
2688
2743
  left: rect.left - mLeft, top: rect.top - mTop,
2689
2744
  right: rect.right + mRight, bottom: rect.bottom + mBottom
2690
- }, range.head < range.anchor ? -1 : 1, center);
2745
+ };
2746
+ scrollRectIntoView(this.view.scrollDOM, targetRect, range.head < range.anchor ? -1 : 1, target.x, target.y, target.xMargin, target.yMargin, this.view.textDirection == exports.Direction.LTR);
2691
2747
  }
2692
2748
  }
2693
2749
  function betweenUneditable(pos) {
@@ -2948,12 +3004,8 @@ function domPosInText(node, x, y) {
2948
3004
  function posAtCoords(view, { x, y }, precise, bias = -1) {
2949
3005
  var _a;
2950
3006
  let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
2951
- let block, yOffset = y - docTop, { docHeight } = view.viewState;
2952
- if (yOffset < 0 || yOffset > docHeight) {
2953
- if (precise)
2954
- return null;
2955
- yOffset = yOffset < 0 ? 0 : docHeight;
2956
- }
3007
+ let block, { docHeight } = view.viewState;
3008
+ let yOffset = Math.max(0, Math.min(y - docTop, docHeight));
2957
3009
  // Scan for a text block near the queried y position
2958
3010
  for (let halfLine = view.defaultLineHeight / 2, bounced = false;;) {
2959
3011
  block = view.elementAtHeight(yOffset);
@@ -2974,20 +3026,29 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
2974
3026
  }
2975
3027
  y = docTop + yOffset;
2976
3028
  let lineStart = block.from;
2977
- // Clip x to the viewport sides
2978
- x = Math.max(content.left + 1, Math.min(Math.max(content.right, content.left + view.docView.minWidth) - 1, x));
2979
3029
  // If this is outside of the rendered viewport, we can't determine a position
2980
3030
  if (lineStart < view.viewport.from)
2981
- return view.viewport.from == 0 ? 0 : posAtCoordsImprecise(view, content, block, x, y);
3031
+ return view.viewport.from == 0 ? 0 : precise ? null : posAtCoordsImprecise(view, content, block, x, y);
2982
3032
  if (lineStart > view.viewport.to)
2983
- return view.viewport.to == view.state.doc.length ? view.state.doc.length : posAtCoordsImprecise(view, content, block, x, y);
3033
+ return view.viewport.to == view.state.doc.length ? view.state.doc.length :
3034
+ precise ? null : posAtCoordsImprecise(view, content, block, x, y);
2984
3035
  // Prefer ShadowRootOrDocument.elementFromPoint if present, fall back to document if not
2985
3036
  let doc = view.dom.ownerDocument;
2986
- let element = (view.root.elementFromPoint ? view.root : doc).elementFromPoint(x, y);
3037
+ let root = view.root.elementFromPoint ? view.root : doc;
3038
+ let element = root.elementFromPoint(x, y);
3039
+ if (element && !view.contentDOM.contains(element))
3040
+ element = null;
3041
+ // If the element is unexpected, clip x at the sides of the content area and try again
3042
+ if (!element) {
3043
+ x = Math.max(content.left + 1, Math.min(content.right - 1, x));
3044
+ element = root.elementFromPoint(x, y);
3045
+ if (element && !view.contentDOM.contains(element))
3046
+ element = null;
3047
+ }
2987
3048
  // There's visible editor content under the point, so we can try
2988
3049
  // using caret(Position|Range)FromPoint as a shortcut
2989
3050
  let node, offset = -1;
2990
- if (element && view.contentDOM.contains(element) && ((_a = view.docView.nearest(element)) === null || _a === void 0 ? void 0 : _a.isEditable) != false) {
3051
+ if (element && ((_a = view.docView.nearest(element)) === null || _a === void 0 ? void 0 : _a.isEditable) != false) {
2991
3052
  if (doc.caretPositionFromPoint) {
2992
3053
  let pos = doc.caretPositionFromPoint(x, y);
2993
3054
  if (pos)
@@ -3005,6 +3066,8 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
3005
3066
  // No luck, do our own (potentially expensive) search
3006
3067
  if (!node || !view.docView.dom.contains(node)) {
3007
3068
  let line = LineView.find(view.docView, lineStart);
3069
+ if (!line)
3070
+ return yOffset > block.top + block.height / 2 ? block.to : block.from;
3008
3071
  ({ node, offset } = domPosAtCoords(line.dom, x, y));
3009
3072
  }
3010
3073
  return view.docView.posFromDOM(node, offset);
@@ -3468,7 +3531,7 @@ handlers.touchmove = view => {
3468
3531
  };
3469
3532
  handlers.mousedown = (view, event) => {
3470
3533
  view.observer.flush();
3471
- if (lastTouch > Date.now() - 2000)
3534
+ if (lastTouch > Date.now() - 2000 && getClickType(event) == 1)
3472
3535
  return; // Ignore touch interaction
3473
3536
  let style = null;
3474
3537
  for (let makeStyle of view.state.facet(mouseSelectionStyle)) {
@@ -3590,9 +3653,9 @@ handlers.dragstart = (view, event) => {
3590
3653
  }
3591
3654
  };
3592
3655
  function dropText(view, event, text, direct) {
3593
- let dropPos = view.posAtCoords({ x: event.clientX, y: event.clientY });
3594
- if (dropPos == null || !text)
3656
+ if (!text)
3595
3657
  return;
3658
+ let dropPos = view.posAtCoords({ x: event.clientX, y: event.clientY }, false);
3596
3659
  event.preventDefault();
3597
3660
  let { mouseSelection } = view.inputState;
3598
3661
  let del = direct && mouseSelection && mouseSelection.dragging && mouseSelection.dragMove ?
@@ -4521,15 +4584,6 @@ class LineGapWidget extends WidgetType {
4521
4584
  }
4522
4585
  get estimatedHeight() { return this.vertical ? this.size : -1; }
4523
4586
  }
4524
- class ScrollTarget {
4525
- constructor(range, center = false) {
4526
- this.range = range;
4527
- this.center = center;
4528
- }
4529
- map(changes) {
4530
- return changes.empty ? this : new ScrollTarget(this.range.map(changes), this.center);
4531
- }
4532
- }
4533
4587
  class ViewState {
4534
4588
  constructor(state) {
4535
4589
  this.state = state;
@@ -4710,9 +4764,9 @@ class ViewState {
4710
4764
  let { head } = scrollTarget.range, viewHeight = this.editorHeight;
4711
4765
  if (head < viewport.from || head > viewport.to) {
4712
4766
  let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4713
- if (scrollTarget.center)
4767
+ if (scrollTarget.y == "center")
4714
4768
  topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
4715
- else if (head < viewport.from)
4769
+ else if (scrollTarget.y == "start" || scrollTarget.y == "nearest" && head < viewport.from)
4716
4770
  topPos = block.top;
4717
4771
  else
4718
4772
  topPos = block.bottom - viewHeight;
@@ -5259,8 +5313,10 @@ class DOMObserver {
5259
5313
  // Deletions on IE11 fire their events in the wrong order, giving
5260
5314
  // us a selection change event before the DOM changes are
5261
5315
  // reported.
5262
- // (Selection.isCollapsed isn't reliable on IE)
5263
- if (browser.ie && browser.ie_version <= 11 && !view.state.selection.main.empty &&
5316
+ // Chrome Android has a similar issue when backspacing out a
5317
+ // selection (#645).
5318
+ if ((browser.ie && browser.ie_version <= 11 || browser.android && browser.chrome) && !view.state.selection.main.empty &&
5319
+ // (Selection.isCollapsed isn't reliable on IE)
5264
5320
  sel.focusNode && isEquivalentPosition(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset))
5265
5321
  this.flushSoon();
5266
5322
  else
@@ -5818,7 +5874,9 @@ class EditorView {
5818
5874
  if (e.is(scrollTo))
5819
5875
  scrollTarget = new ScrollTarget(e.value);
5820
5876
  else if (e.is(centerOn))
5821
- scrollTarget = new ScrollTarget(e.value, true);
5877
+ scrollTarget = new ScrollTarget(e.value, "center");
5878
+ else if (e.is(scrollIntoView))
5879
+ scrollTarget = e.value;
5822
5880
  }
5823
5881
  }
5824
5882
  this.viewState.update(update, scrollTarget);
@@ -6386,6 +6444,14 @@ class EditorView {
6386
6444
  this.destroyed = true;
6387
6445
  }
6388
6446
  /**
6447
+ Returns an effect that can be
6448
+ [added](https://codemirror.net/6/docs/ref/#state.TransactionSpec.effects) to a transaction to
6449
+ cause it to scroll the given position or range into view.
6450
+ */
6451
+ static scrollIntoView(pos, options = {}) {
6452
+ return scrollIntoView.of(new ScrollTarget(typeof pos == "number" ? state.EditorSelection.cursor(pos) : pos, options.y, options.x, options.yMargin, options.xMargin));
6453
+ }
6454
+ /**
6389
6455
  Facet that can be used to add DOM event handlers. The value
6390
6456
  should be an object mapping event names to handler functions. The
6391
6457
  first such function to return true will be assumed to have handled
@@ -6439,11 +6505,15 @@ class EditorView {
6439
6505
  /**
6440
6506
  Effect that can be [added](https://codemirror.net/6/docs/ref/#state.TransactionSpec.effects) to a
6441
6507
  transaction to make it scroll the given range into view.
6508
+
6509
+ *Deprecated*. Use [`scrollIntoView`](https://codemirror.net/6/docs/ref/#view.EditorView^scrollIntoView) instead.
6442
6510
  */
6443
6511
  EditorView.scrollTo = scrollTo;
6444
6512
  /**
6445
6513
  Effect that makes the editor scroll the given range to the
6446
6514
  center of the visible view.
6515
+
6516
+ *Deprecated*. Use [`scrollIntoView`](https://codemirror.net/6/docs/ref/#view.EditorView^scrollIntoView) instead.
6447
6517
  */
6448
6518
  EditorView.centerOn = centerOn;
6449
6519
  /**