@codemirror/view 0.19.7 → 0.19.11

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/dist/index.js CHANGED
@@ -1,12 +1,22 @@
1
1
  import { MapMode, Text as Text$1, Facet, StateEffect, ChangeSet, EditorSelection, CharCategory, EditorState, Transaction, Prec, combineConfig } from '@codemirror/state';
2
2
  import { StyleModule } from 'style-mod';
3
- import { RangeValue, RangeSet, RangeSetBuilder } from '@codemirror/rangeset';
3
+ import { RangeSet, RangeValue, RangeSetBuilder } from '@codemirror/rangeset';
4
4
  export { Range } from '@codemirror/rangeset';
5
5
  import { Text, findClusterBreak, findColumn, codePointAt, countColumn } from '@codemirror/text';
6
6
  import { keyName, base } from 'w3c-keyname';
7
7
 
8
8
  function getSelection(root) {
9
- return (root.getSelection ? root.getSelection() : document.getSelection());
9
+ let target;
10
+ // Browsers differ on whether shadow roots have a getSelection
11
+ // method. If it exists, use that, otherwise, call it on the
12
+ // document.
13
+ if (root.nodeType == 11) { // Shadow root
14
+ target = root.getSelection ? root : root.ownerDocument;
15
+ }
16
+ else {
17
+ target = root;
18
+ }
19
+ return target.getSelection();
10
20
  }
11
21
  function contains(dom, node) {
12
22
  return node ? dom.contains(node.nodeType != 1 ? node.parentNode : node) : false;
@@ -89,9 +99,9 @@ function windowRect(win) {
89
99
  top: 0, bottom: win.innerHeight };
90
100
  }
91
101
  const ScrollSpace = 5;
