@codemirror/view 0.19.8 → 0.19.12

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
@@ -102,9 +102,9 @@ function windowRect(win) {
102
102
  top: 0, bottom: win.innerHeight };
103
103
  }
104
104
  const ScrollSpace = 5;
105
- function scrollRectIntoView(dom, rect, side) {
105
+ function scrollRectIntoView(dom, rect, side, center) {
106
106
  let doc = dom.ownerDocument, win = doc.defaultView;
107
- for (let cur = dom.parentNode; cur;) {
107
+ for (let cur = dom; cur;) {
108
108
  if (cur.nodeType == 1) { // Element
109
109
  let bounding, top = cur == doc.body;
110
110
  if (top) {
@@ -121,7 +121,20 @@ function scrollRectIntoView(dom, rect, side) {
121
121
  top: rect.top, bottom: rect.top + cur.clientHeight };
122
122
  }
123
123
  let moveX = 0, moveY = 0;
124
- 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) {
125
138
  moveY = -(bounding.top - rect.top + ScrollSpace);
126
139
  if (side > 0 && rect.bottom > bounding.bottom + moveY)
127
140
  moveY = rect.bottom - bounding.bottom + moveY + ScrollSpace;
@@ -163,6 +176,7 @@ function scrollRectIntoView(dom, rect, side) {
163
176
  if (top)
164
177
  break;
165
178
  cur = cur.assignedSlot || cur.parentNode;
179
+ center = false;
166
180
  }
167
181
  else if (cur.nodeType == 11) { // A shadow root
168
182
  cur = cur.host;
@@ -251,6 +265,14 @@ function contentEditablePlainTextSupported() {
251
265
  }
252
266
  return _plainTextSupported;
253
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
+ }
254
276
 
255
277
  class DOMPos {
256
278
  constructor(node, offset, precise = true) {
@@ -299,25 +321,30 @@ class ContentView {
299
321
  sync(track) {
300
322
  var _a;
301
323
  if (this.dirty & 2 /* Node */) {
302
- let parent = this.dom, pos = null;
324
+ let parent = this.dom;
325
+ let pos = parent.firstChild;
303
326
  for (let child of this.children) {
304
327
  if (child.dirty) {
305
- let next = pos ? pos.nextSibling : parent.firstChild;
306
- if (!child.dom && next && !((_a = ContentView.get(next)) === null || _a === void 0 ? void 0 : _a.parent))
307
- child.reuseDOM(next);
328
+ if (!child.dom && pos && !((_a = ContentView.get(pos)) === null || _a === void 0 ? void 0 : _a.parent))
329
+ child.reuseDOM(pos);
308
330
  child.sync(track);
309
331
  child.dirty = 0 /* Not */;
310
332
  }
311
- if (track && track.node == parent && pos != child.dom)
333
+ if (track && !track.written && track.node == parent && pos != child.dom)
312
334
  track.written = true;
313
- syncNodeInto(parent, pos, child.dom);
314
- pos = child.dom;
335
+ if (child.dom.parentNode == parent) {
336
+ while (pos && pos != child.dom)
337
+ pos = rm(pos);
338
+ pos = child.dom.nextSibling;
339
+ }
340
+ else {
341
+ parent.insertBefore(child.dom, pos);
342
+ }
315
343
  }
316
- let next = pos ? pos.nextSibling : parent.firstChild;
317
- if (next && track && track.node == parent)
344
+ if (pos && track && track.node == parent)
318
345
  track.written = true;
319
- while (next)
320
- next = rm(next);
346
+ while (pos)
347
+ pos = rm(pos);
321
348
  }
322
349
  else if (this.dirty & 1 /* Child */) {
323
350
  for (let child of this.children)
@@ -448,6 +475,7 @@ class ContentView {
448
475
  (this.breakAfter ? "#" : "");
449
476
  }
450
477
  static get(node) { return node.cmView; }
478
+ get isEditable() { return true; }
451
479
  }
452
480
  ContentView.prototype.breakAfter = 0;
453
481
  // Remove a DOM node and return its next sibling.
@@ -456,14 +484,6 @@ function rm(dom) {
456
484
  dom.parentNode.removeChild(dom);
457
485
  return next;
458
486
  }
459
- function syncNodeInto(parent, after, dom) {
460
- let next = after ? after.nextSibling : parent.firstChild;
461
- if (dom.parentNode == parent)
462
- while (next != dom)
463
- next = rm(next);
464
- else
465
- parent.insertBefore(dom, next);
466
- }
467
487
  class ChildCursor {
468
488
  constructor(children, pos, i) {
469
489
  this.children = children;
@@ -495,15 +515,18 @@ const gecko = !ie && /gecko\/(\d+)/i.test(nav.userAgent);
495
515
  const chrome = !ie && /Chrome\/(\d+)/.exec(nav.userAgent);
496
516
  const webkit = "webkitFontSmoothing" in doc.documentElement.style;
497
517
  const safari = !ie && /Apple Computer/.test(nav.vendor);
518
+ const ios = safari && (/Mobile\/\w+/.test(nav.userAgent) || nav.maxTouchPoints > 2);
498
519
  var browser = {
499
- mac: /Mac/.test(nav.platform),
520
+ mac: ios || /Mac/.test(nav.platform),
521
+ windows: /Win/.test(nav.platform),
522
+ linux: /Linux|X11/.test(nav.platform),
500
523
  ie,
501
524
  ie_version: ie_upto10 ? doc.documentMode || 6 : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : 0,
502
525
  gecko,
503
526
  gecko_version: gecko ? +(/Firefox\/(\d+)/.exec(nav.userAgent) || [0, 0])[1] : 0,
504
527
  chrome: !!chrome,
505
528
  chrome_version: chrome ? +chrome[1] : 0,
506
- ios: safari && (/Mobile\/\w+/.test(nav.userAgent) || nav.maxTouchPoints > 2),
529
+ ios,
507
530
  android: /Android\b/.test(nav.userAgent),
508
531
  webkit,
509
532
  safari,
@@ -725,6 +748,7 @@ class WidgetView extends InlineView {
725
748
  }
726
749
  return (pos == 0 && side > 0 || pos == this.length && side <= 0) ? rect : flattenRect(rect, pos == 0);
727
750
  }
751
+ get isEditable() { return false; }
728
752
  }
729
753
  class CompositionView extends WidgetView {
730
754
  domAtPos(pos) { return new DOMPos(this.widget.text, pos); }
@@ -736,6 +760,38 @@ class CompositionView extends WidgetView {
736
760
  ignoreMutation() { return false; }
737
761
  get overrideDOMText() { return null; }
738
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.Text.of([this.dom.nodeValue.replace(/\u200b/g, "")]);
794
+ }
739
795
  }
740
796
  function mergeInlineChildren(parent, from, to, elts, openStart, openEnd) {
741
797
  let cur = parent.childCursor();
@@ -1231,14 +1287,17 @@ class LineView extends ContentView {
1231
1287
  }
1232
1288
  // Only called when building a line view in ContentBuilder
1233
1289
  addLineDeco(deco) {
1234
- let attrs = deco.spec.attributes;
1290
+ let attrs = deco.spec.attributes, cls = deco.spec.class;
1235
1291
  if (attrs)
1236
1292
  this.attrs = combineAttrs(attrs, this.attrs || {});
1293
+ if (cls)
1294
+ this.attrs = combineAttrs(attrs, { class: cls });
1237
1295
  }
1238
1296
  domAtPos(pos) {
1239
1297
  return inlineDOMAtPos(this.dom, this.children, pos);
1240
1298
  }
1241
1299
  sync(track) {
1300
+ var _a;
1242
1301
  if (!this.dom || (this.dirty & 4 /* Attrs */)) {
1243
1302
  this.setDOM(document.createElement("div"));
1244
1303
  this.dom.className = "cm-line";
@@ -1254,7 +1313,7 @@ class LineView extends ContentView {
1254
1313
  while (last && ContentView.get(last) instanceof MarkView)
1255
1314
  last = last.lastChild;
1256
1315
  if (!last ||
1257
- last.nodeName != "BR" && ContentView.get(last) instanceof WidgetView &&
1316
+ last.nodeName != "BR" && ((_a = ContentView.get(last)) === null || _a === void 0 ? void 0 : _a.isEditable) == false &&
1258
1317
  (!browser.ios || !this.children.some(ch => ch instanceof TextView))) {
1259
1318
  let hack = document.createElement("BR");
1260
1319
  hack.cmIgnore = true;
@@ -1353,6 +1412,9 @@ class ContentBuilder {
1353
1412
  this.content = [];
1354
1413
  this.curLine = null;
1355
1414
  this.breakAtStart = 0;
1415
+ this.pendingBuffer = 0 /* No */;
1416
+ // Set to false directly after a widget that covers the position after it
1417
+ this.atCursorPos = true;
1356
1418
  this.openStart = -1;
1357
1419
  this.openEnd = -1;
1358
1420
  this.text = "";
@@ -1367,23 +1429,31 @@ class ContentBuilder {
1367
1429
  return !last.breakAfter && !(last instanceof BlockWidgetView && last.type == exports.BlockType.WidgetBefore);
1368
1430
  }
1369
1431
  getLine() {
1370
- if (!this.curLine)
1432
+ if (!this.curLine) {
1371
1433
  this.content.push(this.curLine = new LineView);
1434
+ this.atCursorPos = true;
1435
+ }
1372
1436
  return this.curLine;
1373
1437
  }
1374
- addWidget(view) {
1438
+ flushBuffer(active) {
1439
+ if (this.pendingBuffer) {
1440
+ this.curLine.append(wrapMarks(new WidgetBufferView(-1), active), active.length);
1441
+ this.pendingBuffer = 0 /* No */;
1442
+ }
1443
+ }
1444
+ addBlockWidget(view) {
1445
+ this.flushBuffer([]);
1375
1446
  this.curLine = null;
1376
1447
  this.content.push(view);
1377
1448
  }
1378
- finish() {
1449
+ finish(openEnd) {
1450
+ if (!openEnd)
1451
+ this.flushBuffer([]);
1452
+ else
1453
+ this.pendingBuffer = 0 /* No */;
1379
1454
  if (!this.posCovered())
1380
1455
  this.getLine();
1381
1456
  }
1382
- wrapMarks(view, active) {
1383
- for (let mark of active)
1384
- view = new MarkView(mark, [view], view.length);
1385
- return view;
1386
- }
1387
1457
  buildText(length, active, openStart) {
1388
1458
  while (length > 0) {
1389
1459
  if (this.textOff == this.text.length) {
@@ -1398,6 +1468,7 @@ class ContentBuilder {
1398
1468
  this.content[this.content.length - 1].breakAfter = 1;
1399
1469
  else
1400
1470
  this.breakAtStart = 1;
1471
+ this.flushBuffer([]);
1401
1472
  this.curLine = null;
1402
1473
  length--;
1403
1474
  continue;
@@ -1408,7 +1479,9 @@ class ContentBuilder {
1408
1479
  }
1409
1480
  }
1410
1481
  let take = Math.min(this.text.length - this.textOff, length, 512 /* Chunk */);
1411
- this.getLine().append(this.wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
1482
+ this.flushBuffer(active);
1483
+ this.getLine().append(wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
1484
+ this.atCursorPos = true;
1412
1485
  this.textOff += take;
1413
1486
  length -= take;
1414
1487
  openStart = 0;
@@ -1427,11 +1500,23 @@ class ContentBuilder {
1427
1500
  let { type } = deco;
1428
1501
  if (type == exports.BlockType.WidgetAfter && !this.posCovered())
1429
1502
  this.getLine();
1430
- this.addWidget(new BlockWidgetView(deco.widget || new NullWidget("div"), len, type));
1503
+ this.addBlockWidget(new BlockWidgetView(deco.widget || new NullWidget("div"), len, type));
1431
1504
  }
1432
1505
  else {
1433
- let widget = this.wrapMarks(WidgetView.create(deco.widget || new NullWidget("span"), len, deco.startSide), active);
1434
- this.getLine().append(widget, openStart);
1506
+ let view = WidgetView.create(deco.widget || new NullWidget("span"), len, deco.startSide);
1507
+ let cursorBefore = this.atCursorPos && !view.isEditable && openStart <= active.length && (from < to || deco.startSide > 0);
1508
+ let cursorAfter = !view.isEditable && (from < to || deco.startSide <= 0);
1509
+ let line = this.getLine();
1510
+ if (this.pendingBuffer == 2 /* IfCursor */ && !cursorBefore)
1511
+ this.pendingBuffer = 0 /* No */;
1512
+ this.flushBuffer(active);
1513
+ if (cursorBefore) {
1514
+ line.append(wrapMarks(new WidgetBufferView(1), active), openStart);
1515
+ openStart = active.length + Math.max(0, openStart - active.length);
1516
+ }
1517
+ line.append(wrapMarks(view, active), openStart);
1518
+ this.atCursorPos = cursorAfter;
1519
+ this.pendingBuffer = !cursorAfter ? 0 /* No */ : from < to ? 1 /* Yes */ : 2 /* IfCursor */;
1435
1520
  }
1436
1521
  }
1437
1522
  else if (this.doc.lineAt(this.pos).from == this.pos) { // Line decoration
@@ -1457,10 +1542,15 @@ class ContentBuilder {
1457
1542
  builder.openEnd = rangeset.RangeSet.spans(decorations, from, to, builder);
1458
1543
  if (builder.openStart < 0)
1459
1544
  builder.openStart = builder.openEnd;
1460
- builder.finish();
1545
+ builder.finish(builder.openEnd);
1461
1546
  return builder;
1462
1547
  }
1463
1548
  }
1549
+ function wrapMarks(view, active) {
1550
+ for (let mark of active)
1551
+ view = new MarkView(mark, [view], view.length);
1552
+ return view;
1553
+ }
1464
1554
  class NullWidget extends WidgetType {
1465
1555
  constructor(tag) {
1466
1556
  super();
@@ -1481,6 +1571,9 @@ const inputHandler = state.Facet.define();
1481
1571
  const scrollTo = state.StateEffect.define({
1482
1572
  map: (range, changes) => range.map(changes)
1483
1573
  });
1574
+ const centerOn = state.StateEffect.define({
1575
+ map: (range, changes) => range.map(changes)
1576
+ });
1484
1577
  /**
1485
1578
  Log or report an unhandled exception in client code. Should
1486
1579
  probably only be used by extension code that allows client code to
@@ -1907,6 +2000,14 @@ class DocView extends ContentView {
1907
2000
  return true;
1908
2001
  }
1909
2002
  }
2003
+ reset(sel) {
2004
+ if (this.dirty) {
2005
+ this.view.observer.ignore(() => this.view.docView.sync());
2006
+ this.dirty = 0 /* Not */;
2007
+ }
2008
+ if (sel)
2009
+ this.updateSelection();
2010
+ }
1910
2011
  // Used both by update and checkLayout do perform the actual DOM
1911
2012
  // update
1912
2013
  updateInner(changes, deco, oldLength, forceSelection = false, pointerSel = false) {
@@ -1931,6 +2032,12 @@ class DocView extends ContentView {
1931
2032
  this.updateSelection(forceSelection, pointerSel);
1932
2033
  this.dom.style.height = "";
1933
2034
  });
2035
+ let gaps = [];
2036
+ if (this.view.viewport.from || this.view.viewport.to < this.view.state.doc.length)
2037
+ for (let child of this.children)
2038
+ if (child instanceof BlockWidgetView && child.widget instanceof BlockGapWidget)
2039
+ gaps.push(child.dom);
2040
+ observer.updateGaps(gaps);
1934
2041
  }
1935
2042
  updateChildren(changes, deco, oldLength) {
1936
2043
  let cursor = this.childCursor(oldLength);
@@ -2030,6 +2137,14 @@ class DocView extends ContentView {
2030
2137
  !isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) ||
2031
2138
  !isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) {
2032
2139
  this.view.observer.ignore(() => {
2140
+ // Chrome Android will hide the virtual keyboard when tapping
2141
+ // inside an uneditable node, and not bring it back when we
2142
+ // move the cursor to its proper position. This tries to
2143
+ // restore the keyboard by cycling focus.
2144
+ if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) && inUneditable(domSel.focusNode, this.dom)) {
2145
+ this.dom.blur();
2146
+ this.dom.focus({ preventScroll: true });
2147
+ }
2033
2148
  let rawSel = getSelection(this.root);
2034
2149
  if (main.empty) {
2035
2150
  // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=1612076
@@ -2203,7 +2318,7 @@ class DocView extends ContentView {
2203
2318
  this.view.viewState.lineGapDeco
2204
2319
  ];
2205
2320
  }
2206
- scrollRangeIntoView(range) {
2321
+ scrollIntoView({ range, center }) {
2207
2322
  let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
2208
2323
  if (!rect)
2209
2324
  return;
@@ -2223,10 +2338,10 @@ class DocView extends ContentView {
2223
2338
  if (bottom != null)
2224
2339
  mBottom = Math.max(mBottom, bottom);
2225
2340
  }
2226
- scrollRectIntoView(this.dom, {
2341
+ scrollRectIntoView(this.view.scrollDOM, {
2227
2342
  left: rect.left - mLeft, top: rect.top - mTop,
2228
2343
  right: rect.right + mRight, bottom: rect.bottom + mBottom
2229
- }, range.head < range.anchor ? -1 : 1);
2344
+ }, range.head < range.anchor ? -1 : 1, center);
2230
2345
  }
2231
2346
  }
2232
2347
  function betweenUneditable(pos) {
@@ -2337,6 +2452,14 @@ function findChangedDeco(a, b, diff) {
2337
2452
  rangeset.RangeSet.compare(a, b, diff, comp);
2338
2453
  return comp.changes;
2339
2454
  }
2455
+ function inUneditable(node, inside) {
2456
+ for (let cur = node; cur && cur != inside; cur = cur.assignedSlot || cur.parentNode) {
2457
+ if (cur.nodeType == 1 && cur.contentEditable == 'false') {
2458
+ return true;
2459
+ }
2460
+ }
2461
+ return false;
2462
+ }
2340
2463
 
2341
2464
  /**
2342
2465
  Used to indicate [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
@@ -2783,6 +2906,7 @@ function domPosInText(node, x, y) {
2783
2906
  return { node, offset: closestOffset > -1 ? closestOffset : generalSide > 0 ? node.nodeValue.length : 0 };
2784
2907
  }
2785
2908
  function posAtCoords(view, { x, y }, precise, bias = -1) {
2909
+ var _a;
2786
2910
  let content = view.contentDOM.getBoundingClientRect(), block;
2787
2911
  let halfLine = view.defaultLineHeight / 2;
2788
2912
  for (let bounced = false;;) {
@@ -2813,7 +2937,7 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
2813
2937
  // There's visible editor content under the point, so we can try
2814
2938
  // using caret(Position|Range)FromPoint as a shortcut
2815
2939
  let node, offset = -1;
2816
- if (element && view.contentDOM.contains(element) && !(view.docView.nearest(element) instanceof WidgetView)) {
2940
+ if (element && view.contentDOM.contains(element) && ((_a = view.docView.nearest(element)) === null || _a === void 0 ? void 0 : _a.isEditable) != false) {
2817
2941
  if (doc.caretPositionFromPoint) {
2818
2942
  let pos = doc.caretPositionFromPoint(x, y);
2819
2943
  if (pos)
@@ -2954,7 +3078,23 @@ class InputState {
2954
3078
  constructor(view) {
2955
3079
  this.lastKeyCode = 0;
2956
3080
  this.lastKeyTime = 0;
2957
- this.pendingIOSKey = null;
3081
+ // On iOS, some keys need to have their default behavior happen
3082
+ // (after which we retroactively handle them and reset the DOM) to
3083
+ // avoid messing up the virtual keyboard state.
3084
+ //
3085
+ // On Chrome Android, backspace near widgets is just completely
3086
+ // broken, and there are no key events, so we need to handle the
3087
+ // beforeinput event. Deleting stuff will often create a flurry of
3088
+ // events, and interrupting it before it is done just makes
3089
+ // subsequent events even more broken, so again, we hold off doing
3090
+ // anything until the browser is finished with whatever it is trying
3091
+ // to do.
3092
+ //
3093
+ // setPendingKey sets this, causing the DOM observer to pause for a
3094
+ // bit, and setting an animation frame (which seems the most
3095
+ // reliable way to detect 'browser is done flailing') to fire a fake
3096
+ // key event and re-sync the view again.
3097
+ this.pendingKey = undefined;
2958
3098
  this.lastSelectionOrigin = null;
2959
3099
  this.lastSelectionTime = 0;
2960
3100
  this.lastEscPress = 0;
@@ -3063,20 +3203,27 @@ class InputState {
3063
3203
  // state. So we let it go through, and then, in
3064
3204
  // applyDOMChange, notify key handlers of it and reset to
3065
3205
  // the state they produce.
3066
- if (browser.ios && (event.keyCode == 13 || event.keyCode == 8) &&
3206
+ let pending;
3207
+ if (browser.ios && (pending = PendingKeys.find(key => key.keyCode == event.keyCode)) &&
3067
3208
  !(event.ctrlKey || event.altKey || event.metaKey) && !event.synthetic) {
3068
- this.pendingIOSKey = event.keyCode == 13 ? "enter" : "backspace";
3069
- setTimeout(() => this.flushIOSKey(view), 250);
3209
+ this.setPendingKey(view, pending);
3070
3210
  return true;
3071
3211
  }
3072
3212
  return false;
3073
3213
  }
3074
- flushIOSKey(view) {
3075
- if (!this.pendingIOSKey)
3076
- return false;
3077
- let dom = view.contentDOM, key = this.pendingIOSKey;
3078
- this.pendingIOSKey = null;
3079
- return key == "enter" ? dispatchKey(dom, "Enter", 13) : dispatchKey(dom, "Backspace", 8);
3214
+ setPendingKey(view, pending) {
3215
+ this.pendingKey = pending;
3216
+ requestAnimationFrame(() => {
3217
+ if (!this.pendingKey)
3218
+ return false;
3219
+ let key = this.pendingKey;
3220
+ this.pendingKey = undefined;
3221
+ view.observer.processRecords();
3222
+ let startState = view.state;
3223
+ dispatchKey(view.contentDOM, key.key, key.keyCode);
3224
+ if (view.state == startState)
3225
+ view.docView.reset(true);
3226
+ });
3080
3227
  }
3081
3228
  ignoreDuringComposition(event) {
3082
3229
  if (!/^key/.test(event.type))
@@ -3123,6 +3270,11 @@ class InputState {
3123
3270
  this.mouseSelection.destroy();
3124
3271
  }
3125
3272
  }
3273
+ const PendingKeys = [
3274
+ { key: "Backspace", keyCode: 8, inputType: "deleteContentBackward" },
3275
+ { key: "Enter", keyCode: 13, inputType: "insertParagraph" },
3276
+ { key: "Delete", keyCode: 46, inputType: "deleteContentForward" }
3277
+ ];
3126
3278
  // Key codes for modifier keys
3127
3279
  const modifierCodes = [16, 17, 18, 20, 91, 92, 224, 225];
3128
3280
  class MouseSelection {
@@ -3568,6 +3720,33 @@ handlers.compositionend = view => {
3568
3720
  handlers.contextmenu = view => {
3569
3721
  view.inputState.lastContextMenu = Date.now();
3570
3722
  };
3723
+ handlers.beforeinput = (view, event) => {
3724
+ var _a;
3725
+ // Because Chrome Android doesn't fire useful key events, use
3726
+ // beforeinput to detect backspace (and possibly enter and delete,
3727
+ // but those usually don't even seem to fire beforeinput events at
3728
+ // the moment) and fake a key event for it.
3729
+ //
3730
+ // (preventDefault on beforeinput, though supported in the spec,
3731
+ // seems to do nothing at all on Chrome).
3732
+ let pending;
3733
+ if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
3734
+ view.inputState.setPendingKey(view, pending);
3735
+ if (pending.key == "Backspace" || pending.key == "Delete") {
3736
+ let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
3737
+ setTimeout(() => {
3738
+ var _a;
3739
+ // Backspacing near uneditable nodes on Chrome Android sometimes
3740
+ // closes the virtual keyboard. This tries to crudely detect
3741
+ // that and refocus to get it back.
3742
+ if ((((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0) > startViewHeight + 10 && view.hasFocus) {
3743
+ view.contentDOM.blur();
3744
+ view.focus();
3745
+ }
3746
+ }, 100);
3747
+ }
3748
+ }
3749
+ };
3571
3750
 
3572
3751
  const wrappingWhiteSpace = ["pre-wrap", "normal", "pre-line"];
3573
3752
  class HeightOracle {
@@ -4283,6 +4462,15 @@ class LineGapWidget extends WidgetType {
4283
4462
  }
4284
4463
  get estimatedHeight() { return this.vertical ? this.size : -1; }
4285
4464
  }
4465
+ class ScrollTarget {
4466
+ constructor(range, center = false) {
4467
+ this.range = range;
4468
+ this.center = center;
4469
+ }
4470
+ map(changes) {
4471
+ return changes.empty ? this : new ScrollTarget(this.range.map(changes), this.center);
4472
+ }
4473
+ }
4286
4474
  class ViewState {
4287
4475
  constructor(state) {
4288
4476
  this.state = state;
@@ -4295,7 +4483,7 @@ class ViewState {
4295
4483
  this.heightOracle = new HeightOracle;
4296
4484
  // See VP.MaxDOMHeight
4297
4485
  this.scaler = IdScaler;
4298
- this.scrollTo = null;
4486
+ this.scrollTarget = null;
4299
4487
  // Briefly set to true when printing, to disable viewport limiting
4300
4488
  this.printing = false;
4301
4489
  this.visibleRanges = [];
@@ -4328,7 +4516,7 @@ class ViewState {
4328
4516
  this.scaler = this.heightMap.height <= 7000000 /* MaxDOMHeight */ ? IdScaler :
4329
4517
  new BigScaler(this.heightOracle.doc, this.heightMap, this.viewports);
4330
4518
  }
4331
- update(update, scrollTo = null) {
4519
+ update(update, scrollTarget = null) {
4332
4520
  let prev = this.state;
4333
4521
  this.state = update.state;
4334
4522
  let newDeco = this.state.facet(decorations);
@@ -4339,27 +4527,33 @@ class ViewState {
4339
4527
  if (this.heightMap.height != prevHeight)
4340
4528
  update.flags |= 2 /* Height */;
4341
4529
  let viewport = heightChanges.length ? this.mapViewport(this.viewport, update.changes) : this.viewport;
4342
- if (scrollTo && (scrollTo.head < viewport.from || scrollTo.head > viewport.to) || !this.viewportIsAppropriate(viewport))
4343
- viewport = this.getViewport(0, scrollTo);
4530
+ if (scrollTarget && (scrollTarget.range.head < viewport.from || scrollTarget.range.head > viewport.to) ||
4531
+ !this.viewportIsAppropriate(viewport))
4532
+ viewport = this.getViewport(0, scrollTarget);
4344
4533
  this.viewport = viewport;
4345
4534
  this.updateForViewport();
4346
- if (this.lineGaps.length || this.viewport.to - this.viewport.from > 15000 /* MinViewPort */)
4535
+ if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4347
4536
  this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
4348
4537
  update.flags |= this.computeVisibleRanges();
4349
- if (scrollTo)
4350
- this.scrollTo = scrollTo;
4538
+ if (scrollTarget)
4539
+ this.scrollTarget = scrollTarget;
4351
4540
  if (!this.mustEnforceCursorAssoc && update.selectionSet && update.view.lineWrapping &&
4352
4541
  update.state.selection.main.empty && update.state.selection.main.assoc)
4353
4542
  this.mustEnforceCursorAssoc = true;
4354
4543
  }
4355
4544
  measure(docView, repeated) {
4356
4545
  let dom = docView.dom, whiteSpace = "", direction = exports.Direction.LTR;
4546
+ let result = 0;
4357
4547
  if (!repeated) {
4358
4548
  // Vertical padding
4359
4549
  let style = window.getComputedStyle(dom);
4360
4550
  whiteSpace = style.whiteSpace, direction = (style.direction == "rtl" ? exports.Direction.RTL : exports.Direction.LTR);
4361
- this.paddingTop = parseInt(style.paddingTop) || 0;
4362
- this.paddingBottom = parseInt(style.paddingBottom) || 0;
4551
+ let paddingTop = parseInt(style.paddingTop) || 0, paddingBottom = parseInt(style.paddingBottom) || 0;
4552
+ if (this.paddingTop != paddingTop || this.paddingBottom != paddingBottom) {
4553
+ result |= 8 /* Geometry */;
4554
+ this.paddingTop = paddingTop;
4555
+ this.paddingBottom = paddingBottom;
4556
+ }
4363
4557
  }
4364
4558
  // Pixel viewport
4365
4559
  let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 } : visiblePixelRange(dom, this.paddingTop);
@@ -4369,7 +4563,7 @@ class ViewState {
4369
4563
  if (!this.inView)
4370
4564
  return 0;
4371
4565
  let lineHeights = docView.measureVisibleLineHeights();
4372
- let refresh = false, bias = 0, result = 0, oracle = this.heightOracle;
4566
+ let refresh = false, bias = 0, oracle = this.heightOracle;
4373
4567
  if (!repeated) {
4374
4568
  let contentWidth = docView.dom.clientWidth;
4375
4569
  if (oracle.mustRefresh(lineHeights, whiteSpace, direction) ||
@@ -4395,10 +4589,10 @@ class ViewState {
4395
4589
  if (oracle.heightChanged)
4396
4590
  result |= 2 /* Height */;
4397
4591
  if (!this.viewportIsAppropriate(this.viewport, bias) ||
4398
- this.scrollTo && (this.scrollTo.head < this.viewport.from || this.scrollTo.head > this.viewport.to))
4399
- this.viewport = this.getViewport(bias, this.scrollTo);
4592
+ this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to))
4593
+ this.viewport = this.getViewport(bias, this.scrollTarget);
4400
4594
  this.updateForViewport();
4401
- if (this.lineGaps.length || this.viewport.to - this.viewport.from > 15000 /* MinViewPort */)
4595
+ if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4402
4596
  this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
4403
4597
  result |= this.computeVisibleRanges();
4404
4598
  if (this.mustEnforceCursorAssoc) {
@@ -4413,22 +4607,25 @@ class ViewState {
4413
4607
  }
4414
4608
  get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top, 0); }
4415
4609
  get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom, 0); }
4416
- getViewport(bias, scrollTo) {
4610
+ getViewport(bias, scrollTarget) {
4417
4611
  // This will divide VP.Margin between the top and the
4418
4612
  // bottom, depending on the bias (the change in viewport position
4419
4613
  // since the last update). It'll hold a number between 0 and 1
4420
4614
  let marginTop = 0.5 - Math.max(-0.5, Math.min(0.5, bias / 1000 /* Margin */ / 2));
4421
4615
  let map = this.heightMap, doc = this.state.doc, { visibleTop, visibleBottom } = this;
4422
4616
  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);
4423
- // If scrollTo is given, make sure the viewport includes that position
4424
- if (scrollTo) {
4425
- if (scrollTo.head < viewport.from) {
4426
- let { top: newTop } = map.lineAt(scrollTo.head, QueryType.ByPos, doc, 0, 0);
4427
- 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);
4428
- }
4429
- else if (scrollTo.head > viewport.to) {
4430
- let { bottom: newBottom } = map.lineAt(scrollTo.head, QueryType.ByPos, doc, 0, 0);
4431
- 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);
4617
+ // If scrollTarget is given, make sure the viewport includes that position
4618
+ if (scrollTarget) {
4619
+ let { head } = scrollTarget.range, viewHeight = visibleBottom - visibleTop;
4620
+ if (head < viewport.from || head > viewport.to) {
4621
+ let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4622
+ if (scrollTarget.center)
4623
+ topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
4624
+ else if (head < viewport.from)
4625
+ topPos = block.top;
4626
+ else
4627
+ topPos = block.bottom - viewHeight;
4628
+ 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);
4432
4629
  }
4433
4630
  }
4434
4631
  return viewport;
@@ -4470,52 +4667,50 @@ class ViewState {
4470
4667
  if (this.heightOracle.direction != exports.Direction.LTR)
4471
4668
  return gaps;
4472
4669
  this.heightMap.forEachLine(this.viewport.from, this.viewport.to, this.state.doc, 0, 0, line => {
4473
- if (line.length < 10000 /* Margin */)
4670
+ if (line.length < 4000 /* DoubleMargin */)
4474
4671
  return;
4475
4672
  let structure = lineStructure(line.from, line.to, this.state);
4476
- if (structure.total < 10000 /* Margin */)
4673
+ if (structure.total < 4000 /* DoubleMargin */)
4477
4674
  return;
4478
4675
  let viewFrom, viewTo;
4479
4676
  if (this.heightOracle.lineWrapping) {
4480
- if (line.from != this.viewport.from)
4481
- viewFrom = line.from;
4482
- else
4483
- viewFrom = findPosition(structure, (this.visibleTop - line.top) / line.height);
4484
- if (line.to != this.viewport.to)
4485
- viewTo = line.to;
4486
- else
4487
- viewTo = findPosition(structure, (this.visibleBottom - line.top) / line.height);
4677
+ let marginHeight = (2000 /* Margin */ / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
4678
+ viewFrom = findPosition(structure, (this.visibleTop - line.top - marginHeight) / line.height);
4679
+ viewTo = findPosition(structure, (this.visibleBottom - line.top + marginHeight) / line.height);
4488
4680
  }
4489
4681
  else {
4490
4682
  let totalWidth = structure.total * this.heightOracle.charWidth;
4491
- viewFrom = findPosition(structure, this.pixelViewport.left / totalWidth);
4492
- viewTo = findPosition(structure, this.pixelViewport.right / totalWidth);
4683
+ let marginWidth = 2000 /* Margin */ * this.heightOracle.charWidth;
4684
+ viewFrom = findPosition(structure, (this.pixelViewport.left - marginWidth) / totalWidth);
4685
+ viewTo = findPosition(structure, (this.pixelViewport.right + marginWidth) / totalWidth);
4493
4686
  }
4687
+ let outside = [];
4688
+ if (viewFrom > line.from)
4689
+ outside.push({ from: line.from, to: viewFrom });
4690
+ if (viewTo < line.to)
4691
+ outside.push({ from: viewTo, to: line.to });
4494
4692
  let sel = this.state.selection.main;
4495
- // Make sure the gap doesn't cover a selection end
4496
- if (sel.from <= viewFrom && sel.to >= line.from)
4497
- viewFrom = sel.from;
4498
- if (sel.from <= line.to && sel.to >= viewTo)
4499
- viewTo = sel.to;
4500
- let gapTo = viewFrom - 10000 /* Margin */, gapFrom = viewTo + 10000 /* Margin */;
4501
- if (gapTo > line.from + 5000 /* HalfMargin */)
4502
- gaps.push(find(current, gap => gap.from == line.from && gap.to > gapTo - 5000 /* HalfMargin */ && gap.to < gapTo + 5000 /* HalfMargin */) ||
4503
- new LineGap(line.from, gapTo, this.gapSize(line, gapTo, true, structure)));
4504
- if (gapFrom < line.to - 5000 /* HalfMargin */)
4505
- gaps.push(find(current, gap => gap.to == line.to && gap.from > gapFrom - 5000 /* HalfMargin */ &&
4506
- gap.from < gapFrom + 5000 /* HalfMargin */) ||
4507
- new LineGap(gapFrom, line.to, this.gapSize(line, gapFrom, false, structure)));
4693
+ // Make sure the gaps don't cover a selection end
4694
+ if (sel.from >= line.from && sel.from <= line.to)
4695
+ cutRange(outside, sel.from - 10 /* SelectionMargin */, sel.from + 10 /* SelectionMargin */);
4696
+ if (!sel.empty && sel.to >= line.from && sel.to <= line.to)
4697
+ cutRange(outside, sel.to - 10 /* SelectionMargin */, sel.to + 10 /* SelectionMargin */);
4698
+ for (let { from, to } of outside)
4699
+ if (to - from > 1000 /* HalfMargin */) {
4700
+ gaps.push(find(current, gap => gap.from >= line.from && gap.to <= line.to &&
4701
+ Math.abs(gap.from - from) < 1000 /* HalfMargin */ && Math.abs(gap.to - to) < 1000 /* HalfMargin */) ||
4702
+ new LineGap(from, to, this.gapSize(line, from, to, structure)));
4703
+ }
4508
4704
  });
4509
4705
  return gaps;
4510
4706
  }
4511
- gapSize(line, pos, start, structure) {
4707
+ gapSize(line, from, to, structure) {
4708
+ let fraction = findFraction(structure, to) - findFraction(structure, from);
4512
4709
  if (this.heightOracle.lineWrapping) {
4513
- let height = line.height * findFraction(structure, pos);
4514
- return start ? height : line.height - height;
4710
+ return line.height * fraction;
4515
4711
  }
4516
4712
  else {
4517
- let ratio = findFraction(structure, pos);
4518
- return structure.total * this.heightOracle.charWidth * (start ? ratio : 1 - ratio);
4713
+ return structure.total * this.heightOracle.charWidth * fraction;
4519
4714
  }
4520
4715
  }
4521
4716
  updateLineGaps(gaps) {
@@ -4609,6 +4804,20 @@ function findFraction(structure, pos) {
4609
4804
  }
4610
4805
  return counted / structure.total;
4611
4806
  }
4807
+ function cutRange(ranges, from, to) {
4808
+ for (let i = 0; i < ranges.length; i++) {
4809
+ let r = ranges[i];
4810
+ if (r.from < to && r.to > from) {
4811
+ let pieces = [];
4812
+ if (r.from < from)
4813
+ pieces.push({ from: r.from, to: from });
4814
+ if (r.to > to)
4815
+ pieces.push({ from: to, to: r.to });
4816
+ ranges.splice(i, 1, ...pieces);
4817
+ i += pieces.length - 1;
4818
+ }
4819
+ }
4820
+ }
4612
4821
  function find(array, f) {
4613
4822
  for (let val of array)
4614
4823
  if (f(val))
@@ -4691,7 +4900,7 @@ function buildTheme(main, spec, scopes) {
4691
4900
  });
4692
4901
  }
4693
4902
  const baseTheme = buildTheme("." + baseThemeID, {
4694
- "&": {
4903
+ "&.cm-editor": {
4695
4904
  position: "relative !important",
4696
4905
  boxSizing: "border-box",
4697
4906
  "&.cm-focused": {
@@ -4858,6 +5067,8 @@ class DOMObserver {
4858
5067
  this.scrollTargets = [];
4859
5068
  this.intersection = null;
4860
5069
  this.intersecting = false;
5070
+ this.gapIntersection = null;
5071
+ this.gaps = [];
4861
5072
  // Used to work around a Safari Selection/shadow DOM bug (#414)
4862
5073
  this._selectionRange = null;
4863
5074
  // Timeout for scheduling check of the parents that need scroll handlers
@@ -4898,13 +5109,17 @@ class DOMObserver {
4898
5109
  this.intersection = new IntersectionObserver(entries => {
4899
5110
  if (this.parentCheck < 0)
4900
5111
  this.parentCheck = setTimeout(this.listenForScroll.bind(this), 1000);
4901
- if (entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
5112
+ if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
4902
5113
  this.intersecting = !this.intersecting;
4903
5114
  if (this.intersecting != this.view.inView)
4904
5115
  this.onScrollChanged(document.createEvent("Event"));
4905
5116
  }
4906
5117
  }, {});
4907
5118
  this.intersection.observe(this.dom);
5119
+ this.gapIntersection = new IntersectionObserver(entries => {
5120
+ if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0)
5121
+ this.onScrollChanged(document.createEvent("Event"));
5122
+ }, {});
4908
5123
  }
4909
5124
  this.listenForScroll();
4910
5125
  }
@@ -4913,6 +5128,14 @@ class DOMObserver {
4913
5128
  this.flush();
4914
5129
  this.onScrollChanged(e);
4915
5130
  }
5131
+ updateGaps(gaps) {
5132
+ if (this.gapIntersection && (gaps.length != this.gaps.length || this.gaps.some((g, i) => g != gaps[i]))) {
5133
+ this.gapIntersection.disconnect();
5134
+ for (let gap of gaps)
5135
+ this.gapIntersection.observe(gap);
5136
+ this.gaps = gaps;
5137
+ }
5138
+ }
4916
5139
  onSelectionChange(event) {
4917
5140
  if (this.lastFlush < Date.now() - 50)
4918
5141
  this._selectionRange = null;
@@ -5028,20 +5251,12 @@ class DOMObserver {
5028
5251
  this.flush();
5029
5252
  }
5030
5253
  }
5031
- // Apply pending changes, if any
5032
- flush() {
5033
- if (this.delayedFlush >= 0)
5034
- return;
5035
- this.lastFlush = Date.now();
5254
+ processRecords() {
5036
5255
  let records = this.queue;
5037
5256
  for (let mut of this.observer.takeRecords())
5038
5257
  records.push(mut);
5039
5258
  if (records.length)
5040
5259
  this.queue = [];
5041
- let selection = this.selectionRange;
5042
- let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
5043
- if (records.length == 0 && !newSel)
5044
- return;
5045
5260
  let from = -1, to = -1, typeOver = false;
5046
5261
  for (let record of records) {
5047
5262
  let range = this.readMutation(record);
@@ -5057,17 +5272,26 @@ class DOMObserver {
5057
5272
  to = Math.max(range.to, to);
5058
5273
  }
5059
5274
  }
5275
+ return { from, to, typeOver };
5276
+ }
5277
+ // Apply pending changes, if any
5278
+ flush() {
5279
+ // Completely hold off flushing when pending keys are set—the code
5280
+ // managing those will make sure processRecords is called and the
5281
+ // view is resynchronized after
5282
+ if (this.delayedFlush >= 0 || this.view.inputState.pendingKey)
5283
+ return;
5284
+ this.lastFlush = Date.now();
5285
+ let { from, to, typeOver } = this.processRecords();
5286
+ let selection = this.selectionRange;
5287
+ let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
5288
+ if (from < 0 && !newSel)
5289
+ return;
5060
5290
  let startState = this.view.state;
5061
- if (from > -1 || newSel)
5062
- this.onChange(from, to, typeOver);
5063
- if (this.view.state == startState) { // The view wasn't updated
5064
- if (this.view.docView.dirty) {
5065
- this.ignore(() => this.view.docView.sync());
5066
- this.view.docView.dirty = 0 /* Not */;
5067
- }
5068
- if (newSel)
5069
- this.view.docView.updateSelection();
5070
- }
5291
+ this.onChange(from, to, typeOver);
5292
+ // The view wasn't updated
5293
+ if (this.view.state == startState)
5294
+ this.view.docView.reset(newSel);
5071
5295
  this.clearSelection();
5072
5296
  }
5073
5297
  readMutation(rec) {
@@ -5094,6 +5318,8 @@ class DOMObserver {
5094
5318
  this.stop();
5095
5319
  if (this.intersection)
5096
5320
  this.intersection.disconnect();
5321
+ if (this.gapIntersection)
5322
+ this.gapIntersection.disconnect();
5097
5323
  for (let dom of this.scrollTargets)
5098
5324
  dom.removeEventListener("scroll", this.onScroll);
5099
5325
  window.removeEventListener("scroll", this.onScroll);
@@ -5202,9 +5428,9 @@ function applyDOMChange(view, start, end, typeOver) {
5202
5428
  (change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
5203
5429
  dispatchKey(view.contentDOM, "Backspace", 8)) ||
5204
5430
  (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
5205
- dispatchKey(view.contentDOM, "Delete", 46))) ||
5206
- browser.ios && view.inputState.flushIOSKey(view))
5431
+ dispatchKey(view.contentDOM, "Delete", 46)))) {
5207
5432
  return;
5433
+ }
5208
5434
  let text = change.insert.toString();
5209
5435
  if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
5210
5436
  return;
@@ -5425,7 +5651,7 @@ class EditorView {
5425
5651
  this.dom.appendChild(this.scrollDOM);
5426
5652
  this._dispatch = config.dispatch || ((tr) => this.update([tr]));
5427
5653
  this.dispatch = this.dispatch.bind(this);
5428
- this.root = (config.root || document);
5654
+ this.root = (config.root || getRoot(config.parent) || document);
5429
5655
  this.viewState = new ViewState(config.state || state.EditorState.create());
5430
5656
  this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec).update(this));
5431
5657
  this.observer = new DOMObserver(this, (from, to, typeOver) => {
@@ -5506,21 +5732,24 @@ class EditorView {
5506
5732
  if (state$1.facet(state.EditorState.phrases) != this.state.facet(state.EditorState.phrases))
5507
5733
  return this.setState(state$1);
5508
5734
  update = new ViewUpdate(this, state$1, transactions);
5509
- let scrollPos = null;
5735
+ let scrollTarget = null;
5510
5736
  try {
5511
5737
  this.updateState = 2 /* Updating */;
5512
5738
  for (let tr of transactions) {
5513
- if (scrollPos)
5514
- scrollPos = scrollPos.map(tr.changes);
5739
+ if (scrollTarget)
5740
+ scrollTarget = scrollTarget.map(tr.changes);
5515
5741
  if (tr.scrollIntoView) {
5516
5742
  let { main } = tr.state.selection;
5517
- scrollPos = main.empty ? main : state.EditorSelection.cursor(main.head, main.head > main.anchor ? -1 : 1);
5743
+ scrollTarget = new ScrollTarget(main.empty ? main : state.EditorSelection.cursor(main.head, main.head > main.anchor ? -1 : 1));
5518
5744
  }
5519
- for (let e of tr.effects)
5745
+ for (let e of tr.effects) {
5520
5746
  if (e.is(scrollTo))
5521
- scrollPos = e.value;
5747
+ scrollTarget = new ScrollTarget(e.value);
5748
+ else if (e.is(centerOn))
5749
+ scrollTarget = new ScrollTarget(e.value, true);
5750
+ }
5522
5751
  }
5523
- this.viewState.update(update, scrollPos);
5752
+ this.viewState.update(update, scrollTarget);
5524
5753
  this.bidiCache = CachedOrder.update(this.bidiCache, update.changes);
5525
5754
  if (!update.empty) {
5526
5755
  this.updatePlugins(update);
@@ -5535,7 +5764,7 @@ class EditorView {
5535
5764
  finally {
5536
5765
  this.updateState = 0 /* Idle */;
5537
5766
  }
5538
- if (redrawn || scrollPos || this.viewState.mustEnforceCursorAssoc)
5767
+ if (redrawn || scrollTarget || this.viewState.mustEnforceCursorAssoc)
5539
5768
  this.requestMeasure();
5540
5769
  if (!update.empty)
5541
5770
  for (let listener of this.state.facet(updateListener))
@@ -5617,14 +5846,16 @@ class EditorView {
5617
5846
  this.updateState = 1 /* Measuring */;
5618
5847
  let oldViewport = this.viewport;
5619
5848
  let changed = this.viewState.measure(this.docView, i > 0);
5620
- let measuring = this.measureRequests;
5621
- if (!changed && !measuring.length && this.viewState.scrollTo == null)
5849
+ if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
5622
5850
  break;
5623
- this.measureRequests = [];
5624
5851
  if (i > 5) {
5625
5852
  console.warn("Viewport failed to stabilize");
5626
5853
  break;
5627
5854
  }
5855
+ let measuring = [];
5856
+ // Only run measure requests in this cycle when the viewport didn't change
5857
+ if (!(changed & 4 /* Viewport */))
5858
+ [this.measureRequests, measuring] = [measuring, this.measureRequests];
5628
5859
  let measured = measuring.map(m => {
5629
5860
  try {
5630
5861
  return m.read(this);
@@ -5657,9 +5888,9 @@ class EditorView {
5657
5888
  logException(this.state, e);
5658
5889
  }
5659
5890
  }
5660
- if (this.viewState.scrollTo) {
5661
- this.docView.scrollRangeIntoView(this.viewState.scrollTo);
5662
- this.viewState.scrollTo = null;
5891
+ if (this.viewState.scrollTarget) {
5892
+ this.docView.scrollIntoView(this.viewState.scrollTarget);
5893
+ this.viewState.scrollTarget = null;
5663
5894
  }
5664
5895
  if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
5665
5896
  break;
@@ -5691,6 +5922,7 @@ class EditorView {
5691
5922
  spellcheck: "false",
5692
5923
  autocorrect: "off",
5693
5924
  autocapitalize: "off",
5925
+ translate: "no",
5694
5926
  contenteditable: !this.state.facet(editable) ? "false" : contentEditablePlainTextSupported() ? "plaintext-only" : "true",
5695
5927
  class: "cm-content",
5696
5928
  style: `${browser.tabSize}: ${this.state.tabSize}`,
@@ -5881,12 +6113,9 @@ class EditorView {
5881
6113
  moveVertically(start, forward, distance) {
5882
6114
  return skipAtoms(this, start, moveVertically(this, start, forward, distance));
5883
6115
  }
5884
- /**
5885
- Scroll the given document position into view.
5886
- */
6116
+ // FIXME remove on next major version
5887
6117
  scrollPosIntoView(pos) {
5888
- this.viewState.scrollTo = state.EditorSelection.cursor(pos);
5889
- this.requestMeasure();
6118
+ this.dispatch({ effects: scrollTo.of(state.EditorSelection.cursor(pos)) });
5890
6119
  }
5891
6120
  /**
5892
6121
  Find the DOM parent node and offset (child offset if `node` is
@@ -6053,7 +6282,7 @@ class EditorView {
6053
6282
  target editors with a dark or light theme.
6054
6283
  */
6055
6284
  static baseTheme(spec) {
6056
- return state.Prec.fallback(styleModule.of(buildTheme("." + baseThemeID, spec, lightDarkIDs)));
6285
+ return state.Prec.lowest(styleModule.of(buildTheme("." + baseThemeID, spec, lightDarkIDs)));
6057
6286
  }
6058
6287
  }
6059
6288
  /**
@@ -6062,6 +6291,11 @@ transaction to make it scroll the given range into view.
6062
6291
  */
6063
6292
  EditorView.scrollTo = scrollTo;
6064
6293
  /**
6294
+ Effect that makes the editor scroll the given range to the
6295
+ center of the visible view.
6296
+ */
6297
+ EditorView.centerOn = centerOn;
6298
+ /**
6065
6299
  Facet to add a [style
6066
6300
  module](https://github.com/marijnh/style-mod#documentation) to
6067
6301
  an editor view. The view will ensure that the module is
@@ -6191,11 +6425,7 @@ class CachedOrder {
6191
6425
  }
6192
6426
  }
6193
6427
 
6194
- const currentPlatform = typeof navigator == "undefined" ? "key"
6195
- : /Mac/.test(navigator.platform) ? "mac"
6196
- : /Win/.test(navigator.platform) ? "win"
6197
- : /Linux|X11/.test(navigator.platform) ? "linux"
6198
- : "key";
6428
+ const currentPlatform = browser.mac ? "mac" : browser.windows ? "win" : browser.linux ? "linux" : "key";
6199
6429
  function normalizeKeyName(name, platform) {
6200
6430
  const parts = name.split(/-(?!$)/);
6201
6431
  let result = parts[parts.length - 1];
@@ -6494,7 +6724,7 @@ const themeSpec = {
6494
6724
  };
6495
6725
  if (CanHidePrimary)
6496
6726
  themeSpec[".cm-line"].caretColor = "transparent !important";
6497
- const hideNativeSelection = state.Prec.override(EditorView.theme(themeSpec));
6727
+ const hideNativeSelection = state.Prec.highest(EditorView.theme(themeSpec));
6498
6728
  function getBase(view) {
6499
6729
  let rect = view.scrollDOM.getBoundingClientRect();
6500
6730
  let left = view.textDirection == exports.Direction.LTR ? rect.left : rect.right - view.scrollDOM.clientWidth;
@@ -6701,7 +6931,7 @@ class MatchDecorator {
6701
6931
  }
6702
6932
 
6703
6933
  const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g";
6704
- const Specials = new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
6934
+ const Specials = new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
6705
6935
  const Names = {
6706
6936
  0: "null",
6707
6937
  7: "bell",
@@ -6716,6 +6946,8 @@ const Names = {
6716
6946
  8206: "left-to-right mark",
6717
6947
  8207: "right-to-left mark",
6718
6948
  8232: "line separator",
6949
+ 8237: "left-to-right override",
6950
+ 8238: "right-to-left override",
6719
6951
  8233: "paragraph separator",
6720
6952
  65279: "zero width no-break space",
6721
6953
  65532: "object replacement"
@@ -6882,7 +7114,7 @@ DOM class.
6882
7114
  function highlightActiveLine() {
6883
7115
  return activeLineHighlighter;
6884
7116
  }
6885
- const lineDeco = Decoration.line({ attributes: { class: "cm-activeLine" } });
7117
+ const lineDeco = Decoration.line({ class: "cm-activeLine" });
6886
7118
  const activeLineHighlighter = ViewPlugin.fromClass(class {
6887
7119
  constructor(view) {
6888
7120
  this.decorations = this.getDeco(view);