@codemirror/view 0.19.9 → 0.19.13

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) {
@@ -2036,6 +2137,14 @@ class DocView extends ContentView {
2036
2137
  !isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) ||
2037
2138
  !isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) {
2038
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
+ }
2039
2148
  let rawSel = getSelection(this.root);
2040
2149
  if (main.empty) {
2041
2150
  // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=1612076
@@ -2209,7 +2318,7 @@ class DocView extends ContentView {
2209
2318
  this.view.viewState.lineGapDeco
2210
2319
  ];
2211
2320
  }
2212
- scrollRangeIntoView(range) {
2321
+ scrollIntoView({ range, center }) {
2213
2322
  let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
2214
2323
  if (!rect)
2215
2324
  return;
@@ -2229,10 +2338,10 @@ class DocView extends ContentView {
2229
2338
  if (bottom != null)
2230
2339
  mBottom = Math.max(mBottom, bottom);
2231
2340
  }
2232
- scrollRectIntoView(this.dom, {
2341
+ scrollRectIntoView(this.view.scrollDOM, {
2233
2342
  left: rect.left - mLeft, top: rect.top - mTop,
2234
2343
  right: rect.right + mRight, bottom: rect.bottom + mBottom
2235
- }, range.head < range.anchor ? -1 : 1);
2344
+ }, range.head < range.anchor ? -1 : 1, center);
2236
2345
  }
2237
2346
  }