92
- function scrollRectIntoView(dom, rect, side) {
102
+ function scrollRectIntoView(dom, rect, side, center) {
93
103
  let doc = dom.ownerDocument, win = doc.defaultView;
94
- for (let cur = dom.parentNode; cur;) {
104
+ for (let cur = dom; cur;) {
95
105
  if (cur.nodeType == 1) { // Element
96
106
  let bounding, top = cur == doc.body;
97
107
  if (top) {
@@ -108,7 +118,20 @@ function scrollRectIntoView(dom, rect, side) {
108
118
  top: rect.top, bottom: rect.top + cur.clientHeight };
109
119
  }
110
120
  let moveX = 0, moveY = 0;
111
- if (rect.top < bounding.top) {
121
+ if (center) {
122
+ let rectHeight = rect.bottom - rect.top, boundingHeight = bounding.bottom - bounding.top;
123
+ let targetTop;
124
+ if (rectHeight <= boundingHeight)
125
+ targetTop = rect.top + rectHeight / 2 - boundingHeight / 2;
126
+ else if (side < 0)
127
+ targetTop = rect.top - ScrollSpace;
128
+ else
129
+ targetTop = rect.bottom + ScrollSpace - boundingHeight;
130
+ moveY = targetTop - bounding.top;
131
+ if (Math.abs(moveY) <= 1)
132
+ moveY = 0;
133
+ }
134
+ else if (rect.top < bounding.top) {
112
135
  moveY = -(bounding.top - rect.top + ScrollSpace);
113
136
  if (side > 0 && rect.bottom > bounding.bottom + moveY)
114
137
  moveY = rect.bottom - bounding.bottom + moveY + ScrollSpace;
@@ -150,6 +173,7 @@ function scrollRectIntoView(dom, rect, side) {
150
173
  if (top)
151
174
  break;
152
175
  cur = cur.assignedSlot || cur.parentNode;
176
+ center = false;
153
177
  }
154
178
  else if (cur.nodeType == 11) { // A shadow root
155
179
  cur = cur.host;
@@ -238,6 +262,14 @@ function contentEditablePlainTextSupported() {
238
262
  }
239
263
  return _plainTextSupported;
240
264
  }
265
+ function getRoot(node) {
266
+ while (node) {
267
+ node = node.assignedSlot || node.parentNode;
268
+ if (node && (node.nodeType == 9 || node.nodeType == 11 && node.host))
269
+ return node;
270
+ }
271
+ return null;
272
+ }
241
273
 
242
274
  class DOMPos {
243
275
  constructor(node, offset, precise = true) {
@@ -435,6 +467,7 @@ class ContentView {
435
467
  (this.breakAfter ? "#" : "");
436
468
  }
437
469
  static get(node) { return node.cmView; }
470
+ get isEditable() { return true; }
438
471
  }
439
472
  ContentView.prototype.breakAfter = 0;
440
473
  // Remove a DOM node and return its next sibling.
@@ -482,15 +515,18 @@ const gecko = !ie && /*@__PURE__*//gecko\/(\d+)/i.test(nav.userAgent);
482
515
  const chrome = !ie && /*@__PURE__*//Chrome\/(\d+)/.exec(nav.userAgent);
483
516
  const webkit = "webkitFontSmoothing" in doc.documentElement.style;
484
517
  const safari = !ie && /*@__PURE__*//Apple Computer/.test(nav.vendor);
518
+ const ios = safari && (/*@__PURE__*//Mobile\/\w+/.test(nav.userAgent) || nav.maxTouchPoints > 2);
485
519
  var browser = {
486
- mac: /*@__PURE__*//Mac/.test(nav.platform),
520
+ mac: ios || /*@__PURE__*//Mac/.test(nav.platform),
521
+ windows: /*@__PURE__*//Win/.test(nav.platform),
522
+ linux: /*@__PURE__*//Linux|X11/.test(nav.platform),
487
523
  ie,
488
524
  ie_version: ie_upto10 ? doc.documentMode || 6 : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : 0,
489
525
  gecko,
490
526
  gecko_version: gecko ? +(/*@__PURE__*//Firefox\/(\d+)/.exec(nav.userAgent) || [0, 0])[1] : 0,
491
527
  chrome: !!chrome,
492
528
  chrome_version: chrome ? +chrome[1] : 0,
493
- ios: safari && (/*@__PURE__*//Mobile\/\w+/.test(nav.userAgent) || nav.maxTouchPoints > 2),
529
+ ios,
494
530
  android: /*@__PURE__*//Android\b/.test(nav.userAgent),
495
531
  webkit,
496
532
  safari,
@@ -712,6 +748,7 @@ class WidgetView extends InlineView {
712
748
  }
713
749
  return (pos == 0 && side > 0 || pos == this.length && side <= 0) ? rect : flattenRect(rect, pos == 0);
714
750
  }
751
+ get isEditable() { return false; }
715
752
  }
716
753
  class CompositionView extends WidgetView {
717
754
  domAtPos(pos) { return new DOMPos(this.widget.text, pos); }
@@ -723,6 +760,38 @@ class CompositionView extends WidgetView {
723
760
  ignoreMutation() { return false; }
724
761
  get overrideDOMText() { return null; }
725
762
  coordsAt(pos, side) { return textCoords(this.widget.text, pos, side); }
763
+ get isEditable() { return true; }
764
+ }
765
+ // These are drawn around uneditable widgets to avoid a number of
766
+ // browser bugs that show up when the cursor is directly next to
767
+ // uneditable inline content.
768
+ class WidgetBufferView extends InlineView {
769
+ constructor(side) {
770
+ super();
771
+ this.side = side;
772
+ }
773
+ get length() { return 0; }
774
+ merge() { return false; }
775
+ become(other) {
776
+ return other instanceof WidgetBufferView && other.side == this.side;
777
+ }
778
+ slice() { return new WidgetBufferView(this.side); }
779
+ sync() {
780
+ if (!this.dom)
781
+ this.setDOM(document.createTextNode("\u200b"));
782
+ else if (this.dirty && this.dom.nodeValue != "\u200b")
783
+ this.dom.nodeValue = "\u200b";
784
+ }
785
+ getSide() { return this.side; }
786
+ domAtPos(pos) { return DOMPos.before(this.dom); }
787
+ domBoundsAround() { return null; }
788
+ coordsAt(pos) {
789
+ let rects = clientRectsFor(this.dom);
790
+ return rects[rects.length - 1];
791
+ }
792
+ get overrideDOMText() {
793
+ return Text.of([this.dom.nodeValue.replace(/\u200b/g, "")]);
794
+ }
726
795
  }
727
796
  function mergeInlineChildren(parent, from, to, elts, openStart, openEnd) {
728
797
  let cur = parent.childCursor();
@@ -1217,14 +1286,17 @@ class LineView extends ContentView {
1217
1286
  }
1218
1287
  // Only called when building a line view in ContentBuilder
1219
1288
  addLineDeco(deco) {
1220
- let attrs = deco.spec.attributes;
1289
+ let attrs = deco.spec.attributes, cls = deco.spec.class;
1221
1290
  if (attrs)
1222
1291
  this.attrs = combineAttrs(attrs, this.attrs || {});
1292
+ if (cls)
1293
+ this.attrs = combineAttrs(attrs, { class: cls });
1223
1294
  }
1224
1295
  domAtPos(pos) {
1225
1296
  return inlineDOMAtPos(this.dom, this.children, pos);
1226
1297
  }
1227
1298
  sync(track) {
1299
+ var _a;
1228
1300
  if (!this.dom || (this.dirty & 4 /* Attrs */)) {
1229
1301
  this.setDOM(document.createElement("div"));
1230
1302
  this.dom.className = "cm-line";
@@ -1240,7 +1312,7 @@ class LineView extends ContentView {
1240
1312
  while (last && ContentView.get(last) instanceof MarkView)
1241
1313
  last = last.lastChild;
1242
1314
  if (!last ||
1243
- last.nodeName != "BR" && ContentView.get(last) instanceof WidgetView &&
1315
+ last.nodeName != "BR" && ((_a = ContentView.get(last)) === null || _a === void 0 ? void 0 : _a.isEditable) == false &&
1244
1316
  (!browser.ios || !this.children.some(ch => ch instanceof TextView))) {
1245
1317
  let hack = document.createElement("BR");
1246
1318
  hack.cmIgnore = true;
@@ -1339,6 +1411,9 @@ class ContentBuilder {
1339
1411
  this.content = [];
1340
1412
  this.curLine = null;
1341
1413
  this.breakAtStart = 0;
1414
+ this.pendingBuffer = 0 /* No */;
1415
+ // Set to false directly after a widget that covers the position after it
1416
+ this.atCursorPos = true;
1342
1417
  this.openStart = -1;
1343
1418
  this.openEnd = -1;
1344
1419
  this.text = "";
@@ -1353,23 +1428,31 @@ class ContentBuilder {
1353
1428
  return !last.breakAfter && !(last instanceof BlockWidgetView && last.type == BlockType.WidgetBefore);
1354
1429
  }
1355
1430
  getLine() {
1356
- if (!this.curLine)
1431
+ if (!this.curLine) {
1357
1432
  this.content.push(this.curLine = new LineView);
1433
+ this.atCursorPos = true;
1434
+ }
1358
1435
  return this.curLine;
1359
1436
  }
1360
- addWidget(view) {
1437
+ flushBuffer(active) {
1438
+ if (this.pendingBuffer) {
1439
+ this.curLine.append(wrapMarks(new WidgetBufferView(-1), active), active.length);
1440
+ this.pendingBuffer = 0 /* No */;
1441
+ }
1442
+ }
1443
+ addBlockWidget(view) {
1444
+ this.flushBuffer([]);
1361
1445
  this.curLine = null;
1362
1446
  this.content.push(view);
1363
1447
  }
1364
- finish() {
1448
+ finish(openEnd) {
1449
+ if (!openEnd)
1450
+ this.flushBuffer([]);
1451
+ else
1452
+ this.pendingBuffer = 0 /* No */;
1365
1453
  if (!this.posCovered())
1366
1454
  this.getLine();
1367
1455
  }
1368
- wrapMarks(view, active) {
1369
- for (let mark of active)
1370
- view = new MarkView(mark, [view], view.length);
1371
- return view;
1372
- }
1373
1456
  buildText(length, active, openStart) {
1374
1457
  while (length > 0) {
1375
1458
  if (this.textOff == this.text.length) {
@@ -1384,6 +1467,7 @@ class ContentBuilder {
1384
1467
  this.content[this.content.length - 1].breakAfter = 1;
1385
1468
  else
1386
1469
  this.breakAtStart = 1;
1470
+ this.flushBuffer([]);
1387
1471
  this.curLine = null;
1388
1472
  length--;
1389
1473
  continue;
@@ -1394,7 +1478,9 @@ class ContentBuilder {
1394
1478
  }
1395
1479
  }
1396
1480
  let take = Math.min(this.text.length - this.textOff, length, 512 /* Chunk */);
1397
- this.getLine().append(this.wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
1481
+ this.flushBuffer(active);
1482
+ this.getLine().append(wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
1483
+ this.atCursorPos = true;
1398
1484
  this.textOff += take;
1399
1485
  length -= take;
1400
1486
  openStart = 0;
@@ -1413,11 +1499,23 @@ class ContentBuilder {
1413
1499
  let { type } = deco;
1414
1500
  if (type == BlockType.WidgetAfter && !this.posCovered())
1415
1501
  this.getLine();
1416
- this.addWidget(new BlockWidgetView(deco.widget || new NullWidget("div"), len, type));
1502
+ this.addBlockWidget(new BlockWidgetView(deco.widget || new NullWidget("div"), len, type));
1417
1503
  }
1418
1504
  else {
1419
- let widget = this.wrapMarks(WidgetView.create(deco.widget || new NullWidget("span"), len, deco.startSide), active);
1420
- this.getLine().append(widget, openStart);
1505
+ let view = WidgetView.create(deco.widget || new NullWidget("span"), len, deco.startSide);
1506
+ let cursorBefore = this.atCursorPos && !view.isEditable && openStart <= active.length && (from < to || deco.startSide > 0);
1507
+ let cursorAfter = !view.isEditable && (from < to || deco.startSide <= 0);
1508
+ let line = this.getLine();
1509
+ if (this.pendingBuffer == 2 /* IfCursor */ && !cursorBefore)
1510
+ this.pendingBuffer = 0 /* No */;
1511
+ this.flushBuffer(active);
1512
+ if (cursorBefore) {
1513
+ line.append(wrapMarks(new WidgetBufferView(1), active), openStart);
1514
+ openStart = active.length + Math.max(0, openStart - active.length);
1515
+ }
1516
+ line.append(wrapMarks(view, active), openStart);
1517
+ this.atCursorPos = cursorAfter;
1518
+ this.pendingBuffer = !cursorAfter ? 0 /* No */ : from < to ? 1 /* Yes */ : 2 /* IfCursor */;
1421
1519
  }
1422
1520
  }
1423
1521
  else if (this.doc.lineAt(this.pos).from == this.pos) { // Line decoration
@@ -1443,10 +1541,15 @@ class ContentBuilder {
1443
1541
  builder.openEnd = RangeSet.spans(decorations, from, to, builder);
1444
1542
  if (builder.openStart < 0)
1445
1543
  builder.openStart = builder.openEnd;
1446
- builder.finish();
1544
+ builder.finish(builder.openEnd);
1447
1545
  return builder;
1448
1546
  }
1449
1547
  }
1548
+ function wrapMarks(view, active) {
1549
+ for (let mark of active)
1550
+ view = new MarkView(mark, [view], view.length);
1551
+ return view;
1552
+ }
1450
1553
  class NullWidget extends WidgetType {
1451
1554
  constructor(tag) {
1452
1555
  super();
@@ -1467,6 +1570,9 @@ const inputHandler = /*@__PURE__*/Facet.define();
1467
1570
  const scrollTo = /*@__PURE__*/StateEffect.define({
1468
1571
  map: (range, changes) => range.map(changes)
1469
1572
  });
1573
+ const centerOn = /*@__PURE__*/StateEffect.define({
1574
+ map: (range, changes) => range.map(changes)
1575
+ });
1470
1576
  /**
1471
1577
  Log or report an unhandled exception in client code. Should
1472
1578
  probably only be used by extension code that allows client code to
@@ -1779,7 +1885,9 @@ class ViewUpdate {
1779
1885
  this.flags |= 2 /* Height */;
1780
1886
  }
1781
1887
  /**
1782
- Tells you whether the viewport changed in this update.
1888
+ Tells you whether the [viewport](https://codemirror.net/6/docs/ref/#view.EditorView.viewport) or
1889
+ [visible ranges](https://codemirror.net/6/docs/ref/#view.EditorView.visibleRanges) changed in this
1890
+ update.
1783
1891
  */
1784
1892
  get viewportChanged() {
1785
1893
  return (this.flags & 4 /* Viewport */) > 0;
@@ -1795,7 +1903,7 @@ class ViewUpdate {
1795
1903
  or the lines or characters within it has changed.
1796
1904
  */
1797
1905
  get geometryChanged() {
1798
- return this.docChanged || (this.flags & (16 /* Geometry */ | 2 /* Height */)) > 0;
1906
+ return this.docChanged || (this.flags & (8 /* Geometry */ | 2 /* Height */)) > 0;
1799
1907
  }
1800
1908
  /**
1801
1909
  True when this update indicates a focus change.
@@ -1880,7 +1988,7 @@ class DocView extends ContentView {
1880
1988
  changedRanges = ChangedRange.extendWithRanges(changedRanges, decoDiff);
1881
1989
  let pointerSel = update.transactions.some(tr => tr.isUserEvent("select.pointer"));
1882
1990
  if (this.dirty == 0 /* Not */ && changedRanges.length == 0 &&
1883
- !(update.flags & (4 /* Viewport */ | 8 /* LineGaps */)) &&
1991
+ !(update.flags & 4 /* Viewport */) &&
1884
1992
  update.state.selection.main.from >= this.view.viewport.from &&
1885
1993
  update.state.selection.main.to <= this.view.viewport.to) {
1886
1994
  this.updateSelection(forceSelection, pointerSel);
@@ -1891,6 +1999,14 @@ class DocView extends ContentView {
1891
1999
  return true;
1892
2000
  }
1893
2001
  }
2002
+ reset(sel) {
2003
+ if (this.dirty) {
2004
+ this.view.observer.ignore(() => this.view.docView.sync());
2005
+ this.dirty = 0 /* Not */;
2006
+ }
2007
+ if (sel)
2008
+ this.updateSelection();
2009
+ }
1894
2010
  // Used both by update and checkLayout do perform the actual DOM
1895
2011
  // update
1896
2012
  updateInner(changes, deco, oldLength, forceSelection = false, pointerSel = false) {
@@ -1915,6 +2031,12 @@ class DocView extends ContentView {
1915
2031
  this.updateSelection(forceSelection, pointerSel);
1916
2032
  this.dom.style.height = "";
1917
2033
  });
2034
+ let gaps = [];
2035
+ if (this.view.viewport.from || this.view.viewport.to < this.view.state.doc.length)
2036
+ for (let child of this.children)
2037
+ if (child instanceof BlockWidgetView && child.widget instanceof BlockGapWidget)
2038
+ gaps.push(child.dom);
2039
+ observer.updateGaps(gaps);
1918
2040
  }
1919
2041
  updateChildren(changes, deco, oldLength) {
1920
2042
  let cursor = this.childCursor(oldLength);
@@ -2014,6 +2136,14 @@ class DocView extends ContentView {
2014
2136
  !isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) ||
2015
2137
  !isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) {
2016
2138
  this.view.observer.ignore(() => {
2139
+ // Chrome Android will hide the virtual keyboard when tapping
2140
+ // inside an uneditable node, and not bring it back when we
2141
+ // move the cursor to its proper position. This tries to
2142
+ // restore the keyboard by cycling focus.
2143
+ if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) && inUneditable(domSel.focusNode, this.dom)) {
2144
+ this.dom.blur();
2145
+ this.dom.focus({ preventScroll: true });
2146
+ }
2017
2147
  let rawSel = getSelection(this.root);
2018
2148
  if (main.empty) {
2019
2149
  // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=1612076
@@ -2187,7 +2317,7 @@ class DocView extends ContentView {
2187
2317
  this.view.viewState.lineGapDeco
2188
2318
  ];
2189
2319
  }
2190
- scrollRangeIntoView(range) {
2320
+ scrollIntoView({ range, center }) {
2191
2321
  let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
2192
2322
  if (!rect)
2193
2323
  return;
@@ -2207,10 +2337,10 @@ class DocView extends ContentView {
2207
2337
  if (bottom != null)
2208
2338
  mBottom = Math.max(mBottom, bottom);
2209
2339
  }
2210
- scrollRectIntoView(this.dom, {
2340
+ scrollRectIntoView(this.view.scrollDOM, {
2211
2341
  left: rect.left - mLeft, top: rect.top - mTop,
2212
2342
  right: rect.right + mRight, bottom: rect.bottom + mBottom
2213
- }, range.head < range.anchor ? -1 : 1);
2343
+ }, range.head < range.anchor ? -1 : 1, center);
2214
2344
  }
2215
2345
  }
2216
2346
  function betweenUneditable(pos) {
@@ -2321,6 +2451,14 @@ function findChangedDeco(a, b, diff) {
2321
2451
  RangeSet.compare(a, b, diff, comp);
2322
2452
  return comp.changes;
2323
2453
  }
2454
+ function inUneditable(node, inside) {
2455
+ for (let cur = node; cur && cur != inside; cur = cur.assignedSlot || cur.parentNode) {
2456
+ if (cur.nodeType == 1 && cur.contentEditable == 'false') {
2457
+ return true;
2458
+ }
2459
+ }
2460
+ return false;
2461
+ }
2324
2462
 
2325
2463
  /**
2326
2464
  Used to indicate [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
@@ -2766,6 +2904,7 @@ function domPosInText(node, x, y) {
2766
2904
  return { node, offset: closestOffset > -1 ? closestOffset : generalSide > 0 ? node.nodeValue.length : 0 };
2767
2905
  }
2768
2906
  function posAtCoords(view, { x, y }, precise, bias = -1) {
2907
+ var _a;
2769
2908
  let content = view.contentDOM.getBoundingClientRect(), block;
2770
2909
  let halfLine = view.defaultLineHeight / 2;
2771
2910
  for (let bounced = false;;) {
@@ -2783,25 +2922,27 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
2783
2922
  y = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2784
2923
  }
2785
2924
  let lineStart = block.from;
2925
+ // Clip x to the viewport sides
2786
2926
  x = Math.max(content.left + 1, Math.min(content.right - 1, x));
2787
2927
  // If this is outside of the rendered viewport, we can't determine a position
2788
2928
  if (lineStart < view.viewport.from)
2789
2929
  return view.viewport.from == 0 ? 0 : posAtCoordsImprecise(view, content, block, x, y);
2790
2930
  if (lineStart > view.viewport.to)
2791
2931
  return view.viewport.to == view.state.doc.length ? view.state.doc.length : posAtCoordsImprecise(view, content, block, x, y);
2792
- // Clip x to the viewport sides
2793
- let root = view.root, element = root.elementFromPoint(x, y);
2932
+ // Prefer ShadowRootOrDocument.elementFromPoint if present, fall back to document if not
2933
+ let doc = view.dom.ownerDocument;
2934
+ let element = (view.root.elementFromPoint ? view.root : doc).elementFromPoint(x, y);
2794
2935
  // There's visible editor content under the point, so we can try
2795
2936
  // using caret(Position|Range)FromPoint as a shortcut
2796
2937
  let node, offset = -1;
2797
- if (element && view.contentDOM.contains(element) && !(view.docView.nearest(element) instanceof WidgetView)) {
2798
- if (root.caretPositionFromPoint) {
2799
- let pos = root.caretPositionFromPoint(x, y);
2938
+ if (element && view.contentDOM.contains(element) && ((_a = view.docView.nearest(element)) === null || _a === void 0 ? void 0 : _a.isEditable) != false) {
2939
+ if (doc.caretPositionFromPoint) {
2940
+ let pos = doc.caretPositionFromPoint(x, y);
2800
2941
  if (pos)
2801
2942
  ({ offsetNode: node, offset } = pos);
2802
2943
  }
2803
- else if (root.caretRangeFromPoint) {
2804
- let range = root.caretRangeFromPoint(x, y);
2944
+ else if (doc.caretRangeFromPoint) {
2945
+ let range = doc.caretRangeFromPoint(x, y);
2805
2946
  if (range) {
2806
2947
  ({ startContainer: node, startOffset: offset } = range);
2807
2948
  if (browser.safari && isSuspiciousCaretResult(node, offset, x))
@@ -2935,7 +3076,23 @@ class InputState {
2935
3076
  constructor(view) {
2936
3077
  this.lastKeyCode = 0;
2937
3078
  this.lastKeyTime = 0;
2938
- this.pendingIOSKey = null;
3079
+ // On iOS, some keys need to have their default behavior happen
3080
+ // (after which we retroactively handle them and reset the DOM) to
3081
+ // avoid messing up the virtual keyboard state.
3082
+ //
3083
+ // On Chrome Android, backspace near widgets is just completely
3084
+ // broken, and there are no key events, so we need to handle the
3085
+ // beforeinput event. Deleting stuff will often create a flurry of
3086
+ // events, and interrupting it before it is done just makes
3087
+ // subsequent events even more broken, so again, we hold off doing
3088
+ // anything until the browser is finished with whatever it is trying
3089
+ // to do.
3090
+ //
3091
+ // setPendingKey sets this, causing the DOM observer to pause for a
3092
+ // bit, and setting an animation frame (which seems the most
3093
+ // reliable way to detect 'browser is done flailing') to fire a fake
3094
+ // key event and re-sync the view again.
3095
+ this.pendingKey = undefined;
2939
3096
  this.lastSelectionOrigin = null;
2940
3097
  this.lastSelectionTime = 0;
2941
3098
  this.lastEscPress = 0;
@@ -3044,20 +3201,27 @@ class InputState {
3044
3201
  // state. So we let it go through, and then, in
3045
3202
  // applyDOMChange, notify key handlers of it and reset to
3046
3203
  // the state they produce.
3047
- if (browser.ios && (event.keyCode == 13 || event.keyCode == 8) &&
3204
+ let pending;
3205
+ if (browser.ios && (pending = PendingKeys.find(key => key.keyCode == event.keyCode)) &&
3048
3206
  !(event.ctrlKey || event.altKey || event.metaKey) && !event.synthetic) {
3049
- this.pendingIOSKey = event.keyCode == 13 ? "enter" : "backspace";
3050
- setTimeout(() => this.flushIOSKey(view), 250);
3207
+ this.setPendingKey(view, pending);
3051
3208
  return true;
3052
3209
  }
3053
3210
  return false;
3054
3211
  }
3055
- flushIOSKey(view) {
3056
- if (!this.pendingIOSKey)
3057
- return false;
3058
- let dom = view.contentDOM, key = this.pendingIOSKey;
3059
- this.pendingIOSKey = null;
3060
- return key == "enter" ? dispatchKey(dom, "Enter", 13) : dispatchKey(dom, "Backspace", 8);
3212
+ setPendingKey(view, pending) {
3213
+ this.pendingKey = pending;
3214
+ requestAnimationFrame(() => {
3215
+ if (!this.pendingKey)
3216
+ return false;
3217
+ let key = this.pendingKey;
3218
+ this.pendingKey = undefined;
3219
+ view.observer.processRecords();
3220
+ let startState = view.state;
3221
+ dispatchKey(view.contentDOM, key.key, key.keyCode);
3222
+ if (view.state == startState)
3223
+ view.docView.reset(true);
3224
+ });
3061
3225
  }
3062
3226
  ignoreDuringComposition(event) {
3063
3227
  if (!/^key/.test(event.type))
@@ -3104,6 +3268,11 @@ class InputState {
3104
3268
  this.mouseSelection.destroy();
3105
3269
  }
3106
3270
  }
3271
+ const PendingKeys = [
3272
+ { key: "Backspace", keyCode: 8, inputType: "deleteContentBackward" },
3273
+ { key: "Enter", keyCode: 13, inputType: "insertParagraph" },
3274
+ { key: "Delete", keyCode: 46, inputType: "deleteContentForward" }
3275
+ ];
3107
3276
  // Key codes for modifier keys
3108
3277
  const modifierCodes = [16, 17, 18, 20, 91, 92, 224, 225];
3109
3278
  class MouseSelection {
@@ -3220,7 +3389,7 @@ function capturePaste(view) {
3220
3389
  function doPaste(view, input) {
3221
3390
  let { state } = view, changes, i = 1, text = state.toText(input);
3222
3391
  let byLine = text.lines == state.selection.ranges.length;
3223
- let linewise = lastLinewiseCopy && state.selection.ranges.every(r => r.empty) && lastLinewiseCopy == text.toString();
3392
+ let linewise = lastLinewiseCopy != null && state.selection.ranges.every(r => r.empty) && lastLinewiseCopy == text.toString();
3224
3393
  if (linewise) {
3225
3394
  let lastLine = -1;
3226
3395
  changes = state.changeByRange(range => {
@@ -3431,9 +3600,8 @@ handlers.paste = (view, event) => {
3431
3600
  return event.preventDefault();
3432
3601
  view.observer.flush();
3433
3602
  let data = brokenClipboardAPI ? null : event.clipboardData;
3434
- let text = data && data.getData("text/plain");
3435
- if (text) {
3436
- doPaste(view, text);
3603
+ if (data) {
3604
+ doPaste(view, data.getData("text/plain"));
3437
3605
  event.preventDefault();
3438
3606
  }
3439
3607
  else {
@@ -3482,7 +3650,7 @@ function copiedRange(state) {
3482
3650
  let lastLinewiseCopy = null;
3483
3651
  handlers.copy = handlers.cut = (view, event) => {
3484
3652
  let { text, ranges, linewise } = copiedRange(view.state);
3485
- if (!text)
3653
+ if (!text && !linewise)
3486
3654
  return;
3487
3655
  lastLinewiseCopy = linewise ? text : null;
3488
3656
  let data = brokenClipboardAPI ? null : event.clipboardData;
@@ -3550,6 +3718,31 @@ handlers.compositionend = view => {
3550
3718
  handlers.contextmenu = view => {
3551
3719
  view.inputState.lastContextMenu = Date.now();
3552
3720
  };
3721
+ handlers.beforeinput = (view, event) => {
3722
+ var _a;
3723
+ // Because Chrome Android doesn't fire useful key events, use
3724
+ // beforeinput to detect backspace (and possibly enter and delete,
3725
+ // but those usually don't even seem to fire beforeinput events at
3726
+ // the moment) and fake a key event for it.
3727
+ //
3728
+ // (preventDefault on beforeinput, though supported in the spec,
3729
+ // seems to do nothing at all on Chrome).
3730
+ let pending;
3731
+ if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
3732
+ view.inputState.setPendingKey(view, pending);
3733
+ let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
3734
+ setTimeout(() => {
3735
+ var _a;
3736
+ // Backspacing near uneditable nodes on Chrome Android sometimes
3737
+ // closes the virtual keyboard. This tries to crudely detect
3738
+ // that and refocus to get it back.
3739
+ if ((((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0) > startViewHeight + 10 && view.hasFocus) {
3740
+ view.contentDOM.blur();
3741
+ view.focus();
3742
+ }
3743
+ }, 50);
3744
+ }
3745
+ };
3553
3746
 
3554
3747
  const wrappingWhiteSpace = ["pre-wrap", "normal", "pre-line"];
3555
3748
  class HeightOracle {
@@ -4264,6 +4457,15 @@ class LineGapWidget extends WidgetType {
4264
4457
  }
4265
4458
  get estimatedHeight() { return this.vertical ? this.size : -1; }
4266
4459
  }
4460
+ class ScrollTarget {
4461
+ constructor(range, center = false) {
4462
+ this.range = range;
4463
+ this.center = center;
4464
+ }
4465
+ map(changes) {
4466
+ return changes.empty ? this : new ScrollTarget(this.range.map(changes), this.center);
4467
+ }
4468
+ }
4267
4469
  class ViewState {
4268
4470
  constructor(state) {
4269
4471
  this.state = state;
@@ -4276,7 +4478,7 @@ class ViewState {
4276
4478
  this.heightOracle = new HeightOracle;
4277
4479
  // See VP.MaxDOMHeight
4278
4480
  this.scaler = IdScaler;
4279
- this.scrollTo = null;
4481
+ this.scrollTarget = null;
4280
4482
  // Briefly set to true when printing, to disable viewport limiting
4281
4483
  this.printing = false;
4282
4484
  this.visibleRanges = [];
@@ -4309,7 +4511,7 @@ class ViewState {
4309
4511
  this.scaler = this.heightMap.height <= 7000000 /* MaxDOMHeight */ ? IdScaler :
4310
4512
  new BigScaler(this.heightOracle.doc, this.heightMap, this.viewports);
4311
4513
  }
4312
- update(update, scrollTo = null) {
4514
+ update(update, scrollTarget = null) {
4313
4515
  let prev = this.state;
4314
4516
  this.state = update.state;
4315
4517
  let newDeco = this.state.facet(decorations);
@@ -4320,30 +4522,33 @@ class ViewState {
4320
4522
  if (this.heightMap.height != prevHeight)
4321
4523
  update.flags |= 2 /* Height */;
4322
4524
  let viewport = heightChanges.length ? this.mapViewport(this.viewport, update.changes) : this.viewport;
4323
- if (scrollTo && (scrollTo.head < viewport.from || scrollTo.head > viewport.to) || !this.viewportIsAppropriate(viewport))
4324
- viewport = this.getViewport(0, scrollTo);
4325
- if (!viewport.eq(this.viewport)) {
4326
- this.viewport = viewport;
4327
- update.flags |= 4 /* Viewport */;
4328
- }
4525
+ if (scrollTarget && (scrollTarget.range.head < viewport.from || scrollTarget.range.head > viewport.to) ||
4526
+ !this.viewportIsAppropriate(viewport))
4527
+ viewport = this.getViewport(0, scrollTarget);
4528
+ this.viewport = viewport;
4329
4529
  this.updateForViewport();
4330
4530
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 15000 /* MinViewPort */)
4331
- update.flags |= this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
4332
- this.computeVisibleRanges();
4333
- if (scrollTo)
4334
- this.scrollTo = scrollTo;
4531
+ this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
4532
+ update.flags |= this.computeVisibleRanges();
4533
+ if (scrollTarget)
4534
+ this.scrollTarget = scrollTarget;
4335
4535
  if (!this.mustEnforceCursorAssoc && update.selectionSet && update.view.lineWrapping &&
4336
4536
  update.state.selection.main.empty && update.state.selection.main.assoc)
4337
4537
  this.mustEnforceCursorAssoc = true;
4338
4538
  }
4339
4539
  measure(docView, repeated) {
4340
4540
  let dom = docView.dom, whiteSpace = "", direction = Direction.LTR;
4541
+ let result = 0;
4341
4542
  if (!repeated) {
4342
4543
  // Vertical padding
4343
4544
  let style = window.getComputedStyle(dom);
4344
4545
  whiteSpace = style.whiteSpace, direction = (style.direction == "rtl" ? Direction.RTL : Direction.LTR);
4345
- this.paddingTop = parseInt(style.paddingTop) || 0;
4346
- this.paddingBottom = parseInt(style.paddingBottom) || 0;
4546
+ let paddingTop = parseInt(style.paddingTop) || 0, paddingBottom = parseInt(style.paddingBottom) || 0;
4547
+ if (this.paddingTop != paddingTop || this.paddingBottom != paddingBottom) {
4548
+ result |= 8 /* Geometry */;
4549
+ this.paddingTop = paddingTop;
4550
+ this.paddingBottom = paddingBottom;
4551
+ }
4347
4552
  }
4348
4553
  // Pixel viewport
4349
4554
  let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 } : visiblePixelRange(dom, this.paddingTop);
@@ -4353,7 +4558,7 @@ class ViewState {
4353
4558
  if (!this.inView)
4354
4559
  return 0;
4355
4560
  let lineHeights = docView.measureVisibleLineHeights();
4356
- let refresh = false, bias = 0, result = 0, oracle = this.heightOracle;
4561
+ let refresh = false, bias = 0, oracle = this.heightOracle;
4357
4562
  if (!repeated) {
4358
4563
  let contentWidth = docView.dom.clientWidth;
4359
4564
  if (oracle.mustRefresh(lineHeights, whiteSpace, direction) ||
@@ -4362,12 +4567,12 @@ class ViewState {
4362
4567
  refresh = oracle.refresh(whiteSpace, direction, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
4363
4568
  if (refresh) {
4364
4569
  docView.minWidth = 0;
4365
- result |= 16 /* Geometry */;
4570
+ result |= 8 /* Geometry */;
4366
4571
  }
4367
4572
  }
4368
4573
  if (this.contentWidth != contentWidth) {
4369
4574
  this.contentWidth = contentWidth;
4370
- result |= 16 /* Geometry */;
4575
+ result |= 8 /* Geometry */;
4371
4576
  }
4372
4577
  if (dTop > 0 && dBottom > 0)
4373
4578
  bias = Math.max(dTop, dBottom);
@@ -4379,17 +4584,12 @@ class ViewState {
4379
4584
  if (oracle.heightChanged)
4380
4585
  result |= 2 /* Height */;
4381
4586
  if (!this.viewportIsAppropriate(this.viewport, bias) ||
4382
- this.scrollTo && (this.scrollTo.head < this.viewport.from || this.scrollTo.head > this.viewport.to)) {
4383
- let newVP = this.getViewport(bias, this.scrollTo);
4384
- if (newVP.from != this.viewport.from || newVP.to != this.viewport.to) {
4385
- this.viewport = newVP;
4386
- result |= 4 /* Viewport */;
4387
- }
4388
- }
4587
+ this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to))
4588
+ this.viewport = this.getViewport(bias, this.scrollTarget);
4389
4589
  this.updateForViewport();
4390
4590
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 15000 /* MinViewPort */)
4391
- result |= this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
4392
- this.computeVisibleRanges();
4591
+ this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
4592
+ result |= this.computeVisibleRanges();
4393
4593
  if (this.mustEnforceCursorAssoc) {
4394
4594
  this.mustEnforceCursorAssoc = false;
4395
4595
  // This is done in the read stage, because moving the selection
@@ -4402,22 +4602,25 @@ class ViewState {
4402
4602
  }
4403
4603
  get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top, 0); }
4404
4604
  get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom, 0); }
4405
- getViewport(bias, scrollTo) {
4605
+ getViewport(bias, scrollTarget) {
4406
4606
  // This will divide VP.Margin between the top and the
4407
4607
  // bottom, depending on the bias (the change in viewport position
4408
4608
  // since the last update). It'll hold a number between 0 and 1
4409
4609
  let marginTop = 0.5 - Math.max(-0.5, Math.min(0.5, bias / 1000 /* Margin */ / 2));
4410
4610
  let map = this.heightMap, doc = this.state.doc, { visibleTop, visibleBottom } = this;
4411
4611
  let viewport = new Viewport(map.lineAt(visibleTop - marginTop * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).from, map.lineAt(visibleBottom + (1 - marginTop) * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).to);
4412
- // If scrollTo is given, make sure the viewport includes that position
4413
- if (scrollTo) {
4414
- if (scrollTo.head < viewport.from) {
4415
- let { top: newTop } = map.lineAt(scrollTo.head, QueryType.ByPos, doc, 0, 0);
4416
- viewport = new Viewport(map.lineAt(newTop - 1000 /* Margin */ / 2, QueryType.ByHeight, doc, 0, 0).from, map.lineAt(newTop + (visibleBottom - visibleTop) + 1000 /* Margin */ / 2, QueryType.ByHeight, doc, 0, 0).to);
4417
- }
4418
- else if (scrollTo.head > viewport.to) {
4419
- let { bottom: newBottom } = map.lineAt(scrollTo.head, QueryType.ByPos, doc, 0, 0);
4420
- viewport = new Viewport(map.lineAt(newBottom - (visibleBottom - visibleTop) - 1000 /* Margin */ / 2, QueryType.ByHeight, doc, 0, 0).from, map.lineAt(newBottom + 1000 /* Margin */ / 2, QueryType.ByHeight, doc, 0, 0).to);
4612
+ // If scrollTarget is given, make sure the viewport includes that position
4613
+ if (scrollTarget) {
4614
+ let { head } = scrollTarget.range, viewHeight = visibleBottom - visibleTop;
4615
+ if (head < viewport.from || head > viewport.to) {
4616
+ let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4617
+ if (scrollTarget.center)
4618
+ topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
4619
+ else if (head < viewport.from)
4620
+ topPos = block.top;
4621
+ else
4622
+ topPos = block.bottom - viewHeight;
4623
+ viewport = new Viewport(map.lineAt(topPos - 1000 /* Margin */ / 2, QueryType.ByHeight, doc, 0, 0).from, map.lineAt(topPos + viewHeight + 1000 /* Margin */ / 2, QueryType.ByHeight, doc, 0, 0).to);
4421
4624
  }
4422
4625
  }
4423
4626
  return viewport;
@@ -4511,9 +4714,7 @@ class ViewState {
4511
4714
  if (!LineGap.same(gaps, this.lineGaps)) {
4512
4715
  this.lineGaps = gaps;
4513
4716
  this.lineGapDeco = Decoration.set(gaps.map(gap => gap.draw(this.heightOracle.lineWrapping)));
4514
- return 8 /* LineGaps */;
4515
4717
  }
4516
- return 0;
4517
4718
  }
4518
4719
  computeVisibleRanges() {
4519
4720
  let deco = this.state.facet(decorations);
@@ -4524,7 +4725,10 @@ class ViewState {
4524
4725
  span(from, to) { ranges.push({ from, to }); },
4525
4726
  point() { }
4526
4727
  }, 20);
4728
+ let changed = ranges.length != this.visibleRanges.length ||
4729
+ this.visibleRanges.some((r, i) => r.from != ranges[i].from || r.to != ranges[i].to);
4527
4730
  this.visibleRanges = ranges;
4731
+ return changed ? 4 /* Viewport */ : 0;
4528
4732
  }
4529
4733
  lineAt(pos, editorTop) {
4530
4734
  editorTop += this.paddingTop;
@@ -4549,16 +4753,11 @@ class ViewState {
4549
4753
  return this.scaler.toDOM(this.heightMap.height, this.paddingTop);
4550
4754
  }
4551
4755
  }
4552
- /**
4553
- Indicates the range of the document that is in the visible
4554
- viewport.
4555
- */
4556
4756
  class Viewport {
4557
4757
  constructor(from, to) {
4558
4758
  this.from = from;
4559
4759
  this.to = to;
4560
4760
  }
4561
- eq(b) { return this.from == b.from && this.to == b.to; }
4562
4761
  }
4563
4762
  function lineStructure(from, to, state) {
4564
4763
  let ranges = [], pos = from, total = 0;
@@ -4684,7 +4883,7 @@ function buildTheme(main, spec, scopes) {
4684
4883
  });
4685
4884
  }
4686
4885
  const baseTheme = /*@__PURE__*/buildTheme("." + baseThemeID, {
4687
- "&": {
4886
+ "&.cm-editor": {
4688
4887
  position: "relative !important",
4689
4888
  boxSizing: "border-box",
4690
4889
  "&.cm-focused": {
@@ -4851,6 +5050,8 @@ class DOMObserver {
4851
5050
  this.scrollTargets = [];
4852
5051
  this.intersection = null;
4853
5052
  this.intersecting = false;
5053
+ this.gapIntersection = null;
5054
+ this.gaps = [];
4854
5055
  // Used to work around a Safari Selection/shadow DOM bug (#414)
4855
5056
  this._selectionRange = null;
4856
5057
  // Timeout for scheduling check of the parents that need scroll handlers
@@ -4891,13 +5092,17 @@ class DOMObserver {
4891
5092
  this.intersection = new IntersectionObserver(entries => {
4892
5093
  if (this.parentCheck < 0)
4893
5094
  this.parentCheck = setTimeout(this.listenForScroll.bind(this), 1000);
4894
- if (entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
5095
+ if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
4895
5096
  this.intersecting = !this.intersecting;
4896
5097
  if (this.intersecting != this.view.inView)
4897
5098
  this.onScrollChanged(document.createEvent("Event"));
4898
5099
  }
4899
5100
  }, {});
4900
5101
  this.intersection.observe(this.dom);
5102
+ this.gapIntersection = new IntersectionObserver(entries => {
5103
+ if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0)
5104
+ this.onScrollChanged(document.createEvent("Event"));
5105
+ }, {});
4901
5106
  }
4902
5107
  this.listenForScroll();
4903
5108
  }
@@ -4906,6 +5111,14 @@ class DOMObserver {
4906
5111
  this.flush();
4907
5112
  this.onScrollChanged(e);
4908
5113
  }
5114
+ updateGaps(gaps) {
5115
+ if (this.gapIntersection && (gaps.length != this.gaps.length || this.gaps.some((g, i) => g != gaps[i]))) {
5116
+ this.gapIntersection.disconnect();
5117
+ for (let gap of gaps)
5118
+ this.gapIntersection.observe(gap);
5119
+ this.gaps = gaps;
5120
+ }
5121
+ }
4909
5122
  onSelectionChange(event) {
4910
5123
  if (this.lastFlush < Date.now() - 50)
4911
5124
  this._selectionRange = null;
@@ -5021,20 +5234,12 @@ class DOMObserver {
5021
5234
  this.flush();
5022
5235
  }
5023
5236
  }
5024
- // Apply pending changes, if any
5025
- flush() {
5026
- if (this.delayedFlush >= 0)
5027
- return;
5028
- this.lastFlush = Date.now();
5237
+ processRecords() {
5029
5238
  let records = this.queue;
5030
5239
  for (let mut of this.observer.takeRecords())
5031
5240
  records.push(mut);
5032
5241
  if (records.length)
5033
5242
  this.queue = [];
5034
- let selection = this.selectionRange;
5035
- let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
5036
- if (records.length == 0 && !newSel)
5037
- return;
5038
5243
  let from = -1, to = -1, typeOver = false;
5039
5244
  for (let record of records) {
5040
5245
  let range = this.readMutation(record);
@@ -5050,17 +5255,26 @@ class DOMObserver {
5050
5255
  to = Math.max(range.to, to);
5051
5256
  }
5052
5257
  }
5258
+ return { from, to, typeOver };
5259
+ }
5260
+ // Apply pending changes, if any
5261
+ flush() {
5262
+ // Completely hold off flushing when pending keys are set—the code
5263
+ // managing those will make sure processRecords is called and the
5264
+ // view is resynchronized after
5265
+ if (this.delayedFlush >= 0 || this.view.inputState.pendingKey)
5266
+ return;
5267
+ this.lastFlush = Date.now();
5268
+ let { from, to, typeOver } = this.processRecords();
5269
+ let selection = this.selectionRange;
5270
+ let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
5271
+ if (from < 0 && !newSel)
5272
+ return;
5053
5273
  let startState = this.view.state;
5054
- if (from > -1 || newSel)
5055
- this.onChange(from, to, typeOver);
5056
- if (this.view.state == startState) { // The view wasn't updated
5057
- if (this.view.docView.dirty) {
5058
- this.ignore(() => this.view.docView.sync());
5059
- this.view.docView.dirty = 0 /* Not */;
5060
- }
5061
- if (newSel)
5062
- this.view.docView.updateSelection();
5063
- }
5274
+ this.onChange(from, to, typeOver);
5275
+ // The view wasn't updated
5276
+ if (this.view.state == startState)
5277
+ this.view.docView.reset(newSel);
5064
5278
  this.clearSelection();
5065
5279
  }
5066
5280
  readMutation(rec) {
@@ -5087,6 +5301,8 @@ class DOMObserver {
5087
5301
  this.stop();
5088
5302
  if (this.intersection)
5089
5303
  this.intersection.disconnect();
5304
+ if (this.gapIntersection)
5305
+ this.gapIntersection.disconnect();
5090
5306
  for (let dom of this.scrollTargets)
5091
5307
  dom.removeEventListener("scroll", this.onScroll);
5092
5308
  window.removeEventListener("scroll", this.onScroll);
@@ -5195,9 +5411,9 @@ function applyDOMChange(view, start, end, typeOver) {
5195
5411
  (change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
5196
5412
  dispatchKey(view.contentDOM, "Backspace", 8)) ||
5197
5413
  (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
5198
- dispatchKey(view.contentDOM, "Delete", 46))) ||
5199
- browser.ios && view.inputState.flushIOSKey(view))
5414
+ dispatchKey(view.contentDOM, "Delete", 46)))) {
5200
5415
  return;
5416
+ }
5201
5417
  let text = change.insert.toString();
5202
5418
  if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
5203
5419
  return;
@@ -5287,8 +5503,9 @@ class DOMReader {
5287
5503
  if (next == end)
5288
5504
  break;
5289
5505
  let view = ContentView.get(cur), nextView = ContentView.get(next);
5290
- if ((view ? view.breakAfter : isBlockElement(cur)) ||
5291
- ((nextView ? nextView.breakAfter : isBlockElement(next)) && !(cur.nodeName == "BR" && !cur.cmIgnore)))
5506
+ if (view && nextView ? view.breakAfter :
5507
+ (view ? view.breakAfter : isBlockElement(cur)) ||
5508
+ (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
5292
5509
  this.text += this.lineBreak;
5293
5510
  cur = next;
5294
5511
  }
@@ -5391,6 +5608,7 @@ class EditorView {
5391
5608
  this.editorAttrs = {};
5392
5609
  this.contentAttrs = {};
5393
5610
  this.bidiCache = [];
5611
+ this.destroyed = false;
5394
5612
  /**
5395
5613
  @internal
5396
5614
  */
@@ -5416,7 +5634,7 @@ class EditorView {
5416
5634
  this.dom.appendChild(this.scrollDOM);
5417
5635
  this._dispatch = config.dispatch || ((tr) => this.update([tr]));
5418
5636
  this.dispatch = this.dispatch.bind(this);
5419
- this.root = (config.root || document);
5637
+ this.root = (config.root || getRoot(config.parent) || document);
5420
5638
  this.viewState = new ViewState(config.state || EditorState.create());
5421
5639
  this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec).update(this));
5422
5640
  this.observer = new DOMObserver(this, (from, to, typeOver) => {
@@ -5489,25 +5707,32 @@ class EditorView {
5489
5707
  throw new RangeError("Trying to update state with a transaction that doesn't start from the previous state.");
5490
5708
  state = tr.state;
5491
5709
  }
5710
+ if (this.destroyed) {
5711
+ this.viewState.state = state;
5712
+ return;
5713
+ }
5492
5714
  // When the phrases change, redraw the editor
5493
5715
  if (state.facet(EditorState.phrases) != this.state.facet(EditorState.phrases))
5494
5716
  return this.setState(state);
5495
5717
  update = new ViewUpdate(this, state, transactions);
5496
- let scrollPos = null;
5718
+ let scrollTarget = null;
5497
5719
  try {
5498
5720
  this.updateState = 2 /* Updating */;
5499
5721
  for (let tr of transactions) {
5500
- if (scrollPos)
5501
- scrollPos = scrollPos.map(tr.changes);
5722
+ if (scrollTarget)
5723
+ scrollTarget = scrollTarget.map(tr.changes);
5502
5724
  if (tr.scrollIntoView) {
5503
5725
  let { main } = tr.state.selection;
5504
- scrollPos = main.empty ? main : EditorSelection.cursor(main.head, main.head > main.anchor ? -1 : 1);
5726
+ scrollTarget = new ScrollTarget(main.empty ? main : EditorSelection.cursor(main.head, main.head > main.anchor ? -1 : 1));
5505
5727
  }
5506
- for (let e of tr.effects)
5728
+ for (let e of tr.effects) {
5507
5729
  if (e.is(scrollTo))
5508
- scrollPos = e.value;
5730
+ scrollTarget = new ScrollTarget(e.value);
5731
+ else if (e.is(centerOn))
5732
+ scrollTarget = new ScrollTarget(e.value, true);
5733
+ }
5509
5734
  }
5510
- this.viewState.update(update, scrollPos);
5735
+ this.viewState.update(update, scrollTarget);
5511
5736
  this.bidiCache = CachedOrder.update(this.bidiCache, update.changes);
5512
5737
  if (!update.empty) {
5513
5738
  this.updatePlugins(update);
@@ -5522,7 +5747,7 @@ class EditorView {
5522
5747
  finally {
5523
5748
  this.updateState = 0 /* Idle */;
5524
5749
  }
5525
- if (redrawn || scrollPos || this.viewState.mustEnforceCursorAssoc)
5750
+ if (redrawn || scrollTarget || this.viewState.mustEnforceCursorAssoc)
5526
5751
  this.requestMeasure();
5527
5752
  if (!update.empty)
5528
5753
  for (let listener of this.state.facet(updateListener))
@@ -5538,6 +5763,10 @@ class EditorView {
5538
5763
  setState(newState) {
5539
5764
  if (this.updateState != 0 /* Idle */)
5540
5765
  throw new Error("Calls to EditorView.setState are not allowed while an update is in progress");
5766
+ if (this.destroyed) {
5767
+ this.viewState.state = newState;
5768
+ return;
5769
+ }
5541
5770
  this.updateState = 2 /* Updating */;
5542
5771
  try {
5543
5772
  for (let plugin of this.plugins)
@@ -5587,6 +5816,8 @@ class EditorView {
5587
5816
  @internal
5588
5817
  */
5589
5818
  measure(flush = true) {
5819
+ if (this.destroyed)
5820
+ return;
5590
5821
  if (this.measureScheduled > -1)
5591
5822
  cancelAnimationFrame(this.measureScheduled);
5592
5823
  this.measureScheduled = -1; // Prevent requestMeasure calls from scheduling another animation frame
@@ -5596,15 +5827,18 @@ class EditorView {
5596
5827
  try {
5597
5828
  for (let i = 0;; i++) {
5598
5829
  this.updateState = 1 /* Measuring */;
5830
+ let oldViewport = this.viewport;
5599
5831
  let changed = this.viewState.measure(this.docView, i > 0);
5600
- let measuring = this.measureRequests;
5601
- if (!changed && !measuring.length && this.viewState.scrollTo == null)
5832
+ if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
5602
5833
  break;
5603
- this.measureRequests = [];
5604
5834
  if (i > 5) {
5605
5835
  console.warn("Viewport failed to stabilize");
5606
5836
  break;
5607
5837
  }
5838
+ let measuring = [];
5839
+ // Only run measure requests in this cycle when the viewport didn't change
5840
+ if (!(changed & 4 /* Viewport */))
5841
+ [this.measureRequests, measuring] = [measuring, this.measureRequests];
5608
5842
  let measured = measuring.map(m => {
5609
5843
  try {
5610
5844
  return m.read(this);
@@ -5637,11 +5871,11 @@ class EditorView {
5637
5871
  logException(this.state, e);
5638
5872
  }
5639
5873
  }
5640
- if (this.viewState.scrollTo) {
5641
- this.docView.scrollRangeIntoView(this.viewState.scrollTo);
5642
- this.viewState.scrollTo = null;
5874
+ if (this.viewState.scrollTarget) {
5875
+ this.docView.scrollIntoView(this.viewState.scrollTarget);
5876
+ this.viewState.scrollTarget = null;
5643
5877
  }
5644
- if (!(changed & 4 /* Viewport */) && this.measureRequests.length == 0)
5878
+ if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
5645
5879
  break;
5646
5880
  }
5647
5881
  }
@@ -5671,6 +5905,7 @@ class EditorView {
5671
5905
  spellcheck: "false",
5672
5906
  autocorrect: "off",
5673
5907
  autocapitalize: "off",
5908
+ translate: "no",
5674
5909
  contenteditable: !this.state.facet(editable) ? "false" : contentEditablePlainTextSupported() ? "plaintext-only" : "true",
5675
5910
  class: "cm-content",
5676
5911
  style: `${browser.tabSize}: ${this.state.tabSize}`,
@@ -5861,12 +6096,9 @@ class EditorView {
5861
6096
  moveVertically(start, forward, distance) {
5862
6097
  return skipAtoms(this, start, moveVertically(this, start, forward, distance));
5863
6098
  }
5864
- /**
5865
- Scroll the given document position into view.
5866
- */
6099
+ // FIXME remove on next major version
5867
6100
  scrollPosIntoView(pos) {
5868
- this.viewState.scrollTo = EditorSelection.cursor(pos);
5869
- this.requestMeasure();
6101
+ this.dispatch({ effects: scrollTo.of(EditorSelection.cursor(pos)) });
5870
6102
  }
5871
6103
  /**
5872
6104
  Find the DOM parent node and offset (child offset if `node` is
@@ -5977,11 +6209,13 @@ class EditorView {
5977
6209
  destroy() {
5978
6210
  for (let plugin of this.plugins)
5979
6211
  plugin.destroy(this);
6212
+ this.plugins = [];
5980
6213
  this.inputState.destroy();
5981
6214
  this.dom.remove();
5982
6215
  this.observer.destroy();
5983
6216
  if (this.measureScheduled > -1)
5984
6217
  cancelAnimationFrame(this.measureScheduled);
6218
+ this.destroyed = true;
5985
6219
  }
5986
6220
  /**
5987
6221
  Facet that can be used to add DOM event handlers. The value
@@ -6040,6 +6274,11 @@ transaction to make it scroll the given range into view.
6040
6274
  */
6041
6275
  EditorView.scrollTo = scrollTo;
6042
6276
  /**
6277
+ Effect that makes the editor scroll the given range to the
6278
+ center of the visible view.
6279
+ */
6280
+ EditorView.centerOn = centerOn;
6281
+ /**
6043
6282
  Facet to add a [style
6044
6283
  module](https://github.com/marijnh/style-mod#documentation) to
6045
6284
  an editor view. The view will ensure that the module is
@@ -6169,11 +6408,7 @@ class CachedOrder {
6169
6408
  }
6170
6409
  }
6171
6410
 
6172
- const currentPlatform = typeof navigator == "undefined" ? "key"
6173
- : /*@__PURE__*//Mac/.test(navigator.platform) ? "mac"
6174
- : /*@__PURE__*//Win/.test(navigator.platform) ? "win"
6175
- : /*@__PURE__*//Linux|X11/.test(navigator.platform) ? "linux"
6176
- : "key";
6411
+ const currentPlatform = browser.mac ? "mac" : browser.windows ? "win" : browser.linux ? "linux" : "key";
6177
6412
  function normalizeKeyName(name, platform) {
6178
6413
  const parts = name.split(/-(?!$)/);
6179
6414
  let result = parts[parts.length - 1];
@@ -6679,7 +6914,7 @@ class MatchDecorator {
6679
6914
  }
6680
6915
 
6681
6916
  const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g";
6682
- const Specials = /*@__PURE__*/new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
6917
+ const Specials = /*@__PURE__*/new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
6683
6918
  const Names = {
6684
6919
  0: "null",
6685
6920
  7: "bell",
@@ -6694,6 +6929,8 @@ const Names = {
6694
6929
  8206: "left-to-right mark",
6695
6930
  8207: "right-to-left mark",
6696
6931
  8232: "line separator",
6932
+ 8237: "left-to-right override",
6933
+ 8238: "right-to-left override",
6697
6934
  8233: "paragraph separator",
6698
6935
  65279: "zero width no-break space",
6699
6936
  65532: "object replacement"
@@ -6860,7 +7097,7 @@ DOM class.
6860
7097
  function highlightActiveLine() {
6861
7098
  return activeLineHighlighter;
6862
7099
  }
6863
- const lineDeco = /*@__PURE__*/Decoration.line({ attributes: { class: "cm-activeLine" } });
7100
+ const lineDeco = /*@__PURE__*/Decoration.line({ class: "cm-activeLine" });
6864
7101
  const activeLineHighlighter = /*@__PURE__*/ViewPlugin.fromClass(class {
6865
7102
  constructor(view) {
6866
7103
  this.decorations = this.getDeco(view);