@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/CHANGELOG.md +50 -0
- package/dist/index.cjs +327 -120
- package/dist/index.d.ts +17 -8
- package/dist/index.js +328 -121
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -9,7 +9,17 @@ var text = require('@codemirror/text');
|
|
|
9
9
|
var w3cKeyname = require('w3c-keyname');
|
|
10
10
|
|
|
11
11
|
function getSelection(root) {
|
|
12
|
-
|
|
12
|
+
let target;
|
|
13
|
+
// Browsers differ on whether shadow roots have a getSelection
|
|
14
|
+
// method. If it exists, use that, otherwise, call it on the
|
|
15
|
+
// document.
|
|
16
|
+
if (root.nodeType == 11) { // Shadow root
|
|
17
|
+
target = root.getSelection ? root : root.ownerDocument;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
target = root;
|
|
21
|
+
}
|
|
22
|
+
return target.getSelection();
|
|
13
23
|
}
|
|
14
24
|
function contains(dom, node) {
|
|
15
25
|
return node ? dom.contains(node.nodeType != 1 ? node.parentNode : node) : false;
|
|
@@ -241,6 +251,14 @@ function contentEditablePlainTextSupported() {
|
|
|
241
251
|
}
|
|
242
252
|
return _plainTextSupported;
|
|
243
253
|
}
|
|
254
|
+
function getRoot(node) {
|
|
255
|
+
while (node) {
|
|
256
|
+
node = node.assignedSlot || node.parentNode;
|
|
257
|
+
if (node && (node.nodeType == 9 || node.nodeType == 11 && node.host))
|
|
258
|
+
return node;
|
|
259
|
+
}
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
244
262
|
|
|
245
263
|
class DOMPos {
|
|
246
264
|
constructor(node, offset, precise = true) {
|
|
@@ -438,6 +456,7 @@ class ContentView {
|
|
|
438
456
|
(this.breakAfter ? "#" : "");
|
|
439
457
|
}
|
|
440
458
|
static get(node) { return node.cmView; }
|
|
459
|
+
get isEditable() { return true; }
|
|
441
460
|
}
|
|
442
461
|
ContentView.prototype.breakAfter = 0;
|
|
443
462
|
// Remove a DOM node and return its next sibling.
|
|
@@ -485,15 +504,18 @@ const gecko = !ie && /gecko\/(\d+)/i.test(nav.userAgent);
|
|
|
485
504
|
const chrome = !ie && /Chrome\/(\d+)/.exec(nav.userAgent);
|
|
486
505
|
const webkit = "webkitFontSmoothing" in doc.documentElement.style;
|
|
487
506
|
const safari = !ie && /Apple Computer/.test(nav.vendor);
|
|
507
|
+
const ios = safari && (/Mobile\/\w+/.test(nav.userAgent) || nav.maxTouchPoints > 2);
|
|
488
508
|
var browser = {
|
|
489
|
-
mac: /Mac/.test(nav.platform),
|
|
509
|
+
mac: ios || /Mac/.test(nav.platform),
|
|
510
|
+
windows: /Win/.test(nav.platform),
|
|
511
|
+
linux: /Linux|X11/.test(nav.platform),
|
|
490
512
|
ie,
|
|
491
513
|
ie_version: ie_upto10 ? doc.documentMode || 6 : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : 0,
|
|
492
514
|
gecko,
|
|
493
515
|
gecko_version: gecko ? +(/Firefox\/(\d+)/.exec(nav.userAgent) || [0, 0])[1] : 0,
|
|
494
516
|
chrome: !!chrome,
|
|
495
517
|
chrome_version: chrome ? +chrome[1] : 0,
|
|
496
|
-
ios
|
|
518
|
+
ios,
|
|
497
519
|
android: /Android\b/.test(nav.userAgent),
|
|
498
520
|
webkit,
|
|
499
521
|
safari,
|
|
@@ -715,6 +737,7 @@ class WidgetView extends InlineView {
|
|
|
715
737
|
}
|
|
716
738
|
return (pos == 0 && side > 0 || pos == this.length && side <= 0) ? rect : flattenRect(rect, pos == 0);
|
|
717
739
|
}
|
|
740
|
+
get isEditable() { return false; }
|
|
718
741
|
}
|
|
719
742
|
class CompositionView extends WidgetView {
|
|
720
743
|
domAtPos(pos) { return new DOMPos(this.widget.text, pos); }
|
|
@@ -726,6 +749,38 @@ class CompositionView extends WidgetView {
|
|
|
726
749
|
ignoreMutation() { return false; }
|
|
727
750
|
get overrideDOMText() { return null; }
|
|
728
751
|
coordsAt(pos, side) { return textCoords(this.widget.text, pos, side); }
|
|
752
|
+
get isEditable() { return true; }
|
|
753
|
+
}
|
|
754
|
+
// These are drawn around uneditable widgets to avoid a number of
|
|
755
|
+
// browser bugs that show up when the cursor is directly next to
|
|
756
|
+
// uneditable inline content.
|
|
757
|
+
class WidgetBufferView extends InlineView {
|
|
758
|
+
constructor(side) {
|
|
759
|
+
super();
|
|
760
|
+
this.side = side;
|
|
761
|
+
}
|
|
762
|
+
get length() { return 0; }
|
|
763
|
+
merge() { return false; }
|
|
764
|
+
become(other) {
|
|
765
|
+
return other instanceof WidgetBufferView && other.side == this.side;
|
|
766
|
+
}
|
|
767
|
+
slice() { return new WidgetBufferView(this.side); }
|
|
768
|
+
sync() {
|
|
769
|
+
if (!this.dom)
|
|
770
|
+
this.setDOM(document.createTextNode("\u200b"));
|
|
771
|
+
else if (this.dirty && this.dom.nodeValue != "\u200b")
|
|
772
|
+
this.dom.nodeValue = "\u200b";
|
|
773
|
+
}
|
|
774
|
+
getSide() { return this.side; }
|
|
775
|
+
domAtPos(pos) { return DOMPos.before(this.dom); }
|
|
776
|
+
domBoundsAround() { return null; }
|
|
777
|
+
coordsAt(pos) {
|
|
778
|
+
let rects = clientRectsFor(this.dom);
|
|
779
|
+
return rects[rects.length - 1];
|
|
780
|
+
}
|
|
781
|
+
get overrideDOMText() {
|
|
782
|
+
return text.Text.of([this.dom.nodeValue.replace(/\u200b/g, "")]);
|
|
783
|
+
}
|
|
729
784
|
}
|
|
730
785
|
function mergeInlineChildren(parent, from, to, elts, openStart, openEnd) {
|
|
731
786
|
let cur = parent.childCursor();
|
|
@@ -1221,14 +1276,17 @@ class LineView extends ContentView {
|
|
|
1221
1276
|
}
|
|
1222
1277
|
// Only called when building a line view in ContentBuilder
|
|
1223
1278
|
addLineDeco(deco) {
|
|
1224
|
-
let attrs = deco.spec.attributes;
|
|
1279
|
+
let attrs = deco.spec.attributes, cls = deco.spec.class;
|
|
1225
1280
|
if (attrs)
|
|
1226
1281
|
this.attrs = combineAttrs(attrs, this.attrs || {});
|
|
1282
|
+
if (cls)
|
|
1283
|
+
this.attrs = combineAttrs(attrs, { class: cls });
|
|
1227
1284
|
}
|
|
1228
1285
|
domAtPos(pos) {
|
|
1229
1286
|
return inlineDOMAtPos(this.dom, this.children, pos);
|
|
1230
1287
|
}
|
|
1231
1288
|
sync(track) {
|
|
1289
|
+
var _a;
|
|
1232
1290
|
if (!this.dom || (this.dirty & 4 /* Attrs */)) {
|
|
1233
1291
|
this.setDOM(document.createElement("div"));
|
|
1234
1292
|
this.dom.className = "cm-line";
|
|
@@ -1244,7 +1302,7 @@ class LineView extends ContentView {
|
|
|
1244
1302
|
while (last && ContentView.get(last) instanceof MarkView)
|
|
1245
1303
|
last = last.lastChild;
|
|
1246
1304
|
if (!last ||
|
|
1247
|
-
last.nodeName != "BR" && ContentView.get(last)
|
|
1305
|
+
last.nodeName != "BR" && ((_a = ContentView.get(last)) === null || _a === void 0 ? void 0 : _a.isEditable) == false &&
|
|
1248
1306
|
(!browser.ios || !this.children.some(ch => ch instanceof TextView))) {
|
|
1249
1307
|
let hack = document.createElement("BR");
|
|
1250
1308
|
hack.cmIgnore = true;
|
|
@@ -1343,6 +1401,9 @@ class ContentBuilder {
|
|
|
1343
1401
|
this.content = [];
|
|
1344
1402
|
this.curLine = null;
|
|
1345
1403
|
this.breakAtStart = 0;
|
|
1404
|
+
this.pendingBuffer = 0 /* No */;
|
|
1405
|
+
// Set to false directly after a widget that covers the position after it
|
|
1406
|
+
this.atCursorPos = true;
|
|
1346
1407
|
this.openStart = -1;
|
|
1347
1408
|
this.openEnd = -1;
|
|
1348
1409
|
this.text = "";
|
|
@@ -1357,23 +1418,31 @@ class ContentBuilder {
|
|
|
1357
1418
|
return !last.breakAfter && !(last instanceof BlockWidgetView && last.type == exports.BlockType.WidgetBefore);
|
|
1358
1419
|
}
|
|
1359
1420
|
getLine() {
|
|
1360
|
-
if (!this.curLine)
|
|
1421
|
+
if (!this.curLine) {
|
|
1361
1422
|
this.content.push(this.curLine = new LineView);
|
|
1423
|
+
this.atCursorPos = true;
|
|
1424
|
+
}
|
|
1362
1425
|
return this.curLine;
|
|
1363
1426
|
}
|
|
1364
|
-
|
|
1427
|
+
flushBuffer(active) {
|
|
1428
|
+
if (this.pendingBuffer) {
|
|
1429
|
+
this.curLine.append(wrapMarks(new WidgetBufferView(-1), active), active.length);
|
|
1430
|
+
this.pendingBuffer = 0 /* No */;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
addBlockWidget(view) {
|
|
1434
|
+
this.flushBuffer([]);
|
|
1365
1435
|
this.curLine = null;
|
|
1366
1436
|
this.content.push(view);
|
|
1367
1437
|
}
|
|
1368
|
-
finish() {
|
|
1438
|
+
finish(openEnd) {
|
|
1439
|
+
if (!openEnd)
|
|
1440
|
+
this.flushBuffer([]);
|
|
1441
|
+
else
|
|
1442
|
+
this.pendingBuffer = 0 /* No */;
|
|
1369
1443
|
if (!this.posCovered())
|
|
1370
1444
|
this.getLine();
|
|
1371
1445
|
}
|
|
1372
|
-
wrapMarks(view, active) {
|
|
1373
|
-
for (let mark of active)
|
|
1374
|
-
view = new MarkView(mark, [view], view.length);
|
|
1375
|
-
return view;
|
|
1376
|
-
}
|
|
1377
1446
|
buildText(length, active, openStart) {
|
|
1378
1447
|
while (length > 0) {
|
|
1379
1448
|
if (this.textOff == this.text.length) {
|
|
@@ -1388,6 +1457,7 @@ class ContentBuilder {
|
|
|
1388
1457
|
this.content[this.content.length - 1].breakAfter = 1;
|
|
1389
1458
|
else
|
|
1390
1459
|
this.breakAtStart = 1;
|
|
1460
|
+
this.flushBuffer([]);
|
|
1391
1461
|
this.curLine = null;
|
|
1392
1462
|
length--;
|
|
1393
1463
|
continue;
|
|
@@ -1398,7 +1468,9 @@ class ContentBuilder {
|
|
|
1398
1468
|
}
|
|
1399
1469
|
}
|
|
1400
1470
|
let take = Math.min(this.text.length - this.textOff, length, 512 /* Chunk */);
|
|
1401
|
-
this.
|
|
1471
|
+
this.flushBuffer(active);
|
|
1472
|
+
this.getLine().append(wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
|
|
1473
|
+
this.atCursorPos = true;
|
|
1402
1474
|
this.textOff += take;
|
|
1403
1475
|
length -= take;
|
|
1404
1476
|
openStart = 0;
|
|
@@ -1417,11 +1489,23 @@ class ContentBuilder {
|
|
|
1417
1489
|
let { type } = deco;
|
|
1418
1490
|
if (type == exports.BlockType.WidgetAfter && !this.posCovered())
|
|
1419
1491
|
this.getLine();
|
|
1420
|
-
this.
|
|
1492
|
+
this.addBlockWidget(new BlockWidgetView(deco.widget || new NullWidget("div"), len, type));
|
|
1421
1493
|
}
|
|
1422
1494
|
else {
|
|
1423
|
-
let
|
|
1424
|
-
this.
|
|
1495
|
+
let view = WidgetView.create(deco.widget || new NullWidget("span"), len, deco.startSide);
|
|
1496
|
+
let cursorBefore = this.atCursorPos && !view.isEditable && openStart <= active.length && (from < to || deco.startSide > 0);
|
|
1497
|
+
let cursorAfter = !view.isEditable && (from < to || deco.startSide <= 0);
|
|
1498
|
+
let line = this.getLine();
|
|
1499
|
+
if (this.pendingBuffer == 2 /* IfCursor */ && !cursorBefore)
|
|
1500
|
+
this.pendingBuffer = 0 /* No */;
|
|
1501
|
+
this.flushBuffer(active);
|
|
1502
|
+
if (cursorBefore) {
|
|
1503
|
+
line.append(wrapMarks(new WidgetBufferView(1), active), openStart);
|
|
1504
|
+
openStart = active.length + Math.max(0, openStart - active.length);
|
|
1505
|
+
}
|
|
1506
|
+
line.append(wrapMarks(view, active), openStart);
|
|
1507
|
+
this.atCursorPos = cursorAfter;
|
|
1508
|
+
this.pendingBuffer = !cursorAfter ? 0 /* No */ : from < to ? 1 /* Yes */ : 2 /* IfCursor */;
|
|
1425
1509
|
}
|
|
1426
1510
|
}
|
|
1427
1511
|
else if (this.doc.lineAt(this.pos).from == this.pos) { // Line decoration
|
|
@@ -1447,10 +1531,15 @@ class ContentBuilder {
|
|
|
1447
1531
|
builder.openEnd = rangeset.RangeSet.spans(decorations, from, to, builder);
|
|
1448
1532
|
if (builder.openStart < 0)
|
|
1449
1533
|
builder.openStart = builder.openEnd;
|
|
1450
|
-
builder.finish();
|
|
1534
|
+
builder.finish(builder.openEnd);
|
|
1451
1535
|
return builder;
|
|
1452
1536
|
}
|
|
1453
1537
|
}
|
|
1538
|
+
function wrapMarks(view, active) {
|
|
1539
|
+
for (let mark of active)
|
|
1540
|
+
view = new MarkView(mark, [view], view.length);
|
|
1541
|
+
return view;
|
|
1542
|
+
}
|
|
1454
1543
|
class NullWidget extends WidgetType {
|
|
1455
1544
|
constructor(tag) {
|
|
1456
1545
|
super();
|
|
@@ -1783,7 +1872,9 @@ class ViewUpdate {
|
|
|
1783
1872
|
this.flags |= 2 /* Height */;
|
|
1784
1873
|
}
|
|
1785
1874
|
/**
|
|
1786
|
-
Tells you whether the viewport
|
|
1875
|
+
Tells you whether the [viewport](https://codemirror.net/6/docs/ref/#view.EditorView.viewport) or
|
|
1876
|
+
[visible ranges](https://codemirror.net/6/docs/ref/#view.EditorView.visibleRanges) changed in this
|
|
1877
|
+
update.
|
|
1787
1878
|
*/
|
|
1788
1879
|
get viewportChanged() {
|
|
1789
1880
|
return (this.flags & 4 /* Viewport */) > 0;
|
|
@@ -1799,7 +1890,7 @@ class ViewUpdate {
|
|
|
1799
1890
|
or the lines or characters within it has changed.
|
|
1800
1891
|
*/
|
|
1801
1892
|
get geometryChanged() {
|
|
1802
|
-
return this.docChanged || (this.flags & (
|
|
1893
|
+
return this.docChanged || (this.flags & (8 /* Geometry */ | 2 /* Height */)) > 0;
|
|
1803
1894
|
}
|
|
1804
1895
|
/**
|
|
1805
1896
|
True when this update indicates a focus change.
|
|
@@ -1884,7 +1975,7 @@ class DocView extends ContentView {
|
|
|
1884
1975
|
changedRanges = ChangedRange.extendWithRanges(changedRanges, decoDiff);
|
|
1885
1976
|
let pointerSel = update.transactions.some(tr => tr.isUserEvent("select.pointer"));
|
|
1886
1977
|
if (this.dirty == 0 /* Not */ && changedRanges.length == 0 &&
|
|
1887
|
-
!(update.flags &
|
|
1978
|
+
!(update.flags & 4 /* Viewport */) &&
|
|
1888
1979
|
update.state.selection.main.from >= this.view.viewport.from &&
|
|
1889
1980
|
update.state.selection.main.to <= this.view.viewport.to) {
|
|
1890
1981
|
this.updateSelection(forceSelection, pointerSel);
|
|
@@ -1895,6 +1986,14 @@ class DocView extends ContentView {
|
|
|
1895
1986
|
return true;
|
|
1896
1987
|
}
|
|
1897
1988
|
}
|
|
1989
|
+
reset(sel) {
|
|
1990
|
+
if (this.dirty) {
|
|
1991
|
+
this.view.observer.ignore(() => this.view.docView.sync());
|
|
1992
|
+
this.dirty = 0 /* Not */;
|
|
1993
|
+
}
|
|
1994
|
+
if (sel)
|
|
1995
|
+
this.updateSelection();
|
|
1996
|
+
}
|
|
1898
1997
|
// Used both by update and checkLayout do perform the actual DOM
|
|
1899
1998
|
// update
|
|
1900
1999
|
updateInner(changes, deco, oldLength, forceSelection = false, pointerSel = false) {
|
|
@@ -1919,6 +2018,12 @@ class DocView extends ContentView {
|
|
|
1919
2018
|
this.updateSelection(forceSelection, pointerSel);
|
|
1920
2019
|
this.dom.style.height = "";
|
|
1921
2020
|
});
|
|
2021
|
+
let gaps = [];
|
|
2022
|
+
if (this.view.viewport.from || this.view.viewport.to < this.view.state.doc.length)
|
|
2023
|
+
for (let child of this.children)
|
|
2024
|
+
if (child instanceof BlockWidgetView && child.widget instanceof BlockGapWidget)
|
|
2025
|
+
gaps.push(child.dom);
|
|
2026
|
+
observer.updateGaps(gaps);
|
|
1922
2027
|
}
|
|
1923
2028
|
updateChildren(changes, deco, oldLength) {
|
|
1924
2029
|
let cursor = this.childCursor(oldLength);
|
|
@@ -2018,6 +2123,14 @@ class DocView extends ContentView {
|
|
|
2018
2123
|
!isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) ||
|
|
2019
2124
|
!isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) {
|
|
2020
2125
|
this.view.observer.ignore(() => {
|
|
2126
|
+
// Chrome Android will hide the virtual keyboard when tapping
|
|
2127
|
+
// inside an uneditable node, and not bring it back when we
|
|
2128
|
+
// move the cursor to its proper position. This tries to
|
|
2129
|
+
// restore the keyboard by cycling focus.
|
|
2130
|
+
if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) && inUneditable(domSel.focusNode, this.dom)) {
|
|
2131
|
+
this.dom.blur();
|
|
2132
|
+
this.dom.focus({ preventScroll: true });
|
|
2133
|
+
}
|
|
2021
2134
|
let rawSel = getSelection(this.root);
|
|
2022
2135
|
if (main.empty) {
|
|
2023
2136
|
// Work around https://bugzilla.mozilla.org/show_bug.cgi?id=1612076
|
|
@@ -2325,6 +2438,14 @@ function findChangedDeco(a, b, diff) {
|
|
|
2325
2438
|
rangeset.RangeSet.compare(a, b, diff, comp);
|
|
2326
2439
|
return comp.changes;
|
|
2327
2440
|
}
|
|
2441
|
+
function inUneditable(node, inside) {
|
|
2442
|
+
for (let cur = node; cur && cur != inside; cur = cur.assignedSlot || cur.parentNode) {
|
|
2443
|
+
if (cur.nodeType == 1 && cur.contentEditable == 'false') {
|
|
2444
|
+
return true;
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
return false;
|
|
2448
|
+
}
|
|
2328
2449
|
|
|
2329
2450
|
/**
|
|
2330
2451
|
Used to indicate [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
|
|
@@ -2771,6 +2892,7 @@ function domPosInText(node, x, y) {
|
|
|
2771
2892
|
return { node, offset: closestOffset > -1 ? closestOffset : generalSide > 0 ? node.nodeValue.length : 0 };
|
|
2772
2893
|
}
|
|
2773
2894
|
function posAtCoords(view, { x, y }, precise, bias = -1) {
|
|
2895
|
+
var _a;
|
|
2774
2896
|
let content = view.contentDOM.getBoundingClientRect(), block;
|
|
2775
2897
|
let halfLine = view.defaultLineHeight / 2;
|
|
2776
2898
|
for (let bounced = false;;) {
|
|
@@ -2788,25 +2910,27 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
|
|
|
2788
2910
|
y = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
|
|
2789
2911
|
}
|
|
2790
2912
|
let lineStart = block.from;
|
|
2913
|
+
// Clip x to the viewport sides
|
|
2791
2914
|
x = Math.max(content.left + 1, Math.min(content.right - 1, x));
|
|
2792
2915
|
// If this is outside of the rendered viewport, we can't determine a position
|
|
2793
2916
|
if (lineStart < view.viewport.from)
|
|
2794
2917
|
return view.viewport.from == 0 ? 0 : posAtCoordsImprecise(view, content, block, x, y);
|
|
2795
2918
|
if (lineStart > view.viewport.to)
|
|
2796
2919
|
return view.viewport.to == view.state.doc.length ? view.state.doc.length : posAtCoordsImprecise(view, content, block, x, y);
|
|
2797
|
-
//
|
|
2798
|
-
let
|
|
2920
|
+
// Prefer ShadowRootOrDocument.elementFromPoint if present, fall back to document if not
|
|
2921
|
+
let doc = view.dom.ownerDocument;
|
|
2922
|
+
let element = (view.root.elementFromPoint ? view.root : doc).elementFromPoint(x, y);
|
|
2799
2923
|
// There's visible editor content under the point, so we can try
|
|
2800
2924
|
// using caret(Position|Range)FromPoint as a shortcut
|
|
2801
2925
|
let node, offset = -1;
|
|
2802
|
-
if (element && view.contentDOM.contains(element) &&
|
|
2803
|
-
if (
|
|
2804
|
-
let pos =
|
|
2926
|
+
if (element && view.contentDOM.contains(element) && ((_a = view.docView.nearest(element)) === null || _a === void 0 ? void 0 : _a.isEditable) != false) {
|
|
2927
|
+
if (doc.caretPositionFromPoint) {
|
|
2928
|
+
let pos = doc.caretPositionFromPoint(x, y);
|
|
2805
2929
|
if (pos)
|
|
2806
2930
|
({ offsetNode: node, offset } = pos);
|
|
2807
2931
|
}
|
|
2808
|
-
else if (
|
|
2809
|
-
let range =
|
|
2932
|
+
else if (doc.caretRangeFromPoint) {
|
|
2933
|
+
let range = doc.caretRangeFromPoint(x, y);
|
|
2810
2934
|
if (range) {
|
|
2811
2935
|
({ startContainer: node, startOffset: offset } = range);
|
|
2812
2936
|
if (browser.safari && isSuspiciousCaretResult(node, offset, x))
|
|
@@ -2940,7 +3064,23 @@ class InputState {
|
|
|
2940
3064
|
constructor(view) {
|
|
2941
3065
|
this.lastKeyCode = 0;
|
|
2942
3066
|
this.lastKeyTime = 0;
|
|
2943
|
-
|
|
3067
|
+
// On iOS, some keys need to have their default behavior happen
|
|
3068
|
+
// (after which we retroactively handle them and reset the DOM) to
|
|
3069
|
+
// avoid messing up the virtual keyboard state.
|
|
3070
|
+
//
|
|
3071
|
+
// On Chrome Android, backspace near widgets is just completely
|
|
3072
|
+
// broken, and there are no key events, so we need to handle the
|
|
3073
|
+
// beforeinput event. Deleting stuff will often create a flurry of
|
|
3074
|
+
// events, and interrupting it before it is done just makes
|
|
3075
|
+
// subsequent events even more broken, so again, we hold off doing
|
|
3076
|
+
// anything until the browser is finished with whatever it is trying
|
|
3077
|
+
// to do.
|
|
3078
|
+
//
|
|
3079
|
+
// setPendingKey sets this, causing the DOM observer to pause for a
|
|
3080
|
+
// bit, and setting an animation frame (which seems the most
|
|
3081
|
+
// reliable way to detect 'browser is done flailing') to fire a fake
|
|
3082
|
+
// key event and re-sync the view again.
|
|
3083
|
+
this.pendingKey = undefined;
|
|
2944
3084
|
this.lastSelectionOrigin = null;
|
|
2945
3085
|
this.lastSelectionTime = 0;
|
|
2946
3086
|
this.lastEscPress = 0;
|
|
@@ -3049,20 +3189,27 @@ class InputState {
|
|
|
3049
3189
|
// state. So we let it go through, and then, in
|
|
3050
3190
|
// applyDOMChange, notify key handlers of it and reset to
|
|
3051
3191
|
// the state they produce.
|
|
3052
|
-
|
|
3192
|
+
let pending;
|
|
3193
|
+
if (browser.ios && (pending = PendingKeys.find(key => key.keyCode == event.keyCode)) &&
|
|
3053
3194
|
!(event.ctrlKey || event.altKey || event.metaKey) && !event.synthetic) {
|
|
3054
|
-
this.
|
|
3055
|
-
setTimeout(() => this.flushIOSKey(view), 250);
|
|
3195
|
+
this.setPendingKey(view, pending);
|
|
3056
3196
|
return true;
|
|
3057
3197
|
}
|
|
3058
3198
|
return false;
|
|
3059
3199
|
}
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3200
|
+
setPendingKey(view, pending) {
|
|
3201
|
+
this.pendingKey = pending;
|
|
3202
|
+
requestAnimationFrame(() => {
|
|
3203
|
+
if (!this.pendingKey)
|
|
3204
|
+
return false;
|
|
3205
|
+
let key = this.pendingKey;
|
|
3206
|
+
this.pendingKey = undefined;
|
|
3207
|
+
view.observer.processRecords();
|
|
3208
|
+
let startState = view.state;
|
|
3209
|
+
dispatchKey(view.contentDOM, key.key, key.keyCode);
|
|
3210
|
+
if (view.state == startState)
|
|
3211
|
+
view.docView.reset(true);
|
|
3212
|
+
});
|
|
3066
3213
|
}
|
|
3067
3214
|
ignoreDuringComposition(event) {
|
|
3068
3215
|
if (!/^key/.test(event.type))
|
|
@@ -3109,6 +3256,11 @@ class InputState {
|
|
|
3109
3256
|
this.mouseSelection.destroy();
|
|
3110
3257
|
}
|
|
3111
3258
|
}
|
|
3259
|
+
const PendingKeys = [
|
|
3260
|
+
{ key: "Backspace", keyCode: 8, inputType: "deleteContentBackward" },
|
|
3261
|
+
{ key: "Enter", keyCode: 13, inputType: "insertParagraph" },
|
|
3262
|
+
{ key: "Delete", keyCode: 46, inputType: "deleteContentForward" }
|
|
3263
|
+
];
|
|
3112
3264
|
// Key codes for modifier keys
|
|
3113
3265
|
const modifierCodes = [16, 17, 18, 20, 91, 92, 224, 225];
|
|
3114
3266
|
class MouseSelection {
|
|
@@ -3225,7 +3377,7 @@ function capturePaste(view) {
|
|
|
3225
3377
|
function doPaste(view, input) {
|
|
3226
3378
|
let { state: state$1 } = view, changes, i = 1, text = state$1.toText(input);
|
|
3227
3379
|
let byLine = text.lines == state$1.selection.ranges.length;
|
|
3228
|
-
let linewise = lastLinewiseCopy && state$1.selection.ranges.every(r => r.empty) && lastLinewiseCopy == text.toString();
|
|
3380
|
+
let linewise = lastLinewiseCopy != null && state$1.selection.ranges.every(r => r.empty) && lastLinewiseCopy == text.toString();
|
|
3229
3381
|
if (linewise) {
|
|
3230
3382
|
let lastLine = -1;
|
|
3231
3383
|
changes = state$1.changeByRange(range => {
|
|
@@ -3404,8 +3556,10 @@ function dropText(view, event, text, direct) {
|
|
|
3404
3556
|
});
|
|
3405
3557
|
}
|
|
3406
3558
|
handlers.drop = (view, event) => {
|
|
3407
|
-
if (!event.dataTransfer
|
|
3559
|
+
if (!event.dataTransfer)
|
|
3408
3560
|
return;
|
|
3561
|
+
if (view.state.readOnly)
|
|
3562
|
+
return event.preventDefault();
|
|
3409
3563
|
let files = event.dataTransfer.files;
|
|
3410
3564
|
if (files && files.length) { // For a file drop, read the file's text.
|
|
3411
3565
|
event.preventDefault();
|
|
@@ -3430,13 +3584,12 @@ handlers.drop = (view, event) => {
|
|
|
3430
3584
|
}
|
|
3431
3585
|
};
|
|
3432
3586
|
handlers.paste = (view, event) => {
|
|
3433
|
-
if (
|
|
3434
|
-
return;
|
|
3587
|
+
if (view.state.readOnly)
|
|
3588
|
+
return event.preventDefault();
|
|
3435
3589
|
view.observer.flush();
|
|
3436
3590
|
let data = brokenClipboardAPI ? null : event.clipboardData;
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
doPaste(view, text);
|
|
3591
|
+
if (data) {
|
|
3592
|
+
doPaste(view, data.getData("text/plain"));
|
|
3440
3593
|
event.preventDefault();
|
|
3441
3594
|
}
|
|
3442
3595
|
else {
|
|
@@ -3485,7 +3638,7 @@ function copiedRange(state) {
|
|
|
3485
3638
|
let lastLinewiseCopy = null;
|
|
3486
3639
|
handlers.copy = handlers.cut = (view, event) => {
|
|
3487
3640
|
let { text, ranges, linewise } = copiedRange(view.state);
|
|
3488
|
-
if (!text)
|
|
3641
|
+
if (!text && !linewise)
|
|
3489
3642
|
return;
|
|
3490
3643
|
lastLinewiseCopy = linewise ? text : null;
|
|
3491
3644
|
let data = brokenClipboardAPI ? null : event.clipboardData;
|
|
@@ -3497,7 +3650,7 @@ handlers.copy = handlers.cut = (view, event) => {
|
|
|
3497
3650
|
else {
|
|
3498
3651
|
captureCopy(view, text);
|
|
3499
3652
|
}
|
|
3500
|
-
if (event.type == "cut" && view.state.
|
|
3653
|
+
if (event.type == "cut" && !view.state.readOnly)
|
|
3501
3654
|
view.dispatch({
|
|
3502
3655
|
changes: ranges,
|
|
3503
3656
|
scrollIntoView: true,
|
|
@@ -3553,6 +3706,31 @@ handlers.compositionend = view => {
|
|
|
3553
3706
|
handlers.contextmenu = view => {
|
|
3554
3707
|
view.inputState.lastContextMenu = Date.now();
|
|
3555
3708
|
};
|
|
3709
|
+
handlers.beforeinput = (view, event) => {
|
|
3710
|
+
var _a;
|
|
3711
|
+
// Because Chrome Android doesn't fire useful key events, use
|
|
3712
|
+
// beforeinput to detect backspace (and possibly enter and delete,
|
|
3713
|
+
// but those usually don't even seem to fire beforeinput events at
|
|
3714
|
+
// the moment) and fake a key event for it.
|
|
3715
|
+
//
|
|
3716
|
+
// (preventDefault on beforeinput, though supported in the spec,
|
|
3717
|
+
// seems to do nothing at all on Chrome).
|
|
3718
|
+
let pending;
|
|
3719
|
+
if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
|
|
3720
|
+
view.inputState.setPendingKey(view, pending);
|
|
3721
|
+
let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
|
|
3722
|
+
setTimeout(() => {
|
|
3723
|
+
var _a;
|
|
3724
|
+
// Backspacing near uneditable nodes on Chrome Android sometimes
|
|
3725
|
+
// closes the virtual keyboard. This tries to crudely detect
|
|
3726
|
+
// that and refocus to get it back.
|
|
3727
|
+
if ((((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0) > startViewHeight + 10 && view.hasFocus) {
|
|
3728
|
+
view.contentDOM.blur();
|
|
3729
|
+
view.focus();
|
|
3730
|
+
}
|
|
3731
|
+
}, 50);
|
|
3732
|
+
}
|
|
3733
|
+
};
|
|
3556
3734
|
|
|
3557
3735
|
const wrappingWhiteSpace = ["pre-wrap", "normal", "pre-line"];
|
|
3558
3736
|
class HeightOracle {
|
|
@@ -4326,14 +4504,11 @@ class ViewState {
|
|
|
4326
4504
|
let viewport = heightChanges.length ? this.mapViewport(this.viewport, update.changes) : this.viewport;
|
|
4327
4505
|
if (scrollTo && (scrollTo.head < viewport.from || scrollTo.head > viewport.to) || !this.viewportIsAppropriate(viewport))
|
|
4328
4506
|
viewport = this.getViewport(0, scrollTo);
|
|
4329
|
-
|
|
4330
|
-
this.viewport = viewport;
|
|
4331
|
-
update.flags |= 4 /* Viewport */;
|
|
4332
|
-
}
|
|
4507
|
+
this.viewport = viewport;
|
|
4333
4508
|
this.updateForViewport();
|
|
4334
4509
|
if (this.lineGaps.length || this.viewport.to - this.viewport.from > 15000 /* MinViewPort */)
|
|
4335
|
-
|
|
4336
|
-
this.computeVisibleRanges();
|
|
4510
|
+
this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
|
|
4511
|
+
update.flags |= this.computeVisibleRanges();
|
|
4337
4512
|
if (scrollTo)
|
|
4338
4513
|
this.scrollTo = scrollTo;
|
|
4339
4514
|
if (!this.mustEnforceCursorAssoc && update.selectionSet && update.view.lineWrapping &&
|
|
@@ -4342,12 +4517,17 @@ class ViewState {
|
|
|
4342
4517
|
}
|
|
4343
4518
|
measure(docView, repeated) {
|
|
4344
4519
|
let dom = docView.dom, whiteSpace = "", direction = exports.Direction.LTR;
|
|
4520
|
+
let result = 0;
|
|
4345
4521
|
if (!repeated) {
|
|
4346
4522
|
// Vertical padding
|
|
4347
4523
|
let style = window.getComputedStyle(dom);
|
|
4348
4524
|
whiteSpace = style.whiteSpace, direction = (style.direction == "rtl" ? exports.Direction.RTL : exports.Direction.LTR);
|
|
4349
|
-
|
|
4350
|
-
this.
|
|
4525
|
+
let paddingTop = parseInt(style.paddingTop) || 0, paddingBottom = parseInt(style.paddingBottom) || 0;
|
|
4526
|
+
if (this.paddingTop != paddingTop || this.paddingBottom != paddingBottom) {
|
|
4527
|
+
result |= 8 /* Geometry */;
|
|
4528
|
+
this.paddingTop = paddingTop;
|
|
4529
|
+
this.paddingBottom = paddingBottom;
|
|
4530
|
+
}
|
|
4351
4531
|
}
|
|
4352
4532
|
// Pixel viewport
|
|
4353
4533
|
let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 } : visiblePixelRange(dom, this.paddingTop);
|
|
@@ -4357,7 +4537,7 @@ class ViewState {
|
|
|
4357
4537
|
if (!this.inView)
|
|
4358
4538
|
return 0;
|
|
4359
4539
|
let lineHeights = docView.measureVisibleLineHeights();
|
|
4360
|
-
let refresh = false, bias = 0,
|
|
4540
|
+
let refresh = false, bias = 0, oracle = this.heightOracle;
|
|
4361
4541
|
if (!repeated) {
|
|
4362
4542
|
let contentWidth = docView.dom.clientWidth;
|
|
4363
4543
|
if (oracle.mustRefresh(lineHeights, whiteSpace, direction) ||
|
|
@@ -4366,12 +4546,12 @@ class ViewState {
|
|
|
4366
4546
|
refresh = oracle.refresh(whiteSpace, direction, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
|
|
4367
4547
|
if (refresh) {
|
|
4368
4548
|
docView.minWidth = 0;
|
|
4369
|
-
result |=
|
|
4549
|
+
result |= 8 /* Geometry */;
|
|
4370
4550
|
}
|
|
4371
4551
|
}
|
|
4372
4552
|
if (this.contentWidth != contentWidth) {
|
|
4373
4553
|
this.contentWidth = contentWidth;
|
|
4374
|
-
result |=
|
|
4554
|
+
result |= 8 /* Geometry */;
|
|
4375
4555
|
}
|
|
4376
4556
|
if (dTop > 0 && dBottom > 0)
|
|
4377
4557
|
bias = Math.max(dTop, dBottom);
|
|
@@ -4383,17 +4563,12 @@ class ViewState {
|
|
|
4383
4563
|
if (oracle.heightChanged)
|
|
4384
4564
|
result |= 2 /* Height */;
|
|
4385
4565
|
if (!this.viewportIsAppropriate(this.viewport, bias) ||
|
|
4386
|
-
this.scrollTo && (this.scrollTo.head < this.viewport.from || this.scrollTo.head > this.viewport.to))
|
|
4387
|
-
|
|
4388
|
-
if (newVP.from != this.viewport.from || newVP.to != this.viewport.to) {
|
|
4389
|
-
this.viewport = newVP;
|
|
4390
|
-
result |= 4 /* Viewport */;
|
|
4391
|
-
}
|
|
4392
|
-
}
|
|
4566
|
+
this.scrollTo && (this.scrollTo.head < this.viewport.from || this.scrollTo.head > this.viewport.to))
|
|
4567
|
+
this.viewport = this.getViewport(bias, this.scrollTo);
|
|
4393
4568
|
this.updateForViewport();
|
|
4394
4569
|
if (this.lineGaps.length || this.viewport.to - this.viewport.from > 15000 /* MinViewPort */)
|
|
4395
|
-
|
|
4396
|
-
this.computeVisibleRanges();
|
|
4570
|
+
this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
|
|
4571
|
+
result |= this.computeVisibleRanges();
|
|
4397
4572
|
if (this.mustEnforceCursorAssoc) {
|
|
4398
4573
|
this.mustEnforceCursorAssoc = false;
|
|
4399
4574
|
// This is done in the read stage, because moving the selection
|
|
@@ -4515,9 +4690,7 @@ class ViewState {
|
|
|
4515
4690
|
if (!LineGap.same(gaps, this.lineGaps)) {
|
|
4516
4691
|
this.lineGaps = gaps;
|
|
4517
4692
|
this.lineGapDeco = Decoration.set(gaps.map(gap => gap.draw(this.heightOracle.lineWrapping)));
|
|
4518
|
-
return 8 /* LineGaps */;
|
|
4519
4693
|
}
|
|
4520
|
-
return 0;
|
|
4521
4694
|
}
|
|
4522
4695
|
computeVisibleRanges() {
|
|
4523
4696
|
let deco = this.state.facet(decorations);
|
|
@@ -4528,7 +4701,10 @@ class ViewState {
|
|
|
4528
4701
|
span(from, to) { ranges.push({ from, to }); },
|
|
4529
4702
|
point() { }
|
|
4530
4703
|
}, 20);
|
|
4704
|
+
let changed = ranges.length != this.visibleRanges.length ||
|
|
4705
|
+
this.visibleRanges.some((r, i) => r.from != ranges[i].from || r.to != ranges[i].to);
|
|
4531
4706
|
this.visibleRanges = ranges;
|
|
4707
|
+
return changed ? 4 /* Viewport */ : 0;
|
|
4532
4708
|
}
|
|
4533
4709
|
lineAt(pos, editorTop) {
|
|
4534
4710
|
editorTop += this.paddingTop;
|
|
@@ -4553,16 +4729,11 @@ class ViewState {
|
|
|
4553
4729
|
return this.scaler.toDOM(this.heightMap.height, this.paddingTop);
|
|
4554
4730
|
}
|
|
4555
4731
|
}
|
|
4556
|
-
/**
|
|
4557
|
-
Indicates the range of the document that is in the visible
|
|
4558
|
-
viewport.
|
|
4559
|
-
*/
|
|
4560
4732
|
class Viewport {
|
|
4561
4733
|
constructor(from, to) {
|
|
4562
4734
|
this.from = from;
|
|
4563
4735
|
this.to = to;
|
|
4564
4736
|
}
|
|
4565
|
-
eq(b) { return this.from == b.from && this.to == b.to; }
|
|
4566
4737
|
}
|
|
4567
4738
|
function lineStructure(from, to, state) {
|
|
4568
4739
|
let ranges = [], pos = from, total = 0;
|
|
@@ -4688,7 +4859,7 @@ function buildTheme(main, spec, scopes) {
|
|
|
4688
4859
|
});
|
|
4689
4860
|
}
|
|
4690
4861
|
const baseTheme = buildTheme("." + baseThemeID, {
|
|
4691
|
-
"
|
|
4862
|
+
"&.cm-editor": {
|
|
4692
4863
|
position: "relative !important",
|
|
4693
4864
|
boxSizing: "border-box",
|
|
4694
4865
|
"&.cm-focused": {
|
|
@@ -4855,6 +5026,8 @@ class DOMObserver {
|
|
|
4855
5026
|
this.scrollTargets = [];
|
|
4856
5027
|
this.intersection = null;
|
|
4857
5028
|
this.intersecting = false;
|
|
5029
|
+
this.gapIntersection = null;
|
|
5030
|
+
this.gaps = [];
|
|
4858
5031
|
// Used to work around a Safari Selection/shadow DOM bug (#414)
|
|
4859
5032
|
this._selectionRange = null;
|
|
4860
5033
|
// Timeout for scheduling check of the parents that need scroll handlers
|
|
@@ -4895,13 +5068,17 @@ class DOMObserver {
|
|
|
4895
5068
|
this.intersection = new IntersectionObserver(entries => {
|
|
4896
5069
|
if (this.parentCheck < 0)
|
|
4897
5070
|
this.parentCheck = setTimeout(this.listenForScroll.bind(this), 1000);
|
|
4898
|
-
if (entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
|
|
5071
|
+
if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
|
|
4899
5072
|
this.intersecting = !this.intersecting;
|
|
4900
5073
|
if (this.intersecting != this.view.inView)
|
|
4901
5074
|
this.onScrollChanged(document.createEvent("Event"));
|
|
4902
5075
|
}
|
|
4903
5076
|
}, {});
|
|
4904
5077
|
this.intersection.observe(this.dom);
|
|
5078
|
+
this.gapIntersection = new IntersectionObserver(entries => {
|
|
5079
|
+
if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0)
|
|
5080
|
+
this.onScrollChanged(document.createEvent("Event"));
|
|
5081
|
+
}, {});
|
|
4905
5082
|
}
|
|
4906
5083
|
this.listenForScroll();
|
|
4907
5084
|
}
|
|
@@ -4910,6 +5087,14 @@ class DOMObserver {
|
|
|
4910
5087
|
this.flush();
|
|
4911
5088
|
this.onScrollChanged(e);
|
|
4912
5089
|
}
|
|
5090
|
+
updateGaps(gaps) {
|
|
5091
|
+
if (this.gapIntersection && (gaps.length != this.gaps.length || this.gaps.some((g, i) => g != gaps[i]))) {
|
|
5092
|
+
this.gapIntersection.disconnect();
|
|
5093
|
+
for (let gap of gaps)
|
|
5094
|
+
this.gapIntersection.observe(gap);
|
|
5095
|
+
this.gaps = gaps;
|
|
5096
|
+
}
|
|
5097
|
+
}
|
|
4913
5098
|
onSelectionChange(event) {
|
|
4914
5099
|
if (this.lastFlush < Date.now() - 50)
|
|
4915
5100
|
this._selectionRange = null;
|
|
@@ -5025,20 +5210,12 @@ class DOMObserver {
|
|
|
5025
5210
|
this.flush();
|
|
5026
5211
|
}
|
|
5027
5212
|
}
|
|
5028
|
-
|
|
5029
|
-
flush() {
|
|
5030
|
-
if (this.delayedFlush >= 0)
|
|
5031
|
-
return;
|
|
5032
|
-
this.lastFlush = Date.now();
|
|
5213
|
+
processRecords() {
|
|
5033
5214
|
let records = this.queue;
|
|
5034
5215
|
for (let mut of this.observer.takeRecords())
|
|
5035
5216
|
records.push(mut);
|
|
5036
5217
|
if (records.length)
|
|
5037
5218
|
this.queue = [];
|
|
5038
|
-
let selection = this.selectionRange;
|
|
5039
|
-
let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
|
|
5040
|
-
if (records.length == 0 && !newSel)
|
|
5041
|
-
return;
|
|
5042
5219
|
let from = -1, to = -1, typeOver = false;
|
|
5043
5220
|
for (let record of records) {
|
|
5044
5221
|
let range = this.readMutation(record);
|
|
@@ -5054,17 +5231,26 @@ class DOMObserver {
|
|
|
5054
5231
|
to = Math.max(range.to, to);
|
|
5055
5232
|
}
|
|
5056
5233
|
}
|
|
5234
|
+
return { from, to, typeOver };
|
|
5235
|
+
}
|
|
5236
|
+
// Apply pending changes, if any
|
|
5237
|
+
flush() {
|
|
5238
|
+
// Completely hold off flushing when pending keys are set—the code
|
|
5239
|
+
// managing those will make sure processRecords is called and the
|
|
5240
|
+
// view is resynchronized after
|
|
5241
|
+
if (this.delayedFlush >= 0 || this.view.inputState.pendingKey)
|
|
5242
|
+
return;
|
|
5243
|
+
this.lastFlush = Date.now();
|
|
5244
|
+
let { from, to, typeOver } = this.processRecords();
|
|
5245
|
+
let selection = this.selectionRange;
|
|
5246
|
+
let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
|
|
5247
|
+
if (from < 0 && !newSel)
|
|
5248
|
+
return;
|
|
5057
5249
|
let startState = this.view.state;
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
if (this.view.state == startState)
|
|
5061
|
-
|
|
5062
|
-
this.ignore(() => this.view.docView.sync());
|
|
5063
|
-
this.view.docView.dirty = 0 /* Not */;
|
|
5064
|
-
}
|
|
5065
|
-
if (newSel)
|
|
5066
|
-
this.view.docView.updateSelection();
|
|
5067
|
-
}
|
|
5250
|
+
this.onChange(from, to, typeOver);
|
|
5251
|
+
// The view wasn't updated
|
|
5252
|
+
if (this.view.state == startState)
|
|
5253
|
+
this.view.docView.reset(newSel);
|
|
5068
5254
|
this.clearSelection();
|
|
5069
5255
|
}
|
|
5070
5256
|
readMutation(rec) {
|
|
@@ -5091,6 +5277,8 @@ class DOMObserver {
|
|
|
5091
5277
|
this.stop();
|
|
5092
5278
|
if (this.intersection)
|
|
5093
5279
|
this.intersection.disconnect();
|
|
5280
|
+
if (this.gapIntersection)
|
|
5281
|
+
this.gapIntersection.disconnect();
|
|
5094
5282
|
for (let dom of this.scrollTargets)
|
|
5095
5283
|
dom.removeEventListener("scroll", this.onScroll);
|
|
5096
5284
|
window.removeEventListener("scroll", this.onScroll);
|
|
@@ -5138,7 +5326,7 @@ function safariSelectionRangeHack(view) {
|
|
|
5138
5326
|
function applyDOMChange(view, start, end, typeOver) {
|
|
5139
5327
|
let change, newSel;
|
|
5140
5328
|
let sel = view.state.selection.main, bounds;
|
|
5141
|
-
if (start > -1 && (bounds = view.docView.domBoundsAround(start, end, 0))) {
|
|
5329
|
+
if (start > -1 && !view.state.readOnly && (bounds = view.docView.domBoundsAround(start, end, 0))) {
|
|
5142
5330
|
let { from, to } = bounds;
|
|
5143
5331
|
let selPoints = view.docView.impreciseHead || view.docView.impreciseAnchor ? [] : selectionPoints(view);
|
|
5144
5332
|
let reader = new DOMReader(selPoints, view);
|
|
@@ -5199,9 +5387,9 @@ function applyDOMChange(view, start, end, typeOver) {
|
|
|
5199
5387
|
(change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
|
|
5200
5388
|
dispatchKey(view.contentDOM, "Backspace", 8)) ||
|
|
5201
5389
|
(change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
|
|
5202
|
-
dispatchKey(view.contentDOM, "Delete", 46)))
|
|
5203
|
-
browser.ios && view.inputState.flushIOSKey(view))
|
|
5390
|
+
dispatchKey(view.contentDOM, "Delete", 46)))) {
|
|
5204
5391
|
return;
|
|
5392
|
+
}
|
|
5205
5393
|
let text = change.insert.toString();
|
|
5206
5394
|
if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
|
|
5207
5395
|
return;
|
|
@@ -5291,8 +5479,9 @@ class DOMReader {
|
|
|
5291
5479
|
if (next == end)
|
|
5292
5480
|
break;
|
|
5293
5481
|
let view = ContentView.get(cur), nextView = ContentView.get(next);
|
|
5294
|
-
if (
|
|
5295
|
-
(
|
|
5482
|
+
if (view && nextView ? view.breakAfter :
|
|
5483
|
+
(view ? view.breakAfter : isBlockElement(cur)) ||
|
|
5484
|
+
(isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
|
|
5296
5485
|
this.text += this.lineBreak;
|
|
5297
5486
|
cur = next;
|
|
5298
5487
|
}
|
|
@@ -5395,6 +5584,7 @@ class EditorView {
|
|
|
5395
5584
|
this.editorAttrs = {};
|
|
5396
5585
|
this.contentAttrs = {};
|
|
5397
5586
|
this.bidiCache = [];
|
|
5587
|
+
this.destroyed = false;
|
|
5398
5588
|
/**
|
|
5399
5589
|
@internal
|
|
5400
5590
|
*/
|
|
@@ -5420,7 +5610,7 @@ class EditorView {
|
|
|
5420
5610
|
this.dom.appendChild(this.scrollDOM);
|
|
5421
5611
|
this._dispatch = config.dispatch || ((tr) => this.update([tr]));
|
|
5422
5612
|
this.dispatch = this.dispatch.bind(this);
|
|
5423
|
-
this.root = (config.root || document);
|
|
5613
|
+
this.root = (config.root || getRoot(config.parent) || document);
|
|
5424
5614
|
this.viewState = new ViewState(config.state || state.EditorState.create());
|
|
5425
5615
|
this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec).update(this));
|
|
5426
5616
|
this.observer = new DOMObserver(this, (from, to, typeOver) => {
|
|
@@ -5493,6 +5683,10 @@ class EditorView {
|
|
|
5493
5683
|
throw new RangeError("Trying to update state with a transaction that doesn't start from the previous state.");
|
|
5494
5684
|
state$1 = tr.state;
|
|
5495
5685
|
}
|
|
5686
|
+
if (this.destroyed) {
|
|
5687
|
+
this.viewState.state = state$1;
|
|
5688
|
+
return;
|
|
5689
|
+
}
|
|
5496
5690
|
// When the phrases change, redraw the editor
|
|
5497
5691
|
if (state$1.facet(state.EditorState.phrases) != this.state.facet(state.EditorState.phrases))
|
|
5498
5692
|
return this.setState(state$1);
|
|
@@ -5542,6 +5736,10 @@ class EditorView {
|
|
|
5542
5736
|
setState(newState) {
|
|
5543
5737
|
if (this.updateState != 0 /* Idle */)
|
|
5544
5738
|
throw new Error("Calls to EditorView.setState are not allowed while an update is in progress");
|
|
5739
|
+
if (this.destroyed) {
|
|
5740
|
+
this.viewState.state = newState;
|
|
5741
|
+
return;
|
|
5742
|
+
}
|
|
5545
5743
|
this.updateState = 2 /* Updating */;
|
|
5546
5744
|
try {
|
|
5547
5745
|
for (let plugin of this.plugins)
|
|
@@ -5591,6 +5789,8 @@ class EditorView {
|
|
|
5591
5789
|
@internal
|
|
5592
5790
|
*/
|
|
5593
5791
|
measure(flush = true) {
|
|
5792
|
+
if (this.destroyed)
|
|
5793
|
+
return;
|
|
5594
5794
|
if (this.measureScheduled > -1)
|
|
5595
5795
|
cancelAnimationFrame(this.measureScheduled);
|
|
5596
5796
|
this.measureScheduled = -1; // Prevent requestMeasure calls from scheduling another animation frame
|
|
@@ -5600,15 +5800,18 @@ class EditorView {
|
|
|
5600
5800
|
try {
|
|
5601
5801
|
for (let i = 0;; i++) {
|
|
5602
5802
|
this.updateState = 1 /* Measuring */;
|
|
5803
|
+
let oldViewport = this.viewport;
|
|
5603
5804
|
let changed = this.viewState.measure(this.docView, i > 0);
|
|
5604
|
-
|
|
5605
|
-
if (!changed && !measuring.length && this.viewState.scrollTo == null)
|
|
5805
|
+
if (!changed && !this.measureRequests.length && this.viewState.scrollTo == null)
|
|
5606
5806
|
break;
|
|
5607
|
-
this.measureRequests = [];
|
|
5608
5807
|
if (i > 5) {
|
|
5609
5808
|
console.warn("Viewport failed to stabilize");
|
|
5610
5809
|
break;
|
|
5611
5810
|
}
|
|
5811
|
+
let measuring = [];
|
|
5812
|
+
// Only run measure requests in this cycle when the viewport didn't change
|
|
5813
|
+
if (!(changed & 4 /* Viewport */))
|
|
5814
|
+
[this.measureRequests, measuring] = [measuring, this.measureRequests];
|
|
5612
5815
|
let measured = measuring.map(m => {
|
|
5613
5816
|
try {
|
|
5614
5817
|
return m.read(this);
|
|
@@ -5645,7 +5848,7 @@ class EditorView {
|
|
|
5645
5848
|
this.docView.scrollRangeIntoView(this.viewState.scrollTo);
|
|
5646
5849
|
this.viewState.scrollTo = null;
|
|
5647
5850
|
}
|
|
5648
|
-
if (
|
|
5851
|
+
if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
|
|
5649
5852
|
break;
|
|
5650
5853
|
}
|
|
5651
5854
|
}
|
|
@@ -5671,16 +5874,20 @@ class EditorView {
|
|
|
5671
5874
|
});
|
|
5672
5875
|
updateAttrs(this.dom, this.editorAttrs, editorAttrs);
|
|
5673
5876
|
this.editorAttrs = editorAttrs;
|
|
5674
|
-
let contentAttrs =
|
|
5877
|
+
let contentAttrs = {
|
|
5675
5878
|
spellcheck: "false",
|
|
5676
5879
|
autocorrect: "off",
|
|
5677
5880
|
autocapitalize: "off",
|
|
5881
|
+
translate: "no",
|
|
5678
5882
|
contenteditable: !this.state.facet(editable) ? "false" : contentEditablePlainTextSupported() ? "plaintext-only" : "true",
|
|
5679
5883
|
class: "cm-content",
|
|
5680
5884
|
style: `${browser.tabSize}: ${this.state.tabSize}`,
|
|
5681
5885
|
role: "textbox",
|
|
5682
5886
|
"aria-multiline": "true"
|
|
5683
|
-
}
|
|
5887
|
+
};
|
|
5888
|
+
if (this.state.readOnly)
|
|
5889
|
+
contentAttrs["aria-readonly"] = "true";
|
|
5890
|
+
combineAttrs(this.state.facet(contentAttributes), contentAttrs);
|
|
5684
5891
|
updateAttrs(this.contentDOM, this.contentAttrs, contentAttrs);
|
|
5685
5892
|
this.contentAttrs = contentAttrs;
|
|
5686
5893
|
}
|
|
@@ -5978,11 +6185,13 @@ class EditorView {
|
|
|
5978
6185
|
destroy() {
|
|
5979
6186
|
for (let plugin of this.plugins)
|
|
5980
6187
|
plugin.destroy(this);
|
|
6188
|
+
this.plugins = [];
|
|
5981
6189
|
this.inputState.destroy();
|
|
5982
6190
|
this.dom.remove();
|
|
5983
6191
|
this.observer.destroy();
|
|
5984
6192
|
if (this.measureScheduled > -1)
|
|
5985
6193
|
cancelAnimationFrame(this.measureScheduled);
|
|
6194
|
+
this.destroyed = true;
|
|
5986
6195
|
}
|
|
5987
6196
|
/**
|
|
5988
6197
|
Facet that can be used to add DOM event handlers. The value
|
|
@@ -6070,12 +6279,12 @@ every time the view updates.
|
|
|
6070
6279
|
*/
|
|
6071
6280
|
EditorView.updateListener = updateListener;
|
|
6072
6281
|
/**
|
|
6073
|
-
Facet that controls whether the editor content is editable.
|
|
6074
|
-
its highest-precedence value is `false`,
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6282
|
+
Facet that controls whether the editor content DOM is editable.
|
|
6283
|
+
When its highest-precedence value is `false`, the element will
|
|
6284
|
+
not longer have its `contenteditable` attribute set. (Note that
|
|
6285
|
+
this doesn't affect API calls that change the editor content,
|
|
6286
|
+
even when those are bound to keys or buttons. See the
|
|
6287
|
+
[`readOnly`](https://codemirror.net/6/docs/ref/#state.EditorState.readOnly) facet for that.)
|
|
6079
6288
|
*/
|
|
6080
6289
|
EditorView.editable = editable;
|
|
6081
6290
|
/**
|
|
@@ -6170,11 +6379,7 @@ class CachedOrder {
|
|
|
6170
6379
|
}
|
|
6171
6380
|
}
|
|
6172
6381
|
|
|
6173
|
-
const currentPlatform =
|
|
6174
|
-
: /Mac/.test(navigator.platform) ? "mac"
|
|
6175
|
-
: /Win/.test(navigator.platform) ? "win"
|
|
6176
|
-
: /Linux|X11/.test(navigator.platform) ? "linux"
|
|
6177
|
-
: "key";
|
|
6382
|
+
const currentPlatform = browser.mac ? "mac" : browser.windows ? "win" : browser.linux ? "linux" : "key";
|
|
6178
6383
|
function normalizeKeyName(name, platform) {
|
|
6179
6384
|
const parts = name.split(/-(?!$)/);
|
|
6180
6385
|
let result = parts[parts.length - 1];
|
|
@@ -6680,7 +6885,7 @@ class MatchDecorator {
|
|
|
6680
6885
|
}
|
|
6681
6886
|
|
|
6682
6887
|
const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g";
|
|
6683
|
-
const Specials = new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
|
|
6888
|
+
const Specials = new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
|
|
6684
6889
|
const Names = {
|
|
6685
6890
|
0: "null",
|
|
6686
6891
|
7: "bell",
|
|
@@ -6695,6 +6900,8 @@ const Names = {
|
|
|
6695
6900
|
8206: "left-to-right mark",
|
|
6696
6901
|
8207: "right-to-left mark",
|
|
6697
6902
|
8232: "line separator",
|
|
6903
|
+
8237: "left-to-right override",
|
|
6904
|
+
8238: "right-to-left override",
|
|
6698
6905
|
8233: "paragraph separator",
|
|
6699
6906
|
65279: "zero width no-break space",
|
|
6700
6907
|
65532: "object replacement"
|
|
@@ -6861,7 +7068,7 @@ DOM class.
|
|
|
6861
7068
|
function highlightActiveLine() {
|
|
6862
7069
|
return activeLineHighlighter;
|
|
6863
7070
|
}
|
|
6864
|
-
const lineDeco = Decoration.line({
|
|
7071
|
+
const lineDeco = Decoration.line({ class: "cm-activeLine" });
|
|
6865
7072
|
const activeLineHighlighter = ViewPlugin.fromClass(class {
|
|
6866
7073
|
constructor(view) {
|
|
6867
7074
|
this.decorations = this.getDeco(view);
|