2238
2347
  function betweenUneditable(pos) {
@@ -2343,6 +2452,14 @@ function findChangedDeco(a, b, diff) {
2343
2452
  rangeset.RangeSet.compare(a, b, diff, comp);
2344
2453
  return comp.changes;
2345
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
+ }
2346
2463
 
2347
2464
  /**
2348
2465
  Used to indicate [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
@@ -2789,6 +2906,7 @@ function domPosInText(node, x, y) {
2789
2906
  return { node, offset: closestOffset > -1 ? closestOffset : generalSide > 0 ? node.nodeValue.length : 0 };
2790
2907
  }
2791
2908
  function posAtCoords(view, { x, y }, precise, bias = -1) {
2909
+ var _a;
2792
2910
  let content = view.contentDOM.getBoundingClientRect(), block;
2793
2911
  let halfLine = view.defaultLineHeight / 2;
2794
2912
  for (let bounced = false;;) {
@@ -2819,7 +2937,7 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
2819
2937
  // There's visible editor content under the point, so we can try
2820
2938
  // using caret(Position|Range)FromPoint as a shortcut
2821
2939
  let node, offset = -1;
2822
- 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) {
2823
2941
  if (doc.caretPositionFromPoint) {
2824
2942
  let pos = doc.caretPositionFromPoint(x, y);
2825
2943
  if (pos)
@@ -2960,7 +3078,23 @@ class InputState {
2960
3078
  constructor(view) {
2961
3079
  this.lastKeyCode = 0;
2962
3080
  this.lastKeyTime = 0;
2963
- 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;
2964
3098
  this.lastSelectionOrigin = null;
2965
3099
  this.lastSelectionTime = 0;
2966
3100
  this.lastEscPress = 0;
@@ -3069,20 +3203,31 @@ class InputState {
3069
3203
  // state. So we let it go through, and then, in
3070
3204
  // applyDOMChange, notify key handlers of it and reset to
3071
3205
  // the state they produce.
3072
- if (browser.ios && (event.keyCode == 13 || event.keyCode == 8) &&
3206
+ let pending;
3207
+ if (browser.ios && (pending = PendingKeys.find(key => key.keyCode == event.keyCode)) &&
3073
3208
  !(event.ctrlKey || event.altKey || event.metaKey) && !event.synthetic) {
3074
- this.pendingIOSKey = event.keyCode == 13 ? "enter" : "backspace";
3075
- setTimeout(() => this.flushIOSKey(view), 250);
3209
+ this.setPendingKey(view, pending);
3076
3210
  return true;
3077
3211
  }
3078
3212
  return false;
3079
3213
  }
3080
- flushIOSKey(view) {
3081
- if (!this.pendingIOSKey)
3082
- return false;
3083
- let dom = view.contentDOM, key = this.pendingIOSKey;
3084
- this.pendingIOSKey = null;
3085
- return key == "enter" ? dispatchKey(dom, "Enter", 13) : dispatchKey(dom, "Backspace", 8);
3214
+ setPendingKey(view, pending) {
3215
+ this.pendingKey = pending;
3216
+ let flush = () => {
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
+ };
3227
+ if (browser.ios)
3228
+ setTimeout(() => requestAnimationFrame(flush), 50);
3229
+ else
3230
+ requestAnimationFrame(flush);
3086
3231
  }
3087
3232
  ignoreDuringComposition(event) {
3088
3233
  if (!/^key/.test(event.type))
@@ -3129,6 +3274,11 @@ class InputState {
3129
3274
  this.mouseSelection.destroy();
3130
3275
  }
3131
3276
  }
3277
+ const PendingKeys = [
3278
+ { key: "Backspace", keyCode: 8, inputType: "deleteContentBackward" },
3279
+ { key: "Enter", keyCode: 13, inputType: "insertParagraph" },
3280
+ { key: "Delete", keyCode: 46, inputType: "deleteContentForward" }
3281
+ ];
3132
3282
  // Key codes for modifier keys
3133
3283
  const modifierCodes = [16, 17, 18, 20, 91, 92, 224, 225];
3134
3284
  class MouseSelection {
@@ -3574,6 +3724,33 @@ handlers.compositionend = view => {
3574
3724
  handlers.contextmenu = view => {
3575
3725
  view.inputState.lastContextMenu = Date.now();
3576
3726
  };
3727
+ handlers.beforeinput = (view, event) => {
3728
+ var _a;
3729
+ // Because Chrome Android doesn't fire useful key events, use
3730
+ // beforeinput to detect backspace (and possibly enter and delete,
3731
+ // but those usually don't even seem to fire beforeinput events at
3732
+ // the moment) and fake a key event for it.
3733
+ //
3734
+ // (preventDefault on beforeinput, though supported in the spec,
3735
+ // seems to do nothing at all on Chrome).
3736
+ let pending;
3737
+ if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
3738
+ view.inputState.setPendingKey(view, pending);
3739
+ if (pending.key == "Backspace" || pending.key == "Delete") {
3740
+ let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
3741
+ setTimeout(() => {
3742
+ var _a;
3743
+ // Backspacing near uneditable nodes on Chrome Android sometimes
3744
+ // closes the virtual keyboard. This tries to crudely detect
3745
+ // that and refocus to get it back.
3746
+ if ((((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0) > startViewHeight + 10 && view.hasFocus) {
3747
+ view.contentDOM.blur();
3748
+ view.focus();
3749
+ }
3750
+ }, 100);
3751
+ }
3752
+ }
3753
+ };
3577
3754
 
3578
3755
  const wrappingWhiteSpace = ["pre-wrap", "normal", "pre-line"];
3579
3756
  class HeightOracle {
@@ -4289,6 +4466,15 @@ class LineGapWidget extends WidgetType {
4289
4466
  }
4290
4467
  get estimatedHeight() { return this.vertical ? this.size : -1; }
4291
4468
  }
4469
+ class ScrollTarget {
4470
+ constructor(range, center = false) {
4471
+ this.range = range;
4472
+ this.center = center;
4473
+ }
4474
+ map(changes) {
4475
+ return changes.empty ? this : new ScrollTarget(this.range.map(changes), this.center);
4476
+ }
4477
+ }
4292
4478
  class ViewState {
4293
4479
  constructor(state) {
4294
4480
  this.state = state;
@@ -4301,7 +4487,7 @@ class ViewState {
4301
4487
  this.heightOracle = new HeightOracle;
4302
4488
  // See VP.MaxDOMHeight
4303
4489
  this.scaler = IdScaler;
4304
- this.scrollTo = null;
4490
+ this.scrollTarget = null;
4305
4491
  // Briefly set to true when printing, to disable viewport limiting
4306
4492
  this.printing = false;
4307
4493
  this.visibleRanges = [];
@@ -4334,7 +4520,7 @@ class ViewState {
4334
4520
  this.scaler = this.heightMap.height <= 7000000 /* MaxDOMHeight */ ? IdScaler :
4335
4521
  new BigScaler(this.heightOracle.doc, this.heightMap, this.viewports);
4336
4522
  }
