@codemirror/view 0.19.6 → 0.19.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,12 +1,22 @@
1
1
  import { MapMode, Text as Text$1, Facet, StateEffect, ChangeSet, EditorSelection, CharCategory, EditorState, Transaction, Prec, combineConfig } from '@codemirror/state';
2
2
  import { StyleModule } from 'style-mod';
3
- import { RangeValue, RangeSet, RangeSetBuilder } from '@codemirror/rangeset';
3
+ import { RangeSet, RangeValue, RangeSetBuilder } from '@codemirror/rangeset';
4
4
  export { Range } from '@codemirror/rangeset';
5
5
  import { Text, findClusterBreak, findColumn, codePointAt, countColumn } from '@codemirror/text';
6
6
  import { keyName, base } from 'w3c-keyname';
7
7
 
8
8
  function getSelection(root) {
9
- return (root.getSelection ? root.getSelection() : document.getSelection());
9
+ let target;
10
+ // Browsers differ on whether shadow roots have a getSelection
11
+ // method. If it exists, use that, otherwise, call it on the
12
+ // document.
13
+ if (root.nodeType == 11) { // Shadow root
14
+ target = root.getSelection ? root : root.ownerDocument;
15
+ }
16
+ else {
17
+ target = root;
18
+ }
19
+ return target.getSelection();
10
20
  }
11
21
  function contains(dom, node) {
12
22
  return node ? dom.contains(node.nodeType != 1 ? node.parentNode : node) : false;
@@ -238,6 +248,14 @@ function contentEditablePlainTextSupported() {
238
248
  }
239
249
  return _plainTextSupported;
240
250
  }
251
+ function getRoot(node) {
252
+ while (node) {
253
+ node = node.assignedSlot || node.parentNode;
254
+ if (node && (node.nodeType == 9 || node.nodeType == 11 && node.host))
255
+ return node;
256
+ }
257
+ return null;
258
+ }
241
259
 
242
260
  class DOMPos {
243
261
  constructor(node, offset, precise = true) {
@@ -435,6 +453,7 @@ class ContentView {
435
453
  (this.breakAfter ? "#" : "");
436
454
  }
437
455
  static get(node) { return node.cmView; }
456
+ get isEditable() { return true; }
438
457
  }
439
458
  ContentView.prototype.breakAfter = 0;
440
459
  // Remove a DOM node and return its next sibling.
@@ -482,15 +501,18 @@ const gecko = !ie && /*@__PURE__*//gecko\/(\d+)/i.test(nav.userAgent);
482
501
  const chrome = !ie && /*@__PURE__*//Chrome\/(\d+)/.exec(nav.userAgent);
483
502
  const webkit = "webkitFontSmoothing" in doc.documentElement.style;
484
503
  const safari = !ie && /*@__PURE__*//Apple Computer/.test(nav.vendor);
504
+ const ios = safari && (/*@__PURE__*//Mobile\/\w+/.test(nav.userAgent) || nav.maxTouchPoints > 2);
485
505
  var browser = {
486
- mac: /*@__PURE__*//Mac/.test(nav.platform),
506
+ mac: ios || /*@__PURE__*//Mac/.test(nav.platform),
507
+ windows: /*@__PURE__*//Win/.test(nav.platform),
508
+ linux: /*@__PURE__*//Linux|X11/.test(nav.platform),
487
509
  ie,
488
510
  ie_version: ie_upto10 ? doc.documentMode || 6 : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : 0,
489
511
  gecko,
490
512
  gecko_version: gecko ? +(/*@__PURE__*//Firefox\/(\d+)/.exec(nav.userAgent) || [0, 0])[1] : 0,
491
513
  chrome: !!chrome,
492
514
  chrome_version: chrome ? +chrome[1] : 0,
493
- ios: safari && (/*@__PURE__*//Mobile\/\w+/.test(nav.userAgent) || nav.maxTouchPoints > 2),
515
+ ios,
494
516
  android: /*@__PURE__*//Android\b/.test(nav.userAgent),
495
517
  webkit,
496
518
  safari,
@@ -712,6 +734,7 @@ class WidgetView extends InlineView {
712
734
  }
713
735
  return (pos == 0 && side > 0 || pos == this.length && side <= 0) ? rect : flattenRect(rect, pos == 0);
714
736
  }
737
+ get isEditable() { return false; }
715
738
  }
