@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.cjs CHANGED
@@ -9,7 +9,17 @@ var text = require('@codemirror/text');
9
9
  var w3cKeyname = require('w3c-keyname');
10
10
 
11
11
  function getSelection(root) {
12
- return (root.getSelection ? root.getSelection() : document.getSelection());
12
+ let target;
13
+ // Browsers differ on whether shadow roots have a getSelection
14
+ // method. If it exists, use that, otherwise, call it on the
15
+ // document.
16
+ if (root.nodeType == 11) { // Shadow root
17
+ target = root.getSelection ? root : root.ownerDocument;
18
+ }
19
+ else {
20
+ target = root;
21
+ }
22
+ return target.getSelection();
13
23
  }
14
24
  function contains(dom, node) {
15
25
  return node ? dom.contains(node.nodeType != 1 ? node.parentNode : node) : false;
@@ -92,9 +102,9 @@ function windowRect(win) {
92
102
  top: 0, bottom: win.innerHeight };
93
103
  }
94
104
  const ScrollSpace = 5;
95
- function scrollRectIntoView(dom, rect, side) {
105
+ function scrollRectIntoView(dom, rect, side, center) {
96
106
  let doc = dom.ownerDocument, win = doc.defaultView;
97
- for (let cur = dom.parentNode; cur;) {
107
+ for (let cur = dom; cur;) {
98
108
  if (cur.nodeType == 1) { // Element
99
109
  let bounding, top = cur == doc.body;
100
110
  if (top) {
@@ -111,7 +121,20 @@ function scrollRectIntoView(dom, rect, side) {
111
121
  top: rect.top, bottom: rect.top + cur.clientHeight };
112
122
  }
113
123
  let moveX = 0, moveY = 0;
114
- if (rect.top < bounding.top) {
124
+ if (center) {
125
+ 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;
133
+ moveY = targetTop - bounding.top;
134
+ if (Math.abs(moveY) <= 1)
135
+ moveY = 0;
136
+ }
137
+ else if (rect.top < bounding.top) {
115
138
  moveY = -(bounding.top - rect.top + ScrollSpace);
116
139
  if (side > 0 && rect.bottom > bounding.bottom + moveY)
117
140
  moveY = rect.bottom - bounding.bottom + moveY + ScrollSpace;
@@ -153,6 +176,7 @@ function scrollRectIntoView(dom, rect, side) {
153
176
  if (top)
154
177
  break;
155
178
  cur = cur.assignedSlot || cur.parentNode;
179
+ center = false;
156
180
  }
157
181
  else if (cur.nodeType == 11) { // A shadow root
158
182
  cur = cur.host;
@@ -241,6 +265,14 @@ function contentEditablePlainTextSupported() {
241
265
  }
242
266
  return _plainTextSupported;
243
267
  }
268
+ function getRoot(node) {
269
+ while (node) {
270
+ node = node.assignedSlot || node.parentNode;
271
+ if (node && (node.nodeType == 9 || node.nodeType == 11 && node.host))
272
+ return node;
273
+ }
274
+ return null;
275
+ }
244
276
 
245
277
  class DOMPos {
246
278
  constructor(node, offset, precise = true) {
@@ -438,6 +470,7 @@ class ContentView {
438
470
  (this.breakAfter ? "#" : "");
439
471
  }
440
472
  static get(node) { return node.cmView; }
473
+ get isEditable() { return true; }
441
474
  }
442
475
  ContentView.prototype.breakAfter = 0;
443
476
  // Remove a DOM node and return its next sibling.
@@ -485,15 +518,18 @@ const gecko = !ie && /gecko\/(\d+)/i.test(nav.userAgent);
485
518
  const chrome = !ie && /Chrome\/(\d+)/.exec(nav.userAgent);
486
519
  const webkit = "webkitFontSmoothing" in doc.documentElement.style;
487
520
  const safari = !ie && /Apple Computer/.test(nav.vendor);
521
+ const ios = safari && (/Mobile\/\w+/.test(nav.userAgent) || nav.maxTouchPoints > 2);
488
522
  var browser = {
489
- mac: /Mac/.test(nav.platform),
523
+ mac: ios || /Mac/.test(nav.platform),
524
+ windows: /Win/.test(nav.platform),
525
+ linux: /Linux|X11/.test(nav.platform),
490
526
  ie,
491
527
  ie_version: ie_upto10 ? doc.documentMode || 6 : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : 0,
492
528
  gecko,
493
529
  gecko_version: gecko ? +(/Firefox\/(\d+)/.exec(nav.userAgent) || [0, 0])[1] : 0,
494
530
  chrome: !!chrome,
495
531
  chrome_version: chrome ? +chrome[1] : 0,
496
- ios: safari && (/Mobile\/\w+/.test(nav.userAgent) || nav.maxTouchPoints > 2),
532
+ ios,
497
533
  android: /Android\b/.test(nav.userAgent),
498
534
  webkit,
499
535
  safari,
@@ -715,6 +751,7 @@ class WidgetView extends InlineView {
715
751
  }
716
752
  return (pos == 0 && side > 0 || pos == this.length && side <= 0) ? rect : flattenRect(rect, pos == 0);
717
753
  }
754
+ get isEditable() { return false; }
718
755
  }
719
756
  class CompositionView extends WidgetView {
720
757
  domAtPos(pos) { return new DOMPos(this.widget.text, pos); }
@@ -726,6 +763,38 @@ class CompositionView extends WidgetView {
726
763
  ignoreMutation() { return false; }
727
764
  get overrideDOMText() { return null; }
728
765
  coordsAt(pos, side) { return textCoords(this.widget.text, pos, side); }
766
+ get isEditable() { return true; }
767
+ }
768
+ // These are drawn around uneditable widgets to avoid a number of
769
+ // browser bugs that show up when the cursor is directly next to
770
+ // uneditable inline content.
771
+ class WidgetBufferView extends InlineView {
772
+ constructor(side) {
773
+ super();
774
+ this.side = side;
775
+ }
776
+ get length() { return 0; }
777
+ merge() { return false; }
778
+ become(other) {
779
+ return other instanceof WidgetBufferView && other.side == this.side;
780
+ }
781
+ slice() { return new WidgetBufferView(this.side); }
782
+ sync() {
783
+ if (!this.dom)
784
+ this.setDOM(document.createTextNode("\u200b"));
785
+ else if (this.dirty && this.dom.nodeValue != "\u200b")
786
+ this.dom.nodeValue = "\u200b";
787
+ }
788
+ getSide() { return this.side; }
789
+ domAtPos(pos) { return DOMPos.before(this.dom); }
790
+ domBoundsAround() { return null; }
791
+ coordsAt(pos) {
792
+ let rects = clientRectsFor(this.dom);
793
+ return rects[rects.length - 1];
794
+ }
795
+ get overrideDOMText() {
796
+ return text.Text.of([this.dom.nodeValue.replace(/\u200b/g, "")]);
797
+ }
729
798
  }
730
799
  function mergeInlineChildren(parent, from, to, elts, openStart, openEnd) {
731
800
  let cur = parent.childCursor();
@@ -1221,14 +1290,17 @@ class LineView extends ContentView {
1221
1290
  }
1222
1291
  // Only called when building a line view in ContentBuilder
1223
1292
  addLineDeco(deco) {
1224
- let attrs = deco.spec.attributes;
1293
+ let attrs = deco.spec.attributes, cls = deco.spec.class;
1225
1294
  if (attrs)
1226
1295
  this.attrs = combineAttrs(attrs, this.attrs || {});
1296
+ if (cls)
1297
+ this.attrs = combineAttrs(attrs, { class: cls });
1227
1298
  }
1228
1299
  domAtPos(pos) {
1229
1300
  return inlineDOMAtPos(this.dom, this.children, pos);
1230
1301
  }
1231
1302
  sync(track) {
1303
+ var _a;
1232
1304
  if (!this.dom || (this.dirty & 4 /* Attrs */)) {
1233
1305
  this.setDOM(document.createElement("div"));
1234
1306
  this.dom.className = "cm-line";
@@ -1244,7 +1316,7 @@ class LineView extends ContentView {
1244
1316
  while (last && ContentView.get(last) instanceof MarkView)
1245
1317
  last = last.lastChild;
1246
1318
  if (!last ||
1247
- last.nodeName != "BR" && ContentView.get(last) instanceof WidgetView &&
1319
+ last.nodeName != "BR" && ((_a = ContentView.get(last)) === null || _a === void 0 ? void 0 : _a.isEditable) == false &&
1248
1320
  (!browser.ios || !this.children.some(ch => ch instanceof TextView))) {
1249
1321
  let hack = document.createElement("BR");
1250
1322
  hack.cmIgnore = true;
@@ -1343,6 +1415,9 @@ class ContentBuilder {
1343
1415
  this.content = [];
1344
1416
  this.curLine = null;
1345
1417
  this.breakAtStart = 0;
1418
+ this.pendingBuffer = 0 /* No */;
1419
+ // Set to false directly after a widget that covers the position after it
1420
+ this.atCursorPos = true;
1346
1421
  this.openStart = -1;
1347
1422
  this.openEnd = -1;
1348
1423
  this.text = "";
@@ -1357,23 +1432,31 @@ class ContentBuilder {
1357
1432
  return !last.breakAfter && !(last instanceof BlockWidgetView && last.type == exports.BlockType.WidgetBefore);
1358
1433
  }
1359
1434
  getLine() {
1360
- if (!this.curLine)
1435
+ if (!this.curLine) {
1361
1436
  this.content.push(this.curLine = new LineView);
1437
+ this.atCursorPos = true;
1438
+ }
1362
1439
  return this.curLine;
1363
1440
  }
1364
- addWidget(view) {
1441
+ flushBuffer(active) {
1442
+ if (this.pendingBuffer) {
1443
+ this.curLine.append(wrapMarks(new WidgetBufferView(-1), active), active.length);
1444
+ this.pendingBuffer = 0 /* No */;
1445
+ }
1446
+ }
1447
+ addBlockWidget(view) {
1448
+ this.flushBuffer([]);
1365
1449
  this.curLine = null;
1366
1450
  this.content.push(view);
1367
1451
  }
1368
- finish() {
1452
+ finish(openEnd) {
1453
+ if (!openEnd)
1454
+ this.flushBuffer([]);
1455
+ else
1456
+ this.pendingBuffer = 0 /* No */;
1369
1457
  if (!this.posCovered())
1370
1458
  this.getLine();
1371
1459
  }
1372
- wrapMarks(view, active) {
1373
- for (let mark of active)
1374
- view = new MarkView(mark, [view], view.length);
1375
- return view;
1376
- }
1377
1460
  buildText(length, active, openStart) {
1378
1461
  while (length > 0) {
1379
1462
  if (this.textOff == this.text.length) {
@@ -1388,6 +1471,7 @@ class ContentBuilder {
1388
1471
  this.content[this.content.length - 1].breakAfter = 1;
1389
1472
  else
1390
1473
  this.breakAtStart = 1;
1474
+ this.flushBuffer([]);
1391
1475
  this.curLine = null;
1392
1476
  length--;
1393
1477
  continue;
@@ -1398,7 +1482,9 @@ class ContentBuilder {
1398
1482
  }
1399
1483
  }
1400
1484
  let take = Math.min(this.text.length - this.textOff, length, 512 /* Chunk */);
1401
- this.getLine().append(this.wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
1485
+ this.flushBuffer(active);
1486
+ this.getLine().append(wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
1487
+ this.atCursorPos = true;
1402
1488
  this.textOff += take;
1403
1489
  length -= take;
1404
1490
  openStart = 0;
@@ -1417,11 +1503,23 @@ class ContentBuilder {
1417
1503
  let { type } = deco;
1418
1504
  if (type == exports.BlockType.WidgetAfter && !this.posCovered())
1419
1505
  this.getLine();
1420
- this.addWidget(new BlockWidgetView(deco.widget || new NullWidget("div"), len, type));
1506
+ this.addBlockWidget(new BlockWidgetView(deco.widget || new NullWidget("div"), len, type));
1421
1507
  }
1422
1508
  else {
1423
- let widget = this.wrapMarks(WidgetView.create(deco.widget || new NullWidget("span"), len, deco.startSide), active);
1424
- this.getLine().append(widget, openStart);
1509
+ let view = WidgetView.create(deco.widget || new NullWidget("span"), len, deco.startSide);
1510
+ let cursorBefore = this.atCursorPos && !view.isEditable && openStart <= active.length && (from < to || deco.startSide > 0);
1511
+ let cursorAfter = !view.isEditable && (from < to || deco.startSide <= 0);
1512
+ let line = this.getLine();
1513
+ if (this.pendingBuffer == 2 /* IfCursor */ && !cursorBefore)
1514
+ this.pendingBuffer = 0 /* No */;
1515
+ this.flushBuffer(active);
1516
+ if (cursorBefore) {
1517
+ line.append(wrapMarks(new WidgetBufferView(1), active), openStart);
1518
+ openStart = active.length + Math.max(0, openStart - active.length);
1519
+ }
1520
+ line.append(wrapMarks(view, active), openStart);
1521
+ this.atCursorPos = cursorAfter;
1522
+ this.pendingBuffer = !cursorAfter ? 0 /* No */ : from < to ? 1 /* Yes */ : 2 /* IfCursor */;
1425
1523
  }
1426
1524
  }
1427
1525
  else if (this.doc.lineAt(this.pos).from == this.pos) { // Line decoration
@@ -1447,10 +1545,15 @@ class ContentBuilder {
1447
1545
  builder.openEnd = rangeset.RangeSet.spans(decorations, from, to, builder);
1448
1546
  if (builder.openStart < 0)
1449
1547
  builder.openStart = builder.openEnd;
1450
- builder.finish();
1548
+ builder.finish(builder.openEnd);
1451
1549
  return builder;
1452
1550
  }
1453
1551
  }
1552
+ function wrapMarks(view, active) {
1553
+ for (let mark of active)
1554
+ view = new MarkView(mark, [view], view.length);
1555
+ return view;
1556
+ }
1454
1557
  class NullWidget extends WidgetType {
1455
1558
  constructor(tag) {
1456
1559
  super();
@@ -1471,6 +1574,9 @@ const inputHandler = state.Facet.define();
1471
1574
  const scrollTo = state.StateEffect.define({
1472
1575
  map: (range, changes) => range.map(changes)
1473
1576
  });
1577
+ const centerOn = state.StateEffect.define({
1578
+ map: (range, changes) => range.map(changes)
1579
+ });
1474
1580
  /**
1475
1581
  Log or report an unhandled exception in client code. Should
1476
1582
  probably only be used by extension code that allows client code to
@@ -1783,7 +1889,9 @@ class ViewUpdate {
1783
1889
  this.flags |= 2 /* Height */;
1784
1890
  }
1785
1891
  /**
1786
- Tells you whether the viewport changed in this update.
1892
+ Tells you whether the [viewport](https://codemirror.net/6/docs/ref/#view.EditorView.viewport) or
1893
+ [visible ranges](https://codemirror.net/6/docs/ref/#view.EditorView.visibleRanges) changed in this
1894
+ update.
1787
1895
  */
1788
1896
  get viewportChanged() {
1789
1897
  return (this.flags & 4 /* Viewport */) > 0;
@@ -1799,7 +1907,7 @@ class ViewUpdate {
1799
1907
  or the lines or characters within it has changed.
1800
1908
  */
1801
1909
  get geometryChanged() {
1802
- return this.docChanged || (this.flags & (16 /* Geometry */ | 2 /* Height */)) > 0;
1910
+ return this.docChanged || (this.flags & (8 /* Geometry */ | 2 /* Height */)) > 0;
1803
1911
  }
1804
1912
  /**
1805
1913
  True when this update indicates a focus change.
@@ -1884,7 +1992,7 @@ class DocView extends ContentView {
1884
1992
  changedRanges = ChangedRange.extendWithRanges(changedRanges, decoDiff);
1885
1993
  let pointerSel = update.transactions.some(tr => tr.isUserEvent("select.pointer"));
1886
1994
  if (this.dirty == 0 /* Not */ && changedRanges.length == 0 &&
1887
- !(update.flags & (4 /* Viewport */ | 8 /* LineGaps */)) &&
1995
+ !(update.flags & 4 /* Viewport */) &&
1888
1996
  update.state.selection.main.from >= this.view.viewport.from &&
1889
1997
  update.state.selection.main.to <= this.view.viewport.to) {
1890
1998
  this.updateSelection(forceSelection, pointerSel);
@@ -1895,6 +2003,14 @@ class DocView extends ContentView {
1895
2003
  return true;
1896
2004
  }
1897
2005
  }
2006
+ reset(sel) {
2007
+ if (this.dirty) {
2008
+ this.view.observer.ignore(() => this.view.docView.sync());
2009
+ this.dirty = 0 /* Not */;
2010
+ }
2011
+ if (sel)
2012
+ this.updateSelection();
2013
+ }
1898
2014
  // Used both by update and checkLayout do perform the actual DOM
1899
2015
  // update
1900
2016
  updateInner(changes, deco, oldLength, forceSelection = false, pointerSel = false) {
@@ -1919,6 +2035,12 @@ class DocView extends ContentView {
1919
2035
  this.updateSelection(forceSelection, pointerSel);
1920
2036
  this.dom.style.height = "";
1921
2037
  });
2038
+ let gaps = [];
2039
+ if (this.view.viewport.from || this.view.viewport.to < this.view.state.doc.length)
2040
+ for (let child of this.children)
2041
+ if (child instanceof BlockWidgetView && child.widget instanceof BlockGapWidget)
2042
+ gaps.push(child.dom);
2043
+ observer.updateGaps(gaps);
1922
2044
  }
1923
2045
  updateChildren(changes, deco, oldLength) {
1924
2046
  let cursor = this.childCursor(oldLength);
@@ -2018,6 +2140,14 @@ class DocView extends ContentView {
2018
2140
  !isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) ||
2019
2141
  !isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) {
2020
2142
  this.view.observer.ignore(() => {
2143
+ // Chrome Android will hide the virtual keyboard when tapping
2144
+ // inside an uneditable node, and not bring it back when we
2145
+ // move the cursor to its proper position. This tries to
2146
+ // restore the keyboard by cycling focus.
2147
+ if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) && inUneditable(domSel.focusNode, this.dom)) {
2148
+ this.dom.blur();
2149
+ this.dom.focus({ preventScroll: true });
2150
+ }
2021
2151
  let rawSel = getSelection(this.root);
2022
2152
  if (main.empty) {
2023
2153
  // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=1612076
@@ -2191,7 +2321,7 @@ class DocView extends ContentView {
2191
2321
  this.view.viewState.lineGapDeco
2192
2322
  ];
2193
2323
  }
2194
- scrollRangeIntoView(range) {
2324
+ scrollIntoView({ range, center }) {
2195
2325
  let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
2196
2326
  if (!rect)
2197
2327
  return;
@@ -2211,10 +2341,10 @@ class DocView extends ContentView {
2211
2341
  if (bottom != null)
2212
2342
  mBottom = Math.max(mBottom, bottom);
2213
2343
  }
2214
- scrollRectIntoView(this.dom, {
2344
+ scrollRectIntoView(this.view.scrollDOM, {
2215
2345
  left: rect.left - mLeft, top: rect.top - mTop,
2216
2346
  right: rect.right + mRight, bottom: rect.bottom + mBottom
2217
- }, range.head < range.anchor ? -1 : 1);
2347
+ }, range.head < range.anchor ? -1 : 1, center);
2218
2348
  }
2219
2349
  }
2220
2350
  function betweenUneditable(pos) {
@@ -2325,6 +2455,14 @@ function findChangedDeco(a, b, diff) {
2325
2455
  rangeset.RangeSet.compare(a, b, diff, comp);
2326
2456
  return comp.changes;
2327
2457
  }
2458
+ function inUneditable(node, inside) {
2459
+ for (let cur = node; cur && cur != inside; cur = cur.assignedSlot || cur.parentNode) {
2460
+ if (cur.nodeType == 1 && cur.contentEditable == 'false') {
2461
+ return true;
2462
+ }
2463
+ }
2464
+ return false;
2465
+ }
2328
2466
 
2329
2467
  /**
2330
2468
  Used to indicate [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
@@ -2771,6 +2909,7 @@ function domPosInText(node, x, y) {
2771
2909
  return { node, offset: closestOffset > -1 ? closestOffset : generalSide > 0 ? node.nodeValue.length : 0 };
2772
2910
  }
2773
2911
  function posAtCoords(view, { x, y }, precise, bias = -1) {
2912
+ var _a;
2774
2913
  let content = view.contentDOM.getBoundingClientRect(), block;
2775
2914
  let halfLine = view.defaultLineHeight / 2;
2776
2915
  for (let bounced = false;;) {
@@ -2788,25 +2927,27 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
2788
2927
  y = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2789
2928
  }
2790
2929
  let lineStart = block.from;
2930
+ // Clip x to the viewport sides
2791
2931
  x = Math.max(content.left + 1, Math.min(content.right - 1, x));
2792
2932
  // If this is outside of the rendered viewport, we can't determine a position
2793
2933
  if (lineStart < view.viewport.from)
2794
2934
  return view.viewport.from == 0 ? 0 : posAtCoordsImprecise(view, content, block, x, y);
2795
2935
  if (lineStart > view.viewport.to)
2796
2936
  return view.viewport.to == view.state.doc.length ? view.state.doc.length : posAtCoordsImprecise(view, content, block, x, y);
2797
- // Clip x to the viewport sides
2798
- let root = view.root, element = root.elementFromPoint(x, y);
2937
+ // Prefer ShadowRootOrDocument.elementFromPoint if present, fall back to document if not
2938
+ let doc = view.dom.ownerDocument;
2939
+ let element = (view.root.elementFromPoint ? view.root : doc).elementFromPoint(x, y);
2799
2940
  // There's visible editor content under the point, so we can try
2800
2941
  // using caret(Position|Range)FromPoint as a shortcut
2801
2942
  let node, offset = -1;
2802
- if (element && view.contentDOM.contains(element) && !(view.docView.nearest(element) instanceof WidgetView)) {
2803
- if (root.caretPositionFromPoint) {
2804
- let pos = root.caretPositionFromPoint(x, y);
2943
+ if (element && view.contentDOM.contains(element) && ((_a = view.docView.nearest(element)) === null || _a === void 0 ? void 0 : _a.isEditable) != false) {
2944
+ if (doc.caretPositionFromPoint) {
2945
+ let pos = doc.caretPositionFromPoint(x, y);
2805
2946
  if (pos)
2806
2947
  ({ offsetNode: node, offset } = pos);
2807
2948
  }
2808
- else if (root.caretRangeFromPoint) {
2809
- let range = root.caretRangeFromPoint(x, y);
2949
+ else if (doc.caretRangeFromPoint) {
2950
+ let range = doc.caretRangeFromPoint(x, y);
2810
2951
  if (range) {
2811
2952
  ({ startContainer: node, startOffset: offset } = range);
2812
2953
  if (browser.safari && isSuspiciousCaretResult(node, offset, x))
@@ -2940,7 +3081,23 @@ class InputState {
2940
3081
  constructor(view) {
2941
3082
  this.lastKeyCode = 0;
2942
3083
  this.lastKeyTime = 0;
2943
- this.pendingIOSKey = null;
3084
+ // On iOS, some keys need to have their default behavior happen
3085
+ // (after which we retroactively handle them and reset the DOM) to
3086
+ // avoid messing up the virtual keyboard state.
3087
+ //
3088
+ // On Chrome Android, backspace near widgets is just completely
3089
+ // broken, and there are no key events, so we need to handle the
3090
+ // beforeinput event. Deleting stuff will often create a flurry of
3091
+ // events, and interrupting it before it is done just makes
3092
+ // subsequent events even more broken, so again, we hold off doing
3093
+ // anything until the browser is finished with whatever it is trying
3094
+ // to do.
3095
+ //
3096
+ // setPendingKey sets this, causing the DOM observer to pause for a
3097
+ // bit, and setting an animation frame (which seems the most
3098
+ // reliable way to detect 'browser is done flailing') to fire a fake
3099
+ // key event and re-sync the view again.
3100
+ this.pendingKey = undefined;
2944
3101
  this.lastSelectionOrigin = null;
2945
3102
  this.lastSelectionTime = 0;
2946
3103
  this.lastEscPress = 0;
@@ -3049,20 +3206,27 @@ class InputState {
3049
3206
  // state. So we let it go through, and then, in
3050
3207
  // applyDOMChange, notify key handlers of it and reset to
3051
3208
  // the state they produce.
3052
- if (browser.ios && (event.keyCode == 13 || event.keyCode == 8) &&
3209
+ let pending;
3210
+ if (browser.ios && (pending = PendingKeys.find(key => key.keyCode == event.keyCode)) &&
3053
3211
  !(event.ctrlKey || event.altKey || event.metaKey) && !event.synthetic) {
3054
- this.pendingIOSKey = event.keyCode == 13 ? "enter" : "backspace";
3055
- setTimeout(() => this.flushIOSKey(view), 250);
3212
+ this.setPendingKey(view, pending);
3056
3213
  return true;
3057
3214
  }
3058
3215
  return false;
3059
3216
  }
3060
- flushIOSKey(view) {
3061
- if (!this.pendingIOSKey)
3062
- return false;
3063
- let dom = view.contentDOM, key = this.pendingIOSKey;
3064
- this.pendingIOSKey = null;
3065
- return key == "enter" ? dispatchKey(dom, "Enter", 13) : dispatchKey(dom, "Backspace", 8);
3217
+ setPendingKey(view, pending) {
3218
+ this.pendingKey = pending;
3219
+ requestAnimationFrame(() => {
3220
+ if (!this.pendingKey)
3221
+ return false;
3222
+ let key = this.pendingKey;
3223
+ this.pendingKey = undefined;
3224
+ view.observer.processRecords();
3225
+ let startState = view.state;
3226
+ dispatchKey(view.contentDOM, key.key, key.keyCode);
3227
+ if (view.state == startState)
3228
+ view.docView.reset(true);
3229
+ });
3066
3230
  }
3067
3231
  ignoreDuringComposition(event) {
3068
3232
  if (!/^key/.test(event.type))
@@ -3109,6 +3273,11 @@ class InputState {
3109
3273
  this.mouseSelection.destroy();
3110
3274
  }
3111
3275
  }
3276
+ const PendingKeys = [
3277
+ { key: "Backspace", keyCode: 8, inputType: "deleteContentBackward" },
3278
+ { key: "Enter", keyCode: 13, inputType: "insertParagraph" },
3279
+ { key: "Delete", keyCode: 46, inputType: "deleteContentForward" }
3280
+ ];
3112
3281
  // Key codes for modifier keys
3113
3282
  const modifierCodes = [16, 17, 18, 20, 91, 92, 224, 225];
3114
3283
  class MouseSelection {
@@ -3225,7 +3394,7 @@ function capturePaste(view) {
3225
3394
  function doPaste(view, input) {
3226
3395
  let { state: state$1 } = view, changes, i = 1, text = state$1.toText(input);
3227
3396
  let byLine = text.lines == state$1.selection.ranges.length;
3228
- let linewise = lastLinewiseCopy && state$1.selection.ranges.every(r => r.empty) && lastLinewiseCopy == text.toString();
3397
+ let linewise = lastLinewiseCopy != null && state$1.selection.ranges.every(r => r.empty) && lastLinewiseCopy == text.toString();
3229
3398
  if (linewise) {
3230
3399
  let lastLine = -1;
3231
3400
  changes = state$1.changeByRange(range => {
@@ -3436,9 +3605,8 @@ handlers.paste = (view, event) => {
3436
3605
  return event.preventDefault();
3437
3606
  view.observer.flush();
3438
3607
  let data = brokenClipboardAPI ? null : event.clipboardData;
3439
- let text = data && data.getData("text/plain");
3440
- if (text) {
3441
- doPaste(view, text);
3608
+ if (data) {
3609
+ doPaste(view, data.getData("text/plain"));
3442
3610
  event.preventDefault();
3443
3611
  }
3444
3612
  else {
@@ -3487,7 +3655,7 @@ function copiedRange(state) {
3487
3655
  let lastLinewiseCopy = null;
3488
3656
  handlers.copy = handlers.cut = (view, event) => {
3489
3657
  let { text, ranges, linewise } = copiedRange(view.state);
3490
- if (!text)
3658
+ if (!text && !linewise)
3491
3659
  return;
3492
3660
  lastLinewiseCopy = linewise ? text : null;
3493
3661
  let data = brokenClipboardAPI ? null : event.clipboardData;
@@ -3555,6 +3723,31 @@ handlers.compositionend = view => {
3555
3723
  handlers.contextmenu = view => {
3556
3724
  view.inputState.lastContextMenu = Date.now();
3557
3725
  };
3726
+ handlers.beforeinput = (view, event) => {
3727
+ var _a;
3728
+ // Because Chrome Android doesn't fire useful key events, use
3729
+ // beforeinput to detect backspace (and possibly enter and delete,
3730
+ // but those usually don't even seem to fire beforeinput events at
3731
+ // the moment) and fake a key event for it.
3732
+ //
3733
+ // (preventDefault on beforeinput, though supported in the spec,
3734
+ // seems to do nothing at all on Chrome).
3735
+ let pending;
3736
+ if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
3737
+ view.inputState.setPendingKey(view, pending);
3738
+ let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
3739
+ setTimeout(() => {
3740
+ var _a;
3741
+ // Backspacing near uneditable nodes on Chrome Android sometimes
3742
+ // closes the virtual keyboard. This tries to crudely detect
3743
+ // that and refocus to get it back.
3744
+ if ((((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0) > startViewHeight + 10 && view.hasFocus) {
3745
+ view.contentDOM.blur();
3746
+ view.focus();
3747
+ }
3748
+ }, 50);
3749
+ }
3750
+ };
3558
3751
 
3559
3752
  const wrappingWhiteSpace = ["pre-wrap", "normal", "pre-line"];
3560
3753
  class HeightOracle {
@@ -4270,6 +4463,15 @@ class LineGapWidget extends WidgetType {
4270
4463
  }
4271
4464
  get estimatedHeight() { return this.vertical ? this.size : -1; }
4272
4465
  }
4466
+ class ScrollTarget {
4467
+ constructor(range, center = false) {
4468
+ this.range = range;
4469
+ this.center = center;
4470
+ }
4471
+ map(changes) {
4472
+ return changes.empty ? this : new ScrollTarget(this.range.map(changes), this.center);
4473
+ }
4474
+ }
4273
4475
  class ViewState {
4274
4476
  constructor(state) {
4275
4477
  this.state = state;
@@ -4282,7 +4484,7 @@ class ViewState {
4282
4484
  this.heightOracle = new HeightOracle;
4283
4485
  // See VP.MaxDOMHeight
4284
4486
  this.scaler = IdScaler;
4285
- this.scrollTo = null;
4487
+ this.scrollTarget = null;
4286
4488
  // Briefly set to true when printing, to disable viewport limiting
4287
4489
  this.printing = false;
4288
4490
  this.visibleRanges = [];
@@ -4315,7 +4517,7 @@ class ViewState {
4315
4517
  this.scaler = this.heightMap.height <= 7000000 /* MaxDOMHeight */ ? IdScaler :
4316
4518
  new BigScaler(this.heightOracle.doc, this.heightMap, this.viewports);
4317
4519
  }
4318
- update(update, scrollTo = null) {
4520
+ update(update, scrollTarget = null) {
4319
4521
  let prev = this.state;
4320
4522
  this.state = update.state;
4321
4523
  let newDeco = this.state.facet(decorations);
@@ -4326,30 +4528,33 @@ class ViewState {
4326
4528
  if (this.heightMap.height != prevHeight)
4327
4529
  update.flags |= 2 /* Height */;
4328
4530
  let viewport = heightChanges.length ? this.mapViewport(this.viewport, update.changes) : this.viewport;
4329
- if (scrollTo && (scrollTo.head < viewport.from || scrollTo.head > viewport.to) || !this.viewportIsAppropriate(viewport))
4330
- viewport = this.getViewport(0, scrollTo);
4331
- if (!viewport.eq(this.viewport)) {
4332
- this.viewport = viewport;
4333
- update.flags |= 4 /* Viewport */;
4334
- }
4531
+ if (scrollTarget && (scrollTarget.range.head < viewport.from || scrollTarget.range.head > viewport.to) ||
4532
+ !this.viewportIsAppropriate(viewport))
4533
+ viewport = this.getViewport(0, scrollTarget);
4534
+ this.viewport = viewport;
4335
4535
  this.updateForViewport();
4336
4536
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 15000 /* MinViewPort */)
4337
- update.flags |= this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
4338
- this.computeVisibleRanges();
4339
- if (scrollTo)
4340
- this.scrollTo = scrollTo;
4537
+ this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
4538
+ update.flags |= this.computeVisibleRanges();
4539
+ if (scrollTarget)
4540
+ this.scrollTarget = scrollTarget;
4341
4541
  if (!this.mustEnforceCursorAssoc && update.selectionSet && update.view.lineWrapping &&
4342
4542
  update.state.selection.main.empty && update.state.selection.main.assoc)
4343
4543
  this.mustEnforceCursorAssoc = true;
4344
4544
  }
4345
4545
  measure(docView, repeated) {
4346
4546
  let dom = docView.dom, whiteSpace = "", direction = exports.Direction.LTR;
4547
+ let result = 0;
4347
4548
  if (!repeated) {
4348
4549
  // Vertical padding
4349
4550
  let style = window.getComputedStyle(dom);
4350
4551
  whiteSpace = style.whiteSpace, direction = (style.direction == "rtl" ? exports.Direction.RTL : exports.Direction.LTR);
4351
- this.paddingTop = parseInt(style.paddingTop) || 0;
4352
- this.paddingBottom = parseInt(style.paddingBottom) || 0;
4552
+ let paddingTop = parseInt(style.paddingTop) || 0, paddingBottom = parseInt(style.paddingBottom) || 0;
4553
+ if (this.paddingTop != paddingTop || this.paddingBottom != paddingBottom) {
4554
+ result |= 8 /* Geometry */;
4555
+ this.paddingTop = paddingTop;
4556
+ this.paddingBottom = paddingBottom;
4557
+ }
4353
4558
  }
4354
4559
  // Pixel viewport
4355
4560
  let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 } : visiblePixelRange(dom, this.paddingTop);
@@ -4359,7 +4564,7 @@ class ViewState {
4359
4564
  if (!this.inView)
4360
4565
  return 0;
4361
4566
  let lineHeights = docView.measureVisibleLineHeights();
4362
- let refresh = false, bias = 0, result = 0, oracle = this.heightOracle;
4567
+ let refresh = false, bias = 0, oracle = this.heightOracle;
4363
4568
  if (!repeated) {
4364
4569
  let contentWidth = docView.dom.clientWidth;
4365
4570
  if (oracle.mustRefresh(lineHeights, whiteSpace, direction) ||
@@ -4368,12 +4573,12 @@ class ViewState {
4368
4573
  refresh = oracle.refresh(whiteSpace, direction, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
4369
4574
  if (refresh) {
4370
4575
  docView.minWidth = 0;
4371
- result |= 16 /* Geometry */;
4576
+ result |= 8 /* Geometry */;
4372
4577
  }
4373
4578
  }
4374
4579
  if (this.contentWidth != contentWidth) {
4375
4580
  this.contentWidth = contentWidth;
4376
- result |= 16 /* Geometry */;
4581
+ result |= 8 /* Geometry */;
4377
4582
  }
4378
4583
  if (dTop > 0 && dBottom > 0)
4379
4584
  bias = Math.max(dTop, dBottom);
@@ -4385,17 +4590,12 @@ class ViewState {
4385
4590
  if (oracle.heightChanged)
4386
4591
  result |= 2 /* Height */;
4387
4592
  if (!this.viewportIsAppropriate(this.viewport, bias) ||
4388
- this.scrollTo && (this.scrollTo.head < this.viewport.from || this.scrollTo.head > this.viewport.to)) {
4389
- let newVP = this.getViewport(bias, this.scrollTo);
4390
- if (newVP.from != this.viewport.from || newVP.to != this.viewport.to) {
4391
- this.viewport = newVP;
4392
- result |= 4 /* Viewport */;
4393
- }
4394
- }
4593
+ this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to))
4594
+ this.viewport = this.getViewport(bias, this.scrollTarget);
4395
4595
  this.updateForViewport();
4396
4596
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 15000 /* MinViewPort */)
4397
- result |= this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
4398
- this.computeVisibleRanges();
4597
+ this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
4598
+ result |= this.computeVisibleRanges();
4399
4599
  if (this.mustEnforceCursorAssoc) {
4400
4600
  this.mustEnforceCursorAssoc = false;
4401
4601
  // This is done in the read stage, because moving the selection
@@ -4408,22 +4608,25 @@ class ViewState {
4408
4608
  }
4409
4609
  get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top, 0); }
4410
4610
  get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom, 0); }
4411
- getViewport(bias, scrollTo) {
4611
+ getViewport(bias, scrollTarget) {
4412
4612
  // This will divide VP.Margin between the top and the
4413
4613
  // bottom, depending on the bias (the change in viewport position
4414
4614
  // since the last update). It'll hold a number between 0 and 1
4415
4615
  let marginTop = 0.5 - Math.max(-0.5, Math.min(0.5, bias / 1000 /* Margin */ / 2));
4416
4616
  let map = this.heightMap, doc = this.state.doc, { visibleTop, visibleBottom } = this;
4417
4617
  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);
4418
- // If scrollTo is given, make sure the viewport includes that position
4419
- if (scrollTo) {
4420
- if (scrollTo.head < viewport.from) {
4421
- let { top: newTop } = map.lineAt(scrollTo.head, QueryType.ByPos, doc, 0, 0);
4422
- 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);
4423
- }
4424
- else if (scrollTo.head > viewport.to) {
4425
- let { bottom: newBottom } = map.lineAt(scrollTo.head, QueryType.ByPos, doc, 0, 0);
4426
- 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);
4618
+ // If scrollTarget is given, make sure the viewport includes that position
4619
+ if (scrollTarget) {
4620
+ let { head } = scrollTarget.range, viewHeight = visibleBottom - visibleTop;
4621
+ if (head < viewport.from || head > viewport.to) {
4622
+ let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4623
+ if (scrollTarget.center)
4624
+ topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
4625
+ else if (head < viewport.from)
4626
+ topPos = block.top;
4627
+ else
4628
+ topPos = block.bottom - viewHeight;
4629
+ 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);
4427
4630
  }
4428
4631
  }
4429
4632
  return viewport;
@@ -4517,9 +4720,7 @@ class ViewState {
4517
4720
  if (!LineGap.same(gaps, this.lineGaps)) {
4518
4721
  this.lineGaps = gaps;
4519
4722
  this.lineGapDeco = Decoration.set(gaps.map(gap => gap.draw(this.heightOracle.lineWrapping)));
4520
- return 8 /* LineGaps */;
4521
4723
  }
4522
- return 0;
4523
4724
  }
4524
4725
  computeVisibleRanges() {
4525
4726
  let deco = this.state.facet(decorations);
@@ -4530,7 +4731,10 @@ class ViewState {
4530
4731
  span(from, to) { ranges.push({ from, to }); },
4531
4732
  point() { }
4532
4733
  }, 20);
4734
+ let changed = ranges.length != this.visibleRanges.length ||
4735
+ this.visibleRanges.some((r, i) => r.from != ranges[i].from || r.to != ranges[i].to);
4533
4736
  this.visibleRanges = ranges;
4737
+ return changed ? 4 /* Viewport */ : 0;
4534
4738
  }
4535
4739
  lineAt(pos, editorTop) {
4536
4740
  editorTop += this.paddingTop;
@@ -4555,16 +4759,11 @@ class ViewState {
4555
4759
  return this.scaler.toDOM(this.heightMap.height, this.paddingTop);
4556
4760
  }
4557
4761
  }
4558
- /**
4559
- Indicates the range of the document that is in the visible
4560
- viewport.
4561
- */
4562
4762
  class Viewport {
4563
4763
  constructor(from, to) {
4564
4764
  this.from = from;
4565
4765
  this.to = to;
4566
4766
  }
4567
- eq(b) { return this.from == b.from && this.to == b.to; }
4568
4767
  }
4569
4768
  function lineStructure(from, to, state) {
4570
4769
  let ranges = [], pos = from, total = 0;
@@ -4690,7 +4889,7 @@ function buildTheme(main, spec, scopes) {
4690
4889
  });
4691
4890
  }
4692
4891
  const baseTheme = buildTheme("." + baseThemeID, {
4693
- "&": {
4892
+ "&.cm-editor": {
4694
4893
  position: "relative !important",
4695
4894
  boxSizing: "border-box",
4696
4895
  "&.cm-focused": {
@@ -4857,6 +5056,8 @@ class DOMObserver {
4857
5056
  this.scrollTargets = [];
4858
5057
  this.intersection = null;
4859
5058
  this.intersecting = false;
5059
+ this.gapIntersection = null;
5060
+ this.gaps = [];
4860
5061
  // Used to work around a Safari Selection/shadow DOM bug (#414)
4861
5062
  this._selectionRange = null;
4862
5063
  // Timeout for scheduling check of the parents that need scroll handlers
@@ -4897,13 +5098,17 @@ class DOMObserver {
4897
5098
  this.intersection = new IntersectionObserver(entries => {
4898
5099
  if (this.parentCheck < 0)
4899
5100
  this.parentCheck = setTimeout(this.listenForScroll.bind(this), 1000);
4900
- if (entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
5101
+ if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
4901
5102
  this.intersecting = !this.intersecting;
4902
5103
  if (this.intersecting != this.view.inView)
4903
5104
  this.onScrollChanged(document.createEvent("Event"));
4904
5105
  }
4905
5106
  }, {});
4906
5107
  this.intersection.observe(this.dom);
5108
+ this.gapIntersection = new IntersectionObserver(entries => {
5109
+ if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0)
5110
+ this.onScrollChanged(document.createEvent("Event"));
5111
+ }, {});
4907
5112
  }
4908
5113
  this.listenForScroll();
4909
5114
  }
@@ -4912,6 +5117,14 @@ class DOMObserver {
4912
5117
  this.flush();
4913
5118
  this.onScrollChanged(e);
4914
5119
  }
5120
+ updateGaps(gaps) {
5121
+ if (this.gapIntersection && (gaps.length != this.gaps.length || this.gaps.some((g, i) => g != gaps[i]))) {
5122
+ this.gapIntersection.disconnect();
5123
+ for (let gap of gaps)
5124
+ this.gapIntersection.observe(gap);
5125
+ this.gaps = gaps;
5126
+ }
5127
+ }
4915
5128
  onSelectionChange(event) {
4916
5129
  if (this.lastFlush < Date.now() - 50)
4917
5130
  this._selectionRange = null;
@@ -5027,20 +5240,12 @@ class DOMObserver {
5027
5240
  this.flush();
5028
5241
  }
5029
5242
  }
5030
- // Apply pending changes, if any
5031
- flush() {
5032
- if (this.delayedFlush >= 0)
5033
- return;
5034
- this.lastFlush = Date.now();
5243
+ processRecords() {
5035
5244
  let records = this.queue;
5036
5245
  for (let mut of this.observer.takeRecords())
5037
5246
  records.push(mut);
5038
5247
  if (records.length)
5039
5248
  this.queue = [];
5040
- let selection = this.selectionRange;
5041
- let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
5042
- if (records.length == 0 && !newSel)
5043
- return;
5044
5249
  let from = -1, to = -1, typeOver = false;
5045
5250
  for (let record of records) {
5046
5251
  let range = this.readMutation(record);
@@ -5056,17 +5261,26 @@ class DOMObserver {
5056
5261
  to = Math.max(range.to, to);
5057
5262
  }
5058
5263
  }
5264
+ return { from, to, typeOver };
5265
+ }
5266
+ // Apply pending changes, if any
5267
+ flush() {
5268
+ // Completely hold off flushing when pending keys are set—the code
5269
+ // managing those will make sure processRecords is called and the
5270
+ // view is resynchronized after
5271
+ if (this.delayedFlush >= 0 || this.view.inputState.pendingKey)
5272
+ return;
5273
+ this.lastFlush = Date.now();
5274
+ let { from, to, typeOver } = this.processRecords();
5275
+ let selection = this.selectionRange;
5276
+ let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
5277
+ if (from < 0 && !newSel)
5278
+ return;
5059
5279
  let startState = this.view.state;
5060
- if (from > -1 || newSel)
5061
- this.onChange(from, to, typeOver);
5062
- if (this.view.state == startState) { // The view wasn't updated
5063
- if (this.view.docView.dirty) {
5064
- this.ignore(() => this.view.docView.sync());
5065
- this.view.docView.dirty = 0 /* Not */;
5066
- }
5067
- if (newSel)
5068
- this.view.docView.updateSelection();
5069
- }
5280
+ this.onChange(from, to, typeOver);
5281
+ // The view wasn't updated
5282
+ if (this.view.state == startState)
5283
+ this.view.docView.reset(newSel);
5070
5284
  this.clearSelection();
5071
5285
  }
5072
5286
  readMutation(rec) {
@@ -5093,6 +5307,8 @@ class DOMObserver {
5093
5307
  this.stop();
5094
5308
  if (this.intersection)
5095
5309
  this.intersection.disconnect();
5310
+ if (this.gapIntersection)
5311
+ this.gapIntersection.disconnect();
5096
5312
  for (let dom of this.scrollTargets)
5097
5313
  dom.removeEventListener("scroll", this.onScroll);
5098
5314
  window.removeEventListener("scroll", this.onScroll);
@@ -5201,9 +5417,9 @@ function applyDOMChange(view, start, end, typeOver) {
5201
5417
  (change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
5202
5418
  dispatchKey(view.contentDOM, "Backspace", 8)) ||
5203
5419
  (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
5204
- dispatchKey(view.contentDOM, "Delete", 46))) ||
5205
- browser.ios && view.inputState.flushIOSKey(view))
5420
+ dispatchKey(view.contentDOM, "Delete", 46)))) {
5206
5421
  return;
5422
+ }
5207
5423
  let text = change.insert.toString();
5208
5424
  if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
5209
5425
  return;
@@ -5293,8 +5509,9 @@ class DOMReader {
5293
5509
  if (next == end)
5294
5510
  break;
5295
5511
  let view = ContentView.get(cur), nextView = ContentView.get(next);
5296
- if ((view ? view.breakAfter : isBlockElement(cur)) ||
5297
- ((nextView ? nextView.breakAfter : isBlockElement(next)) && !(cur.nodeName == "BR" && !cur.cmIgnore)))
5512
+ if (view && nextView ? view.breakAfter :
5513
+ (view ? view.breakAfter : isBlockElement(cur)) ||
5514
+ (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
5298
5515
  this.text += this.lineBreak;
5299
5516
  cur = next;
5300
5517
  }
@@ -5397,6 +5614,7 @@ class EditorView {
5397
5614
  this.editorAttrs = {};
5398
5615
  this.contentAttrs = {};
5399
5616
  this.bidiCache = [];
5617
+ this.destroyed = false;
5400
5618
  /**
5401
5619
  @internal
5402
5620
  */
@@ -5422,7 +5640,7 @@ class EditorView {
5422
5640
  this.dom.appendChild(this.scrollDOM);
5423
5641
  this._dispatch = config.dispatch || ((tr) => this.update([tr]));
5424
5642
  this.dispatch = this.dispatch.bind(this);
5425
- this.root = (config.root || document);
5643
+ this.root = (config.root || getRoot(config.parent) || document);
5426
5644
  this.viewState = new ViewState(config.state || state.EditorState.create());
5427
5645
  this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec).update(this));
5428
5646
  this.observer = new DOMObserver(this, (from, to, typeOver) => {
@@ -5495,25 +5713,32 @@ class EditorView {
5495
5713
  throw new RangeError("Trying to update state with a transaction that doesn't start from the previous state.");
5496
5714
  state$1 = tr.state;
5497
5715
  }
5716
+ if (this.destroyed) {
5717
+ this.viewState.state = state$1;
5718
+ return;
5719
+ }
5498
5720
  // When the phrases change, redraw the editor
5499
5721
  if (state$1.facet(state.EditorState.phrases) != this.state.facet(state.EditorState.phrases))
5500
5722
  return this.setState(state$1);
5501
5723
  update = new ViewUpdate(this, state$1, transactions);
5502
- let scrollPos = null;
5724
+ let scrollTarget = null;
5503
5725
  try {
5504
5726
  this.updateState = 2 /* Updating */;
5505
5727
  for (let tr of transactions) {
5506
- if (scrollPos)
5507
- scrollPos = scrollPos.map(tr.changes);
5728
+ if (scrollTarget)
5729
+ scrollTarget = scrollTarget.map(tr.changes);
5508
5730
  if (tr.scrollIntoView) {
5509
5731
  let { main } = tr.state.selection;
5510
- scrollPos = main.empty ? main : state.EditorSelection.cursor(main.head, main.head > main.anchor ? -1 : 1);
5732
+ scrollTarget = new ScrollTarget(main.empty ? main : state.EditorSelection.cursor(main.head, main.head > main.anchor ? -1 : 1));
5511
5733
  }
5512
- for (let e of tr.effects)
5734
+ for (let e of tr.effects) {
5513
5735
  if (e.is(scrollTo))
5514
- scrollPos = e.value;
5736
+ scrollTarget = new ScrollTarget(e.value);
5737
+ else if (e.is(centerOn))
5738
+ scrollTarget = new ScrollTarget(e.value, true);
5739
+ }
5515
5740
  }
5516
- this.viewState.update(update, scrollPos);
5741
+ this.viewState.update(update, scrollTarget);
5517
5742
  this.bidiCache = CachedOrder.update(this.bidiCache, update.changes);
5518
5743
  if (!update.empty) {
5519
5744
  this.updatePlugins(update);
@@ -5528,7 +5753,7 @@ class EditorView {
5528
5753
  finally {
5529
5754
  this.updateState = 0 /* Idle */;
5530
5755
  }
5531
- if (redrawn || scrollPos || this.viewState.mustEnforceCursorAssoc)
5756
+ if (redrawn || scrollTarget || this.viewState.mustEnforceCursorAssoc)
5532
5757
  this.requestMeasure();
5533
5758
  if (!update.empty)
5534
5759
  for (let listener of this.state.facet(updateListener))
@@ -5544,6 +5769,10 @@ class EditorView {
5544
5769
  setState(newState) {
5545
5770
  if (this.updateState != 0 /* Idle */)
5546
5771
  throw new Error("Calls to EditorView.setState are not allowed while an update is in progress");
5772
+ if (this.destroyed) {
5773
+ this.viewState.state = newState;
5774
+ return;
5775
+ }
5547
5776
  this.updateState = 2 /* Updating */;
5548
5777
  try {
5549
5778
  for (let plugin of this.plugins)
@@ -5593,6 +5822,8 @@ class EditorView {
5593
5822
  @internal
5594
5823
  */
5595
5824
  measure(flush = true) {
5825
+ if (this.destroyed)
5826
+ return;
5596
5827
  if (this.measureScheduled > -1)
5597
5828
  cancelAnimationFrame(this.measureScheduled);
5598
5829
  this.measureScheduled = -1; // Prevent requestMeasure calls from scheduling another animation frame
@@ -5602,15 +5833,18 @@ class EditorView {
5602
5833
  try {
5603
5834
  for (let i = 0;; i++) {
5604
5835
  this.updateState = 1 /* Measuring */;
5836
+ let oldViewport = this.viewport;
5605
5837
  let changed = this.viewState.measure(this.docView, i > 0);
5606
- let measuring = this.measureRequests;
5607
- if (!changed && !measuring.length && this.viewState.scrollTo == null)
5838
+ if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
5608
5839
  break;
5609
- this.measureRequests = [];
5610
5840
  if (i > 5) {
5611
5841
  console.warn("Viewport failed to stabilize");
5612
5842
  break;
5613
5843
  }
5844
+ let measuring = [];
5845
+ // Only run measure requests in this cycle when the viewport didn't change
5846
+ if (!(changed & 4 /* Viewport */))
5847
+ [this.measureRequests, measuring] = [measuring, this.measureRequests];
5614
5848
  let measured = measuring.map(m => {
5615
5849
  try {
5616
5850
  return m.read(this);
@@ -5643,11 +5877,11 @@ class EditorView {
5643
5877
  logException(this.state, e);
5644
5878
  }
5645
5879
  }
5646
- if (this.viewState.scrollTo) {
5647
- this.docView.scrollRangeIntoView(this.viewState.scrollTo);
5648
- this.viewState.scrollTo = null;
5880
+ if (this.viewState.scrollTarget) {
5881
+ this.docView.scrollIntoView(this.viewState.scrollTarget);
5882
+ this.viewState.scrollTarget = null;
5649
5883
  }
5650
- if (!(changed & 4 /* Viewport */) && this.measureRequests.length == 0)
5884
+ if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
5651
5885
  break;
5652
5886
  }
5653
5887
  }
@@ -5677,6 +5911,7 @@ class EditorView {
5677
5911
  spellcheck: "false",
5678
5912
  autocorrect: "off",
5679
5913
  autocapitalize: "off",
5914
+ translate: "no",
5680
5915
  contenteditable: !this.state.facet(editable) ? "false" : contentEditablePlainTextSupported() ? "plaintext-only" : "true",
5681
5916
  class: "cm-content",
5682
5917
  style: `${browser.tabSize}: ${this.state.tabSize}`,
@@ -5867,12 +6102,9 @@ class EditorView {
5867
6102
  moveVertically(start, forward, distance) {
5868
6103
  return skipAtoms(this, start, moveVertically(this, start, forward, distance));
5869
6104
  }
5870
- /**
5871
- Scroll the given document position into view.
5872
- */
6105
+ // FIXME remove on next major version
5873
6106
  scrollPosIntoView(pos) {
5874
- this.viewState.scrollTo = state.EditorSelection.cursor(pos);
5875
- this.requestMeasure();
6107
+ this.dispatch({ effects: scrollTo.of(state.EditorSelection.cursor(pos)) });
5876
6108
  }
5877
6109
  /**
5878
6110
  Find the DOM parent node and offset (child offset if `node` is
@@ -5983,11 +6215,13 @@ class EditorView {
5983
6215
  destroy() {
5984
6216
  for (let plugin of this.plugins)
5985
6217
  plugin.destroy(this);
6218
+ this.plugins = [];
5986
6219
  this.inputState.destroy();
5987
6220
  this.dom.remove();
5988
6221
  this.observer.destroy();
5989
6222
  if (this.measureScheduled > -1)
5990
6223
  cancelAnimationFrame(this.measureScheduled);
6224
+ this.destroyed = true;
5991
6225
  }
5992
6226
  /**
5993
6227
  Facet that can be used to add DOM event handlers. The value
@@ -6046,6 +6280,11 @@ transaction to make it scroll the given range into view.
6046
6280
  */
6047
6281
  EditorView.scrollTo = scrollTo;
6048
6282
  /**
6283
+ Effect that makes the editor scroll the given range to the
6284
+ center of the visible view.
6285
+ */
6286
+ EditorView.centerOn = centerOn;
6287
+ /**
6049
6288
  Facet to add a [style
6050
6289
  module](https://github.com/marijnh/style-mod#documentation) to
6051
6290
  an editor view. The view will ensure that the module is
@@ -6175,11 +6414,7 @@ class CachedOrder {
6175
6414
  }
6176
6415
  }
6177
6416
 
6178
- const currentPlatform = typeof navigator == "undefined" ? "key"
6179
- : /Mac/.test(navigator.platform) ? "mac"
6180
- : /Win/.test(navigator.platform) ? "win"
6181
- : /Linux|X11/.test(navigator.platform) ? "linux"
6182
- : "key";
6417
+ const currentPlatform = browser.mac ? "mac" : browser.windows ? "win" : browser.linux ? "linux" : "key";
6183
6418
  function normalizeKeyName(name, platform) {
6184
6419
  const parts = name.split(/-(?!$)/);
6185
6420
  let result = parts[parts.length - 1];
@@ -6685,7 +6920,7 @@ class MatchDecorator {
6685
6920
  }
6686
6921
 
6687
6922
  const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g";
6688
- const Specials = new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
6923
+ const Specials = new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
6689
6924
  const Names = {
6690
6925
  0: "null",
6691
6926
  7: "bell",
@@ -6700,6 +6935,8 @@ const Names = {
6700
6935
  8206: "left-to-right mark",
6701
6936
  8207: "right-to-left mark",
6702
6937
  8232: "line separator",
6938
+ 8237: "left-to-right override",
6939
+ 8238: "right-to-left override",
6703
6940
  8233: "paragraph separator",
6704
6941
  65279: "zero width no-break space",
6705
6942
  65532: "object replacement"
@@ -6866,7 +7103,7 @@ DOM class.
6866
7103
  function highlightActiveLine() {
6867
7104
  return activeLineHighlighter;
6868
7105
  }
6869
- const lineDeco = Decoration.line({ attributes: { class: "cm-activeLine" } });
7106
+ const lineDeco = Decoration.line({ class: "cm-activeLine" });
6870
7107
  const activeLineHighlighter = ViewPlugin.fromClass(class {
6871
7108
  constructor(view) {
6872
7109
  this.decorations = this.getDeco(view);