4337
- update(update, scrollTo = null) {
4523
+ update(update, scrollTarget = null) {
4338
4524
  let prev = this.state;
4339
4525
  this.state = update.state;
4340
4526
  let newDeco = this.state.facet(decorations);
@@ -4345,15 +4531,16 @@ class ViewState {
4345
4531
  if (this.heightMap.height != prevHeight)
4346
4532
  update.flags |= 2 /* Height */;
4347
4533
  let viewport = heightChanges.length ? this.mapViewport(this.viewport, update.changes) : this.viewport;
4348
- if (scrollTo && (scrollTo.head < viewport.from || scrollTo.head > viewport.to) || !this.viewportIsAppropriate(viewport))
4349
- viewport = this.getViewport(0, scrollTo);
4534
+ if (scrollTarget && (scrollTarget.range.head < viewport.from || scrollTarget.range.head > viewport.to) ||
4535
+ !this.viewportIsAppropriate(viewport))
4536
+ viewport = this.getViewport(0, scrollTarget);
4350
4537
  this.viewport = viewport;
4351
4538
  this.updateForViewport();
4352
- if (this.lineGaps.length || this.viewport.to - this.viewport.from > 15000 /* MinViewPort */)
4539
+ if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4353
4540
  this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
4354
4541
  update.flags |= this.computeVisibleRanges();
4355
- if (scrollTo)
4356
- this.scrollTo = scrollTo;
4542
+ if (scrollTarget)
4543
+ this.scrollTarget = scrollTarget;
4357
4544
  if (!this.mustEnforceCursorAssoc && update.selectionSet && update.view.lineWrapping &&
4358
4545
  update.state.selection.main.empty && update.state.selection.main.assoc)
4359
4546
  this.mustEnforceCursorAssoc = true;
@@ -4406,10 +4593,10 @@ class ViewState {
4406
4593
  if (oracle.heightChanged)
4407
4594
  result |= 2 /* Height */;
4408
4595
  if (!this.viewportIsAppropriate(this.viewport, bias) ||
4409
- this.scrollTo && (this.scrollTo.head < this.viewport.from || this.scrollTo.head > this.viewport.to))
4410
- this.viewport = this.getViewport(bias, this.scrollTo);
4596
+ this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to))
4597
+ this.viewport = this.getViewport(bias, this.scrollTarget);
4411
4598
  this.updateForViewport();
4412
- if (this.lineGaps.length || this.viewport.to - this.viewport.from > 15000 /* MinViewPort */)
4599
+ if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4413
4600
  this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
4414
4601
  result |= this.computeVisibleRanges();
4415
4602
  if (this.mustEnforceCursorAssoc) {
@@ -4424,22 +4611,25 @@ class ViewState {
4424
4611
  }
4425
4612
  get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top, 0); }
4426
4613
  get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom, 0); }
4427
- getViewport(bias, scrollTo) {
4614
+ getViewport(bias, scrollTarget) {
4428
4615
  // This will divide VP.Margin between the top and the
4429
4616
  // bottom, depending on the bias (the change in viewport position
4430
4617
  // since the last update). It'll hold a number between 0 and 1
4431
4618
  let marginTop = 0.5 - Math.max(-0.5, Math.min(0.5, bias / 1000 /* Margin */ / 2));
4432
4619
  let map = this.heightMap, doc = this.state.doc, { visibleTop, visibleBottom } = this;
4433
4620
  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);
4434
- // If scrollTo is given, make sure the viewport includes that position
4435
- if (scrollTo) {
4436
- if (scrollTo.head < viewport.from) {
4437
- let { top: newTop } = map.lineAt(scrollTo.head, QueryType.ByPos, doc, 0, 0);
4438
- 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);
4439
- }
4440
- else if (scrollTo.head > viewport.to) {
4441
- let { bottom: newBottom } = map.lineAt(scrollTo.head, QueryType.ByPos, doc, 0, 0);
4442
- 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);
4621
+ // If scrollTarget is given, make sure the viewport includes that position
4622
+ if (scrollTarget) {
4623
+ let { head } = scrollTarget.range, viewHeight = visibleBottom - visibleTop;
4624
+ if (head < viewport.from || head > viewport.to) {
4625
+ let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4626
+ if (scrollTarget.center)
4627
+ topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
4628
+ else if (head < viewport.from)
4629
+ topPos = block.top;
4630
+ else
4631
+ topPos = block.bottom - viewHeight;
4632
+ 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);
4443
4633
  }
4444
4634
  }