716
739
  class CompositionView extends WidgetView {
717
740
  domAtPos(pos) { return new DOMPos(this.widget.text, pos); }
@@ -723,6 +746,38 @@ class CompositionView extends WidgetView {
723
746
  ignoreMutation() { return false; }
724
747
  get overrideDOMText() { return null; }
725
748
  coordsAt(pos, side) { return textCoords(this.widget.text, pos, side); }
749
+ get isEditable() { return true; }
750
+ }
751
+ // These are drawn around uneditable widgets to avoid a number of
752
+ // browser bugs that show up when the cursor is directly next to
753
+ // uneditable inline content.
754
+ class WidgetBufferView extends InlineView {
755
+ constructor(side) {
756
+ super();
757
+ this.side = side;
758
+ }
759
+ get length() { return 0; }
760
+ merge() { return false; }
761
+ become(other) {
762
+ return other instanceof WidgetBufferView && other.side == this.side;
763
+ }
764
+ slice() { return new WidgetBufferView(this.side); }
765
+ sync() {
766
+ if (!this.dom)
767
+ this.setDOM(document.createTextNode("\u200b"));
768
+ else if (this.dirty && this.dom.nodeValue != "\u200b")
769
+ this.dom.nodeValue = "\u200b";
770
+ }
771
+ getSide() { return this.side; }
772
+ domAtPos(pos) { return DOMPos.before(this.dom); }
773
+ domBoundsAround() { return null; }
774
+ coordsAt(pos) {
775
+ let rects = clientRectsFor(this.dom);
776
+ return rects[rects.length - 1];
777
+ }
778
+ get overrideDOMText() {
779
+ return Text.of([this.dom.nodeValue.replace(/\u200b/g, "")]);
780
+ }
726
781
  }
727
782
  function mergeInlineChildren(parent, from, to, elts, openStart, openEnd) {
728
783
  let cur = parent.childCursor();
@@ -1217,14 +1272,17 @@ class LineView extends ContentView {
1217
1272
  }
1218
1273
  // Only called when building a line view in ContentBuilder
1219
1274
  addLineDeco(deco) {
1220
- let attrs = deco.spec.attributes;
1275
+ let attrs = deco.spec.attributes, cls = deco.spec.class;
1221
1276
  if (attrs)
1222
1277
  this.attrs = combineAttrs(attrs, this.attrs || {});
1278
+ if (cls)
1279
+ this.attrs = combineAttrs(attrs, { class: cls });
1223
1280
  }
1224
1281
  domAtPos(pos) {
1225
1282
  return inlineDOMAtPos(this.dom, this.children, pos);
1226
1283
  }
1227
1284
  sync(track) {
1285
+ var _a;
1228
1286
  if (!this.dom || (this.dirty & 4 /* Attrs */)) {
1229
1287
  this.setDOM(document.createElement("div"));
1230
1288
  this.dom.className = "cm-line";
@@ -1240,7 +1298,7 @@ class LineView extends ContentView {
1240
1298
  while (last && ContentView.get(last) instanceof MarkView)
1241
1299
  last = last.lastChild;
1242
1300
  if (!last ||
1243
- last.nodeName != "BR" && ContentView.get(last) instanceof WidgetView &&
1301
+ last.nodeName != "BR" && ((_a = ContentView.get(last)) === null || _a === void 0 ? void 0 : _a.isEditable) == false &&
1244
1302
  (!browser.ios || !this.children.some(ch => ch instanceof TextView))) {
1245
1303
  let hack = document.createElement("BR");
1246
1304
  hack.cmIgnore = true;
@@ -1339,6 +1397,9 @@ class ContentBuilder {
1339
1397
  this.content = [];
1340
1398
  this.curLine = null;
1341
1399
  this.breakAtStart = 0;
1400
+ this.pendingBuffer = 0 /* No */;
1401
+ // Set to false directly after a widget that covers the position after it
1402
+ this.atCursorPos = true;
1342
1403
  this.openStart = -1;
1343
1404
  this.openEnd = -1;
1344
1405
  this.text = "";
@@ -1353,23 +1414,31 @@ class ContentBuilder {
1353
1414
  return !last.breakAfter && !(last instanceof BlockWidgetView && last.type == BlockType.WidgetBefore);
1354
1415
  }
1355
1416
  getLine() {
1356
- if (!this.curLine)
1417
+ if (!this.curLine) {
1357
1418
  this.content.push(this.curLine = new LineView);
1419
+ this.atCursorPos = true;
1420
+ }
1358
1421
  return this.curLine;
1359
1422
  }
1360
- addWidget(view) {
1423
+ flushBuffer(active) {
1424
+ if (this.pendingBuffer) {
1425
+ this.curLine.append(wrapMarks(new WidgetBufferView(-1), active), active.length);
1426
+ this.pendingBuffer = 0 /* No */;
1427
+ }
1428
+ }
1429
+ addBlockWidget(view) {
1430
+ this.flushBuffer([]);
1361
1431
  this.curLine = null;
1362
1432
  this.content.push(view);
1363
1433
  }
1364
- finish() {
1434
+ finish(openEnd) {
1435
+ if (!openEnd)
1436
+ this.flushBuffer([]);
1437
+ else
1438
+ this.pendingBuffer = 0 /* No */;
1365
1439
  if (!this.posCovered())
1366
1440
  this.getLine();
1367
1441
  }
1368
- wrapMarks(view, active) {
1369
- for (let mark of active)
1370
- view = new MarkView(mark, [view], view.length);
1371
- return view;
1372
- }
1373
1442
  buildText(length, active, openStart) {
1374
1443
  while (length > 0) {
1375
1444
  if (this.textOff == this.text.length) {
@@ -1384,6 +1453,7 @@ class ContentBuilder {
1384
1453
  this.content[this.content.length - 1].breakAfter = 1;
1385
1454
  else
1386
1455
  this.breakAtStart = 1;
1456
+ this.flushBuffer([]);
1387
1457
  this.curLine = null;
1388
1458
  length--;
1389
1459
  continue;
@@ -1394,7 +1464,9 @@ class ContentBuilder {
1394
1464
  }
1395
1465
  }
1396
1466
  let take = Math.min(this.text.length - this.textOff, length, 512 /* Chunk */);
1397
- this.getLine().append(this.wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
1467
+ this.flushBuffer(active);
1468
+ this.getLine().append(wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
1469
+ this.atCursorPos = true;
1398
1470
  this.textOff += take;
1399
1471
  length -= take;
1400
1472
  openStart = 0;
@@ -1413,11 +1485,23 @@ class ContentBuilder {
1413
1485
  let { type } = deco;
1414
1486
  if (type == BlockType.WidgetAfter && !this.posCovered())
1415
1487
  this.getLine();
1416
- this.addWidget(new BlockWidgetView(deco.widget || new NullWidget("div"), len, type));
1488
+ this.addBlockWidget(new BlockWidgetView(deco.widget || new NullWidget("div"), len, type));
1417
1489
  }
1418
1490
  else {
1419
- let widget = this.wrapMarks(WidgetView.create(deco.widget || new NullWidget("span"), len, deco.startSide), active);
1420
- this.getLine().append(widget, openStart);
1491
+ let view = WidgetView.create(deco.widget || new NullWidget("span"), len, deco.startSide);
1492
+ let cursorBefore = this.atCursorPos && !view.isEditable && openStart <= active.length && (from < to || deco.startSide > 0);
1493
+ let cursorAfter = !view.isEditable && (from < to || deco.startSide <= 0);
1494
+ let line = this.getLine();
1495
+ if (this.pendingBuffer == 2 /* IfCursor */ && !cursorBefore)
1496
+ this.pendingBuffer = 0 /* No */;
1497
+ this.flushBuffer(active);
1498
+ if (cursorBefore) {
1499
+ line.append(wrapMarks(new WidgetBufferView(1), active), openStart);
1500
+ openStart = active.length + Math.max(0, openStart - active.length);
1501
+ }
1502
+ line.append(wrapMarks(view, active), openStart);
1503
+ this.atCursorPos = cursorAfter;
1504
+ this.pendingBuffer = !cursorAfter ? 0 /* No */ : from < to ? 1 /* Yes */ : 2 /* IfCursor */;
1421
1505
  }
1422
1506
  }
1423
1507
  else if (this.doc.lineAt(this.pos).from == this.pos) { // Line decoration
@@ -1443,10 +1527,15 @@ class ContentBuilder {
1443
1527
  builder.openEnd = RangeSet.spans(decorations, from, to, builder);
1444
1528
  if (builder.openStart < 0)
1445
1529
  builder.openStart = builder.openEnd;
1446
- builder.finish();
1530
+ builder.finish(builder.openEnd);
1447
1531
  return builder;
1448
1532
  }
1449
1533
  }
1534
+ function wrapMarks(view, active) {
1535
+ for (let mark of active)
1536
+ view = new MarkView(mark, [view], view.length);
1537
+ return view;
1538
+ }
1450
1539
  class NullWidget extends WidgetType {
1451
1540
  constructor(tag) {
1452
1541
  super();
@@ -1779,7 +1868,9 @@ class ViewUpdate {
1779
1868
  this.flags |= 2 /* Height */;
1780
1869
  }
1781
1870
  /**
1782
- Tells you whether the viewport changed in this update.
1871
+ Tells you whether the [viewport](https://codemirror.net/6/docs/ref/#view.EditorView.viewport) or
1872
+ [visible ranges](https://codemirror.net/6/docs/ref/#view.EditorView.visibleRanges) changed in this
1873
+ update.
1783
1874
  */
1784
1875
  get viewportChanged() {
1785
1876
  return (this.flags & 4 /* Viewport */) > 0;
@@ -1795,7 +1886,7 @@ class ViewUpdate {
1795
1886
  or the lines or characters within it has changed.
1796
1887
  */
1797
1888
  get geometryChanged() {
1798
- return this.docChanged || (this.flags & (16 /* Geometry */ | 2 /* Height */)) > 0;
1889
+ return this.docChanged || (this.flags & (8 /* Geometry */ | 2 /* Height */)) > 0;
1799
1890
  }
1800
1891
  /**
1801
1892
  True when this update indicates a focus change.
@@ -1880,7 +1971,7 @@ class DocView extends ContentView {
1880
1971
  changedRanges = ChangedRange.extendWithRanges(changedRanges, decoDiff);
1881
1972
  let pointerSel = update.transactions.some(tr => tr.isUserEvent("select.pointer"));
1882
1973
  if (this.dirty == 0 /* Not */ && changedRanges.length == 0 &&
1883
- !(update.flags & (4 /* Viewport */ | 8 /* LineGaps */)) &&
1974
+ !(update.flags & 4 /* Viewport */) &&
1884
1975
  update.state.selection.main.from >= this.view.viewport.from &&
1885
1976
  update.state.selection.main.to <= this.view.viewport.to) {
1886
1977
  this.updateSelection(forceSelection, pointerSel);
@@ -1891,6 +1982,14 @@ class DocView extends ContentView {
1891
1982
  return true;
1892
1983
  }
1893
1984
  }
1985
+ reset(sel) {
1986
+ if (this.dirty) {
1987
+ this.view.observer.ignore(() => this.view.docView.sync());
1988
+ this.dirty = 0 /* Not */;
1989
+ }
1990
+ if (sel)
1991
+ this.updateSelection();
1992
+ }
1894
1993
  // Used both by update and checkLayout do perform the actual DOM
1895
1994
  // update
1896
1995
  updateInner(changes, deco, oldLength, forceSelection = false, pointerSel = false) {
@@ -1915,6 +2014,12 @@ class DocView extends ContentView {
1915
2014
  this.updateSelection(forceSelection, pointerSel);
1916
2015
  this.dom.style.height = "";
1917
2016
  });
2017
+ let gaps = [];
2018
+ if (this.view.viewport.from || this.view.viewport.to < this.view.state.doc.length)
2019
+ for (let child of this.children)
2020
+ if (child instanceof BlockWidgetView && child.widget instanceof BlockGapWidget)
2021
+ gaps.push(child.dom);
2022
+ observer.updateGaps(gaps);
1918
2023
  }
1919
2024
  updateChildren(changes, deco, oldLength) {
1920
2025
  let cursor = this.childCursor(oldLength);
@@ -2014,6 +2119,14 @@ class DocView extends ContentView {
2014
2119
  !isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) ||
2015
2120
  !isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) {
2016
2121
  this.view.observer.ignore(() => {
2122
+ // Chrome Android will hide the virtual keyboard when tapping
2123
+ // inside an uneditable node, and not bring it back when we
2124
+ // move the cursor to its proper position. This tries to
2125
+ // restore the keyboard by cycling focus.
2126
+ if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) && inUneditable(domSel.focusNode, this.dom)) {
2127
+ this.dom.blur();
2128
+ this.dom.focus({ preventScroll: true });
2129
+ }
2017
2130
  let rawSel = getSelection(this.root);
2018
2131
  if (main.empty) {
2019
2132
  // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=1612076
@@ -2321,6 +2434,14 @@ function findChangedDeco(a, b, diff) {
2321
2434
  RangeSet.compare(a, b, diff, comp);
2322
2435
  return comp.changes;
2323
2436
  }
2437
+ function inUneditable(node, inside) {
2438
+ for (let cur = node; cur && cur != inside; cur = cur.assignedSlot || cur.parentNode) {
2439
+ if (cur.nodeType == 1 && cur.contentEditable == 'false') {
2440
+ return true;
2441
+ }
2442
+ }
2443
+ return false;
2444
+ }
2324
2445
 
2325
2446
  /**
2326
2447
  Used to indicate [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
@@ -2766,6 +2887,7 @@ function domPosInText(node, x, y) {
2766
2887
  return { node, offset: closestOffset > -1 ? closestOffset : generalSide > 0 ? node.nodeValue.length : 0 };
2767
2888
  }
2768
2889
  function posAtCoords(view, { x, y }, precise, bias = -1) {
2890
+ var _a;
2769
2891
  let content = view.contentDOM.getBoundingClientRect(), block;
2770
2892
  let halfLine = view.defaultLineHeight / 2;
2771
2893
  for (let bounced = false;;) {
@@ -2783,25 +2905,27 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
2783
2905
  y = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2784
2906
  }
2785
2907
  let lineStart = block.from;
2908
+ // Clip x to the viewport sides
2786
2909
  x = Math.max(content.left + 1, Math.min(content.right - 1, x));
2787
2910
  // If this is outside of the rendered viewport, we can't determine a position
2788
2911
  if (lineStart < view.viewport.from)
2789
2912
  return view.viewport.from == 0 ? 0 : posAtCoordsImprecise(view, content, block, x, y);
2790
2913
  if (lineStart > view.viewport.to)
2791
2914
  return view.viewport.to == view.state.doc.length ? view.state.doc.length : posAtCoordsImprecise(view, content, block, x, y);
2792
- // Clip x to the viewport sides
2793
- let root = view.root, element = root.elementFromPoint(x, y);
2915
+ // Prefer ShadowRootOrDocument.elementFromPoint if present, fall back to document if not
2916
+ let doc = view.dom.ownerDocument;
2917
+ let element = (view.root.elementFromPoint ? view.root : doc).elementFromPoint(x, y);
2794
2918
  // There's visible editor content under the point, so we can try
2795
2919
  // using caret(Position|Range)FromPoint as a shortcut
2796
2920
  let node, offset = -1;
2797
- if (element && view.contentDOM.contains(element) && !(view.docView.nearest(element) instanceof WidgetView)) {
2798
- if (root.caretPositionFromPoint) {
2799
- let pos = root.caretPositionFromPoint(x, y);
2921
+ if (element && view.contentDOM.contains(element) && ((_a = view.docView.nearest(element)) === null || _a === void 0 ? void 0 : _a.isEditable) != false) {
2922
+ if (doc.caretPositionFromPoint) {
2923
+ let pos = doc.caretPositionFromPoint(x, y);
2800
2924
  if (pos)
2801
2925
  ({ offsetNode: node, offset } = pos);
2802
2926
  }
2803
- else if (root.caretRangeFromPoint) {
2804
- let range = root.caretRangeFromPoint(x, y);
2927
+ else if (doc.caretRangeFromPoint) {
2928
+ let range = doc.caretRangeFromPoint(x, y);
2805
2929
  if (range) {
2806
2930
  ({ startContainer: node, startOffset: offset } = range);
2807
2931
  if (browser.safari && isSuspiciousCaretResult(node, offset, x))
@@ -2935,7 +3059,23 @@ class InputState {
2935
3059
  constructor(view) {
2936
3060
  this.lastKeyCode = 0;
2937
3061
  this.lastKeyTime = 0;
2938
- this.pendingIOSKey = null;
3062
+ // On iOS, some keys need to have their default behavior happen
3063
+ // (after which we retroactively handle them and reset the DOM) to
3064
+ // avoid messing up the virtual keyboard state.
3065
+ //
3066
+ // On Chrome Android, backspace near widgets is just completely
3067
+ // broken, and there are no key events, so we need to handle the
3068
+ // beforeinput event. Deleting stuff will often create a flurry of
3069
+ // events, and interrupting it before it is done just makes
3070
+ // subsequent events even more broken, so again, we hold off doing
3071
+ // anything until the browser is finished with whatever it is trying
3072
+ // to do.
3073
+ //
3074
+ // setPendingKey sets this, causing the DOM observer to pause for a
3075
+ // bit, and setting an animation frame (which seems the most
3076
+ // reliable way to detect 'browser is done flailing') to fire a fake
3077
+ // key event and re-sync the view again.
3078
+ this.pendingKey = undefined;
2939
3079
  this.lastSelectionOrigin = null;
2940
3080
  this.lastSelectionTime = 0;
2941
3081
  this.lastEscPress = 0;
@@ -3044,20 +3184,27 @@ class InputState {
3044
3184
  // state. So we let it go through, and then, in
3045
3185
  // applyDOMChange, notify key handlers of it and reset to
3046
3186
  // the state they produce.
3047
- if (browser.ios && (event.keyCode == 13 || event.keyCode == 8) &&
3187
+ let pending;
3188
+ if (browser.ios && (pending = PendingKeys.find(key => key.keyCode == event.keyCode)) &&
3048
3189
  !(event.ctrlKey || event.altKey || event.metaKey) && !event.synthetic) {
3049
- this.pendingIOSKey = event.keyCode == 13 ? "enter" : "backspace";
3050
- setTimeout(() => this.flushIOSKey(view), 250);
3190
+ this.setPendingKey(view, pending);
3051
3191
  return true;
3052
3192
  }
3053
3193
  return false;
3054
3194
  }
3055
- flushIOSKey(view) {
3056
- if (!this.pendingIOSKey)
3057
- return false;
3058
- let dom = view.contentDOM, key = this.pendingIOSKey;
3059
- this.pendingIOSKey = null;
3060
- return key == "enter" ? dispatchKey(dom, "Enter", 13) : dispatchKey(dom, "Backspace", 8);
3195
+ setPendingKey(view, pending) {
3196
+ this.pendingKey = pending;
3197
+ requestAnimationFrame(() => {
3198
+ if (!this.pendingKey)
3199
+ return false;
3200
+ let key = this.pendingKey;
3201
+ this.pendingKey = undefined;
3202
+ view.observer.processRecords();
3203
+ let startState = view.state;
3204
+ dispatchKey(view.contentDOM, key.key, key.keyCode);
3205
+ if (view.state == startState)
3206
+ view.docView.reset(true);
3207
+ });
3061
3208
  }
3062
3209
  ignoreDuringComposition(event) {
3063
3210
  if (!/^key/.test(event.type))
@@ -3104,6 +3251,11 @@ class InputState {
3104
3251
  this.mouseSelection.destroy();
3105
3252
  }
3106
3253
  }
3254
+ const PendingKeys = [
3255
+ { key: "Backspace", keyCode: 8, inputType: "deleteContentBackward" },
3256
+ { key: "Enter", keyCode: 13, inputType: "insertParagraph" },
3257
+ { key: "Delete", keyCode: 46, inputType: "deleteContentForward" }
3258
+ ];
3107
3259
  // Key codes for modifier keys
3108
3260
  const modifierCodes = [16, 17, 18, 20, 91, 92, 224, 225];
3109
3261
  class MouseSelection {
@@ -3220,7 +3372,7 @@ function capturePaste(view) {
3220
3372
  function doPaste(view, input) {
3221
3373
  let { state } = view, changes, i = 1, text = state.toText(input);
3222
3374
  let byLine = text.lines == state.selection.ranges.length;
3223
- let linewise = lastLinewiseCopy && state.selection.ranges.every(r => r.empty) && lastLinewiseCopy == text.toString();
3375
+ let linewise = lastLinewiseCopy != null && state.selection.ranges.every(r => r.empty) && lastLinewiseCopy == text.toString();
3224
3376
  if (linewise) {
3225
3377
  let lastLine = -1;
3226
3378
  changes = state.changeByRange(range => {
@@ -3399,8 +3551,10 @@ function dropText(view, event, text, direct) {
3399
3551
  });
3400
3552
  }
3401
3553
  handlers.drop = (view, event) => {
3402
- if (!event.dataTransfer || !view.state.facet(editable))
3554
+ if (!event.dataTransfer)
3403
3555
  return;
3556
+ if (view.state.readOnly)
3557
+ return event.preventDefault();
3404
3558
  let files = event.dataTransfer.files;
3405
3559
  if (files && files.length) { // For a file drop, read the file's text.
3406
3560
  event.preventDefault();
@@ -3425,13 +3579,12 @@ handlers.drop = (view, event) => {
3425
3579
  }
3426
3580
  };
3427
3581
  handlers.paste = (view, event) => {
3428
- if (!view.state.facet(editable))
3429
- return;
3582
+ if (view.state.readOnly)
3583
+ return event.preventDefault();
3430
3584
  view.observer.flush();
3431
3585
  let data = brokenClipboardAPI ? null : event.clipboardData;
3432
- let text = data && data.getData("text/plain");
3433
- if (text) {
3434
- doPaste(view, text);
3586
+ if (data) {
3587
+ doPaste(view, data.getData("text/plain"));
3435
3588
  event.preventDefault();
3436
3589
  }
3437
3590
  else {
@@ -3480,7 +3633,7 @@ function copiedRange(state) {
3480
3633
  let lastLinewiseCopy = null;
3481
3634
  handlers.copy = handlers.cut = (view, event) => {
3482
3635
  let { text, ranges, linewise } = copiedRange(view.state);
3483
- if (!text)
3636
+ if (!text && !linewise)
3484
3637
  return;
3485
3638
  lastLinewiseCopy = linewise ? text : null;
3486
3639
  let data = brokenClipboardAPI ? null : event.clipboardData;
@@ -3492,7 +3645,7 @@ handlers.copy = handlers.cut = (view, event) => {
3492
3645
  else {
3493
3646
  captureCopy(view, text);
3494
3647
  }
3495
- if (event.type == "cut" && view.state.facet(editable))
3648
+ if (event.type == "cut" && !view.state.readOnly)
3496
3649
  view.dispatch({
3497
3650
  changes: ranges,
3498
3651
  scrollIntoView: true,
@@ -3548,6 +3701,31 @@ handlers.compositionend = view => {
3548
3701
  handlers.contextmenu = view => {
3549
3702
  view.inputState.lastContextMenu = Date.now();
3550
3703
  };
3704
+ handlers.beforeinput = (view, event) => {
3705
+ var _a;
3706
+ // Because Chrome Android doesn't fire useful key events, use
3707
+ // beforeinput to detect backspace (and possibly enter and delete,
3708
+ // but those usually don't even seem to fire beforeinput events at
3709
+ // the moment) and fake a key event for it.
3710
+ //
3711
+ // (preventDefault on beforeinput, though supported in the spec,
3712
+ // seems to do nothing at all on Chrome).
3713
+ let pending;
3714
+ if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
3715
+ view.inputState.setPendingKey(view, pending);
3716
+ let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
3717
+ setTimeout(() => {
3718
+ var _a;
3719
+ // Backspacing near uneditable nodes on Chrome Android sometimes
3720
+ // closes the virtual keyboard. This tries to crudely detect
3721
+ // that and refocus to get it back.
3722
+ if ((((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0) > startViewHeight + 10 && view.hasFocus) {
3723
+ view.contentDOM.blur();
3724
+ view.focus();
3725
+ }
3726
+ }, 50);
3727
+ }
3728
+ };
3551
3729
 
3552
3730
  const wrappingWhiteSpace = ["pre-wrap", "normal", "pre-line"];
3553
3731
  class HeightOracle {
@@ -4320,14 +4498,11 @@ class ViewState {
4320
4498
  let viewport = heightChanges.length ? this.mapViewport(this.viewport, update.changes) : this.viewport;
4321
4499
  if (scrollTo && (scrollTo.head < viewport.from || scrollTo.head > viewport.to) || !this.viewportIsAppropriate(viewport))
4322
4500
  viewport = this.getViewport(0, scrollTo);
4323
- if (!viewport.eq(this.viewport)) {
4324
- this.viewport = viewport;
4325
- update.flags |= 4 /* Viewport */;
4326
- }
4501
+ this.viewport = viewport;
4327
4502
  this.updateForViewport();
4328
4503
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 15000 /* MinViewPort */)
4329
- update.flags |= this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
4330
- this.computeVisibleRanges();
4504
+ this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
4505
+ update.flags |= this.computeVisibleRanges();
4331
4506
  if (scrollTo)
4332
4507
  this.scrollTo = scrollTo;
4333
4508
  if (!this.mustEnforceCursorAssoc && update.selectionSet && update.view.lineWrapping &&
@@ -4336,12 +4511,17 @@ class ViewState {
4336
4511
  }
4337
4512
  measure(docView, repeated) {
4338
4513
  let dom = docView.dom, whiteSpace = "", direction = Direction.LTR;
4514
+ let result = 0;
4339
4515
  if (!repeated) {
4340
4516
  // Vertical padding
4341
4517
  let style = window.getComputedStyle(dom);
4342
4518
  whiteSpace = style.whiteSpace, direction = (style.direction == "rtl" ? Direction.RTL : Direction.LTR);
4343
- this.paddingTop = parseInt(style.paddingTop) || 0;
4344
- this.paddingBottom = parseInt(style.paddingBottom) || 0;
4519
+ let paddingTop = parseInt(style.paddingTop) || 0, paddingBottom = parseInt(style.paddingBottom) || 0;
4520
+ if (this.paddingTop != paddingTop || this.paddingBottom != paddingBottom) {
4521
+ result |= 8 /* Geometry */;
4522
+ this.paddingTop = paddingTop;
4523
+ this.paddingBottom = paddingBottom;
4524
+ }
4345
4525
  }
4346
4526
  // Pixel viewport
4347
4527
  let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 } : visiblePixelRange(dom, this.paddingTop);
@@ -4351,7 +4531,7 @@ class ViewState {
4351
4531
  if (!this.inView)
4352
4532
  return 0;
4353
4533
  let lineHeights = docView.measureVisibleLineHeights();
4354
- let refresh = false, bias = 0, result = 0, oracle = this.heightOracle;
4534
+ let refresh = false, bias = 0, oracle = this.heightOracle;
4355
4535
  if (!repeated) {
4356
4536
  let contentWidth = docView.dom.clientWidth;
4357
4537
  if (oracle.mustRefresh(lineHeights, whiteSpace, direction) ||
@@ -4360,12 +4540,12 @@ class ViewState {
4360
4540
  refresh = oracle.refresh(whiteSpace, direction, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
4361
4541
  if (refresh) {
4362
4542
  docView.minWidth = 0;
4363
- result |= 16 /* Geometry */;
4543
+ result |= 8 /* Geometry */;
4364
4544
  }
4365
4545
  }
4366
4546
  if (this.contentWidth != contentWidth) {
4367
4547
  this.contentWidth = contentWidth;
4368
- result |= 16 /* Geometry */;
4548
+ result |= 8 /* Geometry */;
4369
4549
  }
4370
4550
  if (dTop > 0 && dBottom > 0)
4371
4551
  bias = Math.max(dTop, dBottom);
@@ -4377,17 +4557,12 @@ class ViewState {
4377
4557
  if (oracle.heightChanged)
4378
4558
  result |= 2 /* Height */;
4379
4559
  if (!this.viewportIsAppropriate(this.viewport, bias) ||
4380
- this.scrollTo && (this.scrollTo.head < this.viewport.from || this.scrollTo.head > this.viewport.to)) {
4381
- let newVP = this.getViewport(bias, this.scrollTo);
4382
- if (newVP.from != this.viewport.from || newVP.to != this.viewport.to) {
4383
- this.viewport = newVP;
4384
- result |= 4 /* Viewport */;
4385
- }
4386
- }
4560
+ this.scrollTo && (this.scrollTo.head < this.viewport.from || this.scrollTo.head > this.viewport.to))
4561
+ this.viewport = this.getViewport(bias, this.scrollTo);
4387
4562
  this.updateForViewport();
4388
4563
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 15000 /* MinViewPort */)
4389
- result |= this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
4390
- this.computeVisibleRanges();
4564
+ this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
4565
+ result |= this.computeVisibleRanges();
4391
4566
  if (this.mustEnforceCursorAssoc) {
4392
4567
  this.mustEnforceCursorAssoc = false;
4393
4568
  // This is done in the read stage, because moving the selection
@@ -4509,9 +4684,7 @@ class ViewState {
4509
4684
  if (!LineGap.same(gaps, this.lineGaps)) {
4510
4685
  this.lineGaps = gaps;
4511
4686
  this.lineGapDeco = Decoration.set(gaps.map(gap => gap.draw(this.heightOracle.lineWrapping)));
4512
- return 8 /* LineGaps */;
4513
4687
  }
4514
- return 0;
4515
4688
  }
4516
4689
  computeVisibleRanges() {
4517
4690
  let deco = this.state.facet(decorations);
@@ -4522,7 +4695,10 @@ class ViewState {
4522
4695
  span(from, to) { ranges.push({ from, to }); },
4523
4696
  point() { }
4524
4697
  }, 20);
4698
+ let changed = ranges.length != this.visibleRanges.length ||
4699
+ this.visibleRanges.some((r, i) => r.from != ranges[i].from || r.to != ranges[i].to);
4525
4700
  this.visibleRanges = ranges;
4701
+ return changed ? 4 /* Viewport */ : 0;
4526
4702
  }
4527
4703
  lineAt(pos, editorTop) {
4528
4704
  editorTop += this.paddingTop;
@@ -4547,16 +4723,11 @@ class ViewState {
4547
4723
  return this.scaler.toDOM(this.heightMap.height, this.paddingTop);
4548
4724
  }
4549
4725
  }
4550
- /**
4551
- Indicates the range of the document that is in the visible
4552
- viewport.
4553
- */
4554
4726
  class Viewport {
4555
4727
  constructor(from, to) {
4556
4728
  this.from = from;
4557
4729
  this.to = to;
4558
4730
  }
4559
- eq(b) { return this.from == b.from && this.to == b.to; }
4560
4731
  }
4561
4732
  function lineStructure(from, to, state) {
4562
4733
  let ranges = [], pos = from, total = 0;
@@ -4682,7 +4853,7 @@ function buildTheme(main, spec, scopes) {
4682
4853
  });
4683
4854
  }
4684
4855
  const baseTheme = /*@__PURE__*/buildTheme("." + baseThemeID, {
4685
- "&": {
4856
+ "&.cm-editor": {
4686
4857
  position: "relative !important",
4687
4858
  boxSizing: "border-box",
4688
4859
  "&.cm-focused": {
@@ -4849,6 +5020,8 @@ class DOMObserver {
4849
5020
  this.scrollTargets = [];
4850
5021
  this.intersection = null;
4851
5022
  this.intersecting = false;
5023
+ this.gapIntersection = null;
5024
+ this.gaps = [];
4852
5025
  // Used to work around a Safari Selection/shadow DOM bug (#414)
4853
5026
  this._selectionRange = null;
4854
5027
  // Timeout for scheduling check of the parents that need scroll handlers
@@ -4889,13 +5062,17 @@ class DOMObserver {
4889
5062
  this.intersection = new IntersectionObserver(entries => {
4890
5063
  if (this.parentCheck < 0)
4891
5064
  this.parentCheck = setTimeout(this.listenForScroll.bind(this), 1000);
4892
- if (entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
5065
+ if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
4893
5066
  this.intersecting = !this.intersecting;
4894
5067
  if (this.intersecting != this.view.inView)
4895
5068
  this.onScrollChanged(document.createEvent("Event"));
4896
5069
  }
4897
5070
  }, {});
4898
5071
  this.intersection.observe(this.dom);
5072
+ this.gapIntersection = new IntersectionObserver(entries => {
5073
+ if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0)
5074
+ this.onScrollChanged(document.createEvent("Event"));
5075
+ }, {});
4899
5076
  }
4900
5077
  this.listenForScroll();
4901
5078
  }
@@ -4904,6 +5081,14 @@ class DOMObserver {
4904
5081
  this.flush();
4905
5082
  this.onScrollChanged(e);
4906
5083
  }
5084
+ updateGaps(gaps) {
5085
+ if (this.gapIntersection && (gaps.length != this.gaps.length || this.gaps.some((g, i) => g != gaps[i]))) {
5086
+ this.gapIntersection.disconnect();
5087
+ for (let gap of gaps)
5088
+ this.gapIntersection.observe(gap);
5089
+ this.gaps = gaps;
5090
+ }
5091
+ }
4907
5092
  onSelectionChange(event) {
4908
5093
  if (this.lastFlush < Date.now() - 50)
4909
5094
  this._selectionRange = null;
@@ -5019,20 +5204,12 @@ class DOMObserver {
5019
5204
  this.flush();
5020
5205
  }
5021
5206
  }
5022
- // Apply pending changes, if any
5023
- flush() {
5024
- if (this.delayedFlush >= 0)
5025
- return;
5026
- this.lastFlush = Date.now();
5207
+ processRecords() {
5027
5208
  let records = this.queue;
5028
5209
  for (let mut of this.observer.takeRecords())
5029
5210
  records.push(mut);
5030
5211
  if (records.length)
5031
5212
  this.queue = [];
5032
- let selection = this.selectionRange;
5033
- let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
5034
- if (records.length == 0 && !newSel)
5035
- return;
5036
5213
  let from = -1, to = -1, typeOver = false;
5037
5214
  for (let record of records) {
5038
5215
  let range = this.readMutation(record);
@@ -5048,17 +5225,26 @@ class DOMObserver {
5048
5225
  to = Math.max(range.to, to);
5049
5226
  }
5050
5227
  }
5228
+ return { from, to, typeOver };
5229
+ }
5230
+ // Apply pending changes, if any
5231
+ flush() {
5232
+ // Completely hold off flushing when pending keys are set—the code
5233
+ // managing those will make sure processRecords is called and the
5234
+ // view is resynchronized after
5235
+ if (this.delayedFlush >= 0 || this.view.inputState.pendingKey)
5236
+ return;
5237
+ this.lastFlush = Date.now();
5238
+ let { from, to, typeOver } = this.processRecords();
5239
+ let selection = this.selectionRange;
5240
+ let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
5241
+ if (from < 0 && !newSel)
5242
+ return;
5051
5243
  let startState = this.view.state;
5052
- if (from > -1 || newSel)
5053
- this.onChange(from, to, typeOver);
5054
- if (this.view.state == startState) { // The view wasn't updated
5055
- if (this.view.docView.dirty) {
5056
- this.ignore(() => this.view.docView.sync());
5057
- this.view.docView.dirty = 0 /* Not */;
5058
- }
5059
- if (newSel)
5060
- this.view.docView.updateSelection();
5061
- }
5244
+ this.onChange(from, to, typeOver);
5245
+ // The view wasn't updated
5246
+ if (this.view.state == startState)
5247
+ this.view.docView.reset(newSel);
5062
5248
  this.clearSelection();
5063
5249
  }
5064
5250
  readMutation(rec) {
@@ -5085,6 +5271,8 @@ class DOMObserver {
5085
5271
  this.stop();
5086
5272
  if (this.intersection)
5087
5273
  this.intersection.disconnect();
5274
+ if (this.gapIntersection)
5275
+ this.gapIntersection.disconnect();
5088
5276
  for (let dom of this.scrollTargets)
5089
5277
  dom.removeEventListener("scroll", this.onScroll);
5090
5278
  window.removeEventListener("scroll", this.onScroll);
@@ -5132,7 +5320,7 @@ function safariSelectionRangeHack(view) {
5132
5320
  function applyDOMChange(view, start, end, typeOver) {
5133
5321
  let change, newSel;
5134
5322
  let sel = view.state.selection.main, bounds;
5135
- if (start > -1 && (bounds = view.docView.domBoundsAround(start, end, 0))) {
5323
+ if (start > -1 && !view.state.readOnly && (bounds = view.docView.domBoundsAround(start, end, 0))) {
5136
5324
  let { from, to } = bounds;
5137
5325
  let selPoints = view.docView.impreciseHead || view.docView.impreciseAnchor ? [] : selectionPoints(view);
5138
5326
  let reader = new DOMReader(selPoints, view);
@@ -5193,9 +5381,9 @@ function applyDOMChange(view, start, end, typeOver) {
5193
5381
  (change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
5194
5382
  dispatchKey(view.contentDOM, "Backspace", 8)) ||
5195
5383
  (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
5196
- dispatchKey(view.contentDOM, "Delete", 46))) ||
5197
- browser.ios && view.inputState.flushIOSKey(view))
5384
+ dispatchKey(view.contentDOM, "Delete", 46)))) {
5198
5385
  return;
5386
+ }
5199
5387
  let text = change.insert.toString();
5200
5388
  if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
5201
5389
  return;
@@ -5285,8 +5473,9 @@ class DOMReader {
5285
5473
  if (next == end)
5286
5474
  break;
5287
5475
  let view = ContentView.get(cur), nextView = ContentView.get(next);
5288
- if ((view ? view.breakAfter : isBlockElement(cur)) ||
5289
- ((nextView ? nextView.breakAfter : isBlockElement(next)) && !(cur.nodeName == "BR" && !cur.cmIgnore)))
5476
+ if (view && nextView ? view.breakAfter :
5477
+ (view ? view.breakAfter : isBlockElement(cur)) ||
5478
+ (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
5290
5479
  this.text += this.lineBreak;
5291
5480
  cur = next;
5292
5481
  }
@@ -5389,6 +5578,7 @@ class EditorView {
5389
5578
  this.editorAttrs = {};
5390
5579
  this.contentAttrs = {};
5391
5580
  this.bidiCache = [];
5581
+ this.destroyed = false;
5392
5582
  /**
5393
5583
  @internal
5394
5584
  */
@@ -5414,7 +5604,7 @@ class EditorView {
5414
5604
  this.dom.appendChild(this.scrollDOM);
5415
5605
  this._dispatch = config.dispatch || ((tr) => this.update([tr]));
5416
5606
  this.dispatch = this.dispatch.bind(this);
5417
- this.root = (config.root || document);
5607
+ this.root = (config.root || getRoot(config.parent) || document);
5418
5608
  this.viewState = new ViewState(config.state || EditorState.create());
5419
5609
  this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec).update(this));
5420
5610
  this.observer = new DOMObserver(this, (from, to, typeOver) => {
@@ -5487,6 +5677,10 @@ class EditorView {
5487
5677
  throw new RangeError("Trying to update state with a transaction that doesn't start from the previous state.");
5488
5678
  state = tr.state;
5489
5679
  }
5680
+ if (this.destroyed) {
5681
+ this.viewState.state = state;
5682
+ return;
5683
+ }
5490
5684
  // When the phrases change, redraw the editor
5491
5685
  if (state.facet(EditorState.phrases) != this.state.facet(EditorState.phrases))
5492
5686
  return this.setState(state);
@@ -5536,6 +5730,10 @@ class EditorView {
5536
5730
  setState(newState) {
5537
5731
  if (this.updateState != 0 /* Idle */)
5538
5732
  throw new Error("Calls to EditorView.setState are not allowed while an update is in progress");
5733
+ if (this.destroyed) {
5734
+ this.viewState.state = newState;
5735
+ return;
5736
+ }
5539
5737
  this.updateState = 2 /* Updating */;
5540
5738
  try {
5541
5739
  for (let plugin of this.plugins)
@@ -5585,6 +5783,8 @@ class EditorView {
5585
5783
  @internal
5586
5784
  */
5587
5785
  measure(flush = true) {
5786
+ if (this.destroyed)
5787
+ return;
5588
5788
  if (this.measureScheduled > -1)
5589
5789
  cancelAnimationFrame(this.measureScheduled);
5590
5790
  this.measureScheduled = -1; // Prevent requestMeasure calls from scheduling another animation frame
@@ -5594,15 +5794,18 @@ class EditorView {
5594
5794
  try {
5595
5795
  for (let i = 0;; i++) {
5596
5796
  this.updateState = 1 /* Measuring */;
5797
+ let oldViewport = this.viewport;
5597
5798
  let changed = this.viewState.measure(this.docView, i > 0);
5598
- let measuring = this.measureRequests;
5599
- if (!changed && !measuring.length && this.viewState.scrollTo == null)
5799
+ if (!changed && !this.measureRequests.length && this.viewState.scrollTo == null)
5600
5800
  break;
5601
- this.measureRequests = [];
5602
5801
  if (i > 5) {
5603
5802
  console.warn("Viewport failed to stabilize");
5604
5803
  break;
5605
5804
  }
5805
+ let measuring = [];
5806
+ // Only run measure requests in this cycle when the viewport didn't change
5807
+ if (!(changed & 4 /* Viewport */))
5808
+ [this.measureRequests, measuring] = [measuring, this.measureRequests];
5606
5809
  let measured = measuring.map(m => {
5607
5810
  try {
5608
5811
  return m.read(this);
@@ -5639,7 +5842,7 @@ class EditorView {
5639
5842
  this.docView.scrollRangeIntoView(this.viewState.scrollTo);
5640
5843
  this.viewState.scrollTo = null;
5641
5844
  }
5642
- if (!(changed & 4 /* Viewport */) && this.measureRequests.length == 0)
5845
+ if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
5643
5846
  break;
5644
5847
  }
5645
5848
  }
@@ -5665,16 +5868,20 @@ class EditorView {
5665
5868
  });
5666
5869
  updateAttrs(this.dom, this.editorAttrs, editorAttrs);
5667
5870
  this.editorAttrs = editorAttrs;
5668
- let contentAttrs = combineAttrs(this.state.facet(contentAttributes), {
5871
+ let contentAttrs = {
5669
5872
  spellcheck: "false",
5670
5873
  autocorrect: "off",
5671
5874
  autocapitalize: "off",
5875
+ translate: "no",
5672
5876
  contenteditable: !this.state.facet(editable) ? "false" : contentEditablePlainTextSupported() ? "plaintext-only" : "true",
5673
5877
  class: "cm-content",
5674
5878
  style: `${browser.tabSize}: ${this.state.tabSize}`,
5675
5879
  role: "textbox",
5676
5880
  "aria-multiline": "true"
5677
- });
5881
+ };
5882
+ if (this.state.readOnly)
5883
+ contentAttrs["aria-readonly"] = "true";
5884
+ combineAttrs(this.state.facet(contentAttributes), contentAttrs);
5678
5885
  updateAttrs(this.contentDOM, this.contentAttrs, contentAttrs);
5679
5886
  this.contentAttrs = contentAttrs;
5680
5887
  }
@@ -5972,11 +6179,13 @@ class EditorView {
5972
6179
  destroy() {
5973
6180
  for (let plugin of this.plugins)
5974
6181
  plugin.destroy(this);
6182
+ this.plugins = [];
5975
6183
  this.inputState.destroy();
5976
6184
  this.dom.remove();
5977
6185
  this.observer.destroy();
5978
6186
  if (this.measureScheduled > -1)
5979
6187
  cancelAnimationFrame(this.measureScheduled);
6188
+ this.destroyed = true;
5980
6189
  }
5981
6190
  /**
5982
6191
  Facet that can be used to add DOM event handlers. The value
@@ -6064,12 +6273,12 @@ every time the view updates.
6064
6273
  */
6065
6274
  EditorView.updateListener = updateListener;
6066
6275
  /**
6067
- Facet that controls whether the editor content is editable. When
6068
- its highest-precedence value is `false`, editing is disabled,
6069
- and the content element will no longer have its
6070
- `contenteditable` attribute set to `true`. (Note that this
6071
- doesn't affect API calls that change the editor content, even
6072
- when those are bound to keys or buttons.)
6276
+ Facet that controls whether the editor content DOM is editable.
6277
+ When its highest-precedence value is `false`, the element will
6278
+ not longer have its `contenteditable` attribute set. (Note that
6279
+ this doesn't affect API calls that change the editor content,
6280
+ even when those are bound to keys or buttons. See the
6281
+ [`readOnly`](https://codemirror.net/6/docs/ref/#state.EditorState.readOnly) facet for that.)
6073
6282
  */
6074
6283
  EditorView.editable = editable;
6075
6284
  /**
@@ -6164,11 +6373,7 @@ class CachedOrder {
6164
6373
  }
6165
6374
  }
6166
6375
 
6167
- const currentPlatform = typeof navigator == "undefined" ? "key"
6168
- : /*@__PURE__*//Mac/.test(navigator.platform) ? "mac"
6169
- : /*@__PURE__*//Win/.test(navigator.platform) ? "win"
6170
- : /*@__PURE__*//Linux|X11/.test(navigator.platform) ? "linux"
6171
- : "key";
6376
+ const currentPlatform = browser.mac ? "mac" : browser.windows ? "win" : browser.linux ? "linux" : "key";
6172
6377
  function normalizeKeyName(name, platform) {
6173
6378
  const parts = name.split(/-(?!$)/);
6174
6379
  let result = parts[parts.length - 1];
@@ -6674,7 +6879,7 @@ class MatchDecorator {
6674
6879
  }
6675
6880
 
6676
6881
  const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g";
6677
- const Specials = /*@__PURE__*/new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
6882
+ const Specials = /*@__PURE__*/new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
6678
6883
  const Names = {
6679
6884
  0: "null",
6680
6885
  7: "bell",
@@ -6689,6 +6894,8 @@ const Names = {
6689
6894
  8206: "left-to-right mark",
6690
6895
  8207: "right-to-left mark",
6691
6896
  8232: "line separator",
6897
+ 8237: "left-to-right override",
6898
+ 8238: "right-to-left override",
6692
6899
  8233: "paragraph separator",
6693
6900
  65279: "zero width no-break space",
6694
6901
  65532: "object replacement"
@@ -6855,7 +7062,7 @@ DOM class.
6855
7062
  function highlightActiveLine() {
6856
7063
  return activeLineHighlighter;
6857
7064
  }
6858
- const lineDeco = /*@__PURE__*/Decoration.line({ attributes: { class: "cm-activeLine" } });
7065
+ const lineDeco = /*@__PURE__*/Decoration.line({ class: "cm-activeLine" });
6859
7066
  const activeLineHighlighter = /*@__PURE__*/ViewPlugin.fromClass(class {
6860
7067
  constructor(view) {
6861
7068
  this.decorations = this.getDeco(view);