@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.js CHANGED
@@ -99,9 +99,9 @@ function windowRect(win) {
99
99
  top: 0, bottom: win.innerHeight };
100
100
  }
101
101
  const ScrollSpace = 5;
102
- function scrollRectIntoView(dom, rect, side) {
102
+ function scrollRectIntoView(dom, rect, side, center) {
103
103
  let doc = dom.ownerDocument, win = doc.defaultView;
104
- for (let cur = dom.parentNode; cur;) {
104
+ for (let cur = dom; cur;) {
105
105
  if (cur.nodeType == 1) { // Element
106
106
  let bounding, top = cur == doc.body;
107
107
  if (top) {
@@ -118,7 +118,20 @@ function scrollRectIntoView(dom, rect, side) {
118
118
  top: rect.top, bottom: rect.top + cur.clientHeight };
119
119
  }
120
120
  let moveX = 0, moveY = 0;
121
- if (rect.top < bounding.top) {
121
+ if (center) {
122
+ let rectHeight = rect.bottom - rect.top, boundingHeight = bounding.bottom - bounding.top;
123
+ let targetTop;
124
+ if (rectHeight <= boundingHeight)
125
+ targetTop = rect.top + rectHeight / 2 - boundingHeight / 2;
126
+ else if (side < 0)
127
+ targetTop = rect.top - ScrollSpace;
128
+ else
129
+ targetTop = rect.bottom + ScrollSpace - boundingHeight;
130
+ moveY = targetTop - bounding.top;
131
+ if (Math.abs(moveY) <= 1)
132
+ moveY = 0;
133
+ }
134
+ else if (rect.top < bounding.top) {
122
135
  moveY = -(bounding.top - rect.top + ScrollSpace);
123
136
  if (side > 0 && rect.bottom > bounding.bottom + moveY)
124
137
  moveY = rect.bottom - bounding.bottom + moveY + ScrollSpace;
@@ -160,6 +173,7 @@ function scrollRectIntoView(dom, rect, side) {
160
173
  if (top)
161
174
  break;
162
175
  cur = cur.assignedSlot || cur.parentNode;
176
+ center = false;
163
177
  }
164
178
  else if (cur.nodeType == 11) { // A shadow root
165
179
  cur = cur.host;
@@ -248,6 +262,14 @@ function contentEditablePlainTextSupported() {
248
262
  }
249
263
  return _plainTextSupported;
250
264
  }
265
+ function getRoot(node) {
266
+ while (node) {
267
+ node = node.assignedSlot || node.parentNode;
268
+ if (node && (node.nodeType == 9 || node.nodeType == 11 && node.host))
269
+ return node;
270
+ }
271
+ return null;
272
+ }
251
273
 
252
274
  class DOMPos {
253
275
  constructor(node, offset, precise = true) {
@@ -296,25 +318,30 @@ class ContentView {
296
318
  sync(track) {
297
319
  var _a;
298
320
  if (this.dirty & 2 /* Node */) {
299
- let parent = this.dom, pos = null;
321
+ let parent = this.dom;
322
+ let pos = parent.firstChild;
300
323
  for (let child of this.children) {
301
324
  if (child.dirty) {
302
- let next = pos ? pos.nextSibling : parent.firstChild;
303
- if (!child.dom && next && !((_a = ContentView.get(next)) === null || _a === void 0 ? void 0 : _a.parent))
304
- child.reuseDOM(next);
325
+ if (!child.dom && pos && !((_a = ContentView.get(pos)) === null || _a === void 0 ? void 0 : _a.parent))
326
+ child.reuseDOM(pos);
305
327
  child.sync(track);
306
328
  child.dirty = 0 /* Not */;
307
329
  }
308
- if (track && track.node == parent && pos != child.dom)
330
+ if (track && !track.written && track.node == parent && pos != child.dom)
309
331
  track.written = true;
310
- syncNodeInto(parent, pos, child.dom);
311
- pos = child.dom;
332
+ if (child.dom.parentNode == parent) {
333
+ while (pos && pos != child.dom)
334
+ pos = rm(pos);
335
+ pos = child.dom.nextSibling;
336
+ }
337
+ else {
338
+ parent.insertBefore(child.dom, pos);
339
+ }
312
340
  }
313
- let next = pos ? pos.nextSibling : parent.firstChild;
314
- if (next && track && track.node == parent)
341
+ if (pos && track && track.node == parent)
315
342
  track.written = true;
316
- while (next)
317
- next = rm(next);
343
+ while (pos)
344
+ pos = rm(pos);
318
345
  }
319
346
  else if (this.dirty & 1 /* Child */) {
320
347
  for (let child of this.children)
@@ -445,6 +472,7 @@ class ContentView {
445
472
  (this.breakAfter ? "#" : "");
446
473
  }
447
474
  static get(node) { return node.cmView; }
475
+ get isEditable() { return true; }
448
476
  }
449
477
  ContentView.prototype.breakAfter = 0;
450
478
  // Remove a DOM node and return its next sibling.
@@ -453,14 +481,6 @@ function rm(dom) {
453
481
  dom.parentNode.removeChild(dom);
454
482
  return next;
455
483
  }
456
- function syncNodeInto(parent, after, dom) {
457
- let next = after ? after.nextSibling : parent.firstChild;
458
- if (dom.parentNode == parent)
459
- while (next != dom)
460
- next = rm(next);
461
- else
462
- parent.insertBefore(dom, next);
463
- }
464
484
  class ChildCursor {
465
485
  constructor(children, pos, i) {
466
486
  this.children = children;
@@ -492,15 +512,18 @@ const gecko = !ie && /*@__PURE__*//gecko\/(\d+)/i.test(nav.userAgent);
492
512
  const chrome = !ie && /*@__PURE__*//Chrome\/(\d+)/.exec(nav.userAgent);
493
513
  const webkit = "webkitFontSmoothing" in doc.documentElement.style;
494
514
  const safari = !ie && /*@__PURE__*//Apple Computer/.test(nav.vendor);
515
+ const ios = safari && (/*@__PURE__*//Mobile\/\w+/.test(nav.userAgent) || nav.maxTouchPoints > 2);
495
516
  var browser = {
496
- mac: /*@__PURE__*//Mac/.test(nav.platform),
517
+ mac: ios || /*@__PURE__*//Mac/.test(nav.platform),
518
+ windows: /*@__PURE__*//Win/.test(nav.platform),
519
+ linux: /*@__PURE__*//Linux|X11/.test(nav.platform),
497
520
  ie,
498
521
  ie_version: ie_upto10 ? doc.documentMode || 6 : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : 0,
499
522
  gecko,
500
523
  gecko_version: gecko ? +(/*@__PURE__*//Firefox\/(\d+)/.exec(nav.userAgent) || [0, 0])[1] : 0,
501
524
  chrome: !!chrome,
502
525
  chrome_version: chrome ? +chrome[1] : 0,
503
- ios: safari && (/*@__PURE__*//Mobile\/\w+/.test(nav.userAgent) || nav.maxTouchPoints > 2),
526
+ ios,
504
527
  android: /*@__PURE__*//Android\b/.test(nav.userAgent),
505
528
  webkit,
506
529
  safari,
@@ -722,6 +745,7 @@ class WidgetView extends InlineView {
722
745
  }
723
746
  return (pos == 0 && side > 0 || pos == this.length && side <= 0) ? rect : flattenRect(rect, pos == 0);
724
747
  }
748
+ get isEditable() { return false; }
725
749
  }
726
750
  class CompositionView extends WidgetView {
727
751
  domAtPos(pos) { return new DOMPos(this.widget.text, pos); }
@@ -733,6 +757,38 @@ class CompositionView extends WidgetView {
733
757
  ignoreMutation() { return false; }
734
758
  get overrideDOMText() { return null; }
735
759
  coordsAt(pos, side) { return textCoords(this.widget.text, pos, side); }
760
+ get isEditable() { return true; }
761
+ }
762
+ // These are drawn around uneditable widgets to avoid a number of
763
+ // browser bugs that show up when the cursor is directly next to
764
+ // uneditable inline content.
765
+ class WidgetBufferView extends InlineView {
766
+ constructor(side) {
767
+ super();
768
+ this.side = side;
769
+ }
770
+ get length() { return 0; }
771
+ merge() { return false; }
772
+ become(other) {
773
+ return other instanceof WidgetBufferView && other.side == this.side;
774
+ }
775
+ slice() { return new WidgetBufferView(this.side); }
776
+ sync() {
777
+ if (!this.dom)
778
+ this.setDOM(document.createTextNode("\u200b"));
779
+ else if (this.dirty && this.dom.nodeValue != "\u200b")
780
+ this.dom.nodeValue = "\u200b";
781
+ }
782
+ getSide() { return this.side; }
783
+ domAtPos(pos) { return DOMPos.before(this.dom); }
784
+ domBoundsAround() { return null; }
785
+ coordsAt(pos) {
786
+ let rects = clientRectsFor(this.dom);
787
+ return rects[rects.length - 1];
788
+ }
789
+ get overrideDOMText() {
790
+ return Text.of([this.dom.nodeValue.replace(/\u200b/g, "")]);
791
+ }
736
792
  }
737
793
  function mergeInlineChildren(parent, from, to, elts, openStart, openEnd) {
738
794
  let cur = parent.childCursor();
@@ -1227,14 +1283,17 @@ class LineView extends ContentView {
1227
1283
  }
1228
1284
  // Only called when building a line view in ContentBuilder
1229
1285
  addLineDeco(deco) {
1230
- let attrs = deco.spec.attributes;
1286
+ let attrs = deco.spec.attributes, cls = deco.spec.class;
1231
1287
  if (attrs)
1232
1288
  this.attrs = combineAttrs(attrs, this.attrs || {});
1289
+ if (cls)
1290
+ this.attrs = combineAttrs(attrs, { class: cls });
1233
1291
  }
1234
1292
  domAtPos(pos) {
1235
1293
  return inlineDOMAtPos(this.dom, this.children, pos);
1236
1294
  }
1237
1295
  sync(track) {
1296
+ var _a;
1238
1297
  if (!this.dom || (this.dirty & 4 /* Attrs */)) {
1239
1298
  this.setDOM(document.createElement("div"));
1240
1299
  this.dom.className = "cm-line";
@@ -1250,7 +1309,7 @@ class LineView extends ContentView {
1250
1309
  while (last && ContentView.get(last) instanceof MarkView)
1251
1310
  last = last.lastChild;
1252
1311
  if (!last ||
1253
- last.nodeName != "BR" && ContentView.get(last) instanceof WidgetView &&
1312
+ last.nodeName != "BR" && ((_a = ContentView.get(last)) === null || _a === void 0 ? void 0 : _a.isEditable) == false &&
1254
1313
  (!browser.ios || !this.children.some(ch => ch instanceof TextView))) {
1255
1314
  let hack = document.createElement("BR");
1256
1315
  hack.cmIgnore = true;
@@ -1349,6 +1408,9 @@ class ContentBuilder {
1349
1408
  this.content = [];
1350
1409
  this.curLine = null;
1351
1410
  this.breakAtStart = 0;
1411
+ this.pendingBuffer = 0 /* No */;
1412
+ // Set to false directly after a widget that covers the position after it
1413
+ this.atCursorPos = true;
1352
1414
  this.openStart = -1;
1353
1415
  this.openEnd = -1;
1354
1416
  this.text = "";
@@ -1363,23 +1425,31 @@ class ContentBuilder {
1363
1425
  return !last.breakAfter && !(last instanceof BlockWidgetView && last.type == BlockType.WidgetBefore);
1364
1426
  }
1365
1427
  getLine() {
1366
- if (!this.curLine)
1428
+ if (!this.curLine) {
1367
1429
  this.content.push(this.curLine = new LineView);
1430
+ this.atCursorPos = true;
1431
+ }
1368
1432
  return this.curLine;
1369
1433
  }
1370
- addWidget(view) {
1434
+ flushBuffer(active) {
1435
+ if (this.pendingBuffer) {
1436
+ this.curLine.append(wrapMarks(new WidgetBufferView(-1), active), active.length);
1437
+ this.pendingBuffer = 0 /* No */;
1438
+ }
1439
+ }
1440
+ addBlockWidget(view) {
1441
+ this.flushBuffer([]);
1371
1442
  this.curLine = null;
1372
1443
  this.content.push(view);
1373
1444
  }
1374
- finish() {
1445
+ finish(openEnd) {
1446
+ if (!openEnd)
1447
+ this.flushBuffer([]);
1448
+ else
1449
+ this.pendingBuffer = 0 /* No */;
1375
1450
  if (!this.posCovered())
1376
1451
  this.getLine();
1377
1452
  }
1378
- wrapMarks(view, active) {
1379
- for (let mark of active)
1380
- view = new MarkView(mark, [view], view.length);
1381
- return view;
1382
- }
1383
1453
  buildText(length, active, openStart) {
1384
1454
  while (length > 0) {
1385
1455
  if (this.textOff == this.text.length) {
@@ -1394,6 +1464,7 @@ class ContentBuilder {
1394
1464
  this.content[this.content.length - 1].breakAfter = 1;
1395
1465
  else
1396
1466
  this.breakAtStart = 1;
1467
+ this.flushBuffer([]);
1397
1468
  this.curLine = null;
1398
1469
  length--;
1399
1470
  continue;
@@ -1404,7 +1475,9 @@ class ContentBuilder {
1404
1475
  }
1405
1476
  }
1406
1477
  let take = Math.min(this.text.length - this.textOff, length, 512 /* Chunk */);
1407
- this.getLine().append(this.wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
1478
+ this.flushBuffer(active);
1479
+ this.getLine().append(wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
1480
+ this.atCursorPos = true;
1408
1481
  this.textOff += take;
1409
1482
  length -= take;
1410
1483
  openStart = 0;
@@ -1423,11 +1496,23 @@ class ContentBuilder {
1423
1496
  let { type } = deco;
1424
1497
  if (type == BlockType.WidgetAfter && !this.posCovered())
1425
1498
  this.getLine();
1426
- this.addWidget(new BlockWidgetView(deco.widget || new NullWidget("div"), len, type));
1499
+ this.addBlockWidget(new BlockWidgetView(deco.widget || new NullWidget("div"), len, type));
1427
1500
  }
1428
1501
  else {
1429
- let widget = this.wrapMarks(WidgetView.create(deco.widget || new NullWidget("span"), len, deco.startSide), active);
1430
- this.getLine().append(widget, openStart);
1502
+ let view = WidgetView.create(deco.widget || new NullWidget("span"), len, deco.startSide);
1503
+ let cursorBefore = this.atCursorPos && !view.isEditable && openStart <= active.length && (from < to || deco.startSide > 0);
1504
+ let cursorAfter = !view.isEditable && (from < to || deco.startSide <= 0);
1505
+ let line = this.getLine();
1506
+ if (this.pendingBuffer == 2 /* IfCursor */ && !cursorBefore)
1507
+ this.pendingBuffer = 0 /* No */;
1508
+ this.flushBuffer(active);
1509
+ if (cursorBefore) {
1510
+ line.append(wrapMarks(new WidgetBufferView(1), active), openStart);
1511
+ openStart = active.length + Math.max(0, openStart - active.length);
1512
+ }
1513
+ line.append(wrapMarks(view, active), openStart);
1514
+ this.atCursorPos = cursorAfter;
1515
+ this.pendingBuffer = !cursorAfter ? 0 /* No */ : from < to ? 1 /* Yes */ : 2 /* IfCursor */;
1431
1516
  }
1432
1517
  }
1433
1518
  else if (this.doc.lineAt(this.pos).from == this.pos) { // Line decoration
@@ -1453,10 +1538,15 @@ class ContentBuilder {
1453
1538
  builder.openEnd = RangeSet.spans(decorations, from, to, builder);
1454
1539
  if (builder.openStart < 0)
1455
1540
  builder.openStart = builder.openEnd;
1456
- builder.finish();
1541
+ builder.finish(builder.openEnd);
1457
1542
  return builder;
1458
1543
  }
1459
1544
  }
1545
+ function wrapMarks(view, active) {
1546
+ for (let mark of active)
1547
+ view = new MarkView(mark, [view], view.length);
1548
+ return view;
1549
+ }
1460
1550
  class NullWidget extends WidgetType {
1461
1551
  constructor(tag) {
1462
1552
  super();
@@ -1477,6 +1567,9 @@ const inputHandler = /*@__PURE__*/Facet.define();
1477
1567
  const scrollTo = /*@__PURE__*/StateEffect.define({
1478
1568
  map: (range, changes) => range.map(changes)
1479
1569
  });
1570
+ const centerOn = /*@__PURE__*/StateEffect.define({
1571
+ map: (range, changes) => range.map(changes)
1572
+ });
1480
1573
  /**
1481
1574
  Log or report an unhandled exception in client code. Should
1482
1575
  probably only be used by extension code that allows client code to
@@ -1903,6 +1996,14 @@ class DocView extends ContentView {
1903
1996
  return true;
1904
1997
  }
1905
1998
  }
1999
+ reset(sel) {
2000
+ if (this.dirty) {
2001
+ this.view.observer.ignore(() => this.view.docView.sync());
2002
+ this.dirty = 0 /* Not */;
2003
+ }
2004
+ if (sel)
2005
+ this.updateSelection();
2006
+ }
1906
2007
  // Used both by update and checkLayout do perform the actual DOM
1907
2008
  // update
1908
2009
  updateInner(changes, deco, oldLength, forceSelection = false, pointerSel = false) {
@@ -1927,6 +2028,12 @@ class DocView extends ContentView {
1927
2028
  this.updateSelection(forceSelection, pointerSel);
1928
2029
  this.dom.style.height = "";
1929
2030
  });
2031
+ let gaps = [];
2032
+ if (this.view.viewport.from || this.view.viewport.to < this.view.state.doc.length)
2033
+ for (let child of this.children)
2034
+ if (child instanceof BlockWidgetView && child.widget instanceof BlockGapWidget)
2035
+ gaps.push(child.dom);
2036
+ observer.updateGaps(gaps);
1930
2037
  }
1931
2038
  updateChildren(changes, deco, oldLength) {
1932
2039
  let cursor = this.childCursor(oldLength);
@@ -2026,6 +2133,14 @@ class DocView extends ContentView {
2026
2133
  !isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) ||
2027
2134
  !isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) {
2028
2135
  this.view.observer.ignore(() => {
2136
+ // Chrome Android will hide the virtual keyboard when tapping
2137
+ // inside an uneditable node, and not bring it back when we
2138
+ // move the cursor to its proper position. This tries to
2139
+ // restore the keyboard by cycling focus.
2140
+ if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) && inUneditable(domSel.focusNode, this.dom)) {
2141
+ this.dom.blur();
2142
+ this.dom.focus({ preventScroll: true });
2143
+ }
2029
2144
  let rawSel = getSelection(this.root);
2030
2145
  if (main.empty) {
2031
2146
  // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=1612076
@@ -2199,7 +2314,7 @@ class DocView extends ContentView {
2199
2314
  this.view.viewState.lineGapDeco
2200
2315
  ];
2201
2316
  }
2202
- scrollRangeIntoView(range) {
2317
+ scrollIntoView({ range, center }) {
2203
2318
  let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
2204
2319
  if (!rect)
2205
2320
  return;
@@ -2219,10 +2334,10 @@ class DocView extends ContentView {
2219
2334
  if (bottom != null)
2220
2335
  mBottom = Math.max(mBottom, bottom);
2221
2336
  }
2222
- scrollRectIntoView(this.dom, {
2337
+ scrollRectIntoView(this.view.scrollDOM, {
2223
2338
  left: rect.left - mLeft, top: rect.top - mTop,
2224
2339
  right: rect.right + mRight, bottom: rect.bottom + mBottom
2225
- }, range.head < range.anchor ? -1 : 1);
2340
+ }, range.head < range.anchor ? -1 : 1, center);
2226
2341
  }
2227
2342
  }
2228
2343
  function betweenUneditable(pos) {
@@ -2333,6 +2448,14 @@ function findChangedDeco(a, b, diff) {
2333
2448
  RangeSet.compare(a, b, diff, comp);
2334
2449
  return comp.changes;
2335
2450
  }
2451
+ function inUneditable(node, inside) {
2452
+ for (let cur = node; cur && cur != inside; cur = cur.assignedSlot || cur.parentNode) {
2453
+ if (cur.nodeType == 1 && cur.contentEditable == 'false') {
2454
+ return true;
2455
+ }
2456
+ }
2457
+ return false;
2458
+ }
2336
2459
 
2337
2460
  /**
2338
2461
  Used to indicate [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
@@ -2778,6 +2901,7 @@ function domPosInText(node, x, y) {
2778
2901
  return { node, offset: closestOffset > -1 ? closestOffset : generalSide > 0 ? node.nodeValue.length : 0 };
2779
2902
  }
2780
2903
  function posAtCoords(view, { x, y }, precise, bias = -1) {
2904
+ var _a;
2781
2905
  let content = view.contentDOM.getBoundingClientRect(), block;
2782
2906
  let halfLine = view.defaultLineHeight / 2;
2783
2907
  for (let bounced = false;;) {
@@ -2808,7 +2932,7 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
2808
2932
  // There's visible editor content under the point, so we can try
2809
2933
  // using caret(Position|Range)FromPoint as a shortcut
2810
2934
  let node, offset = -1;
2811
- if (element && view.contentDOM.contains(element) && !(view.docView.nearest(element) instanceof WidgetView)) {
2935
+ if (element && view.contentDOM.contains(element) && ((_a = view.docView.nearest(element)) === null || _a === void 0 ? void 0 : _a.isEditable) != false) {
2812
2936
  if (doc.caretPositionFromPoint) {
2813
2937
  let pos = doc.caretPositionFromPoint(x, y);
2814
2938
  if (pos)
@@ -2949,7 +3073,23 @@ class InputState {
2949
3073
  constructor(view) {
2950
3074
  this.lastKeyCode = 0;
2951
3075
  this.lastKeyTime = 0;
2952
- this.pendingIOSKey = null;
3076
+ // On iOS, some keys need to have their default behavior happen
3077
+ // (after which we retroactively handle them and reset the DOM) to
3078
+ // avoid messing up the virtual keyboard state.
3079
+ //
3080
+ // On Chrome Android, backspace near widgets is just completely
3081
+ // broken, and there are no key events, so we need to handle the
3082
+ // beforeinput event. Deleting stuff will often create a flurry of
3083
+ // events, and interrupting it before it is done just makes
3084
+ // subsequent events even more broken, so again, we hold off doing
3085
+ // anything until the browser is finished with whatever it is trying
3086
+ // to do.
3087
+ //
3088
+ // setPendingKey sets this, causing the DOM observer to pause for a
3089
+ // bit, and setting an animation frame (which seems the most
3090
+ // reliable way to detect 'browser is done flailing') to fire a fake
3091
+ // key event and re-sync the view again.
3092
+ this.pendingKey = undefined;
2953
3093
  this.lastSelectionOrigin = null;
2954
3094
  this.lastSelectionTime = 0;
2955
3095
  this.lastEscPress = 0;
@@ -3058,20 +3198,27 @@ class InputState {
3058
3198
  // state. So we let it go through, and then, in
3059
3199
  // applyDOMChange, notify key handlers of it and reset to
3060
3200
  // the state they produce.
3061
- if (browser.ios && (event.keyCode == 13 || event.keyCode == 8) &&
3201
+ let pending;
3202
+ if (browser.ios && (pending = PendingKeys.find(key => key.keyCode == event.keyCode)) &&
3062
3203
  !(event.ctrlKey || event.altKey || event.metaKey) && !event.synthetic) {
3063
- this.pendingIOSKey = event.keyCode == 13 ? "enter" : "backspace";
3064
- setTimeout(() => this.flushIOSKey(view), 250);
3204
+ this.setPendingKey(view, pending);
3065
3205
  return true;
3066
3206
  }
3067
3207
  return false;
3068
3208
  }
3069
- flushIOSKey(view) {
3070
- if (!this.pendingIOSKey)
3071
- return false;
3072
- let dom = view.contentDOM, key = this.pendingIOSKey;
3073
- this.pendingIOSKey = null;
3074
- return key == "enter" ? dispatchKey(dom, "Enter", 13) : dispatchKey(dom, "Backspace", 8);
3209
+ setPendingKey(view, pending) {
3210
+ this.pendingKey = pending;
3211
+ requestAnimationFrame(() => {
3212
+ if (!this.pendingKey)
3213
+ return false;
3214
+ let key = this.pendingKey;
3215
+ this.pendingKey = undefined;
3216
+ view.observer.processRecords();
3217
+ let startState = view.state;
3218
+ dispatchKey(view.contentDOM, key.key, key.keyCode);
3219
+ if (view.state == startState)
3220
+ view.docView.reset(true);
3221
+ });
3075
3222
  }
3076
3223
  ignoreDuringComposition(event) {
3077
3224
  if (!/^key/.test(event.type))
@@ -3118,6 +3265,11 @@ class InputState {
3118
3265
  this.mouseSelection.destroy();
3119
3266
  }
3120
3267
  }
3268
+ const PendingKeys = [
3269
+ { key: "Backspace", keyCode: 8, inputType: "deleteContentBackward" },
3270
+ { key: "Enter", keyCode: 13, inputType: "insertParagraph" },
3271
+ { key: "Delete", keyCode: 46, inputType: "deleteContentForward" }
3272
+ ];
3121
3273
  // Key codes for modifier keys
3122
3274
  const modifierCodes = [16, 17, 18, 20, 91, 92, 224, 225];
3123
3275
  class MouseSelection {
@@ -3563,6 +3715,33 @@ handlers.compositionend = view => {
3563
3715
  handlers.contextmenu = view => {
3564
3716
  view.inputState.lastContextMenu = Date.now();
3565
3717
  };
3718
+ handlers.beforeinput = (view, event) => {
3719
+ var _a;
3720
+ // Because Chrome Android doesn't fire useful key events, use
3721
+ // beforeinput to detect backspace (and possibly enter and delete,
3722
+ // but those usually don't even seem to fire beforeinput events at
3723
+ // the moment) and fake a key event for it.
3724
+ //
3725
+ // (preventDefault on beforeinput, though supported in the spec,
3726
+ // seems to do nothing at all on Chrome).
3727
+ let pending;
3728
+ if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
3729
+ view.inputState.setPendingKey(view, pending);
3730
+ if (pending.key == "Backspace" || pending.key == "Delete") {
3731
+ let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
3732
+ setTimeout(() => {
3733
+ var _a;
3734
+ // Backspacing near uneditable nodes on Chrome Android sometimes
3735
+ // closes the virtual keyboard. This tries to crudely detect
3736
+ // that and refocus to get it back.
3737
+ if ((((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0) > startViewHeight + 10 && view.hasFocus) {
3738
+ view.contentDOM.blur();
3739
+ view.focus();
3740
+ }
3741
+ }, 100);
3742
+ }
3743
+ }
3744
+ };
3566
3745
 
3567
3746
  const wrappingWhiteSpace = ["pre-wrap", "normal", "pre-line"];
3568
3747
  class HeightOracle {
@@ -4277,6 +4456,15 @@ class LineGapWidget extends WidgetType {
4277
4456
  }
4278
4457
  get estimatedHeight() { return this.vertical ? this.size : -1; }
4279
4458
  }
4459
+ class ScrollTarget {
4460
+ constructor(range, center = false) {
4461
+ this.range = range;
4462
+ this.center = center;
4463
+ }
4464
+ map(changes) {
4465
+ return changes.empty ? this : new ScrollTarget(this.range.map(changes), this.center);
4466
+ }
4467
+ }
4280
4468
  class ViewState {
4281
4469
  constructor(state) {
4282
4470
  this.state = state;
@@ -4289,7 +4477,7 @@ class ViewState {
4289
4477
  this.heightOracle = new HeightOracle;
4290
4478
  // See VP.MaxDOMHeight
4291
4479
  this.scaler = IdScaler;
4292
- this.scrollTo = null;
4480
+ this.scrollTarget = null;
4293
4481
  // Briefly set to true when printing, to disable viewport limiting
4294
4482
  this.printing = false;
4295
4483
  this.visibleRanges = [];
@@ -4322,7 +4510,7 @@ class ViewState {
4322
4510
  this.scaler = this.heightMap.height <= 7000000 /* MaxDOMHeight */ ? IdScaler :
4323
4511
  new BigScaler(this.heightOracle.doc, this.heightMap, this.viewports);
4324
4512
  }
4325
- update(update, scrollTo = null) {
4513
+ update(update, scrollTarget = null) {
4326
4514
  let prev = this.state;
4327
4515
  this.state = update.state;
4328
4516
  let newDeco = this.state.facet(decorations);
@@ -4333,27 +4521,33 @@ class ViewState {
4333
4521
  if (this.heightMap.height != prevHeight)
4334
4522
  update.flags |= 2 /* Height */;
4335
4523
  let viewport = heightChanges.length ? this.mapViewport(this.viewport, update.changes) : this.viewport;
4336
- if (scrollTo && (scrollTo.head < viewport.from || scrollTo.head > viewport.to) || !this.viewportIsAppropriate(viewport))
4337
- viewport = this.getViewport(0, scrollTo);
4524
+ if (scrollTarget && (scrollTarget.range.head < viewport.from || scrollTarget.range.head > viewport.to) ||
4525
+ !this.viewportIsAppropriate(viewport))
4526
+ viewport = this.getViewport(0, scrollTarget);
4338
4527
  this.viewport = viewport;
4339
4528
  this.updateForViewport();
4340
- if (this.lineGaps.length || this.viewport.to - this.viewport.from > 15000 /* MinViewPort */)
4529
+ if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4341
4530
  this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
4342
4531
  update.flags |= this.computeVisibleRanges();
4343
- if (scrollTo)
4344
- this.scrollTo = scrollTo;
4532
+ if (scrollTarget)
4533
+ this.scrollTarget = scrollTarget;
4345
4534
  if (!this.mustEnforceCursorAssoc && update.selectionSet && update.view.lineWrapping &&
4346
4535
  update.state.selection.main.empty && update.state.selection.main.assoc)
4347
4536
  this.mustEnforceCursorAssoc = true;
4348
4537
  }
4349
4538
  measure(docView, repeated) {
4350
4539
  let dom = docView.dom, whiteSpace = "", direction = Direction.LTR;
4540
+ let result = 0;
4351
4541
  if (!repeated) {
4352
4542
  // Vertical padding
4353
4543
  let style = window.getComputedStyle(dom);
4354
4544
  whiteSpace = style.whiteSpace, direction = (style.direction == "rtl" ? Direction.RTL : Direction.LTR);
4355
- this.paddingTop = parseInt(style.paddingTop) || 0;
4356
- this.paddingBottom = parseInt(style.paddingBottom) || 0;
4545
+ let paddingTop = parseInt(style.paddingTop) || 0, paddingBottom = parseInt(style.paddingBottom) || 0;
4546
+ if (this.paddingTop != paddingTop || this.paddingBottom != paddingBottom) {
4547
+ result |= 8 /* Geometry */;
4548
+ this.paddingTop = paddingTop;
4549
+ this.paddingBottom = paddingBottom;
4550
+ }
4357
4551
  }
4358
4552
  // Pixel viewport
4359
4553
  let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 } : visiblePixelRange(dom, this.paddingTop);
@@ -4363,7 +4557,7 @@ class ViewState {
4363
4557
  if (!this.inView)
4364
4558
  return 0;
4365
4559
  let lineHeights = docView.measureVisibleLineHeights();
4366
- let refresh = false, bias = 0, result = 0, oracle = this.heightOracle;
4560
+ let refresh = false, bias = 0, oracle = this.heightOracle;
4367
4561
  if (!repeated) {
4368
4562
  let contentWidth = docView.dom.clientWidth;
4369
4563
  if (oracle.mustRefresh(lineHeights, whiteSpace, direction) ||
@@ -4389,10 +4583,10 @@ class ViewState {
4389
4583
  if (oracle.heightChanged)
4390
4584
  result |= 2 /* Height */;
4391
4585
  if (!this.viewportIsAppropriate(this.viewport, bias) ||
4392
- this.scrollTo && (this.scrollTo.head < this.viewport.from || this.scrollTo.head > this.viewport.to))
4393
- this.viewport = this.getViewport(bias, this.scrollTo);
4586
+ this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to))
4587
+ this.viewport = this.getViewport(bias, this.scrollTarget);
4394
4588
  this.updateForViewport();
4395
- if (this.lineGaps.length || this.viewport.to - this.viewport.from > 15000 /* MinViewPort */)
4589
+ if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4396
4590
  this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
4397
4591
  result |= this.computeVisibleRanges();
4398
4592
  if (this.mustEnforceCursorAssoc) {
@@ -4407,22 +4601,25 @@ class ViewState {
4407
4601
  }
4408
4602
  get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top, 0); }
4409
4603
  get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom, 0); }
4410
- getViewport(bias, scrollTo) {
4604
+ getViewport(bias, scrollTarget) {
4411
4605
  // This will divide VP.Margin between the top and the
4412
4606
  // bottom, depending on the bias (the change in viewport position
4413
4607
  // since the last update). It'll hold a number between 0 and 1
4414
4608
  let marginTop = 0.5 - Math.max(-0.5, Math.min(0.5, bias / 1000 /* Margin */ / 2));
4415
4609
  let map = this.heightMap, doc = this.state.doc, { visibleTop, visibleBottom } = this;
4416
4610
  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);
4417
- // If scrollTo is given, make sure the viewport includes that position
4418
- if (scrollTo) {
4419
- if (scrollTo.head < viewport.from) {
4420
- let { top: newTop } = map.lineAt(scrollTo.head, QueryType.ByPos, doc, 0, 0);
4421
- 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);
4422
- }
4423
- else if (scrollTo.head > viewport.to) {
4424
- let { bottom: newBottom } = map.lineAt(scrollTo.head, QueryType.ByPos, doc, 0, 0);
4425
- 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);
4611
+ // If scrollTarget is given, make sure the viewport includes that position
4612
+ if (scrollTarget) {
4613
+ let { head } = scrollTarget.range, viewHeight = visibleBottom - visibleTop;
4614
+ if (head < viewport.from || head > viewport.to) {
4615
+ let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4616
+ if (scrollTarget.center)
4617
+ topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
4618
+ else if (head < viewport.from)
4619
+ topPos = block.top;
4620
+ else
4621
+ topPos = block.bottom - viewHeight;
4622
+ 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);
4426
4623
  }
4427
4624
  }
4428
4625
  return viewport;
@@ -4464,52 +4661,50 @@ class ViewState {
4464
4661
  if (this.heightOracle.direction != Direction.LTR)
4465
4662
  return gaps;
4466
4663
  this.heightMap.forEachLine(this.viewport.from, this.viewport.to, this.state.doc, 0, 0, line => {
4467
- if (line.length < 10000 /* Margin */)
4664
+ if (line.length < 4000 /* DoubleMargin */)
4468
4665
  return;
4469
4666
  let structure = lineStructure(line.from, line.to, this.state);
4470
- if (structure.total < 10000 /* Margin */)
4667
+ if (structure.total < 4000 /* DoubleMargin */)
4471
4668
  return;
4472
4669
  let viewFrom, viewTo;
4473
4670
  if (this.heightOracle.lineWrapping) {
4474
- if (line.from != this.viewport.from)
4475
- viewFrom = line.from;
4476
- else
4477
- viewFrom = findPosition(structure, (this.visibleTop - line.top) / line.height);
4478
- if (line.to != this.viewport.to)
4479
- viewTo = line.to;
4480
- else
4481
- viewTo = findPosition(structure, (this.visibleBottom - line.top) / line.height);
4671
+ let marginHeight = (2000 /* Margin */ / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
4672
+ viewFrom = findPosition(structure, (this.visibleTop - line.top - marginHeight) / line.height);
4673
+ viewTo = findPosition(structure, (this.visibleBottom - line.top + marginHeight) / line.height);
4482
4674
  }
4483
4675
  else {
4484
4676
  let totalWidth = structure.total * this.heightOracle.charWidth;
4485
- viewFrom = findPosition(structure, this.pixelViewport.left / totalWidth);
4486
- viewTo = findPosition(structure, this.pixelViewport.right / totalWidth);
4677
+ let marginWidth = 2000 /* Margin */ * this.heightOracle.charWidth;
4678
+ viewFrom = findPosition(structure, (this.pixelViewport.left - marginWidth) / totalWidth);
4679
+ viewTo = findPosition(structure, (this.pixelViewport.right + marginWidth) / totalWidth);
4487
4680
  }
4681
+ let outside = [];
4682
+ if (viewFrom > line.from)
4683
+ outside.push({ from: line.from, to: viewFrom });
4684
+ if (viewTo < line.to)
4685
+ outside.push({ from: viewTo, to: line.to });
4488
4686
  let sel = this.state.selection.main;
4489
- // Make sure the gap doesn't cover a selection end
4490
- if (sel.from <= viewFrom && sel.to >= line.from)
4491
- viewFrom = sel.from;
4492
- if (sel.from <= line.to && sel.to >= viewTo)
4493
- viewTo = sel.to;
4494
- let gapTo = viewFrom - 10000 /* Margin */, gapFrom = viewTo + 10000 /* Margin */;
4495
- if (gapTo > line.from + 5000 /* HalfMargin */)
4496
- gaps.push(find(current, gap => gap.from == line.from && gap.to > gapTo - 5000 /* HalfMargin */ && gap.to < gapTo + 5000 /* HalfMargin */) ||
4497
- new LineGap(line.from, gapTo, this.gapSize(line, gapTo, true, structure)));
4498
- if (gapFrom < line.to - 5000 /* HalfMargin */)
4499
- gaps.push(find(current, gap => gap.to == line.to && gap.from > gapFrom - 5000 /* HalfMargin */ &&
4500
- gap.from < gapFrom + 5000 /* HalfMargin */) ||
4501
- new LineGap(gapFrom, line.to, this.gapSize(line, gapFrom, false, structure)));
4687
+ // Make sure the gaps don't cover a selection end
4688
+ if (sel.from >= line.from && sel.from <= line.to)
4689
+ cutRange(outside, sel.from - 10 /* SelectionMargin */, sel.from + 10 /* SelectionMargin */);
4690
+ if (!sel.empty && sel.to >= line.from && sel.to <= line.to)
4691
+ cutRange(outside, sel.to - 10 /* SelectionMargin */, sel.to + 10 /* SelectionMargin */);
4692
+ for (let { from, to } of outside)
4693
+ if (to - from > 1000 /* HalfMargin */) {
4694
+ gaps.push(find(current, gap => gap.from >= line.from && gap.to <= line.to &&
4695
+ Math.abs(gap.from - from) < 1000 /* HalfMargin */ && Math.abs(gap.to - to) < 1000 /* HalfMargin */) ||
4696
+ new LineGap(from, to, this.gapSize(line, from, to, structure)));
4697
+ }
4502
4698
  });
4503
4699
  return gaps;
4504
4700
  }
4505
- gapSize(line, pos, start, structure) {
4701
+ gapSize(line, from, to, structure) {
4702
+ let fraction = findFraction(structure, to) - findFraction(structure, from);
4506
4703
  if (this.heightOracle.lineWrapping) {
4507
- let height = line.height * findFraction(structure, pos);
4508
- return start ? height : line.height - height;
4704
+ return line.height * fraction;
4509
4705
  }
4510
4706
  else {
4511
- let ratio = findFraction(structure, pos);
4512
- return structure.total * this.heightOracle.charWidth * (start ? ratio : 1 - ratio);
4707
+ return structure.total * this.heightOracle.charWidth * fraction;
4513
4708
  }
4514
4709
  }
4515
4710
  updateLineGaps(gaps) {
@@ -4603,6 +4798,20 @@ function findFraction(structure, pos) {
4603
4798
  }
4604
4799
  return counted / structure.total;
4605
4800
  }
4801
+ function cutRange(ranges, from, to) {
4802
+ for (let i = 0; i < ranges.length; i++) {
4803
+ let r = ranges[i];
4804
+ if (r.from < to && r.to > from) {
4805
+ let pieces = [];
4806
+ if (r.from < from)
4807
+ pieces.push({ from: r.from, to: from });
4808
+ if (r.to > to)
4809
+ pieces.push({ from: to, to: r.to });
4810
+ ranges.splice(i, 1, ...pieces);
4811
+ i += pieces.length - 1;
4812
+ }
4813
+ }
4814
+ }
4606
4815
  function find(array, f) {
4607
4816
  for (let val of array)
4608
4817
  if (f(val))
@@ -4685,7 +4894,7 @@ function buildTheme(main, spec, scopes) {
4685
4894
  });
4686
4895
  }
4687
4896
  const baseTheme = /*@__PURE__*/buildTheme("." + baseThemeID, {
4688
- "&": {
4897
+ "&.cm-editor": {
4689
4898
  position: "relative !important",
4690
4899
  boxSizing: "border-box",
4691
4900
  "&.cm-focused": {
@@ -4852,6 +5061,8 @@ class DOMObserver {
4852
5061
  this.scrollTargets = [];
4853
5062
  this.intersection = null;
4854
5063
  this.intersecting = false;
5064
+ this.gapIntersection = null;
5065
+ this.gaps = [];
4855
5066
  // Used to work around a Safari Selection/shadow DOM bug (#414)
4856
5067
  this._selectionRange = null;
4857
5068
  // Timeout for scheduling check of the parents that need scroll handlers
@@ -4892,13 +5103,17 @@ class DOMObserver {
4892
5103
  this.intersection = new IntersectionObserver(entries => {
4893
5104
  if (this.parentCheck < 0)
4894
5105
  this.parentCheck = setTimeout(this.listenForScroll.bind(this), 1000);
4895
- if (entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
5106
+ if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
4896
5107
  this.intersecting = !this.intersecting;
4897
5108
  if (this.intersecting != this.view.inView)
4898
5109
  this.onScrollChanged(document.createEvent("Event"));
4899
5110
  }
4900
5111
  }, {});
4901
5112
  this.intersection.observe(this.dom);
5113
+ this.gapIntersection = new IntersectionObserver(entries => {
5114
+ if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0)
5115
+ this.onScrollChanged(document.createEvent("Event"));
5116
+ }, {});
4902
5117
  }
4903
5118
  this.listenForScroll();
4904
5119
  }
@@ -4907,6 +5122,14 @@ class DOMObserver {
4907
5122
  this.flush();
4908
5123
  this.onScrollChanged(e);
4909
5124
  }
5125
+ updateGaps(gaps) {
5126
+ if (this.gapIntersection && (gaps.length != this.gaps.length || this.gaps.some((g, i) => g != gaps[i]))) {
5127
+ this.gapIntersection.disconnect();
5128
+ for (let gap of gaps)
5129
+ this.gapIntersection.observe(gap);
5130
+ this.gaps = gaps;
5131
+ }
5132
+ }
4910
5133
  onSelectionChange(event) {
4911
5134
  if (this.lastFlush < Date.now() - 50)
4912
5135
  this._selectionRange = null;
@@ -5022,20 +5245,12 @@ class DOMObserver {
5022
5245
  this.flush();
5023
5246
  }
5024
5247
  }
5025
- // Apply pending changes, if any
5026
- flush() {
5027
- if (this.delayedFlush >= 0)
5028
- return;
5029
- this.lastFlush = Date.now();
5248
+ processRecords() {
5030
5249
  let records = this.queue;
5031
5250
  for (let mut of this.observer.takeRecords())
5032
5251
  records.push(mut);
5033
5252
  if (records.length)
5034
5253
  this.queue = [];
5035
- let selection = this.selectionRange;
5036
- let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
5037
- if (records.length == 0 && !newSel)
5038
- return;
5039
5254
  let from = -1, to = -1, typeOver = false;
5040
5255
  for (let record of records) {
5041
5256
  let range = this.readMutation(record);
@@ -5051,17 +5266,26 @@ class DOMObserver {
5051
5266
  to = Math.max(range.to, to);
5052
5267
  }
5053
5268
  }
5269
+ return { from, to, typeOver };
5270
+ }
5271
+ // Apply pending changes, if any
5272
+ flush() {
5273
+ // Completely hold off flushing when pending keys are set—the code
5274
+ // managing those will make sure processRecords is called and the
5275
+ // view is resynchronized after
5276
+ if (this.delayedFlush >= 0 || this.view.inputState.pendingKey)
5277
+ return;
5278
+ this.lastFlush = Date.now();
5279
+ let { from, to, typeOver } = this.processRecords();
5280
+ let selection = this.selectionRange;
5281
+ let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
5282
+ if (from < 0 && !newSel)
5283
+ return;
5054
5284
  let startState = this.view.state;
5055
- if (from > -1 || newSel)
5056
- this.onChange(from, to, typeOver);
5057
- if (this.view.state == startState) { // The view wasn't updated
5058
- if (this.view.docView.dirty) {
5059
- this.ignore(() => this.view.docView.sync());
5060
- this.view.docView.dirty = 0 /* Not */;
5061
- }
5062
- if (newSel)
5063
- this.view.docView.updateSelection();
5064
- }
5285
+ this.onChange(from, to, typeOver);
5286
+ // The view wasn't updated
5287
+ if (this.view.state == startState)
5288
+ this.view.docView.reset(newSel);
5065
5289
  this.clearSelection();
5066
5290
  }
5067
5291
  readMutation(rec) {
@@ -5088,6 +5312,8 @@ class DOMObserver {
5088
5312
  this.stop();
5089
5313
  if (this.intersection)
5090
5314
  this.intersection.disconnect();
5315
+ if (this.gapIntersection)
5316
+ this.gapIntersection.disconnect();
5091
5317
  for (let dom of this.scrollTargets)
5092
5318
  dom.removeEventListener("scroll", this.onScroll);
5093
5319
  window.removeEventListener("scroll", this.onScroll);
@@ -5196,9 +5422,9 @@ function applyDOMChange(view, start, end, typeOver) {
5196
5422
  (change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
5197
5423
  dispatchKey(view.contentDOM, "Backspace", 8)) ||
5198
5424
  (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
5199
- dispatchKey(view.contentDOM, "Delete", 46))) ||
5200
- browser.ios && view.inputState.flushIOSKey(view))
5425
+ dispatchKey(view.contentDOM, "Delete", 46)))) {
5201
5426
  return;
5427
+ }
5202
5428
  let text = change.insert.toString();
5203
5429
  if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
5204
5430
  return;
@@ -5419,7 +5645,7 @@ class EditorView {
5419
5645
  this.dom.appendChild(this.scrollDOM);
5420
5646
  this._dispatch = config.dispatch || ((tr) => this.update([tr]));
5421
5647
  this.dispatch = this.dispatch.bind(this);
5422
- this.root = (config.root || document);
5648
+ this.root = (config.root || getRoot(config.parent) || document);
5423
5649
  this.viewState = new ViewState(config.state || EditorState.create());
5424
5650
  this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec).update(this));
5425
5651
  this.observer = new DOMObserver(this, (from, to, typeOver) => {
@@ -5500,21 +5726,24 @@ class EditorView {
5500
5726
  if (state.facet(EditorState.phrases) != this.state.facet(EditorState.phrases))
5501
5727
  return this.setState(state);
5502
5728
  update = new ViewUpdate(this, state, transactions);
5503
- let scrollPos = null;
5729
+ let scrollTarget = null;
5504
5730
  try {
5505
5731
  this.updateState = 2 /* Updating */;
5506
5732
  for (let tr of transactions) {
5507
- if (scrollPos)
5508
- scrollPos = scrollPos.map(tr.changes);
5733
+ if (scrollTarget)
5734
+ scrollTarget = scrollTarget.map(tr.changes);
5509
5735
  if (tr.scrollIntoView) {
5510
5736
  let { main } = tr.state.selection;
5511
- scrollPos = main.empty ? main : EditorSelection.cursor(main.head, main.head > main.anchor ? -1 : 1);
5737
+ scrollTarget = new ScrollTarget(main.empty ? main : EditorSelection.cursor(main.head, main.head > main.anchor ? -1 : 1));
5512
5738
  }
5513
- for (let e of tr.effects)
5739
+ for (let e of tr.effects) {
5514
5740
  if (e.is(scrollTo))
5515
- scrollPos = e.value;
5741
+ scrollTarget = new ScrollTarget(e.value);
5742
+ else if (e.is(centerOn))
5743
+ scrollTarget = new ScrollTarget(e.value, true);
5744
+ }
5516
5745
  }
5517
- this.viewState.update(update, scrollPos);
5746
+ this.viewState.update(update, scrollTarget);
5518
5747
  this.bidiCache = CachedOrder.update(this.bidiCache, update.changes);
5519
5748
  if (!update.empty) {
5520
5749
  this.updatePlugins(update);
@@ -5529,7 +5758,7 @@ class EditorView {
5529
5758
  finally {
5530
5759
  this.updateState = 0 /* Idle */;
5531
5760
  }
5532
- if (redrawn || scrollPos || this.viewState.mustEnforceCursorAssoc)
5761
+ if (redrawn || scrollTarget || this.viewState.mustEnforceCursorAssoc)
5533
5762
  this.requestMeasure();
5534
5763
  if (!update.empty)
5535
5764
  for (let listener of this.state.facet(updateListener))
@@ -5611,14 +5840,16 @@ class EditorView {
5611
5840
  this.updateState = 1 /* Measuring */;
5612
5841
  let oldViewport = this.viewport;
5613
5842
  let changed = this.viewState.measure(this.docView, i > 0);
5614
- let measuring = this.measureRequests;
5615
- if (!changed && !measuring.length && this.viewState.scrollTo == null)
5843
+ if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
5616
5844
  break;
5617
- this.measureRequests = [];
5618
5845
  if (i > 5) {
5619
5846
  console.warn("Viewport failed to stabilize");
5620
5847
  break;
5621
5848
  }
5849
+ let measuring = [];
5850
+ // Only run measure requests in this cycle when the viewport didn't change
5851
+ if (!(changed & 4 /* Viewport */))
5852
+ [this.measureRequests, measuring] = [measuring, this.measureRequests];
5622
5853
  let measured = measuring.map(m => {
5623
5854
  try {
5624
5855
  return m.read(this);
@@ -5651,9 +5882,9 @@ class EditorView {
5651
5882
  logException(this.state, e);
5652
5883
  }
5653
5884
  }
5654
- if (this.viewState.scrollTo) {
5655
- this.docView.scrollRangeIntoView(this.viewState.scrollTo);
5656
- this.viewState.scrollTo = null;
5885
+ if (this.viewState.scrollTarget) {
5886
+ this.docView.scrollIntoView(this.viewState.scrollTarget);
5887
+ this.viewState.scrollTarget = null;
5657
5888
  }
5658
5889
  if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
5659
5890
  break;
@@ -5685,6 +5916,7 @@ class EditorView {
5685
5916
  spellcheck: "false",
5686
5917
  autocorrect: "off",
5687
5918
  autocapitalize: "off",
5919
+ translate: "no",
5688
5920
  contenteditable: !this.state.facet(editable) ? "false" : contentEditablePlainTextSupported() ? "plaintext-only" : "true",
5689
5921
  class: "cm-content",
5690
5922
  style: `${browser.tabSize}: ${this.state.tabSize}`,
@@ -5875,12 +6107,9 @@ class EditorView {
5875
6107
  moveVertically(start, forward, distance) {
5876
6108
  return skipAtoms(this, start, moveVertically(this, start, forward, distance));
5877
6109
  }
5878
- /**
5879
- Scroll the given document position into view.
5880
- */
6110
+ // FIXME remove on next major version
5881
6111
  scrollPosIntoView(pos) {
5882
- this.viewState.scrollTo = EditorSelection.cursor(pos);
5883
- this.requestMeasure();
6112
+ this.dispatch({ effects: scrollTo.of(EditorSelection.cursor(pos)) });
5884
6113
  }
5885
6114
  /**
5886
6115
  Find the DOM parent node and offset (child offset if `node` is
@@ -6047,7 +6276,7 @@ class EditorView {
6047
6276
  target editors with a dark or light theme.
6048
6277
  */
6049
6278
  static baseTheme(spec) {
6050
- return Prec.fallback(styleModule.of(buildTheme("." + baseThemeID, spec, lightDarkIDs)));
6279
+ return Prec.lowest(styleModule.of(buildTheme("." + baseThemeID, spec, lightDarkIDs)));
6051
6280
  }
6052
6281
  }
6053
6282
  /**
@@ -6056,6 +6285,11 @@ transaction to make it scroll the given range into view.
6056
6285
  */
6057
6286
  EditorView.scrollTo = scrollTo;
6058
6287
  /**
6288
+ Effect that makes the editor scroll the given range to the
6289
+ center of the visible view.
6290
+ */
6291
+ EditorView.centerOn = centerOn;
6292
+ /**
6059
6293
  Facet to add a [style
6060
6294
  module](https://github.com/marijnh/style-mod#documentation) to
6061
6295
  an editor view. The view will ensure that the module is
@@ -6185,11 +6419,7 @@ class CachedOrder {
6185
6419
  }
6186
6420
  }
6187
6421
 
6188
- const currentPlatform = typeof navigator == "undefined" ? "key"
6189
- : /*@__PURE__*//Mac/.test(navigator.platform) ? "mac"
6190
- : /*@__PURE__*//Win/.test(navigator.platform) ? "win"
6191
- : /*@__PURE__*//Linux|X11/.test(navigator.platform) ? "linux"
6192
- : "key";
6422
+ const currentPlatform = browser.mac ? "mac" : browser.windows ? "win" : browser.linux ? "linux" : "key";
6193
6423
  function normalizeKeyName(name, platform) {
6194
6424
  const parts = name.split(/-(?!$)/);
6195
6425
  let result = parts[parts.length - 1];
@@ -6488,7 +6718,7 @@ const themeSpec = {
6488
6718
  };
6489
6719
  if (CanHidePrimary)
6490
6720
  themeSpec[".cm-line"].caretColor = "transparent !important";
6491
- const hideNativeSelection = /*@__PURE__*/Prec.override(/*@__PURE__*/EditorView.theme(themeSpec));
6721
+ const hideNativeSelection = /*@__PURE__*/Prec.highest(/*@__PURE__*/EditorView.theme(themeSpec));
6492
6722
  function getBase(view) {
6493
6723
  let rect = view.scrollDOM.getBoundingClientRect();
6494
6724
  let left = view.textDirection == Direction.LTR ? rect.left : rect.right - view.scrollDOM.clientWidth;
@@ -6695,7 +6925,7 @@ class MatchDecorator {
6695
6925
  }
6696
6926
 
6697
6927
  const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g";
6698
- const Specials = /*@__PURE__*/new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
6928
+ const Specials = /*@__PURE__*/new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
6699
6929
  const Names = {
6700
6930
  0: "null",
6701
6931
  7: "bell",
@@ -6710,6 +6940,8 @@ const Names = {
6710
6940
  8206: "left-to-right mark",
6711
6941
  8207: "right-to-left mark",
6712
6942
  8232: "line separator",
6943
+ 8237: "left-to-right override",
6944
+ 8238: "right-to-left override",
6713
6945
  8233: "paragraph separator",
6714
6946
  65279: "zero width no-break space",
6715
6947
  65532: "object replacement"
@@ -6876,7 +7108,7 @@ DOM class.
6876
7108
  function highlightActiveLine() {
6877
7109
  return activeLineHighlighter;
6878
7110
  }
6879
- const lineDeco = /*@__PURE__*/Decoration.line({ attributes: { class: "cm-activeLine" } });
7111
+ const lineDeco = /*@__PURE__*/Decoration.line({ class: "cm-activeLine" });
6880
7112
  const activeLineHighlighter = /*@__PURE__*/ViewPlugin.fromClass(class {
6881
7113
  constructor(view) {
6882
7114
  this.decorations = this.getDeco(view);