4445
4635
  return viewport;
@@ -4481,52 +4671,50 @@ class ViewState {
4481
4671
  if (this.heightOracle.direction != exports.Direction.LTR)
4482
4672
  return gaps;
4483
4673
  this.heightMap.forEachLine(this.viewport.from, this.viewport.to, this.state.doc, 0, 0, line => {
4484
- if (line.length < 10000 /* Margin */)
4674
+ if (line.length < 4000 /* DoubleMargin */)
4485
4675
  return;
4486
4676
  let structure = lineStructure(line.from, line.to, this.state);
4487
- if (structure.total < 10000 /* Margin */)
4677
+ if (structure.total < 4000 /* DoubleMargin */)
4488
4678
  return;
4489
4679
  let viewFrom, viewTo;
4490
4680
  if (this.heightOracle.lineWrapping) {
4491
- if (line.from != this.viewport.from)
4492
- viewFrom = line.from;
4493
- else
4494
- viewFrom = findPosition(structure, (this.visibleTop - line.top) / line.height);
4495
- if (line.to != this.viewport.to)
4496
- viewTo = line.to;
4497
- else
4498
- viewTo = findPosition(structure, (this.visibleBottom - line.top) / line.height);
4681
+ let marginHeight = (2000 /* Margin */ / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
4682
+ viewFrom = findPosition(structure, (this.visibleTop - line.top - marginHeight) / line.height);
4683
+ viewTo = findPosition(structure, (this.visibleBottom - line.top + marginHeight) / line.height);
4499
4684
  }
4500
4685
  else {
4501
4686
  let totalWidth = structure.total * this.heightOracle.charWidth;
4502
- viewFrom = findPosition(structure, this.pixelViewport.left / totalWidth);
4503
- viewTo = findPosition(structure, this.pixelViewport.right / totalWidth);
4687
+ let marginWidth = 2000 /* Margin */ * this.heightOracle.charWidth;
4688
+ viewFrom = findPosition(structure, (this.pixelViewport.left - marginWidth) / totalWidth);
4689
+ viewTo = findPosition(structure, (this.pixelViewport.right + marginWidth) / totalWidth);
4504
4690
  }
4691
+ let outside = [];
4692
+ if (viewFrom > line.from)
4693
+ outside.push({ from: line.from, to: viewFrom });
4694
+ if (viewTo < line.to)
4695
+ outside.push({ from: viewTo, to: line.to });
4505
4696
  let sel = this.state.selection.main;
4506
- // Make sure the gap doesn't cover a selection end
4507
- if (sel.from <= viewFrom && sel.to >= line.from)
4508
- viewFrom = sel.from;
4509
- if (sel.from <= line.to && sel.to >= viewTo)
4510
- viewTo = sel.to;
4511
- let gapTo = viewFrom - 10000 /* Margin */, gapFrom = viewTo + 10000 /* Margin */;
4512
- if (gapTo > line.from + 5000 /* HalfMargin */)
4513
- gaps.push(find(current, gap => gap.from == line.from && gap.to > gapTo - 5000 /* HalfMargin */ && gap.to < gapTo + 5000 /* HalfMargin */) ||
4514
- new LineGap(line.from, gapTo, this.gapSize(line, gapTo, true, structure)));
4515
- if (gapFrom < line.to - 5000 /* HalfMargin */)
4516
- gaps.push(find(current, gap => gap.to == line.to && gap.from > gapFrom - 5000 /* HalfMargin */ &&
4517
- gap.from < gapFrom + 5000 /* HalfMargin */) ||
4518
- new LineGap(gapFrom, line.to, this.gapSize(line, gapFrom, false, structure)));
4697
+ // Make sure the gaps don't cover a selection end
4698
+ if (sel.from >= line.from && sel.from <= line.to)
4699
+ cutRange(outside, sel.from - 10 /* SelectionMargin */, sel.from + 10 /* SelectionMargin */);
4700
+ if (!sel.empty && sel.to >= line.from && sel.to <= line.to)
4701
+ cutRange(outside, sel.to - 10 /* SelectionMargin */, sel.to + 10 /* SelectionMargin */);
4702
+ for (let { from, to } of outside)
4703
+ if (to - from > 1000 /* HalfMargin */) {
4704
+ gaps.push(find(current, gap => gap.from >= line.from && gap.to <= line.to &&
4705
+ Math.abs(gap.from - from) < 1000 /* HalfMargin */ && Math.abs(gap.to - to) < 1000 /* HalfMargin */) ||
4706
+ new LineGap(from, to, this.gapSize(line, from, to, structure)));
4707
+ }
4519
4708
  });
4520
4709
  return gaps;
4521
4710
  }
4522
- gapSize(line, pos, start, structure) {
4711
+ gapSize(line, from, to, structure) {
4712
+ let fraction = findFraction(structure, to) - findFraction(structure, from);
4523
4713
  if (this.heightOracle.lineWrapping) {
4524
- let height = line.height * findFraction(structure, pos);
4525
- return start ? height : line.height - height;
4714
+ return line.height * fraction;
4526
4715
  }
4527
4716
  else {
4528
- let ratio = findFraction(structure, pos);
4529
- return structure.total * this.heightOracle.charWidth * (start ? ratio : 1 - ratio);
4717
+ return structure.total * this.heightOracle.charWidth * fraction;
4530
4718
  }
4531
4719
  }
4532
4720
  updateLineGaps(gaps) {
@@ -4620,6 +4808,20 @@ function findFraction(structure, pos) {
4620
4808
  }
4621
4809
  return counted / structure.total;
4622
4810
  }
4811
+ function cutRange(ranges, from, to) {
4812
+ for (let i = 0; i < ranges.length; i++) {
4813
+ let r = ranges[i];
4814
+ if (r.from < to && r.to > from) {
4815
+ let pieces = [];
4816
+ if (r.from < from)
4817
+ pieces.push({ from: r.from, to: from });
4818
+ if (r.to > to)
4819
+ pieces.push({ from: to, to: r.to });
4820
+ ranges.splice(i, 1, ...pieces);
4821
+ i += pieces.length - 1;
4822
+ }
4823
+ }
4824
+ }
4623
4825
  function find(array, f) {
4624
4826
  for (let val of array)
4625
4827
  if (f(val))
@@ -4702,7 +4904,7 @@ function buildTheme(main, spec, scopes) {
4702
4904
  });
4703
4905
  }
4704
4906
  const baseTheme = buildTheme("." + baseThemeID, {
4705
- "&": {
4907
+ "&.cm-editor": {
4706
4908
  position: "relative !important",
4707
4909
  boxSizing: "border-box",
4708
4910
  "&.cm-focused": {
@@ -4911,7 +5113,7 @@ class DOMObserver {
4911
5113
  this.intersection = new IntersectionObserver(entries => {
4912
5114
  if (this.parentCheck < 0)
4913
5115
  this.parentCheck = setTimeout(this.listenForScroll.bind(this), 1000);
4914
- if (entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
5116
+ if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
4915
5117
  this.intersecting = !this.intersecting;
4916
5118
  if (this.intersecting != this.view.inView)
4917
5119
  this.onScrollChanged(document.createEvent("Event"));
@@ -4919,7 +5121,7 @@ class DOMObserver {
4919
5121
  }, {});
4920
5122
  this.intersection.observe(this.dom);
4921
5123
  this.gapIntersection = new IntersectionObserver(entries => {
4922
- if (entries[entries.length - 1].intersectionRatio > 0)
5124
+ if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0)
4923
5125
  this.onScrollChanged(document.createEvent("Event"));
4924
5126
  }, {});
4925
5127
  }
@@ -5053,20 +5255,12 @@ class DOMObserver {
5053
5255
  this.flush();
5054
5256
  }
5055
5257
  }
5056
- // Apply pending changes, if any
5057
- flush() {
5058
- if (this.delayedFlush >= 0)
5059
- return;
5060
- this.lastFlush = Date.now();
5258
+ processRecords() {
5061
5259
  let records = this.queue;
5062
5260
  for (let mut of this.observer.takeRecords())
5063
5261
  records.push(mut);
5064
5262
  if (records.length)
5065
5263
  this.queue = [];
5066
- let selection = this.selectionRange;
5067
- let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
5068
- if (records.length == 0 && !newSel)
5069
- return;
5070
5264
  let from = -1, to = -1, typeOver = false;
5071
5265
  for (let record of records) {
5072
5266
  let range = this.readMutation(record);
@@ -5082,17 +5276,26 @@ class DOMObserver {
5082
5276
  to = Math.max(range.to, to);
5083
5277
  }
5084
5278
  }
5279
+ return { from, to, typeOver };
5280
+ }
5281
+ // Apply pending changes, if any
5282
+ flush() {
5283
+ // Completely hold off flushing when pending keys are set—the code
5284
+ // managing those will make sure processRecords is called and the
5285
+ // view is resynchronized after
5286
+ if (this.delayedFlush >= 0 || this.view.inputState.pendingKey)
5287
+ return;
5288
+ this.lastFlush = Date.now();
5289
+ let { from, to, typeOver } = this.processRecords();
5290
+ let selection = this.selectionRange;
5291
+ let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
5292
+ if (from < 0 && !newSel)
5293
+ return;
5085
5294
  let startState = this.view.state;
5086
- if (from > -1 || newSel)
5087
- this.onChange(from, to, typeOver);
5088
- if (this.view.state == startState) { // The view wasn't updated
5089
- if (this.view.docView.dirty) {
5090
- this.ignore(() => this.view.docView.sync());
5091
- this.view.docView.dirty = 0 /* Not */;
5092
- }
5093
- if (newSel)
5094
- this.view.docView.updateSelection();
5095
- }
5295
+ this.onChange(from, to, typeOver);
5296
+ // The view wasn't updated
5297
+ if (this.view.state == startState)
5298
+ this.view.docView.reset(newSel);
5096
5299
  this.clearSelection();
5097
5300
  }
5098
5301
  readMutation(rec) {
@@ -5229,9 +5432,9 @@ function applyDOMChange(view, start, end, typeOver) {
5229
5432
  (change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
5230
5433
  dispatchKey(view.contentDOM, "Backspace", 8)) ||
5231
5434
  (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
5232
- dispatchKey(view.contentDOM, "Delete", 46))) ||
5233
- browser.ios && view.inputState.flushIOSKey(view))
5435
+ dispatchKey(view.contentDOM, "Delete", 46)))) {
5234
5436
  return;
5437
+ }
5235
5438
  let text = change.insert.toString();
5236
5439
  if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
5237
5440
  return;
@@ -5452,7 +5655,7 @@ class EditorView {
5452
5655
  this.dom.appendChild(this.scrollDOM);
5453
5656
  this._dispatch = config.dispatch || ((tr) => this.update([tr]));
5454
5657
  this.dispatch = this.dispatch.bind(this);
5455
- this.root = (config.root || document);
5658
+ this.root = (config.root || getRoot(config.parent) || document);
5456
5659
  this.viewState = new ViewState(config.state || state.EditorState.create());
5457
5660
  this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec).update(this));
5458
5661
  this.observer = new DOMObserver(this, (from, to, typeOver) => {
@@ -5533,21 +5736,24 @@ class EditorView {
5533
5736
  if (state$1.facet(state.EditorState.phrases) != this.state.facet(state.EditorState.phrases))
5534
5737
  return this.setState(state$1);
5535
5738
  update = new ViewUpdate(this, state$1, transactions);
5536
- let scrollPos = null;
5739
+ let scrollTarget = null;
5537
5740
  try {
5538
5741
  this.updateState = 2 /* Updating */;
5539
5742
  for (let tr of transactions) {
5540
- if (scrollPos)
5541
- scrollPos = scrollPos.map(tr.changes);
5743
+ if (scrollTarget)
5744
+ scrollTarget = scrollTarget.map(tr.changes);
5542
5745
  if (tr.scrollIntoView) {
5543
5746
  let { main } = tr.state.selection;
5544
- scrollPos = main.empty ? main : state.EditorSelection.cursor(main.head, main.head > main.anchor ? -1 : 1);
5747
+ scrollTarget = new ScrollTarget(main.empty ? main : state.EditorSelection.cursor(main.head, main.head > main.anchor ? -1 : 1));
5545
5748
  }
5546
- for (let e of tr.effects)
5749
+ for (let e of tr.effects) {
5547
5750
  if (e.is(scrollTo))
5548
- scrollPos = e.value;
5751
+ scrollTarget = new ScrollTarget(e.value);
5752
+ else if (e.is(centerOn))
5753
+ scrollTarget = new ScrollTarget(e.value, true);
5754
+ }
5549
5755
  }
5550
- this.viewState.update(update, scrollPos);
5756
+ this.viewState.update(update, scrollTarget);
5551
5757
  this.bidiCache = CachedOrder.update(this.bidiCache, update.changes);
5552
5758
  if (!update.empty) {
5553
5759
  this.updatePlugins(update);
@@ -5562,7 +5768,7 @@ class EditorView {
5562
5768
  finally {
5563
5769
  this.updateState = 0 /* Idle */;
5564
5770
  }
5565
- if (redrawn || scrollPos || this.viewState.mustEnforceCursorAssoc)
5771
+ if (redrawn || scrollTarget || this.viewState.mustEnforceCursorAssoc)
5566
5772
  this.requestMeasure();
5567
5773
  if (!update.empty)
5568
5774
  for (let listener of this.state.facet(updateListener))
@@ -5644,7 +5850,7 @@ class EditorView {
5644
5850
  this.updateState = 1 /* Measuring */;
5645
5851
  let oldViewport = this.viewport;
5646
5852
  let changed = this.viewState.measure(this.docView, i > 0);
5647
- if (!changed && !this.measureRequests.length && this.viewState.scrollTo == null)
5853
+ if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
5648
5854
  break;
5649
5855
  if (i > 5) {
5650
5856
  console.warn("Viewport failed to stabilize");
@@ -5686,9 +5892,9 @@ class EditorView {
5686
5892
  logException(this.state, e);
5687
5893
  }
5688
5894
  }
5689
- if (this.viewState.scrollTo) {
5690
- this.docView.scrollRangeIntoView(this.viewState.scrollTo);
5691
- this.viewState.scrollTo = null;
5895
+ if (this.viewState.scrollTarget) {
5896
+ this.docView.scrollIntoView(this.viewState.scrollTarget);
5897
+ this.viewState.scrollTarget = null;
5692
5898
  }
5693
5899
  if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
5694
5900
  break;
@@ -5720,6 +5926,7 @@ class EditorView {
5720
5926
  spellcheck: "false",
5721
5927
  autocorrect: "off",
5722
5928
  autocapitalize: "off",
5929
+ translate: "no",
5723
5930
  contenteditable: !this.state.facet(editable) ? "false" : contentEditablePlainTextSupported() ? "plaintext-only" : "true",
5724
5931
  class: "cm-content",
5725
5932
  style: `${browser.tabSize}: ${this.state.tabSize}`,
@@ -5910,12 +6117,9 @@ class EditorView {
5910
6117
  moveVertically(start, forward, distance) {
5911
6118
  return skipAtoms(this, start, moveVertically(this, start, forward, distance));
5912
6119
  }
5913
- /**
5914
- Scroll the given document position into view.
5915
- */
6120
+ // FIXME remove on next major version
5916
6121
  scrollPosIntoView(pos) {
5917
- this.viewState.scrollTo = state.EditorSelection.cursor(pos);
5918
- this.requestMeasure();
6122
+ this.dispatch({ effects: scrollTo.of(state.EditorSelection.cursor(pos)) });
5919
6123
  }
5920
6124
  /**
5921
6125
  Find the DOM parent node and offset (child offset if `node` is
@@ -6082,7 +6286,7 @@ class EditorView {
6082
6286
  target editors with a dark or light theme.
6083
6287
  */
6084
6288
  static baseTheme(spec) {
6085
- return state.Prec.fallback(styleModule.of(buildTheme("." + baseThemeID, spec, lightDarkIDs)));
6289
+ return state.Prec.lowest(styleModule.of(buildTheme("." + baseThemeID, spec, lightDarkIDs)));
6086
6290
  }
6087
6291
  }
6088
6292
  /**
@@ -6091,6 +6295,11 @@ transaction to make it scroll the given range into view.
6091
6295
  */
6092
6296
  EditorView.scrollTo = scrollTo;
6093
6297
  /**
6298
+ Effect that makes the editor scroll the given range to the
6299
+ center of the visible view.
6300
+ */
6301
+ EditorView.centerOn = centerOn;
6302
+ /**
6094
6303
  Facet to add a [style
6095
6304
  module](https://github.com/marijnh/style-mod#documentation) to
6096
6305
  an editor view. The view will ensure that the module is
@@ -6220,11 +6429,7 @@ class CachedOrder {
6220
6429
  }
6221
6430
  }
6222
6431
 
6223
- const currentPlatform = typeof navigator == "undefined" ? "key"
6224
- : /Mac/.test(navigator.platform) ? "mac"
6225
- : /Win/.test(navigator.platform) ? "win"
6226
- : /Linux|X11/.test(navigator.platform) ? "linux"
6227
- : "key";
6432
+ const currentPlatform = browser.mac ? "mac" : browser.windows ? "win" : browser.linux ? "linux" : "key";
6228
6433
  function normalizeKeyName(name, platform) {
6229
6434
  const parts = name.split(/-(?!$)/);
6230
6435
  let result = parts[parts.length - 1];
@@ -6523,7 +6728,7 @@ const themeSpec = {
6523
6728
  };
6524
6729
  if (CanHidePrimary)
6525
6730
  themeSpec[".cm-line"].caretColor = "transparent !important";
6526
- const hideNativeSelection = state.Prec.override(EditorView.theme(themeSpec));
6731
+ const hideNativeSelection = state.Prec.highest(EditorView.theme(themeSpec));
6527
6732
  function getBase(view) {
6528
6733
  let rect = view.scrollDOM.getBoundingClientRect();
6529
6734
  let left = view.textDirection == exports.Direction.LTR ? rect.left : rect.right - view.scrollDOM.clientWidth;
@@ -6730,7 +6935,7 @@ class MatchDecorator {
6730
6935
  }
6731
6936
 
6732
6937
  const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g";
6733
- const Specials = new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
6938
+ const Specials = new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
6734
6939
  const Names = {
6735
6940
  0: "null",
6736
6941
  7: "bell",
@@ -6745,6 +6950,8 @@ const Names = {
6745
6950
  8206: "left-to-right mark",
6746
6951
  8207: "right-to-left mark",
6747
6952
  8232: "line separator",
6953
+ 8237: "left-to-right override",
6954
+ 8238: "right-to-left override",
6748
6955
  8233: "paragraph separator",
6749
6956
  65279: "zero width no-break space",
6750
6957
  65532: "object replacement"
@@ -6911,7 +7118,7 @@ DOM class.
6911
7118
  function highlightActiveLine() {
6912
7119
  return activeLineHighlighter;
6913
7120
  }
6914
- const lineDeco = Decoration.line({ attributes: { class: "cm-activeLine" } });
7121
+ const lineDeco = Decoration.line({ class: "cm-activeLine" });
6915
7122
  const activeLineHighlighter = ViewPlugin.fromClass(class {
6916
7123
  constructor(view) {
6917
7124
  this.decorations = this.getDeco(view);