@codemirror/view 0.19.7 → 0.19.11
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 +54 -0
- package/dist/index.cjs +382 -145
- package/dist/index.d.ts +16 -5
- package/dist/index.js +383 -146
- package/package.json +1 -1
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;
|
|
@@ -92,9 +102,9 @@ function windowRect(win) {
|
|
|
92
102
|
top: 0, bottom: win.innerHeight };
|
|
93
103
|
}
|
|
94
104
|
const ScrollSpace = 5;
|
|
95
|
-
function scrollRectIntoView(dom, rect, side) {
|
|
105
|
+
function scrollRectIntoView(dom, rect, side, center) {
|
|
96
106
|
let doc = dom.ownerDocument, win = doc.defaultView;
|
|
97
|
-
for (let cur = dom
|
|
107
|
+
for (let cur = dom; cur;) {
|
|
98
108
|
if (cur.nodeType == 1) { // Element
|
|
99
109
|
let bounding, top = cur == doc.body;
|
|
100
110
|
if (top) {
|
|
@@ -111,7 +121,20 @@ function scrollRectIntoView(dom, rect, side) {
|
|
|
111
121
|
top: rect.top, bottom: rect.top + cur.clientHeight };
|
|
112
122
|
}
|
|
113
123
|
let moveX = 0, moveY = 0;
|
|
114
|
-
if (
|
|
124
|
+
if (center) {
|
|
125
|
+
let rectHeight = rect.bottom - rect.top, boundingHeight = bounding.bottom - bounding.top;
|
|
126
|
+
let targetTop;
|
|
127
|
+
if (rectHeight <= boundingHeight)
|
|
128
|
+
targetTop = rect.top + rectHeight / 2 - boundingHeight / 2;
|
|
129
|
+
else if (side < 0)
|
|
130
|
+
targetTop = rect.top - ScrollSpace;
|
|
131
|
+
else
|
|
132
|
+
targetTop = rect.bottom + ScrollSpace - boundingHeight;
|
|
133
|
+
moveY = targetTop - bounding.top;
|
|
134
|
+
if (Math.abs(moveY) <= 1)
|
|
135
|
+
moveY = 0;
|
|
136
|
+
}
|
|
137
|
+
else if (rect.top < bounding.top) {
|
|
115
138
|
moveY = -(bounding.top - rect.top + ScrollSpace);
|
|
116
139
|
if (side > 0 && rect.bottom > bounding.bottom + moveY)
|
|
117
140
|
moveY = rect.bottom - bounding.bottom + moveY + ScrollSpace;
|
|
@@ -153,6 +176,7 @@ function scrollRectIntoView(dom, rect, side) {
|
|
|
153
176
|
if (top)
|
|
154
177
|
break;
|
|
155
178
|
cur = cur.assignedSlot || cur.parentNode;
|
|
179
|
+
center = false;
|
|
156
180
|
}
|
|
157
181
|
else if (cur.nodeType == 11) { // A shadow root
|
|
158
182
|
cur = cur.host;
|
|
@@ -241,6 +265,14 @@ function contentEditablePlainTextSupported() {
|
|
|
241
265
|
}
|
|
242
266
|
return _plainTextSupported;
|
|
243
267
|
}
|
|
268
|
+
function getRoot(node) {
|
|
269
|
+
while (node) {
|
|
270
|
+
node = node.assignedSlot || node.parentNode;
|
|
271
|
+
if (node && (node.nodeType == 9 || node.nodeType == 11 && node.host))
|
|
272
|
+
return node;
|
|
273
|
+
}
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
244
276
|
|
|
245
277
|
class DOMPos {
|
|
246
278
|
constructor(node, offset, precise = true) {
|
|
@@ -438,6 +470,7 @@ class ContentView {
|
|
|
438
470
|
(this.breakAfter ? "#" : "");
|
|
439
471
|
}
|
|
440
472
|
static get(node) { return node.cmView; }
|
|
473
|
+
get isEditable() { return true; }
|
|
441
474
|
}
|
|
442
475
|
ContentView.prototype.breakAfter = 0;
|
|
443
476
|
// Remove a DOM node and return its next sibling.
|
|
@@ -485,15 +518,18 @@ const gecko = !ie && /gecko\/(\d+)/i.test(nav.userAgent);
|
|
|
485
518
|
const chrome = !ie && /Chrome\/(\d+)/.exec(nav.userAgent);
|
|
486
519
|
const webkit = "webkitFontSmoothing" in doc.documentElement.style;
|
|
487
520
|
const safari = !ie && /Apple Computer/.test(nav.vendor);
|
|
521
|
+
const ios = safari && (/Mobile\/\w+/.test(nav.userAgent) || nav.maxTouchPoints > 2);
|
|
488
522
|
var browser = {
|
|
489
|
-
mac: /Mac/.test(nav.platform),
|
|
523
|
+
mac: ios || /Mac/.test(nav.platform),
|
|
524
|
+
windows: /Win/.test(nav.platform),
|
|
525
|
+
linux: /Linux|X11/.test(nav.platform),
|
|
490
526
|
ie,
|
|
491
527
|
ie_version: ie_upto10 ? doc.documentMode || 6 : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : 0,
|
|
492
528
|
gecko,
|
|
493
529
|
gecko_version: gecko ? +(/Firefox\/(\d+)/.exec(nav.userAgent) || [0, 0])[1] : 0,
|
|
494
530
|
chrome: !!chrome,
|
|
495
531
|
chrome_version: chrome ? +chrome[1] : 0,
|
|
496
|
-
ios
|
|
532
|
+
ios,
|
|
497
533
|
android: /Android\b/.test(nav.userAgent),
|
|
498
534
|
webkit,
|
|
499
535
|
safari,
|
|
@@ -715,6 +751,7 @@ class WidgetView extends InlineView {
|
|
|
715
751
|
}
|
|
716
752
|
return (pos == 0 && side > 0 || pos == this.length && side <= 0) ? rect : flattenRect(rect, pos == 0);
|
|
717
753
|
}
|
|
754
|
+
get isEditable() { return false; }
|
|
718
755
|
}
|
|
719
756
|
class CompositionView extends WidgetView {
|
|
720
757
|
domAtPos(pos) { return new DOMPos(this.widget.text, pos); }
|
|
@@ -726,6 +763,38 @@ class CompositionView extends WidgetView {
|
|
|
726
763
|
ignoreMutation() { return false; }
|
|
727
764
|
get overrideDOMText() { return null; }
|
|
728
765
|
coordsAt(pos, side) { return textCoords(this.widget.text, pos, side); }
|
|
766
|
+
get isEditable() { return true; }
|
|
767
|
+
}
|
|
768
|
+
// These are drawn around uneditable widgets to avoid a number of
|
|
769
|
+
// browser bugs that show up when the cursor is directly next to
|
|
770
|
+
// uneditable inline content.
|
|
771
|
+
class WidgetBufferView extends InlineView {
|
|
772
|
+
constructor(side) {
|
|
773
|
+
super();
|
|
774
|
+
this.side = side;
|
|
775
|
+
}
|
|
776
|
+
get length() { return 0; }
|
|
777
|
+
merge() { return false; }
|
|
778
|
+
become(other) {
|
|
779
|
+
return other instanceof WidgetBufferView && other.side == this.side;
|
|
780
|
+
}
|
|
781
|
+
slice() { return new WidgetBufferView(this.side); }
|
|
782
|
+
sync() {
|
|
783
|
+
if (!this.dom)
|
|
784
|
+
this.setDOM(document.createTextNode("\u200b"));
|
|
785
|
+
else if (this.dirty && this.dom.nodeValue != "\u200b")
|
|
786
|
+
this.dom.nodeValue = "\u200b";
|
|
787
|
+
}
|
|
788
|
+
getSide() { return this.side; }
|
|
789
|
+
domAtPos(pos) { return DOMPos.before(this.dom); }
|
|
790
|
+
domBoundsAround() { return null; }
|
|
791
|
+
coordsAt(pos) {
|
|
792
|
+
let rects = clientRectsFor(this.dom);
|
|
793
|
+
return rects[rects.length - 1];
|
|
794
|
+
}
|
|
795
|
+
get overrideDOMText() {
|
|
796
|
+
return text.Text.of([this.dom.nodeValue.replace(/\u200b/g, "")]);
|
|
797
|
+
}
|
|
729
798
|
}
|
|
730
799
|
function mergeInlineChildren(parent, from, to, elts, openStart, openEnd) {
|
|
731
800
|
let cur = parent.childCursor();
|
|
@@ -1221,14 +1290,17 @@ class LineView extends ContentView {
|
|
|
1221
1290
|
}
|
|
1222
1291
|
// Only called when building a line view in ContentBuilder
|
|
1223
1292
|
addLineDeco(deco) {
|
|
1224
|
-
let attrs = deco.spec.attributes;
|
|
1293
|
+
let attrs = deco.spec.attributes, cls = deco.spec.class;
|
|
1225
1294
|
if (attrs)
|
|
1226
1295
|
this.attrs = combineAttrs(attrs, this.attrs || {});
|
|
1296
|
+
if (cls)
|
|
1297
|
+
this.attrs = combineAttrs(attrs, { class: cls });
|
|
1227
1298
|
}
|
|
1228
1299
|
domAtPos(pos) {
|
|
1229
1300
|
return inlineDOMAtPos(this.dom, this.children, pos);
|
|
1230
1301
|
}
|
|
1231
1302
|
sync(track) {
|
|
1303
|
+
var _a;
|
|
1232
1304
|
if (!this.dom || (this.dirty & 4 /* Attrs */)) {
|
|
1233
1305
|
this.setDOM(document.createElement("div"));
|
|
1234
1306
|
this.dom.className = "cm-line";
|
|
@@ -1244,7 +1316,7 @@ class LineView extends ContentView {
|
|
|
1244
1316
|
while (last && ContentView.get(last) instanceof MarkView)
|
|
1245
1317
|
last = last.lastChild;
|
|
1246
1318
|
if (!last ||
|
|
1247
|
-
last.nodeName != "BR" && ContentView.get(last)
|
|
1319
|
+
last.nodeName != "BR" && ((_a = ContentView.get(last)) === null || _a === void 0 ? void 0 : _a.isEditable) == false &&
|
|
1248
1320
|
(!browser.ios || !this.children.some(ch => ch instanceof TextView))) {
|
|
1249
1321
|
let hack = document.createElement("BR");
|
|
1250
1322
|
hack.cmIgnore = true;
|
|
@@ -1343,6 +1415,9 @@ class ContentBuilder {
|
|
|
1343
1415
|
this.content = [];
|
|
1344
1416
|
this.curLine = null;
|
|
1345
1417
|
this.breakAtStart = 0;
|
|
1418
|
+
this.pendingBuffer = 0 /* No */;
|
|
1419
|
+
// Set to false directly after a widget that covers the position after it
|
|
1420
|
+
this.atCursorPos = true;
|
|
1346
1421
|
this.openStart = -1;
|
|
1347
1422
|
this.openEnd = -1;
|
|
1348
1423
|
this.text = "";
|
|
@@ -1357,23 +1432,31 @@ class ContentBuilder {
|
|
|
1357
1432
|
return !last.breakAfter && !(last instanceof BlockWidgetView && last.type == exports.BlockType.WidgetBefore);
|
|
1358
1433
|
}
|
|
1359
1434
|
getLine() {
|
|
1360
|
-
if (!this.curLine)
|
|
1435
|
+
if (!this.curLine) {
|
|
1361
1436
|
this.content.push(this.curLine = new LineView);
|
|
1437
|
+
this.atCursorPos = true;
|
|
1438
|
+
}
|
|
1362
1439
|
return this.curLine;
|
|
1363
1440
|
}
|
|
1364
|
-
|
|
1441
|
+
flushBuffer(active) {
|
|
1442
|
+
if (this.pendingBuffer) {
|
|
1443
|
+
this.curLine.append(wrapMarks(new WidgetBufferView(-1), active), active.length);
|
|
1444
|
+
this.pendingBuffer = 0 /* No */;
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
addBlockWidget(view) {
|
|
1448
|
+
this.flushBuffer([]);
|
|
1365
1449
|
this.curLine = null;
|
|
1366
1450
|
this.content.push(view);
|
|
1367
1451
|
}
|
|
1368
|
-
finish() {
|
|
1452
|
+
finish(openEnd) {
|
|
1453
|
+
if (!openEnd)
|
|
1454
|
+
this.flushBuffer([]);
|
|
1455
|
+
else
|
|
1456
|
+
this.pendingBuffer = 0 /* No */;
|
|
1369
1457
|
if (!this.posCovered())
|
|
1370
1458
|
this.getLine();
|
|
1371
1459
|
}
|
|
1372
|
-
wrapMarks(view, active) {
|
|
1373
|
-
for (let mark of active)
|
|
1374
|
-
view = new MarkView(mark, [view], view.length);
|
|
1375
|
-
return view;
|
|
1376
|
-
}
|
|
1377
1460
|
buildText(length, active, openStart) {
|
|
1378
1461
|
while (length > 0) {
|
|
1379
1462
|
if (this.textOff == this.text.length) {
|
|
@@ -1388,6 +1471,7 @@ class ContentBuilder {
|
|
|
1388
1471
|
this.content[this.content.length - 1].breakAfter = 1;
|
|
1389
1472
|
else
|
|
1390
1473
|
this.breakAtStart = 1;
|
|
1474
|
+
this.flushBuffer([]);
|
|
1391
1475
|
this.curLine = null;
|
|
1392
1476
|
length--;
|
|
1393
1477
|
continue;
|
|
@@ -1398,7 +1482,9 @@ class ContentBuilder {
|
|
|
1398
1482
|
}
|
|
1399
1483
|
}
|
|
1400
1484
|
let take = Math.min(this.text.length - this.textOff, length, 512 /* Chunk */);
|
|
1401
|
-
this.
|
|
1485
|
+
this.flushBuffer(active);
|
|
1486
|
+
this.getLine().append(wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
|
|
1487
|
+
this.atCursorPos = true;
|
|
1402
1488
|
this.textOff += take;
|
|
1403
1489
|
length -= take;
|
|
1404
1490
|
openStart = 0;
|
|
@@ -1417,11 +1503,23 @@ class ContentBuilder {
|
|
|
1417
1503
|
let { type } = deco;
|
|
1418
1504
|
if (type == exports.BlockType.WidgetAfter && !this.posCovered())
|
|
1419
1505
|
this.getLine();
|
|
1420
|
-
this.
|
|
1506
|
+
this.addBlockWidget(new BlockWidgetView(deco.widget || new NullWidget("div"), len, type));
|
|
1421
1507
|
}
|
|
1422
1508
|
else {
|
|
1423
|
-
let
|
|
1424
|
-
this.
|
|
1509
|
+
let view = WidgetView.create(deco.widget || new NullWidget("span"), len, deco.startSide);
|
|
1510
|
+
let cursorBefore = this.atCursorPos && !view.isEditable && openStart <= active.length && (from < to || deco.startSide > 0);
|
|
1511
|
+
let cursorAfter = !view.isEditable && (from < to || deco.startSide <= 0);
|
|
1512
|
+
let line = this.getLine();
|
|
1513
|
+
if (this.pendingBuffer == 2 /* IfCursor */ && !cursorBefore)
|
|
1514
|
+
this.pendingBuffer = 0 /* No */;
|
|
1515
|
+
this.flushBuffer(active);
|
|
1516
|
+
if (cursorBefore) {
|
|
1517
|
+
line.append(wrapMarks(new WidgetBufferView(1), active), openStart);
|
|
1518
|
+
openStart = active.length + Math.max(0, openStart - active.length);
|
|
1519
|
+
}
|
|
1520
|
+
line.append(wrapMarks(view, active), openStart);
|
|
1521
|
+
this.atCursorPos = cursorAfter;
|
|
1522
|
+
this.pendingBuffer = !cursorAfter ? 0 /* No */ : from < to ? 1 /* Yes */ : 2 /* IfCursor */;
|
|
1425
1523
|
}
|
|
1426
1524
|
}
|
|
1427
1525
|
else if (this.doc.lineAt(this.pos).from == this.pos) { // Line decoration
|
|
@@ -1447,10 +1545,15 @@ class ContentBuilder {
|
|
|
1447
1545
|
builder.openEnd = rangeset.RangeSet.spans(decorations, from, to, builder);
|
|
1448
1546
|
if (builder.openStart < 0)
|
|
1449
1547
|
builder.openStart = builder.openEnd;
|
|
1450
|
-
builder.finish();
|
|
1548
|
+
builder.finish(builder.openEnd);
|
|
1451
1549
|
return builder;
|
|
1452
1550
|
}
|
|
1453
1551
|
}
|
|
1552
|
+
function wrapMarks(view, active) {
|
|
1553
|
+
for (let mark of active)
|
|
1554
|
+
view = new MarkView(mark, [view], view.length);
|
|
1555
|
+
return view;
|
|
1556
|
+
}
|
|
1454
1557
|
class NullWidget extends WidgetType {
|
|
1455
1558
|
constructor(tag) {
|
|
1456
1559
|
super();
|
|
@@ -1471,6 +1574,9 @@ const inputHandler = state.Facet.define();
|
|
|
1471
1574
|
const scrollTo = state.StateEffect.define({
|
|
1472
1575
|
map: (range, changes) => range.map(changes)
|
|
1473
1576
|
});
|
|
1577
|
+
const centerOn = state.StateEffect.define({
|
|
1578
|
+
map: (range, changes) => range.map(changes)
|
|
1579
|
+
});
|
|
1474
1580
|
/**
|
|
1475
1581
|
Log or report an unhandled exception in client code. Should
|
|
1476
1582
|
probably only be used by extension code that allows client code to
|
|
@@ -1783,7 +1889,9 @@ class ViewUpdate {
|
|
|
1783
1889
|
this.flags |= 2 /* Height */;
|
|
1784
1890
|
}
|
|
1785
1891
|
/**
|
|
1786
|
-
Tells you whether the viewport
|
|
1892
|
+
Tells you whether the [viewport](https://codemirror.net/6/docs/ref/#view.EditorView.viewport) or
|
|
1893
|
+
[visible ranges](https://codemirror.net/6/docs/ref/#view.EditorView.visibleRanges) changed in this
|
|
1894
|
+
update.
|
|
1787
1895
|
*/
|
|
1788
1896
|
get viewportChanged() {
|
|
1789
1897
|
return (this.flags & 4 /* Viewport */) > 0;
|
|
@@ -1799,7 +1907,7 @@ class ViewUpdate {
|
|
|
1799
1907
|
or the lines or characters within it has changed.
|
|
1800
1908
|
*/
|
|
1801
1909
|
get geometryChanged() {
|
|
1802
|
-
return this.docChanged || (this.flags & (
|
|
1910
|
+
return this.docChanged || (this.flags & (8 /* Geometry */ | 2 /* Height */)) > 0;
|
|
1803
1911
|
}
|
|
1804
1912
|
/**
|
|
1805
1913
|
True when this update indicates a focus change.
|
|
@@ -1884,7 +1992,7 @@ class DocView extends ContentView {
|
|
|
1884
1992
|
changedRanges = ChangedRange.extendWithRanges(changedRanges, decoDiff);
|
|
1885
1993
|
let pointerSel = update.transactions.some(tr => tr.isUserEvent("select.pointer"));
|
|
1886
1994
|
if (this.dirty == 0 /* Not */ && changedRanges.length == 0 &&
|
|
1887
|
-
!(update.flags &
|
|
1995
|
+
!(update.flags & 4 /* Viewport */) &&
|
|
1888
1996
|
update.state.selection.main.from >= this.view.viewport.from &&
|
|
1889
1997
|
update.state.selection.main.to <= this.view.viewport.to) {
|
|
1890
1998
|
this.updateSelection(forceSelection, pointerSel);
|
|
@@ -1895,6 +2003,14 @@ class DocView extends ContentView {
|
|
|
1895
2003
|
return true;
|
|
1896
2004
|
}
|
|
1897
2005
|
}
|
|
2006
|
+
reset(sel) {
|
|
2007
|
+
if (this.dirty) {
|
|
2008
|
+
this.view.observer.ignore(() => this.view.docView.sync());
|
|
2009
|
+
this.dirty = 0 /* Not */;
|
|
2010
|
+
}
|
|
2011
|
+
if (sel)
|
|
2012
|
+
this.updateSelection();
|
|
2013
|
+
}
|
|
1898
2014
|
// Used both by update and checkLayout do perform the actual DOM
|
|
1899
2015
|
// update
|
|
1900
2016
|
updateInner(changes, deco, oldLength, forceSelection = false, pointerSel = false) {
|
|
@@ -1919,6 +2035,12 @@ class DocView extends ContentView {
|
|
|
1919
2035
|
this.updateSelection(forceSelection, pointerSel);
|
|
1920
2036
|
this.dom.style.height = "";
|
|
1921
2037
|
});
|
|
2038
|
+
let gaps = [];
|
|
2039
|
+
if (this.view.viewport.from || this.view.viewport.to < this.view.state.doc.length)
|
|
2040
|
+
for (let child of this.children)
|
|
2041
|
+
if (child instanceof BlockWidgetView && child.widget instanceof BlockGapWidget)
|
|
2042
|
+
gaps.push(child.dom);
|
|
2043
|
+
observer.updateGaps(gaps);
|
|
1922
2044
|
}
|
|
1923
2045
|
updateChildren(changes, deco, oldLength) {
|
|
1924
2046
|
let cursor = this.childCursor(oldLength);
|
|
@@ -2018,6 +2140,14 @@ class DocView extends ContentView {
|
|
|
2018
2140
|
!isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) ||
|
|
2019
2141
|
!isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) {
|
|
2020
2142
|
this.view.observer.ignore(() => {
|
|
2143
|
+
// Chrome Android will hide the virtual keyboard when tapping
|
|
2144
|
+
// inside an uneditable node, and not bring it back when we
|
|
2145
|
+
// move the cursor to its proper position. This tries to
|
|
2146
|
+
// restore the keyboard by cycling focus.
|
|
2147
|
+
if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) && inUneditable(domSel.focusNode, this.dom)) {
|
|
2148
|
+
this.dom.blur();
|
|
2149
|
+
this.dom.focus({ preventScroll: true });
|
|
2150
|
+
}
|
|
2021
2151
|
let rawSel = getSelection(this.root);
|
|
2022
2152
|
if (main.empty) {
|
|
2023
2153
|
// Work around https://bugzilla.mozilla.org/show_bug.cgi?id=1612076
|
|
@@ -2191,7 +2321,7 @@ class DocView extends ContentView {
|
|
|
2191
2321
|
this.view.viewState.lineGapDeco
|
|
2192
2322
|
];
|
|
2193
2323
|
}
|
|
2194
|
-
|
|
2324
|
+
scrollIntoView({ range, center }) {
|
|
2195
2325
|
let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
|
|
2196
2326
|
if (!rect)
|
|
2197
2327
|
return;
|
|
@@ -2211,10 +2341,10 @@ class DocView extends ContentView {
|
|
|
2211
2341
|
if (bottom != null)
|
|
2212
2342
|
mBottom = Math.max(mBottom, bottom);
|
|
2213
2343
|
}
|
|
2214
|
-
scrollRectIntoView(this.
|
|
2344
|
+
scrollRectIntoView(this.view.scrollDOM, {
|
|
2215
2345
|
left: rect.left - mLeft, top: rect.top - mTop,
|
|
2216
2346
|
right: rect.right + mRight, bottom: rect.bottom + mBottom
|
|
2217
|
-
}, range.head < range.anchor ? -1 : 1);
|
|
2347
|
+
}, range.head < range.anchor ? -1 : 1, center);
|
|
2218
2348
|
}
|
|
2219
2349
|
}
|
|
2220
2350
|
function betweenUneditable(pos) {
|
|
@@ -2325,6 +2455,14 @@ function findChangedDeco(a, b, diff) {
|
|
|
2325
2455
|
rangeset.RangeSet.compare(a, b, diff, comp);
|
|
2326
2456
|
return comp.changes;
|
|
2327
2457
|
}
|
|
2458
|
+
function inUneditable(node, inside) {
|
|
2459
|
+
for (let cur = node; cur && cur != inside; cur = cur.assignedSlot || cur.parentNode) {
|
|
2460
|
+
if (cur.nodeType == 1 && cur.contentEditable == 'false') {
|
|
2461
|
+
return true;
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
return false;
|
|
2465
|
+
}
|
|
2328
2466
|
|
|
2329
2467
|
/**
|
|
2330
2468
|
Used to indicate [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
|
|
@@ -2771,6 +2909,7 @@ function domPosInText(node, x, y) {
|
|
|
2771
2909
|
return { node, offset: closestOffset > -1 ? closestOffset : generalSide > 0 ? node.nodeValue.length : 0 };
|
|
2772
2910
|
}
|
|
2773
2911
|
function posAtCoords(view, { x, y }, precise, bias = -1) {
|
|
2912
|
+
var _a;
|
|
2774
2913
|
let content = view.contentDOM.getBoundingClientRect(), block;
|
|
2775
2914
|
let halfLine = view.defaultLineHeight / 2;
|
|
2776
2915
|
for (let bounced = false;;) {
|
|
@@ -2788,25 +2927,27 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
|
|
|
2788
2927
|
y = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
|
|
2789
2928
|
}
|
|
2790
2929
|
let lineStart = block.from;
|
|
2930
|
+
// Clip x to the viewport sides
|
|
2791
2931
|
x = Math.max(content.left + 1, Math.min(content.right - 1, x));
|
|
2792
2932
|
// If this is outside of the rendered viewport, we can't determine a position
|
|
2793
2933
|
if (lineStart < view.viewport.from)
|
|
2794
2934
|
return view.viewport.from == 0 ? 0 : posAtCoordsImprecise(view, content, block, x, y);
|
|
2795
2935
|
if (lineStart > view.viewport.to)
|
|
2796
2936
|
return view.viewport.to == view.state.doc.length ? view.state.doc.length : posAtCoordsImprecise(view, content, block, x, y);
|
|
2797
|
-
//
|
|
2798
|
-
let
|
|
2937
|
+
// Prefer ShadowRootOrDocument.elementFromPoint if present, fall back to document if not
|
|
2938
|
+
let doc = view.dom.ownerDocument;
|
|
2939
|
+
let element = (view.root.elementFromPoint ? view.root : doc).elementFromPoint(x, y);
|
|
2799
2940
|
// There's visible editor content under the point, so we can try
|
|
2800
2941
|
// using caret(Position|Range)FromPoint as a shortcut
|
|
2801
2942
|
let node, offset = -1;
|
|
2802
|
-
if (element && view.contentDOM.contains(element) &&
|
|
2803
|
-
if (
|
|
2804
|
-
let pos =
|
|
2943
|
+
if (element && view.contentDOM.contains(element) && ((_a = view.docView.nearest(element)) === null || _a === void 0 ? void 0 : _a.isEditable) != false) {
|
|
2944
|
+
if (doc.caretPositionFromPoint) {
|
|
2945
|
+
let pos = doc.caretPositionFromPoint(x, y);
|
|
2805
2946
|
if (pos)
|
|
2806
2947
|
({ offsetNode: node, offset } = pos);
|
|
2807
2948
|
}
|
|
2808
|
-
else if (
|
|
2809
|
-
let range =
|
|
2949
|
+
else if (doc.caretRangeFromPoint) {
|
|
2950
|
+
let range = doc.caretRangeFromPoint(x, y);
|
|
2810
2951
|
if (range) {
|
|
2811
2952
|
({ startContainer: node, startOffset: offset } = range);
|
|
2812
2953
|
if (browser.safari && isSuspiciousCaretResult(node, offset, x))
|
|
@@ -2940,7 +3081,23 @@ class InputState {
|
|
|
2940
3081
|
constructor(view) {
|
|
2941
3082
|
this.lastKeyCode = 0;
|
|
2942
3083
|
this.lastKeyTime = 0;
|
|
2943
|
-
|
|
3084
|
+
// On iOS, some keys need to have their default behavior happen
|
|
3085
|
+
// (after which we retroactively handle them and reset the DOM) to
|
|
3086
|
+
// avoid messing up the virtual keyboard state.
|
|
3087
|
+
//
|
|
3088
|
+
// On Chrome Android, backspace near widgets is just completely
|
|
3089
|
+
// broken, and there are no key events, so we need to handle the
|
|
3090
|
+
// beforeinput event. Deleting stuff will often create a flurry of
|
|
3091
|
+
// events, and interrupting it before it is done just makes
|
|
3092
|
+
// subsequent events even more broken, so again, we hold off doing
|
|
3093
|
+
// anything until the browser is finished with whatever it is trying
|
|
3094
|
+
// to do.
|
|
3095
|
+
//
|
|
3096
|
+
// setPendingKey sets this, causing the DOM observer to pause for a
|
|
3097
|
+
// bit, and setting an animation frame (which seems the most
|
|
3098
|
+
// reliable way to detect 'browser is done flailing') to fire a fake
|
|
3099
|
+
// key event and re-sync the view again.
|
|
3100
|
+
this.pendingKey = undefined;
|
|
2944
3101
|
this.lastSelectionOrigin = null;
|
|
2945
3102
|
this.lastSelectionTime = 0;
|
|
2946
3103
|
this.lastEscPress = 0;
|
|
@@ -3049,20 +3206,27 @@ class InputState {
|
|
|
3049
3206
|
// state. So we let it go through, and then, in
|
|
3050
3207
|
// applyDOMChange, notify key handlers of it and reset to
|
|
3051
3208
|
// the state they produce.
|
|
3052
|
-
|
|
3209
|
+
let pending;
|
|
3210
|
+
if (browser.ios && (pending = PendingKeys.find(key => key.keyCode == event.keyCode)) &&
|
|
3053
3211
|
!(event.ctrlKey || event.altKey || event.metaKey) && !event.synthetic) {
|
|
3054
|
-
this.
|
|
3055
|
-
setTimeout(() => this.flushIOSKey(view), 250);
|
|
3212
|
+
this.setPendingKey(view, pending);
|
|
3056
3213
|
return true;
|
|
3057
3214
|
}
|
|
3058
3215
|
return false;
|
|
3059
3216
|
}
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3217
|
+
setPendingKey(view, pending) {
|
|
3218
|
+
this.pendingKey = pending;
|
|
3219
|
+
requestAnimationFrame(() => {
|
|
3220
|
+
if (!this.pendingKey)
|
|
3221
|
+
return false;
|
|
3222
|
+
let key = this.pendingKey;
|
|
3223
|
+
this.pendingKey = undefined;
|
|
3224
|
+
view.observer.processRecords();
|
|
3225
|
+
let startState = view.state;
|
|
3226
|
+
dispatchKey(view.contentDOM, key.key, key.keyCode);
|
|
3227
|
+
if (view.state == startState)
|
|
3228
|
+
view.docView.reset(true);
|
|
3229
|
+
});
|
|
3066
3230
|
}
|
|
3067
3231
|
ignoreDuringComposition(event) {
|
|
3068
3232
|
if (!/^key/.test(event.type))
|
|
@@ -3109,6 +3273,11 @@ class InputState {
|
|
|
3109
3273
|
this.mouseSelection.destroy();
|
|
3110
3274
|
}
|
|
3111
3275
|
}
|
|
3276
|
+
const PendingKeys = [
|
|
3277
|
+
{ key: "Backspace", keyCode: 8, inputType: "deleteContentBackward" },
|
|
3278
|
+
{ key: "Enter", keyCode: 13, inputType: "insertParagraph" },
|
|
3279
|
+
{ key: "Delete", keyCode: 46, inputType: "deleteContentForward" }
|
|
3280
|
+
];
|
|
3112
3281
|
// Key codes for modifier keys
|
|
3113
3282
|
const modifierCodes = [16, 17, 18, 20, 91, 92, 224, 225];
|
|
3114
3283
|
class MouseSelection {
|
|
@@ -3225,7 +3394,7 @@ function capturePaste(view) {
|
|
|
3225
3394
|
function doPaste(view, input) {
|
|
3226
3395
|
let { state: state$1 } = view, changes, i = 1, text = state$1.toText(input);
|
|
3227
3396
|
let byLine = text.lines == state$1.selection.ranges.length;
|
|
3228
|
-
let linewise = lastLinewiseCopy && state$1.selection.ranges.every(r => r.empty) && lastLinewiseCopy == text.toString();
|
|
3397
|
+
let linewise = lastLinewiseCopy != null && state$1.selection.ranges.every(r => r.empty) && lastLinewiseCopy == text.toString();
|
|
3229
3398
|
if (linewise) {
|
|
3230
3399
|
let lastLine = -1;
|
|
3231
3400
|
changes = state$1.changeByRange(range => {
|
|
@@ -3436,9 +3605,8 @@ handlers.paste = (view, event) => {
|
|
|
3436
3605
|
return event.preventDefault();
|
|
3437
3606
|
view.observer.flush();
|
|
3438
3607
|
let data = brokenClipboardAPI ? null : event.clipboardData;
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
doPaste(view, text);
|
|
3608
|
+
if (data) {
|
|
3609
|
+
doPaste(view, data.getData("text/plain"));
|
|
3442
3610
|
event.preventDefault();
|
|
3443
3611
|
}
|
|
3444
3612
|
else {
|
|
@@ -3487,7 +3655,7 @@ function copiedRange(state) {
|
|
|
3487
3655
|
let lastLinewiseCopy = null;
|
|
3488
3656
|
handlers.copy = handlers.cut = (view, event) => {
|
|
3489
3657
|
let { text, ranges, linewise } = copiedRange(view.state);
|
|
3490
|
-
if (!text)
|
|
3658
|
+
if (!text && !linewise)
|
|
3491
3659
|
return;
|
|
3492
3660
|
lastLinewiseCopy = linewise ? text : null;
|
|
3493
3661
|
let data = brokenClipboardAPI ? null : event.clipboardData;
|
|
@@ -3555,6 +3723,31 @@ handlers.compositionend = view => {
|
|
|
3555
3723
|
handlers.contextmenu = view => {
|
|
3556
3724
|
view.inputState.lastContextMenu = Date.now();
|
|
3557
3725
|
};
|
|
3726
|
+
handlers.beforeinput = (view, event) => {
|
|
3727
|
+
var _a;
|
|
3728
|
+
// Because Chrome Android doesn't fire useful key events, use
|
|
3729
|
+
// beforeinput to detect backspace (and possibly enter and delete,
|
|
3730
|
+
// but those usually don't even seem to fire beforeinput events at
|
|
3731
|
+
// the moment) and fake a key event for it.
|
|
3732
|
+
//
|
|
3733
|
+
// (preventDefault on beforeinput, though supported in the spec,
|
|
3734
|
+
// seems to do nothing at all on Chrome).
|
|
3735
|
+
let pending;
|
|
3736
|
+
if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
|
|
3737
|
+
view.inputState.setPendingKey(view, pending);
|
|
3738
|
+
let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
|
|
3739
|
+
setTimeout(() => {
|
|
3740
|
+
var _a;
|
|
3741
|
+
// Backspacing near uneditable nodes on Chrome Android sometimes
|
|
3742
|
+
// closes the virtual keyboard. This tries to crudely detect
|
|
3743
|
+
// that and refocus to get it back.
|
|
3744
|
+
if ((((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0) > startViewHeight + 10 && view.hasFocus) {
|
|
3745
|
+
view.contentDOM.blur();
|
|
3746
|
+
view.focus();
|
|
3747
|
+
}
|
|
3748
|
+
}, 50);
|
|
3749
|
+
}
|
|
3750
|
+
};
|
|
3558
3751
|
|
|
3559
3752
|
const wrappingWhiteSpace = ["pre-wrap", "normal", "pre-line"];
|
|
3560
3753
|
class HeightOracle {
|
|
@@ -4270,6 +4463,15 @@ class LineGapWidget extends WidgetType {
|
|
|
4270
4463
|
}
|
|
4271
4464
|
get estimatedHeight() { return this.vertical ? this.size : -1; }
|
|
4272
4465
|
}
|
|
4466
|
+
class ScrollTarget {
|
|
4467
|
+
constructor(range, center = false) {
|
|
4468
|
+
this.range = range;
|
|
4469
|
+
this.center = center;
|
|
4470
|
+
}
|
|
4471
|
+
map(changes) {
|
|
4472
|
+
return changes.empty ? this : new ScrollTarget(this.range.map(changes), this.center);
|
|
4473
|
+
}
|
|
4474
|
+
}
|
|
4273
4475
|
class ViewState {
|
|
4274
4476
|
constructor(state) {
|
|
4275
4477
|
this.state = state;
|
|
@@ -4282,7 +4484,7 @@ class ViewState {
|
|
|
4282
4484
|
this.heightOracle = new HeightOracle;
|
|
4283
4485
|
// See VP.MaxDOMHeight
|
|
4284
4486
|
this.scaler = IdScaler;
|
|
4285
|
-
this.
|
|
4487
|
+
this.scrollTarget = null;
|
|
4286
4488
|
// Briefly set to true when printing, to disable viewport limiting
|
|
4287
4489
|
this.printing = false;
|
|
4288
4490
|
this.visibleRanges = [];
|
|
@@ -4315,7 +4517,7 @@ class ViewState {
|
|
|
4315
4517
|
this.scaler = this.heightMap.height <= 7000000 /* MaxDOMHeight */ ? IdScaler :
|
|
4316
4518
|
new BigScaler(this.heightOracle.doc, this.heightMap, this.viewports);
|
|
4317
4519
|
}
|
|
4318
|
-
update(update,
|
|
4520
|
+
update(update, scrollTarget = null) {
|
|
4319
4521
|
let prev = this.state;
|
|
4320
4522
|
this.state = update.state;
|
|
4321
4523
|
let newDeco = this.state.facet(decorations);
|
|
@@ -4326,30 +4528,33 @@ class ViewState {
|
|
|
4326
4528
|
if (this.heightMap.height != prevHeight)
|
|
4327
4529
|
update.flags |= 2 /* Height */;
|
|
4328
4530
|
let viewport = heightChanges.length ? this.mapViewport(this.viewport, update.changes) : this.viewport;
|
|
4329
|
-
if (
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
update.flags |= 4 /* Viewport */;
|
|
4334
|
-
}
|
|
4531
|
+
if (scrollTarget && (scrollTarget.range.head < viewport.from || scrollTarget.range.head > viewport.to) ||
|
|
4532
|
+
!this.viewportIsAppropriate(viewport))
|
|
4533
|
+
viewport = this.getViewport(0, scrollTarget);
|
|
4534
|
+
this.viewport = viewport;
|
|
4335
4535
|
this.updateForViewport();
|
|
4336
4536
|
if (this.lineGaps.length || this.viewport.to - this.viewport.from > 15000 /* MinViewPort */)
|
|
4337
|
-
|
|
4338
|
-
this.computeVisibleRanges();
|
|
4339
|
-
if (
|
|
4340
|
-
this.
|
|
4537
|
+
this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
|
|
4538
|
+
update.flags |= this.computeVisibleRanges();
|
|
4539
|
+
if (scrollTarget)
|
|
4540
|
+
this.scrollTarget = scrollTarget;
|
|
4341
4541
|
if (!this.mustEnforceCursorAssoc && update.selectionSet && update.view.lineWrapping &&
|
|
4342
4542
|
update.state.selection.main.empty && update.state.selection.main.assoc)
|
|
4343
4543
|
this.mustEnforceCursorAssoc = true;
|
|
4344
4544
|
}
|
|
4345
4545
|
measure(docView, repeated) {
|
|
4346
4546
|
let dom = docView.dom, whiteSpace = "", direction = exports.Direction.LTR;
|
|
4547
|
+
let result = 0;
|
|
4347
4548
|
if (!repeated) {
|
|
4348
4549
|
// Vertical padding
|
|
4349
4550
|
let style = window.getComputedStyle(dom);
|
|
4350
4551
|
whiteSpace = style.whiteSpace, direction = (style.direction == "rtl" ? exports.Direction.RTL : exports.Direction.LTR);
|
|
4351
|
-
|
|
4352
|
-
this.
|
|
4552
|
+
let paddingTop = parseInt(style.paddingTop) || 0, paddingBottom = parseInt(style.paddingBottom) || 0;
|
|
4553
|
+
if (this.paddingTop != paddingTop || this.paddingBottom != paddingBottom) {
|
|
4554
|
+
result |= 8 /* Geometry */;
|
|
4555
|
+
this.paddingTop = paddingTop;
|
|
4556
|
+
this.paddingBottom = paddingBottom;
|
|
4557
|
+
}
|
|
4353
4558
|
}
|
|
4354
4559
|
// Pixel viewport
|
|
4355
4560
|
let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 } : visiblePixelRange(dom, this.paddingTop);
|
|
@@ -4359,7 +4564,7 @@ class ViewState {
|
|
|
4359
4564
|
if (!this.inView)
|
|
4360
4565
|
return 0;
|
|
4361
4566
|
let lineHeights = docView.measureVisibleLineHeights();
|
|
4362
|
-
let refresh = false, bias = 0,
|
|
4567
|
+
let refresh = false, bias = 0, oracle = this.heightOracle;
|
|
4363
4568
|
if (!repeated) {
|
|
4364
4569
|
let contentWidth = docView.dom.clientWidth;
|
|
4365
4570
|
if (oracle.mustRefresh(lineHeights, whiteSpace, direction) ||
|
|
@@ -4368,12 +4573,12 @@ class ViewState {
|
|
|
4368
4573
|
refresh = oracle.refresh(whiteSpace, direction, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
|
|
4369
4574
|
if (refresh) {
|
|
4370
4575
|
docView.minWidth = 0;
|
|
4371
|
-
result |=
|
|
4576
|
+
result |= 8 /* Geometry */;
|
|
4372
4577
|
}
|
|
4373
4578
|
}
|
|
4374
4579
|
if (this.contentWidth != contentWidth) {
|
|
4375
4580
|
this.contentWidth = contentWidth;
|
|
4376
|
-
result |=
|
|
4581
|
+
result |= 8 /* Geometry */;
|
|
4377
4582
|
}
|
|
4378
4583
|
if (dTop > 0 && dBottom > 0)
|
|
4379
4584
|
bias = Math.max(dTop, dBottom);
|
|
@@ -4385,17 +4590,12 @@ class ViewState {
|
|
|
4385
4590
|
if (oracle.heightChanged)
|
|
4386
4591
|
result |= 2 /* Height */;
|
|
4387
4592
|
if (!this.viewportIsAppropriate(this.viewport, bias) ||
|
|
4388
|
-
this.
|
|
4389
|
-
|
|
4390
|
-
if (newVP.from != this.viewport.from || newVP.to != this.viewport.to) {
|
|
4391
|
-
this.viewport = newVP;
|
|
4392
|
-
result |= 4 /* Viewport */;
|
|
4393
|
-
}
|
|
4394
|
-
}
|
|
4593
|
+
this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to))
|
|
4594
|
+
this.viewport = this.getViewport(bias, this.scrollTarget);
|
|
4395
4595
|
this.updateForViewport();
|
|
4396
4596
|
if (this.lineGaps.length || this.viewport.to - this.viewport.from > 15000 /* MinViewPort */)
|
|
4397
|
-
|
|
4398
|
-
this.computeVisibleRanges();
|
|
4597
|
+
this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
|
|
4598
|
+
result |= this.computeVisibleRanges();
|
|
4399
4599
|
if (this.mustEnforceCursorAssoc) {
|
|
4400
4600
|
this.mustEnforceCursorAssoc = false;
|
|
4401
4601
|
// This is done in the read stage, because moving the selection
|
|
@@ -4408,22 +4608,25 @@ class ViewState {
|
|
|
4408
4608
|
}
|
|
4409
4609
|
get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top, 0); }
|
|
4410
4610
|
get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom, 0); }
|
|
4411
|
-
getViewport(bias,
|
|
4611
|
+
getViewport(bias, scrollTarget) {
|
|
4412
4612
|
// This will divide VP.Margin between the top and the
|
|
4413
4613
|
// bottom, depending on the bias (the change in viewport position
|
|
4414
4614
|
// since the last update). It'll hold a number between 0 and 1
|
|
4415
4615
|
let marginTop = 0.5 - Math.max(-0.5, Math.min(0.5, bias / 1000 /* Margin */ / 2));
|
|
4416
4616
|
let map = this.heightMap, doc = this.state.doc, { visibleTop, visibleBottom } = this;
|
|
4417
4617
|
let viewport = new Viewport(map.lineAt(visibleTop - marginTop * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).from, map.lineAt(visibleBottom + (1 - marginTop) * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).to);
|
|
4418
|
-
// If
|
|
4419
|
-
if (
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4618
|
+
// If scrollTarget is given, make sure the viewport includes that position
|
|
4619
|
+
if (scrollTarget) {
|
|
4620
|
+
let { head } = scrollTarget.range, viewHeight = visibleBottom - visibleTop;
|
|
4621
|
+
if (head < viewport.from || head > viewport.to) {
|
|
4622
|
+
let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
|
|
4623
|
+
if (scrollTarget.center)
|
|
4624
|
+
topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
|
|
4625
|
+
else if (head < viewport.from)
|
|
4626
|
+
topPos = block.top;
|
|
4627
|
+
else
|
|
4628
|
+
topPos = block.bottom - viewHeight;
|
|
4629
|
+
viewport = new Viewport(map.lineAt(topPos - 1000 /* Margin */ / 2, QueryType.ByHeight, doc, 0, 0).from, map.lineAt(topPos + viewHeight + 1000 /* Margin */ / 2, QueryType.ByHeight, doc, 0, 0).to);
|
|
4427
4630
|
}
|
|
4428
4631
|
}
|
|
4429
4632
|
return viewport;
|
|
@@ -4517,9 +4720,7 @@ class ViewState {
|
|
|
4517
4720
|
if (!LineGap.same(gaps, this.lineGaps)) {
|
|
4518
4721
|
this.lineGaps = gaps;
|
|
4519
4722
|
this.lineGapDeco = Decoration.set(gaps.map(gap => gap.draw(this.heightOracle.lineWrapping)));
|
|
4520
|
-
return 8 /* LineGaps */;
|
|
4521
4723
|
}
|
|
4522
|
-
return 0;
|
|
4523
4724
|
}
|
|
4524
4725
|
computeVisibleRanges() {
|
|
4525
4726
|
let deco = this.state.facet(decorations);
|
|
@@ -4530,7 +4731,10 @@ class ViewState {
|
|
|
4530
4731
|
span(from, to) { ranges.push({ from, to }); },
|
|
4531
4732
|
point() { }
|
|
4532
4733
|
}, 20);
|
|
4734
|
+
let changed = ranges.length != this.visibleRanges.length ||
|
|
4735
|
+
this.visibleRanges.some((r, i) => r.from != ranges[i].from || r.to != ranges[i].to);
|
|
4533
4736
|
this.visibleRanges = ranges;
|
|
4737
|
+
return changed ? 4 /* Viewport */ : 0;
|
|
4534
4738
|
}
|
|
4535
4739
|
lineAt(pos, editorTop) {
|
|
4536
4740
|
editorTop += this.paddingTop;
|
|
@@ -4555,16 +4759,11 @@ class ViewState {
|
|
|
4555
4759
|
return this.scaler.toDOM(this.heightMap.height, this.paddingTop);
|
|
4556
4760
|
}
|
|
4557
4761
|
}
|
|
4558
|
-
/**
|
|
4559
|
-
Indicates the range of the document that is in the visible
|
|
4560
|
-
viewport.
|
|
4561
|
-
*/
|
|
4562
4762
|
class Viewport {
|
|
4563
4763
|
constructor(from, to) {
|
|
4564
4764
|
this.from = from;
|
|
4565
4765
|
this.to = to;
|
|
4566
4766
|
}
|
|
4567
|
-
eq(b) { return this.from == b.from && this.to == b.to; }
|
|
4568
4767
|
}
|
|
4569
4768
|
function lineStructure(from, to, state) {
|
|
4570
4769
|
let ranges = [], pos = from, total = 0;
|
|
@@ -4690,7 +4889,7 @@ function buildTheme(main, spec, scopes) {
|
|
|
4690
4889
|
});
|
|
4691
4890
|
}
|
|
4692
4891
|
const baseTheme = buildTheme("." + baseThemeID, {
|
|
4693
|
-
"
|
|
4892
|
+
"&.cm-editor": {
|
|
4694
4893
|
position: "relative !important",
|
|
4695
4894
|
boxSizing: "border-box",
|
|
4696
4895
|
"&.cm-focused": {
|
|
@@ -4857,6 +5056,8 @@ class DOMObserver {
|
|
|
4857
5056
|
this.scrollTargets = [];
|
|
4858
5057
|
this.intersection = null;
|
|
4859
5058
|
this.intersecting = false;
|
|
5059
|
+
this.gapIntersection = null;
|
|
5060
|
+
this.gaps = [];
|
|
4860
5061
|
// Used to work around a Safari Selection/shadow DOM bug (#414)
|
|
4861
5062
|
this._selectionRange = null;
|
|
4862
5063
|
// Timeout for scheduling check of the parents that need scroll handlers
|
|
@@ -4897,13 +5098,17 @@ class DOMObserver {
|
|
|
4897
5098
|
this.intersection = new IntersectionObserver(entries => {
|
|
4898
5099
|
if (this.parentCheck < 0)
|
|
4899
5100
|
this.parentCheck = setTimeout(this.listenForScroll.bind(this), 1000);
|
|
4900
|
-
if (entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
|
|
5101
|
+
if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
|
|
4901
5102
|
this.intersecting = !this.intersecting;
|
|
4902
5103
|
if (this.intersecting != this.view.inView)
|
|
4903
5104
|
this.onScrollChanged(document.createEvent("Event"));
|
|
4904
5105
|
}
|
|
4905
5106
|
}, {});
|
|
4906
5107
|
this.intersection.observe(this.dom);
|
|
5108
|
+
this.gapIntersection = new IntersectionObserver(entries => {
|
|
5109
|
+
if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0)
|
|
5110
|
+
this.onScrollChanged(document.createEvent("Event"));
|
|
5111
|
+
}, {});
|
|
4907
5112
|
}
|
|
4908
5113
|
this.listenForScroll();
|
|
4909
5114
|
}
|
|
@@ -4912,6 +5117,14 @@ class DOMObserver {
|
|
|
4912
5117
|
this.flush();
|
|
4913
5118
|
this.onScrollChanged(e);
|
|
4914
5119
|
}
|
|
5120
|
+
updateGaps(gaps) {
|
|
5121
|
+
if (this.gapIntersection && (gaps.length != this.gaps.length || this.gaps.some((g, i) => g != gaps[i]))) {
|
|
5122
|
+
this.gapIntersection.disconnect();
|
|
5123
|
+
for (let gap of gaps)
|
|
5124
|
+
this.gapIntersection.observe(gap);
|
|
5125
|
+
this.gaps = gaps;
|
|
5126
|
+
}
|
|
5127
|
+
}
|
|
4915
5128
|
onSelectionChange(event) {
|
|
4916
5129
|
if (this.lastFlush < Date.now() - 50)
|
|
4917
5130
|
this._selectionRange = null;
|
|
@@ -5027,20 +5240,12 @@ class DOMObserver {
|
|
|
5027
5240
|
this.flush();
|
|
5028
5241
|
}
|
|
5029
5242
|
}
|
|
5030
|
-
|
|
5031
|
-
flush() {
|
|
5032
|
-
if (this.delayedFlush >= 0)
|
|
5033
|
-
return;
|
|
5034
|
-
this.lastFlush = Date.now();
|
|
5243
|
+
processRecords() {
|
|
5035
5244
|
let records = this.queue;
|
|
5036
5245
|
for (let mut of this.observer.takeRecords())
|
|
5037
5246
|
records.push(mut);
|
|
5038
5247
|
if (records.length)
|
|
5039
5248
|
this.queue = [];
|
|
5040
|
-
let selection = this.selectionRange;
|
|
5041
|
-
let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
|
|
5042
|
-
if (records.length == 0 && !newSel)
|
|
5043
|
-
return;
|
|
5044
5249
|
let from = -1, to = -1, typeOver = false;
|
|
5045
5250
|
for (let record of records) {
|
|
5046
5251
|
let range = this.readMutation(record);
|
|
@@ -5056,17 +5261,26 @@ class DOMObserver {
|
|
|
5056
5261
|
to = Math.max(range.to, to);
|
|
5057
5262
|
}
|
|
5058
5263
|
}
|
|
5264
|
+
return { from, to, typeOver };
|
|
5265
|
+
}
|
|
5266
|
+
// Apply pending changes, if any
|
|
5267
|
+
flush() {
|
|
5268
|
+
// Completely hold off flushing when pending keys are set—the code
|
|
5269
|
+
// managing those will make sure processRecords is called and the
|
|
5270
|
+
// view is resynchronized after
|
|
5271
|
+
if (this.delayedFlush >= 0 || this.view.inputState.pendingKey)
|
|
5272
|
+
return;
|
|
5273
|
+
this.lastFlush = Date.now();
|
|
5274
|
+
let { from, to, typeOver } = this.processRecords();
|
|
5275
|
+
let selection = this.selectionRange;
|
|
5276
|
+
let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
|
|
5277
|
+
if (from < 0 && !newSel)
|
|
5278
|
+
return;
|
|
5059
5279
|
let startState = this.view.state;
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
if (this.view.state == startState)
|
|
5063
|
-
|
|
5064
|
-
this.ignore(() => this.view.docView.sync());
|
|
5065
|
-
this.view.docView.dirty = 0 /* Not */;
|
|
5066
|
-
}
|
|
5067
|
-
if (newSel)
|
|
5068
|
-
this.view.docView.updateSelection();
|
|
5069
|
-
}
|
|
5280
|
+
this.onChange(from, to, typeOver);
|
|
5281
|
+
// The view wasn't updated
|
|
5282
|
+
if (this.view.state == startState)
|
|
5283
|
+
this.view.docView.reset(newSel);
|
|
5070
5284
|
this.clearSelection();
|
|
5071
5285
|
}
|
|
5072
5286
|
readMutation(rec) {
|
|
@@ -5093,6 +5307,8 @@ class DOMObserver {
|
|
|
5093
5307
|
this.stop();
|
|
5094
5308
|
if (this.intersection)
|
|
5095
5309
|
this.intersection.disconnect();
|
|
5310
|
+
if (this.gapIntersection)
|
|
5311
|
+
this.gapIntersection.disconnect();
|
|
5096
5312
|
for (let dom of this.scrollTargets)
|
|
5097
5313
|
dom.removeEventListener("scroll", this.onScroll);
|
|
5098
5314
|
window.removeEventListener("scroll", this.onScroll);
|
|
@@ -5201,9 +5417,9 @@ function applyDOMChange(view, start, end, typeOver) {
|
|
|
5201
5417
|
(change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
|
|
5202
5418
|
dispatchKey(view.contentDOM, "Backspace", 8)) ||
|
|
5203
5419
|
(change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
|
|
5204
|
-
dispatchKey(view.contentDOM, "Delete", 46)))
|
|
5205
|
-
browser.ios && view.inputState.flushIOSKey(view))
|
|
5420
|
+
dispatchKey(view.contentDOM, "Delete", 46)))) {
|
|
5206
5421
|
return;
|
|
5422
|
+
}
|
|
5207
5423
|
let text = change.insert.toString();
|
|
5208
5424
|
if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
|
|
5209
5425
|
return;
|
|
@@ -5293,8 +5509,9 @@ class DOMReader {
|
|
|
5293
5509
|
if (next == end)
|
|
5294
5510
|
break;
|
|
5295
5511
|
let view = ContentView.get(cur), nextView = ContentView.get(next);
|
|
5296
|
-
if (
|
|
5297
|
-
(
|
|
5512
|
+
if (view && nextView ? view.breakAfter :
|
|
5513
|
+
(view ? view.breakAfter : isBlockElement(cur)) ||
|
|
5514
|
+
(isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
|
|
5298
5515
|
this.text += this.lineBreak;
|
|
5299
5516
|
cur = next;
|
|
5300
5517
|
}
|
|
@@ -5397,6 +5614,7 @@ class EditorView {
|
|
|
5397
5614
|
this.editorAttrs = {};
|
|
5398
5615
|
this.contentAttrs = {};
|
|
5399
5616
|
this.bidiCache = [];
|
|
5617
|
+
this.destroyed = false;
|
|
5400
5618
|
/**
|
|
5401
5619
|
@internal
|
|
5402
5620
|
*/
|
|
@@ -5422,7 +5640,7 @@ class EditorView {
|
|
|
5422
5640
|
this.dom.appendChild(this.scrollDOM);
|
|
5423
5641
|
this._dispatch = config.dispatch || ((tr) => this.update([tr]));
|
|
5424
5642
|
this.dispatch = this.dispatch.bind(this);
|
|
5425
|
-
this.root = (config.root || document);
|
|
5643
|
+
this.root = (config.root || getRoot(config.parent) || document);
|
|
5426
5644
|
this.viewState = new ViewState(config.state || state.EditorState.create());
|
|
5427
5645
|
this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec).update(this));
|
|
5428
5646
|
this.observer = new DOMObserver(this, (from, to, typeOver) => {
|
|
@@ -5495,25 +5713,32 @@ class EditorView {
|
|
|
5495
5713
|
throw new RangeError("Trying to update state with a transaction that doesn't start from the previous state.");
|
|
5496
5714
|
state$1 = tr.state;
|
|
5497
5715
|
}
|
|
5716
|
+
if (this.destroyed) {
|
|
5717
|
+
this.viewState.state = state$1;
|
|
5718
|
+
return;
|
|
5719
|
+
}
|
|
5498
5720
|
// When the phrases change, redraw the editor
|
|
5499
5721
|
if (state$1.facet(state.EditorState.phrases) != this.state.facet(state.EditorState.phrases))
|
|
5500
5722
|
return this.setState(state$1);
|
|
5501
5723
|
update = new ViewUpdate(this, state$1, transactions);
|
|
5502
|
-
let
|
|
5724
|
+
let scrollTarget = null;
|
|
5503
5725
|
try {
|
|
5504
5726
|
this.updateState = 2 /* Updating */;
|
|
5505
5727
|
for (let tr of transactions) {
|
|
5506
|
-
if (
|
|
5507
|
-
|
|
5728
|
+
if (scrollTarget)
|
|
5729
|
+
scrollTarget = scrollTarget.map(tr.changes);
|
|
5508
5730
|
if (tr.scrollIntoView) {
|
|
5509
5731
|
let { main } = tr.state.selection;
|
|
5510
|
-
|
|
5732
|
+
scrollTarget = new ScrollTarget(main.empty ? main : state.EditorSelection.cursor(main.head, main.head > main.anchor ? -1 : 1));
|
|
5511
5733
|
}
|
|
5512
|
-
for (let e of tr.effects)
|
|
5734
|
+
for (let e of tr.effects) {
|
|
5513
5735
|
if (e.is(scrollTo))
|
|
5514
|
-
|
|
5736
|
+
scrollTarget = new ScrollTarget(e.value);
|
|
5737
|
+
else if (e.is(centerOn))
|
|
5738
|
+
scrollTarget = new ScrollTarget(e.value, true);
|
|
5739
|
+
}
|
|
5515
5740
|
}
|
|
5516
|
-
this.viewState.update(update,
|
|
5741
|
+
this.viewState.update(update, scrollTarget);
|
|
5517
5742
|
this.bidiCache = CachedOrder.update(this.bidiCache, update.changes);
|
|
5518
5743
|
if (!update.empty) {
|
|
5519
5744
|
this.updatePlugins(update);
|
|
@@ -5528,7 +5753,7 @@ class EditorView {
|
|
|
5528
5753
|
finally {
|
|
5529
5754
|
this.updateState = 0 /* Idle */;
|
|
5530
5755
|
}
|
|
5531
|
-
if (redrawn ||
|
|
5756
|
+
if (redrawn || scrollTarget || this.viewState.mustEnforceCursorAssoc)
|
|
5532
5757
|
this.requestMeasure();
|
|
5533
5758
|
if (!update.empty)
|
|
5534
5759
|
for (let listener of this.state.facet(updateListener))
|
|
@@ -5544,6 +5769,10 @@ class EditorView {
|
|
|
5544
5769
|
setState(newState) {
|
|
5545
5770
|
if (this.updateState != 0 /* Idle */)
|
|
5546
5771
|
throw new Error("Calls to EditorView.setState are not allowed while an update is in progress");
|
|
5772
|
+
if (this.destroyed) {
|
|
5773
|
+
this.viewState.state = newState;
|
|
5774
|
+
return;
|
|
5775
|
+
}
|
|
5547
5776
|
this.updateState = 2 /* Updating */;
|
|
5548
5777
|
try {
|
|
5549
5778
|
for (let plugin of this.plugins)
|
|
@@ -5593,6 +5822,8 @@ class EditorView {
|
|
|
5593
5822
|
@internal
|
|
5594
5823
|
*/
|
|
5595
5824
|
measure(flush = true) {
|
|
5825
|
+
if (this.destroyed)
|
|
5826
|
+
return;
|
|
5596
5827
|
if (this.measureScheduled > -1)
|
|
5597
5828
|
cancelAnimationFrame(this.measureScheduled);
|
|
5598
5829
|
this.measureScheduled = -1; // Prevent requestMeasure calls from scheduling another animation frame
|
|
@@ -5602,15 +5833,18 @@ class EditorView {
|
|
|
5602
5833
|
try {
|
|
5603
5834
|
for (let i = 0;; i++) {
|
|
5604
5835
|
this.updateState = 1 /* Measuring */;
|
|
5836
|
+
let oldViewport = this.viewport;
|
|
5605
5837
|
let changed = this.viewState.measure(this.docView, i > 0);
|
|
5606
|
-
|
|
5607
|
-
if (!changed && !measuring.length && this.viewState.scrollTo == null)
|
|
5838
|
+
if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
|
|
5608
5839
|
break;
|
|
5609
|
-
this.measureRequests = [];
|
|
5610
5840
|
if (i > 5) {
|
|
5611
5841
|
console.warn("Viewport failed to stabilize");
|
|
5612
5842
|
break;
|
|
5613
5843
|
}
|
|
5844
|
+
let measuring = [];
|
|
5845
|
+
// Only run measure requests in this cycle when the viewport didn't change
|
|
5846
|
+
if (!(changed & 4 /* Viewport */))
|
|
5847
|
+
[this.measureRequests, measuring] = [measuring, this.measureRequests];
|
|
5614
5848
|
let measured = measuring.map(m => {
|
|
5615
5849
|
try {
|
|
5616
5850
|
return m.read(this);
|
|
@@ -5643,11 +5877,11 @@ class EditorView {
|
|
|
5643
5877
|
logException(this.state, e);
|
|
5644
5878
|
}
|
|
5645
5879
|
}
|
|
5646
|
-
if (this.viewState.
|
|
5647
|
-
this.docView.
|
|
5648
|
-
this.viewState.
|
|
5880
|
+
if (this.viewState.scrollTarget) {
|
|
5881
|
+
this.docView.scrollIntoView(this.viewState.scrollTarget);
|
|
5882
|
+
this.viewState.scrollTarget = null;
|
|
5649
5883
|
}
|
|
5650
|
-
if (
|
|
5884
|
+
if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
|
|
5651
5885
|
break;
|
|
5652
5886
|
}
|
|
5653
5887
|
}
|
|
@@ -5677,6 +5911,7 @@ class EditorView {
|
|
|
5677
5911
|
spellcheck: "false",
|
|
5678
5912
|
autocorrect: "off",
|
|
5679
5913
|
autocapitalize: "off",
|
|
5914
|
+
translate: "no",
|
|
5680
5915
|
contenteditable: !this.state.facet(editable) ? "false" : contentEditablePlainTextSupported() ? "plaintext-only" : "true",
|
|
5681
5916
|
class: "cm-content",
|
|
5682
5917
|
style: `${browser.tabSize}: ${this.state.tabSize}`,
|
|
@@ -5867,12 +6102,9 @@ class EditorView {
|
|
|
5867
6102
|
moveVertically(start, forward, distance) {
|
|
5868
6103
|
return skipAtoms(this, start, moveVertically(this, start, forward, distance));
|
|
5869
6104
|
}
|
|
5870
|
-
|
|
5871
|
-
Scroll the given document position into view.
|
|
5872
|
-
*/
|
|
6105
|
+
// FIXME remove on next major version
|
|
5873
6106
|
scrollPosIntoView(pos) {
|
|
5874
|
-
this.
|
|
5875
|
-
this.requestMeasure();
|
|
6107
|
+
this.dispatch({ effects: scrollTo.of(state.EditorSelection.cursor(pos)) });
|
|
5876
6108
|
}
|
|
5877
6109
|
/**
|
|
5878
6110
|
Find the DOM parent node and offset (child offset if `node` is
|
|
@@ -5983,11 +6215,13 @@ class EditorView {
|
|
|
5983
6215
|
destroy() {
|
|
5984
6216
|
for (let plugin of this.plugins)
|
|
5985
6217
|
plugin.destroy(this);
|
|
6218
|
+
this.plugins = [];
|
|
5986
6219
|
this.inputState.destroy();
|
|
5987
6220
|
this.dom.remove();
|
|
5988
6221
|
this.observer.destroy();
|
|
5989
6222
|
if (this.measureScheduled > -1)
|
|
5990
6223
|
cancelAnimationFrame(this.measureScheduled);
|
|
6224
|
+
this.destroyed = true;
|
|
5991
6225
|
}
|
|
5992
6226
|
/**
|
|
5993
6227
|
Facet that can be used to add DOM event handlers. The value
|
|
@@ -6046,6 +6280,11 @@ transaction to make it scroll the given range into view.
|
|
|
6046
6280
|
*/
|
|
6047
6281
|
EditorView.scrollTo = scrollTo;
|
|
6048
6282
|
/**
|
|
6283
|
+
Effect that makes the editor scroll the given range to the
|
|
6284
|
+
center of the visible view.
|
|
6285
|
+
*/
|
|
6286
|
+
EditorView.centerOn = centerOn;
|
|
6287
|
+
/**
|
|
6049
6288
|
Facet to add a [style
|
|
6050
6289
|
module](https://github.com/marijnh/style-mod#documentation) to
|
|
6051
6290
|
an editor view. The view will ensure that the module is
|
|
@@ -6175,11 +6414,7 @@ class CachedOrder {
|
|
|
6175
6414
|
}
|
|
6176
6415
|
}
|
|
6177
6416
|
|
|
6178
|
-
const currentPlatform =
|
|
6179
|
-
: /Mac/.test(navigator.platform) ? "mac"
|
|
6180
|
-
: /Win/.test(navigator.platform) ? "win"
|
|
6181
|
-
: /Linux|X11/.test(navigator.platform) ? "linux"
|
|
6182
|
-
: "key";
|
|
6417
|
+
const currentPlatform = browser.mac ? "mac" : browser.windows ? "win" : browser.linux ? "linux" : "key";
|
|
6183
6418
|
function normalizeKeyName(name, platform) {
|
|
6184
6419
|
const parts = name.split(/-(?!$)/);
|
|
6185
6420
|
let result = parts[parts.length - 1];
|
|
@@ -6685,7 +6920,7 @@ class MatchDecorator {
|
|
|
6685
6920
|
}
|
|
6686
6921
|
|
|
6687
6922
|
const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g";
|
|
6688
|
-
const Specials = new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
|
|
6923
|
+
const Specials = new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
|
|
6689
6924
|
const Names = {
|
|
6690
6925
|
0: "null",
|
|
6691
6926
|
7: "bell",
|
|
@@ -6700,6 +6935,8 @@ const Names = {
|
|
|
6700
6935
|
8206: "left-to-right mark",
|
|
6701
6936
|
8207: "right-to-left mark",
|
|
6702
6937
|
8232: "line separator",
|
|
6938
|
+
8237: "left-to-right override",
|
|
6939
|
+
8238: "right-to-left override",
|
|
6703
6940
|
8233: "paragraph separator",
|
|
6704
6941
|
65279: "zero width no-break space",
|
|
6705
6942
|
65532: "object replacement"
|
|
@@ -6866,7 +7103,7 @@ DOM class.
|
|
|
6866
7103
|
function highlightActiveLine() {
|
|
6867
7104
|
return activeLineHighlighter;
|
|
6868
7105
|
}
|
|
6869
|
-
const lineDeco = Decoration.line({
|
|
7106
|
+
const lineDeco = Decoration.line({ class: "cm-activeLine" });
|
|
6870
7107
|
const activeLineHighlighter = ViewPlugin.fromClass(class {
|
|
6871
7108
|
constructor(view) {
|
|
6872
7109
|
this.decorations = this.getDeco(view);
|