@codemirror/view 0.19.8 → 0.19.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/dist/index.cjs +390 -158
- package/dist/index.d.ts +12 -4
- package/dist/index.js +390 -158
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -102,9 +102,9 @@ function windowRect(win) {
|
|
|
102
102
|
top: 0, bottom: win.innerHeight };
|
|
103
103
|
}
|
|
104
104
|
const ScrollSpace = 5;
|
|
105
|
-
function scrollRectIntoView(dom, rect, side) {
|
|
105
|
+
function scrollRectIntoView(dom, rect, side, center) {
|
|
106
106
|
let doc = dom.ownerDocument, win = doc.defaultView;
|
|
107
|
-
for (let cur = dom
|
|
107
|
+
for (let cur = dom; cur;) {
|
|
108
108
|
if (cur.nodeType == 1) { // Element
|
|
109
109
|
let bounding, top = cur == doc.body;
|
|
110
110
|
if (top) {
|
|
@@ -121,7 +121,20 @@ function scrollRectIntoView(dom, rect, side) {
|
|
|
121
121
|
top: rect.top, bottom: rect.top + cur.clientHeight };
|
|
122
122
|
}
|
|
123
123
|
let moveX = 0, moveY = 0;
|
|
124
|
-
if (
|
|
124
|
+
if (center) {
|
|
125
|
+
let rectHeight = rect.bottom - rect.top, boundingHeight = bounding.bottom - bounding.top;
|
|
126
|
+
let targetTop;
|
|
127
|
+
if (rectHeight <= boundingHeight)
|
|
128
|
+
targetTop = rect.top + rectHeight / 2 - boundingHeight / 2;
|
|
129
|
+
else if (side < 0)
|
|
130
|
+
targetTop = rect.top - ScrollSpace;
|
|
131
|
+
else
|
|
132
|
+
targetTop = rect.bottom + ScrollSpace - boundingHeight;
|
|
133
|
+
moveY = targetTop - bounding.top;
|
|
134
|
+
if (Math.abs(moveY) <= 1)
|
|
135
|
+
moveY = 0;
|
|
136
|
+
}
|
|
137
|
+
else if (rect.top < bounding.top) {
|
|
125
138
|
moveY = -(bounding.top - rect.top + ScrollSpace);
|
|
126
139
|
if (side > 0 && rect.bottom > bounding.bottom + moveY)
|
|
127
140
|
moveY = rect.bottom - bounding.bottom + moveY + ScrollSpace;
|
|
@@ -163,6 +176,7 @@ function scrollRectIntoView(dom, rect, side) {
|
|
|
163
176
|
if (top)
|
|
164
177
|
break;
|
|
165
178
|
cur = cur.assignedSlot || cur.parentNode;
|
|
179
|
+
center = false;
|
|
166
180
|
}
|
|
167
181
|
else if (cur.nodeType == 11) { // A shadow root
|
|
168
182
|
cur = cur.host;
|
|
@@ -251,6 +265,14 @@ function contentEditablePlainTextSupported() {
|
|
|
251
265
|
}
|
|
252
266
|
return _plainTextSupported;
|
|
253
267
|
}
|
|
268
|
+
function getRoot(node) {
|
|
269
|
+
while (node) {
|
|
270
|
+
node = node.assignedSlot || node.parentNode;
|
|
271
|
+
if (node && (node.nodeType == 9 || node.nodeType == 11 && node.host))
|
|
272
|
+
return node;
|
|
273
|
+
}
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
254
276
|
|
|
255
277
|
class DOMPos {
|
|
256
278
|
constructor(node, offset, precise = true) {
|
|
@@ -299,25 +321,30 @@ class ContentView {
|
|
|
299
321
|
sync(track) {
|
|
300
322
|
var _a;
|
|
301
323
|
if (this.dirty & 2 /* Node */) {
|
|
302
|
-
let parent = this.dom
|
|
324
|
+
let parent = this.dom;
|
|
325
|
+
let pos = parent.firstChild;
|
|
303
326
|
for (let child of this.children) {
|
|
304
327
|
if (child.dirty) {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
child.reuseDOM(next);
|
|
328
|
+
if (!child.dom && pos && !((_a = ContentView.get(pos)) === null || _a === void 0 ? void 0 : _a.parent))
|
|
329
|
+
child.reuseDOM(pos);
|
|
308
330
|
child.sync(track);
|
|
309
331
|
child.dirty = 0 /* Not */;
|
|
310
332
|
}
|
|
311
|
-
if (track && track.node == parent && pos != child.dom)
|
|
333
|
+
if (track && !track.written && track.node == parent && pos != child.dom)
|
|
312
334
|
track.written = true;
|
|
313
|
-
|
|
314
|
-
|
|
335
|
+
if (child.dom.parentNode == parent) {
|
|
336
|
+
while (pos && pos != child.dom)
|
|
337
|
+
pos = rm(pos);
|
|
338
|
+
pos = child.dom.nextSibling;
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
parent.insertBefore(child.dom, pos);
|
|
342
|
+
}
|
|
315
343
|
}
|
|
316
|
-
|
|
317
|
-
if (next && track && track.node == parent)
|
|
344
|
+
if (pos && track && track.node == parent)
|
|
318
345
|
track.written = true;
|
|
319
|
-
while (
|
|
320
|
-
|
|
346
|
+
while (pos)
|
|
347
|
+
pos = rm(pos);
|
|
321
348
|
}
|
|
322
349
|
else if (this.dirty & 1 /* Child */) {
|
|
323
350
|
for (let child of this.children)
|
|
@@ -448,6 +475,7 @@ class ContentView {
|
|
|
448
475
|
(this.breakAfter ? "#" : "");
|
|
449
476
|
}
|
|
450
477
|
static get(node) { return node.cmView; }
|
|
478
|
+
get isEditable() { return true; }
|
|
451
479
|
}
|
|
452
480
|
ContentView.prototype.breakAfter = 0;
|
|
453
481
|
// Remove a DOM node and return its next sibling.
|
|
@@ -456,14 +484,6 @@ function rm(dom) {
|
|
|
456
484
|
dom.parentNode.removeChild(dom);
|
|
457
485
|
return next;
|
|
458
486
|
}
|
|
459
|
-
function syncNodeInto(parent, after, dom) {
|
|
460
|
-
let next = after ? after.nextSibling : parent.firstChild;
|
|
461
|
-
if (dom.parentNode == parent)
|
|
462
|
-
while (next != dom)
|
|
463
|
-
next = rm(next);
|
|
464
|
-
else
|
|
465
|
-
parent.insertBefore(dom, next);
|
|
466
|
-
}
|
|
467
487
|
class ChildCursor {
|
|
468
488
|
constructor(children, pos, i) {
|
|
469
489
|
this.children = children;
|
|
@@ -495,15 +515,18 @@ const gecko = !ie && /gecko\/(\d+)/i.test(nav.userAgent);
|
|
|
495
515
|
const chrome = !ie && /Chrome\/(\d+)/.exec(nav.userAgent);
|
|
496
516
|
const webkit = "webkitFontSmoothing" in doc.documentElement.style;
|
|
497
517
|
const safari = !ie && /Apple Computer/.test(nav.vendor);
|
|
518
|
+
const ios = safari && (/Mobile\/\w+/.test(nav.userAgent) || nav.maxTouchPoints > 2);
|
|
498
519
|
var browser = {
|
|
499
|
-
mac: /Mac/.test(nav.platform),
|
|
520
|
+
mac: ios || /Mac/.test(nav.platform),
|
|
521
|
+
windows: /Win/.test(nav.platform),
|
|
522
|
+
linux: /Linux|X11/.test(nav.platform),
|
|
500
523
|
ie,
|
|
501
524
|
ie_version: ie_upto10 ? doc.documentMode || 6 : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : 0,
|
|
502
525
|
gecko,
|
|
503
526
|
gecko_version: gecko ? +(/Firefox\/(\d+)/.exec(nav.userAgent) || [0, 0])[1] : 0,
|
|
504
527
|
chrome: !!chrome,
|
|
505
528
|
chrome_version: chrome ? +chrome[1] : 0,
|
|
506
|
-
ios
|
|
529
|
+
ios,
|
|
507
530
|
android: /Android\b/.test(nav.userAgent),
|
|
508
531
|
webkit,
|
|
509
532
|
safari,
|
|
@@ -725,6 +748,7 @@ class WidgetView extends InlineView {
|
|
|
725
748
|
}
|
|
726
749
|
return (pos == 0 && side > 0 || pos == this.length && side <= 0) ? rect : flattenRect(rect, pos == 0);
|
|
727
750
|
}
|
|
751
|
+
get isEditable() { return false; }
|
|
728
752
|
}
|
|
729
753
|
class CompositionView extends WidgetView {
|
|
730
754
|
domAtPos(pos) { return new DOMPos(this.widget.text, pos); }
|
|
@@ -736,6 +760,38 @@ class CompositionView extends WidgetView {
|
|
|
736
760
|
ignoreMutation() { return false; }
|
|
737
761
|
get overrideDOMText() { return null; }
|
|
738
762
|
coordsAt(pos, side) { return textCoords(this.widget.text, pos, side); }
|
|
763
|
+
get isEditable() { return true; }
|
|
764
|
+
}
|
|
765
|
+
// These are drawn around uneditable widgets to avoid a number of
|
|
766
|
+
// browser bugs that show up when the cursor is directly next to
|
|
767
|
+
// uneditable inline content.
|
|
768
|
+
class WidgetBufferView extends InlineView {
|
|
769
|
+
constructor(side) {
|
|
770
|
+
super();
|
|
771
|
+
this.side = side;
|
|
772
|
+
}
|
|
773
|
+
get length() { return 0; }
|
|
774
|
+
merge() { return false; }
|
|
775
|
+
become(other) {
|
|
776
|
+
return other instanceof WidgetBufferView && other.side == this.side;
|
|
777
|
+
}
|
|
778
|
+
slice() { return new WidgetBufferView(this.side); }
|
|
779
|
+
sync() {
|
|
780
|
+
if (!this.dom)
|
|
781
|
+
this.setDOM(document.createTextNode("\u200b"));
|
|
782
|
+
else if (this.dirty && this.dom.nodeValue != "\u200b")
|
|
783
|
+
this.dom.nodeValue = "\u200b";
|
|
784
|
+
}
|
|
785
|
+
getSide() { return this.side; }
|
|
786
|
+
domAtPos(pos) { return DOMPos.before(this.dom); }
|
|
787
|
+
domBoundsAround() { return null; }
|
|
788
|
+
coordsAt(pos) {
|
|
789
|
+
let rects = clientRectsFor(this.dom);
|
|
790
|
+
return rects[rects.length - 1];
|
|
791
|
+
}
|
|
792
|
+
get overrideDOMText() {
|
|
793
|
+
return text.Text.of([this.dom.nodeValue.replace(/\u200b/g, "")]);
|
|
794
|
+
}
|
|
739
795
|
}
|
|
740
796
|
function mergeInlineChildren(parent, from, to, elts, openStart, openEnd) {
|
|
741
797
|
let cur = parent.childCursor();
|
|
@@ -1231,14 +1287,17 @@ class LineView extends ContentView {
|
|
|
1231
1287
|
}
|
|
1232
1288
|
// Only called when building a line view in ContentBuilder
|
|
1233
1289
|
addLineDeco(deco) {
|
|
1234
|
-
let attrs = deco.spec.attributes;
|
|
1290
|
+
let attrs = deco.spec.attributes, cls = deco.spec.class;
|
|
1235
1291
|
if (attrs)
|
|
1236
1292
|
this.attrs = combineAttrs(attrs, this.attrs || {});
|
|
1293
|
+
if (cls)
|
|
1294
|
+
this.attrs = combineAttrs(attrs, { class: cls });
|
|
1237
1295
|
}
|
|
1238
1296
|
domAtPos(pos) {
|
|
1239
1297
|
return inlineDOMAtPos(this.dom, this.children, pos);
|
|
1240
1298
|
}
|
|
1241
1299
|
sync(track) {
|
|
1300
|
+
var _a;
|
|
1242
1301
|
if (!this.dom || (this.dirty & 4 /* Attrs */)) {
|
|
1243
1302
|
this.setDOM(document.createElement("div"));
|
|
1244
1303
|
this.dom.className = "cm-line";
|
|
@@ -1254,7 +1313,7 @@ class LineView extends ContentView {
|
|
|
1254
1313
|
while (last && ContentView.get(last) instanceof MarkView)
|
|
1255
1314
|
last = last.lastChild;
|
|
1256
1315
|
if (!last ||
|
|
1257
|
-
last.nodeName != "BR" && ContentView.get(last)
|
|
1316
|
+
last.nodeName != "BR" && ((_a = ContentView.get(last)) === null || _a === void 0 ? void 0 : _a.isEditable) == false &&
|
|
1258
1317
|
(!browser.ios || !this.children.some(ch => ch instanceof TextView))) {
|
|
1259
1318
|
let hack = document.createElement("BR");
|
|
1260
1319
|
hack.cmIgnore = true;
|
|
@@ -1353,6 +1412,9 @@ class ContentBuilder {
|
|
|
1353
1412
|
this.content = [];
|
|
1354
1413
|
this.curLine = null;
|
|
1355
1414
|
this.breakAtStart = 0;
|
|
1415
|
+
this.pendingBuffer = 0 /* No */;
|
|
1416
|
+
// Set to false directly after a widget that covers the position after it
|
|
1417
|
+
this.atCursorPos = true;
|
|
1356
1418
|
this.openStart = -1;
|
|
1357
1419
|
this.openEnd = -1;
|
|
1358
1420
|
this.text = "";
|
|
@@ -1367,23 +1429,31 @@ class ContentBuilder {
|
|
|
1367
1429
|
return !last.breakAfter && !(last instanceof BlockWidgetView && last.type == exports.BlockType.WidgetBefore);
|
|
1368
1430
|
}
|
|
1369
1431
|
getLine() {
|
|
1370
|
-
if (!this.curLine)
|
|
1432
|
+
if (!this.curLine) {
|
|
1371
1433
|
this.content.push(this.curLine = new LineView);
|
|
1434
|
+
this.atCursorPos = true;
|
|
1435
|
+
}
|
|
1372
1436
|
return this.curLine;
|
|
1373
1437
|
}
|
|
1374
|
-
|
|
1438
|
+
flushBuffer(active) {
|
|
1439
|
+
if (this.pendingBuffer) {
|
|
1440
|
+
this.curLine.append(wrapMarks(new WidgetBufferView(-1), active), active.length);
|
|
1441
|
+
this.pendingBuffer = 0 /* No */;
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
addBlockWidget(view) {
|
|
1445
|
+
this.flushBuffer([]);
|
|
1375
1446
|
this.curLine = null;
|
|
1376
1447
|
this.content.push(view);
|
|
1377
1448
|
}
|
|
1378
|
-
finish() {
|
|
1449
|
+
finish(openEnd) {
|
|
1450
|
+
if (!openEnd)
|
|
1451
|
+
this.flushBuffer([]);
|
|
1452
|
+
else
|
|
1453
|
+
this.pendingBuffer = 0 /* No */;
|
|
1379
1454
|
if (!this.posCovered())
|
|
1380
1455
|
this.getLine();
|
|
1381
1456
|
}
|
|
1382
|
-
wrapMarks(view, active) {
|
|
1383
|
-
for (let mark of active)
|
|
1384
|
-
view = new MarkView(mark, [view], view.length);
|
|
1385
|
-
return view;
|
|
1386
|
-
}
|
|
1387
1457
|
buildText(length, active, openStart) {
|
|
1388
1458
|
while (length > 0) {
|
|
1389
1459
|
if (this.textOff == this.text.length) {
|
|
@@ -1398,6 +1468,7 @@ class ContentBuilder {
|
|
|
1398
1468
|
this.content[this.content.length - 1].breakAfter = 1;
|
|
1399
1469
|
else
|
|
1400
1470
|
this.breakAtStart = 1;
|
|
1471
|
+
this.flushBuffer([]);
|
|
1401
1472
|
this.curLine = null;
|
|
1402
1473
|
length--;
|
|
1403
1474
|
continue;
|
|
@@ -1408,7 +1479,9 @@ class ContentBuilder {
|
|
|
1408
1479
|
}
|
|
1409
1480
|
}
|
|
1410
1481
|
let take = Math.min(this.text.length - this.textOff, length, 512 /* Chunk */);
|
|
1411
|
-
this.
|
|
1482
|
+
this.flushBuffer(active);
|
|
1483
|
+
this.getLine().append(wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
|
|
1484
|
+
this.atCursorPos = true;
|
|
1412
1485
|
this.textOff += take;
|
|
1413
1486
|
length -= take;
|
|
1414
1487
|
openStart = 0;
|
|
@@ -1427,11 +1500,23 @@ class ContentBuilder {
|
|
|
1427
1500
|
let { type } = deco;
|
|
1428
1501
|
if (type == exports.BlockType.WidgetAfter && !this.posCovered())
|
|
1429
1502
|
this.getLine();
|
|
1430
|
-
this.
|
|
1503
|
+
this.addBlockWidget(new BlockWidgetView(deco.widget || new NullWidget("div"), len, type));
|
|
1431
1504
|
}
|
|
1432
1505
|
else {
|
|
1433
|
-
let
|
|
1434
|
-
this.
|
|
1506
|
+
let view = WidgetView.create(deco.widget || new NullWidget("span"), len, deco.startSide);
|
|
1507
|
+
let cursorBefore = this.atCursorPos && !view.isEditable && openStart <= active.length && (from < to || deco.startSide > 0);
|
|
1508
|
+
let cursorAfter = !view.isEditable && (from < to || deco.startSide <= 0);
|
|
1509
|
+
let line = this.getLine();
|
|
1510
|
+
if (this.pendingBuffer == 2 /* IfCursor */ && !cursorBefore)
|
|
1511
|
+
this.pendingBuffer = 0 /* No */;
|
|
1512
|
+
this.flushBuffer(active);
|
|
1513
|
+
if (cursorBefore) {
|
|
1514
|
+
line.append(wrapMarks(new WidgetBufferView(1), active), openStart);
|
|
1515
|
+
openStart = active.length + Math.max(0, openStart - active.length);
|
|
1516
|
+
}
|
|
1517
|
+
line.append(wrapMarks(view, active), openStart);
|
|
1518
|
+
this.atCursorPos = cursorAfter;
|
|
1519
|
+
this.pendingBuffer = !cursorAfter ? 0 /* No */ : from < to ? 1 /* Yes */ : 2 /* IfCursor */;
|
|
1435
1520
|
}
|
|
1436
1521
|
}
|
|
1437
1522
|
else if (this.doc.lineAt(this.pos).from == this.pos) { // Line decoration
|
|
@@ -1457,10 +1542,15 @@ class ContentBuilder {
|
|
|
1457
1542
|
builder.openEnd = rangeset.RangeSet.spans(decorations, from, to, builder);
|
|
1458
1543
|
if (builder.openStart < 0)
|
|
1459
1544
|
builder.openStart = builder.openEnd;
|
|
1460
|
-
builder.finish();
|
|
1545
|
+
builder.finish(builder.openEnd);
|
|
1461
1546
|
return builder;
|
|
1462
1547
|
}
|
|
1463
1548
|
}
|
|
1549
|
+
function wrapMarks(view, active) {
|
|
1550
|
+
for (let mark of active)
|
|
1551
|
+
view = new MarkView(mark, [view], view.length);
|
|
1552
|
+
return view;
|
|
1553
|
+
}
|
|
1464
1554
|
class NullWidget extends WidgetType {
|
|
1465
1555
|
constructor(tag) {
|
|
1466
1556
|
super();
|
|
@@ -1481,6 +1571,9 @@ const inputHandler = state.Facet.define();
|
|
|
1481
1571
|
const scrollTo = state.StateEffect.define({
|
|
1482
1572
|
map: (range, changes) => range.map(changes)
|
|
1483
1573
|
});
|
|
1574
|
+
const centerOn = state.StateEffect.define({
|
|
1575
|
+
map: (range, changes) => range.map(changes)
|
|
1576
|
+
});
|
|
1484
1577
|
/**
|
|
1485
1578
|
Log or report an unhandled exception in client code. Should
|
|
1486
1579
|
probably only be used by extension code that allows client code to
|
|
@@ -1907,6 +2000,14 @@ class DocView extends ContentView {
|
|
|
1907
2000
|
return true;
|
|
1908
2001
|
}
|
|
1909
2002
|
}
|
|
2003
|
+
reset(sel) {
|
|
2004
|
+
if (this.dirty) {
|
|
2005
|
+
this.view.observer.ignore(() => this.view.docView.sync());
|
|
2006
|
+
this.dirty = 0 /* Not */;
|
|
2007
|
+
}
|
|
2008
|
+
if (sel)
|
|
2009
|
+
this.updateSelection();
|
|
2010
|
+
}
|
|
1910
2011
|
// Used both by update and checkLayout do perform the actual DOM
|
|
1911
2012
|
// update
|
|
1912
2013
|
updateInner(changes, deco, oldLength, forceSelection = false, pointerSel = false) {
|
|
@@ -1931,6 +2032,12 @@ class DocView extends ContentView {
|
|
|
1931
2032
|
this.updateSelection(forceSelection, pointerSel);
|
|
1932
2033
|
this.dom.style.height = "";
|
|
1933
2034
|
});
|
|
2035
|
+
let gaps = [];
|
|
2036
|
+
if (this.view.viewport.from || this.view.viewport.to < this.view.state.doc.length)
|
|
2037
|
+
for (let child of this.children)
|
|
2038
|
+
if (child instanceof BlockWidgetView && child.widget instanceof BlockGapWidget)
|
|
2039
|
+
gaps.push(child.dom);
|
|
2040
|
+
observer.updateGaps(gaps);
|
|
1934
2041
|
}
|
|
1935
2042
|
updateChildren(changes, deco, oldLength) {
|
|
1936
2043
|
let cursor = this.childCursor(oldLength);
|
|
@@ -2030,6 +2137,14 @@ class DocView extends ContentView {
|
|
|
2030
2137
|
!isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) ||
|
|
2031
2138
|
!isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) {
|
|
2032
2139
|
this.view.observer.ignore(() => {
|
|
2140
|
+
// Chrome Android will hide the virtual keyboard when tapping
|
|
2141
|
+
// inside an uneditable node, and not bring it back when we
|
|
2142
|
+
// move the cursor to its proper position. This tries to
|
|
2143
|
+
// restore the keyboard by cycling focus.
|
|
2144
|
+
if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) && inUneditable(domSel.focusNode, this.dom)) {
|
|
2145
|
+
this.dom.blur();
|
|
2146
|
+
this.dom.focus({ preventScroll: true });
|
|
2147
|
+
}
|
|
2033
2148
|
let rawSel = getSelection(this.root);
|
|
2034
2149
|
if (main.empty) {
|
|
2035
2150
|
// Work around https://bugzilla.mozilla.org/show_bug.cgi?id=1612076
|
|
@@ -2203,7 +2318,7 @@ class DocView extends ContentView {
|
|
|
2203
2318
|
this.view.viewState.lineGapDeco
|
|
2204
2319
|
];
|
|
2205
2320
|
}
|
|
2206
|
-
|
|
2321
|
+
scrollIntoView({ range, center }) {
|
|
2207
2322
|
let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
|
|
2208
2323
|
if (!rect)
|
|
2209
2324
|
return;
|
|
@@ -2223,10 +2338,10 @@ class DocView extends ContentView {
|
|
|
2223
2338
|
if (bottom != null)
|
|
2224
2339
|
mBottom = Math.max(mBottom, bottom);
|
|
2225
2340
|
}
|
|
2226
|
-
scrollRectIntoView(this.
|
|
2341
|
+
scrollRectIntoView(this.view.scrollDOM, {
|
|
2227
2342
|
left: rect.left - mLeft, top: rect.top - mTop,
|
|
2228
2343
|
right: rect.right + mRight, bottom: rect.bottom + mBottom
|
|
2229
|
-
}, range.head < range.anchor ? -1 : 1);
|
|
2344
|
+
}, range.head < range.anchor ? -1 : 1, center);
|
|
2230
2345
|
}
|
|
2231
2346
|
}
|
|
2232
2347
|
function betweenUneditable(pos) {
|
|
@@ -2337,6 +2452,14 @@ function findChangedDeco(a, b, diff) {
|
|
|
2337
2452
|
rangeset.RangeSet.compare(a, b, diff, comp);
|
|
2338
2453
|
return comp.changes;
|
|
2339
2454
|
}
|
|
2455
|
+
function inUneditable(node, inside) {
|
|
2456
|
+
for (let cur = node; cur && cur != inside; cur = cur.assignedSlot || cur.parentNode) {
|
|
2457
|
+
if (cur.nodeType == 1 && cur.contentEditable == 'false') {
|
|
2458
|
+
return true;
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
return false;
|
|
2462
|
+
}
|
|
2340
2463
|
|
|
2341
2464
|
/**
|
|
2342
2465
|
Used to indicate [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
|
|
@@ -2783,6 +2906,7 @@ function domPosInText(node, x, y) {
|
|
|
2783
2906
|
return { node, offset: closestOffset > -1 ? closestOffset : generalSide > 0 ? node.nodeValue.length : 0 };
|
|
2784
2907
|
}
|
|
2785
2908
|
function posAtCoords(view, { x, y }, precise, bias = -1) {
|
|
2909
|
+
var _a;
|
|
2786
2910
|
let content = view.contentDOM.getBoundingClientRect(), block;
|
|
2787
2911
|
let halfLine = view.defaultLineHeight / 2;
|
|
2788
2912
|
for (let bounced = false;;) {
|
|
@@ -2813,7 +2937,7 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
|
|
|
2813
2937
|
// There's visible editor content under the point, so we can try
|
|
2814
2938
|
// using caret(Position|Range)FromPoint as a shortcut
|
|
2815
2939
|
let node, offset = -1;
|
|
2816
|
-
if (element && view.contentDOM.contains(element) &&
|
|
2940
|
+
if (element && view.contentDOM.contains(element) && ((_a = view.docView.nearest(element)) === null || _a === void 0 ? void 0 : _a.isEditable) != false) {
|
|
2817
2941
|
if (doc.caretPositionFromPoint) {
|
|
2818
2942
|
let pos = doc.caretPositionFromPoint(x, y);
|
|
2819
2943
|
if (pos)
|
|
@@ -2954,7 +3078,23 @@ class InputState {
|
|
|
2954
3078
|
constructor(view) {
|
|
2955
3079
|
this.lastKeyCode = 0;
|
|
2956
3080
|
this.lastKeyTime = 0;
|
|
2957
|
-
|
|
3081
|
+
// On iOS, some keys need to have their default behavior happen
|
|
3082
|
+
// (after which we retroactively handle them and reset the DOM) to
|
|
3083
|
+
// avoid messing up the virtual keyboard state.
|
|
3084
|
+
//
|
|
3085
|
+
// On Chrome Android, backspace near widgets is just completely
|
|
3086
|
+
// broken, and there are no key events, so we need to handle the
|
|
3087
|
+
// beforeinput event. Deleting stuff will often create a flurry of
|
|
3088
|
+
// events, and interrupting it before it is done just makes
|
|
3089
|
+
// subsequent events even more broken, so again, we hold off doing
|
|
3090
|
+
// anything until the browser is finished with whatever it is trying
|
|
3091
|
+
// to do.
|
|
3092
|
+
//
|
|
3093
|
+
// setPendingKey sets this, causing the DOM observer to pause for a
|
|
3094
|
+
// bit, and setting an animation frame (which seems the most
|
|
3095
|
+
// reliable way to detect 'browser is done flailing') to fire a fake
|
|
3096
|
+
// key event and re-sync the view again.
|
|
3097
|
+
this.pendingKey = undefined;
|
|
2958
3098
|
this.lastSelectionOrigin = null;
|
|
2959
3099
|
this.lastSelectionTime = 0;
|
|
2960
3100
|
this.lastEscPress = 0;
|
|
@@ -3063,20 +3203,27 @@ class InputState {
|
|
|
3063
3203
|
// state. So we let it go through, and then, in
|
|
3064
3204
|
// applyDOMChange, notify key handlers of it and reset to
|
|
3065
3205
|
// the state they produce.
|
|
3066
|
-
|
|
3206
|
+
let pending;
|
|
3207
|
+
if (browser.ios && (pending = PendingKeys.find(key => key.keyCode == event.keyCode)) &&
|
|
3067
3208
|
!(event.ctrlKey || event.altKey || event.metaKey) && !event.synthetic) {
|
|
3068
|
-
this.
|
|
3069
|
-
setTimeout(() => this.flushIOSKey(view), 250);
|
|
3209
|
+
this.setPendingKey(view, pending);
|
|
3070
3210
|
return true;
|
|
3071
3211
|
}
|
|
3072
3212
|
return false;
|
|
3073
3213
|
}
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3214
|
+
setPendingKey(view, pending) {
|
|
3215
|
+
this.pendingKey = pending;
|
|
3216
|
+
requestAnimationFrame(() => {
|
|
3217
|
+
if (!this.pendingKey)
|
|
3218
|
+
return false;
|
|
3219
|
+
let key = this.pendingKey;
|
|
3220
|
+
this.pendingKey = undefined;
|
|
3221
|
+
view.observer.processRecords();
|
|
3222
|
+
let startState = view.state;
|
|
3223
|
+
dispatchKey(view.contentDOM, key.key, key.keyCode);
|
|
3224
|
+
if (view.state == startState)
|
|
3225
|
+
view.docView.reset(true);
|
|
3226
|
+
});
|
|
3080
3227
|
}
|
|
3081
3228
|
ignoreDuringComposition(event) {
|
|
3082
3229
|
if (!/^key/.test(event.type))
|
|
@@ -3123,6 +3270,11 @@ class InputState {
|
|
|
3123
3270
|
this.mouseSelection.destroy();
|
|
3124
3271
|
}
|
|
3125
3272
|
}
|
|
3273
|
+
const PendingKeys = [
|
|
3274
|
+
{ key: "Backspace", keyCode: 8, inputType: "deleteContentBackward" },
|
|
3275
|
+
{ key: "Enter", keyCode: 13, inputType: "insertParagraph" },
|
|
3276
|
+
{ key: "Delete", keyCode: 46, inputType: "deleteContentForward" }
|
|
3277
|
+
];
|
|
3126
3278
|
// Key codes for modifier keys
|
|
3127
3279
|
const modifierCodes = [16, 17, 18, 20, 91, 92, 224, 225];
|
|
3128
3280
|
class MouseSelection {
|
|
@@ -3568,6 +3720,33 @@ handlers.compositionend = view => {
|
|
|
3568
3720
|
handlers.contextmenu = view => {
|
|
3569
3721
|
view.inputState.lastContextMenu = Date.now();
|
|
3570
3722
|
};
|
|
3723
|
+
handlers.beforeinput = (view, event) => {
|
|
3724
|
+
var _a;
|
|
3725
|
+
// Because Chrome Android doesn't fire useful key events, use
|
|
3726
|
+
// beforeinput to detect backspace (and possibly enter and delete,
|
|
3727
|
+
// but those usually don't even seem to fire beforeinput events at
|
|
3728
|
+
// the moment) and fake a key event for it.
|
|
3729
|
+
//
|
|
3730
|
+
// (preventDefault on beforeinput, though supported in the spec,
|
|
3731
|
+
// seems to do nothing at all on Chrome).
|
|
3732
|
+
let pending;
|
|
3733
|
+
if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
|
|
3734
|
+
view.inputState.setPendingKey(view, pending);
|
|
3735
|
+
if (pending.key == "Backspace" || pending.key == "Delete") {
|
|
3736
|
+
let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
|
|
3737
|
+
setTimeout(() => {
|
|
3738
|
+
var _a;
|
|
3739
|
+
// Backspacing near uneditable nodes on Chrome Android sometimes
|
|
3740
|
+
// closes the virtual keyboard. This tries to crudely detect
|
|
3741
|
+
// that and refocus to get it back.
|
|
3742
|
+
if ((((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0) > startViewHeight + 10 && view.hasFocus) {
|
|
3743
|
+
view.contentDOM.blur();
|
|
3744
|
+
view.focus();
|
|
3745
|
+
}
|
|
3746
|
+
}, 100);
|
|
3747
|
+
}
|
|
3748
|
+
}
|
|
3749
|
+
};
|
|
3571
3750
|
|
|
3572
3751
|
const wrappingWhiteSpace = ["pre-wrap", "normal", "pre-line"];
|
|
3573
3752
|
class HeightOracle {
|
|
@@ -4283,6 +4462,15 @@ class LineGapWidget extends WidgetType {
|
|
|
4283
4462
|
}
|
|
4284
4463
|
get estimatedHeight() { return this.vertical ? this.size : -1; }
|
|
4285
4464
|
}
|
|
4465
|
+
class ScrollTarget {
|
|
4466
|
+
constructor(range, center = false) {
|
|
4467
|
+
this.range = range;
|
|
4468
|
+
this.center = center;
|
|
4469
|
+
}
|
|
4470
|
+
map(changes) {
|
|
4471
|
+
return changes.empty ? this : new ScrollTarget(this.range.map(changes), this.center);
|
|
4472
|
+
}
|
|
4473
|
+
}
|
|
4286
4474
|
class ViewState {
|
|
4287
4475
|
constructor(state) {
|
|
4288
4476
|
this.state = state;
|
|
@@ -4295,7 +4483,7 @@ class ViewState {
|
|
|
4295
4483
|
this.heightOracle = new HeightOracle;
|
|
4296
4484
|
// See VP.MaxDOMHeight
|
|
4297
4485
|
this.scaler = IdScaler;
|
|
4298
|
-
this.
|
|
4486
|
+
this.scrollTarget = null;
|
|
4299
4487
|
// Briefly set to true when printing, to disable viewport limiting
|
|
4300
4488
|
this.printing = false;
|
|
4301
4489
|
this.visibleRanges = [];
|
|
@@ -4328,7 +4516,7 @@ class ViewState {
|
|
|
4328
4516
|
this.scaler = this.heightMap.height <= 7000000 /* MaxDOMHeight */ ? IdScaler :
|
|
4329
4517
|
new BigScaler(this.heightOracle.doc, this.heightMap, this.viewports);
|
|
4330
4518
|
}
|
|
4331
|
-
update(update,
|
|
4519
|
+
update(update, scrollTarget = null) {
|
|
4332
4520
|
let prev = this.state;
|
|
4333
4521
|
this.state = update.state;
|
|
4334
4522
|
let newDeco = this.state.facet(decorations);
|
|
@@ -4339,27 +4527,33 @@ class ViewState {
|
|
|
4339
4527
|
if (this.heightMap.height != prevHeight)
|
|
4340
4528
|
update.flags |= 2 /* Height */;
|
|
4341
4529
|
let viewport = heightChanges.length ? this.mapViewport(this.viewport, update.changes) : this.viewport;
|
|
4342
|
-
if (
|
|
4343
|
-
|
|
4530
|
+
if (scrollTarget && (scrollTarget.range.head < viewport.from || scrollTarget.range.head > viewport.to) ||
|
|
4531
|
+
!this.viewportIsAppropriate(viewport))
|
|
4532
|
+
viewport = this.getViewport(0, scrollTarget);
|
|
4344
4533
|
this.viewport = viewport;
|
|
4345
4534
|
this.updateForViewport();
|
|
4346
|
-
if (this.lineGaps.length || this.viewport.to - this.viewport.from >
|
|
4535
|
+
if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
|
|
4347
4536
|
this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
|
|
4348
4537
|
update.flags |= this.computeVisibleRanges();
|
|
4349
|
-
if (
|
|
4350
|
-
this.
|
|
4538
|
+
if (scrollTarget)
|
|
4539
|
+
this.scrollTarget = scrollTarget;
|
|
4351
4540
|
if (!this.mustEnforceCursorAssoc && update.selectionSet && update.view.lineWrapping &&
|
|
4352
4541
|
update.state.selection.main.empty && update.state.selection.main.assoc)
|
|
4353
4542
|
this.mustEnforceCursorAssoc = true;
|
|
4354
4543
|
}
|
|
4355
4544
|
measure(docView, repeated) {
|
|
4356
4545
|
let dom = docView.dom, whiteSpace = "", direction = exports.Direction.LTR;
|
|
4546
|
+
let result = 0;
|
|
4357
4547
|
if (!repeated) {
|
|
4358
4548
|
// Vertical padding
|
|
4359
4549
|
let style = window.getComputedStyle(dom);
|
|
4360
4550
|
whiteSpace = style.whiteSpace, direction = (style.direction == "rtl" ? exports.Direction.RTL : exports.Direction.LTR);
|
|
4361
|
-
|
|
4362
|
-
this.
|
|
4551
|
+
let paddingTop = parseInt(style.paddingTop) || 0, paddingBottom = parseInt(style.paddingBottom) || 0;
|
|
4552
|
+
if (this.paddingTop != paddingTop || this.paddingBottom != paddingBottom) {
|
|
4553
|
+
result |= 8 /* Geometry */;
|
|
4554
|
+
this.paddingTop = paddingTop;
|
|
4555
|
+
this.paddingBottom = paddingBottom;
|
|
4556
|
+
}
|
|
4363
4557
|
}
|
|
4364
4558
|
// Pixel viewport
|
|
4365
4559
|
let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 } : visiblePixelRange(dom, this.paddingTop);
|
|
@@ -4369,7 +4563,7 @@ class ViewState {
|
|
|
4369
4563
|
if (!this.inView)
|
|
4370
4564
|
return 0;
|
|
4371
4565
|
let lineHeights = docView.measureVisibleLineHeights();
|
|
4372
|
-
let refresh = false, bias = 0,
|
|
4566
|
+
let refresh = false, bias = 0, oracle = this.heightOracle;
|
|
4373
4567
|
if (!repeated) {
|
|
4374
4568
|
let contentWidth = docView.dom.clientWidth;
|
|
4375
4569
|
if (oracle.mustRefresh(lineHeights, whiteSpace, direction) ||
|
|
@@ -4395,10 +4589,10 @@ class ViewState {
|
|
|
4395
4589
|
if (oracle.heightChanged)
|
|
4396
4590
|
result |= 2 /* Height */;
|
|
4397
4591
|
if (!this.viewportIsAppropriate(this.viewport, bias) ||
|
|
4398
|
-
this.
|
|
4399
|
-
this.viewport = this.getViewport(bias, this.
|
|
4592
|
+
this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to))
|
|
4593
|
+
this.viewport = this.getViewport(bias, this.scrollTarget);
|
|
4400
4594
|
this.updateForViewport();
|
|
4401
|
-
if (this.lineGaps.length || this.viewport.to - this.viewport.from >
|
|
4595
|
+
if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
|
|
4402
4596
|
this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
|
|
4403
4597
|
result |= this.computeVisibleRanges();
|
|
4404
4598
|
if (this.mustEnforceCursorAssoc) {
|
|
@@ -4413,22 +4607,25 @@ class ViewState {
|
|
|
4413
4607
|
}
|
|
4414
4608
|
get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top, 0); }
|
|
4415
4609
|
get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom, 0); }
|
|
4416
|
-
getViewport(bias,
|
|
4610
|
+
getViewport(bias, scrollTarget) {
|
|
4417
4611
|
// This will divide VP.Margin between the top and the
|
|
4418
4612
|
// bottom, depending on the bias (the change in viewport position
|
|
4419
4613
|
// since the last update). It'll hold a number between 0 and 1
|
|
4420
4614
|
let marginTop = 0.5 - Math.max(-0.5, Math.min(0.5, bias / 1000 /* Margin */ / 2));
|
|
4421
4615
|
let map = this.heightMap, doc = this.state.doc, { visibleTop, visibleBottom } = this;
|
|
4422
4616
|
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);
|
|
4423
|
-
// If
|
|
4424
|
-
if (
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4617
|
+
// If scrollTarget is given, make sure the viewport includes that position
|
|
4618
|
+
if (scrollTarget) {
|
|
4619
|
+
let { head } = scrollTarget.range, viewHeight = visibleBottom - visibleTop;
|
|
4620
|
+
if (head < viewport.from || head > viewport.to) {
|
|
4621
|
+
let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
|
|
4622
|
+
if (scrollTarget.center)
|
|
4623
|
+
topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
|
|
4624
|
+
else if (head < viewport.from)
|
|
4625
|
+
topPos = block.top;
|
|
4626
|
+
else
|
|
4627
|
+
topPos = block.bottom - viewHeight;
|
|
4628
|
+
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);
|
|
4432
4629
|
}
|
|
4433
4630
|
}
|
|
4434
4631
|
return viewport;
|
|
@@ -4470,52 +4667,50 @@ class ViewState {
|
|
|
4470
4667
|
if (this.heightOracle.direction != exports.Direction.LTR)
|
|
4471
4668
|
return gaps;
|
|
4472
4669
|
this.heightMap.forEachLine(this.viewport.from, this.viewport.to, this.state.doc, 0, 0, line => {
|
|
4473
|
-
if (line.length <
|
|
4670
|
+
if (line.length < 4000 /* DoubleMargin */)
|
|
4474
4671
|
return;
|
|
4475
4672
|
let structure = lineStructure(line.from, line.to, this.state);
|
|
4476
|
-
if (structure.total <
|
|
4673
|
+
if (structure.total < 4000 /* DoubleMargin */)
|
|
4477
4674
|
return;
|
|
4478
4675
|
let viewFrom, viewTo;
|
|
4479
4676
|
if (this.heightOracle.lineWrapping) {
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
viewFrom = findPosition(structure, (this.visibleTop - line.top) / line.height);
|
|
4484
|
-
if (line.to != this.viewport.to)
|
|
4485
|
-
viewTo = line.to;
|
|
4486
|
-
else
|
|
4487
|
-
viewTo = findPosition(structure, (this.visibleBottom - line.top) / line.height);
|
|
4677
|
+
let marginHeight = (2000 /* Margin */ / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
|
|
4678
|
+
viewFrom = findPosition(structure, (this.visibleTop - line.top - marginHeight) / line.height);
|
|
4679
|
+
viewTo = findPosition(structure, (this.visibleBottom - line.top + marginHeight) / line.height);
|
|
4488
4680
|
}
|
|
4489
4681
|
else {
|
|
4490
4682
|
let totalWidth = structure.total * this.heightOracle.charWidth;
|
|
4491
|
-
|
|
4492
|
-
|
|
4683
|
+
let marginWidth = 2000 /* Margin */ * this.heightOracle.charWidth;
|
|
4684
|
+
viewFrom = findPosition(structure, (this.pixelViewport.left - marginWidth) / totalWidth);
|
|
4685
|
+
viewTo = findPosition(structure, (this.pixelViewport.right + marginWidth) / totalWidth);
|
|
4493
4686
|
}
|
|
4687
|
+
let outside = [];
|
|
4688
|
+
if (viewFrom > line.from)
|
|
4689
|
+
outside.push({ from: line.from, to: viewFrom });
|
|
4690
|
+
if (viewTo < line.to)
|
|
4691
|
+
outside.push({ from: viewTo, to: line.to });
|
|
4494
4692
|
let sel = this.state.selection.main;
|
|
4495
|
-
// Make sure the
|
|
4496
|
-
if (sel.from
|
|
4497
|
-
|
|
4498
|
-
if (sel.
|
|
4499
|
-
|
|
4500
|
-
let
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
gap.from < gapFrom + 5000 /* HalfMargin */) ||
|
|
4507
|
-
new LineGap(gapFrom, line.to, this.gapSize(line, gapFrom, false, structure)));
|
|
4693
|
+
// Make sure the gaps don't cover a selection end
|
|
4694
|
+
if (sel.from >= line.from && sel.from <= line.to)
|
|
4695
|
+
cutRange(outside, sel.from - 10 /* SelectionMargin */, sel.from + 10 /* SelectionMargin */);
|
|
4696
|
+
if (!sel.empty && sel.to >= line.from && sel.to <= line.to)
|
|
4697
|
+
cutRange(outside, sel.to - 10 /* SelectionMargin */, sel.to + 10 /* SelectionMargin */);
|
|
4698
|
+
for (let { from, to } of outside)
|
|
4699
|
+
if (to - from > 1000 /* HalfMargin */) {
|
|
4700
|
+
gaps.push(find(current, gap => gap.from >= line.from && gap.to <= line.to &&
|
|
4701
|
+
Math.abs(gap.from - from) < 1000 /* HalfMargin */ && Math.abs(gap.to - to) < 1000 /* HalfMargin */) ||
|
|
4702
|
+
new LineGap(from, to, this.gapSize(line, from, to, structure)));
|
|
4703
|
+
}
|
|
4508
4704
|
});
|
|
4509
4705
|
return gaps;
|
|
4510
4706
|
}
|
|
4511
|
-
gapSize(line,
|
|
4707
|
+
gapSize(line, from, to, structure) {
|
|
4708
|
+
let fraction = findFraction(structure, to) - findFraction(structure, from);
|
|
4512
4709
|
if (this.heightOracle.lineWrapping) {
|
|
4513
|
-
|
|
4514
|
-
return start ? height : line.height - height;
|
|
4710
|
+
return line.height * fraction;
|
|
4515
4711
|
}
|
|
4516
4712
|
else {
|
|
4517
|
-
|
|
4518
|
-
return structure.total * this.heightOracle.charWidth * (start ? ratio : 1 - ratio);
|
|
4713
|
+
return structure.total * this.heightOracle.charWidth * fraction;
|
|
4519
4714
|
}
|
|
4520
4715
|
}
|
|
4521
4716
|
updateLineGaps(gaps) {
|
|
@@ -4609,6 +4804,20 @@ function findFraction(structure, pos) {
|
|
|
4609
4804
|
}
|
|
4610
4805
|
return counted / structure.total;
|
|
4611
4806
|
}
|
|
4807
|
+
function cutRange(ranges, from, to) {
|
|
4808
|
+
for (let i = 0; i < ranges.length; i++) {
|
|
4809
|
+
let r = ranges[i];
|
|
4810
|
+
if (r.from < to && r.to > from) {
|
|
4811
|
+
let pieces = [];
|
|
4812
|
+
if (r.from < from)
|
|
4813
|
+
pieces.push({ from: r.from, to: from });
|
|
4814
|
+
if (r.to > to)
|
|
4815
|
+
pieces.push({ from: to, to: r.to });
|
|
4816
|
+
ranges.splice(i, 1, ...pieces);
|
|
4817
|
+
i += pieces.length - 1;
|
|
4818
|
+
}
|
|
4819
|
+
}
|
|
4820
|
+
}
|
|
4612
4821
|
function find(array, f) {
|
|
4613
4822
|
for (let val of array)
|
|
4614
4823
|
if (f(val))
|
|
@@ -4691,7 +4900,7 @@ function buildTheme(main, spec, scopes) {
|
|
|
4691
4900
|
});
|
|
4692
4901
|
}
|
|
4693
4902
|
const baseTheme = buildTheme("." + baseThemeID, {
|
|
4694
|
-
"
|
|
4903
|
+
"&.cm-editor": {
|
|
4695
4904
|
position: "relative !important",
|
|
4696
4905
|
boxSizing: "border-box",
|
|
4697
4906
|
"&.cm-focused": {
|
|
@@ -4858,6 +5067,8 @@ class DOMObserver {
|
|
|
4858
5067
|
this.scrollTargets = [];
|
|
4859
5068
|
this.intersection = null;
|
|
4860
5069
|
this.intersecting = false;
|
|
5070
|
+
this.gapIntersection = null;
|
|
5071
|
+
this.gaps = [];
|
|
4861
5072
|
// Used to work around a Safari Selection/shadow DOM bug (#414)
|
|
4862
5073
|
this._selectionRange = null;
|
|
4863
5074
|
// Timeout for scheduling check of the parents that need scroll handlers
|
|
@@ -4898,13 +5109,17 @@ class DOMObserver {
|
|
|
4898
5109
|
this.intersection = new IntersectionObserver(entries => {
|
|
4899
5110
|
if (this.parentCheck < 0)
|
|
4900
5111
|
this.parentCheck = setTimeout(this.listenForScroll.bind(this), 1000);
|
|
4901
|
-
if (entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
|
|
5112
|
+
if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
|
|
4902
5113
|
this.intersecting = !this.intersecting;
|
|
4903
5114
|
if (this.intersecting != this.view.inView)
|
|
4904
5115
|
this.onScrollChanged(document.createEvent("Event"));
|
|
4905
5116
|
}
|
|
4906
5117
|
}, {});
|
|
4907
5118
|
this.intersection.observe(this.dom);
|
|
5119
|
+
this.gapIntersection = new IntersectionObserver(entries => {
|
|
5120
|
+
if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0)
|
|
5121
|
+
this.onScrollChanged(document.createEvent("Event"));
|
|
5122
|
+
}, {});
|
|
4908
5123
|
}
|
|
4909
5124
|
this.listenForScroll();
|
|
4910
5125
|
}
|
|
@@ -4913,6 +5128,14 @@ class DOMObserver {
|
|
|
4913
5128
|
this.flush();
|
|
4914
5129
|
this.onScrollChanged(e);
|
|
4915
5130
|
}
|
|
5131
|
+
updateGaps(gaps) {
|
|
5132
|
+
if (this.gapIntersection && (gaps.length != this.gaps.length || this.gaps.some((g, i) => g != gaps[i]))) {
|
|
5133
|
+
this.gapIntersection.disconnect();
|
|
5134
|
+
for (let gap of gaps)
|
|
5135
|
+
this.gapIntersection.observe(gap);
|
|
5136
|
+
this.gaps = gaps;
|
|
5137
|
+
}
|
|
5138
|
+
}
|
|
4916
5139
|
onSelectionChange(event) {
|
|
4917
5140
|
if (this.lastFlush < Date.now() - 50)
|
|
4918
5141
|
this._selectionRange = null;
|
|
@@ -5028,20 +5251,12 @@ class DOMObserver {
|
|
|
5028
5251
|
this.flush();
|
|
5029
5252
|
}
|
|
5030
5253
|
}
|
|
5031
|
-
|
|
5032
|
-
flush() {
|
|
5033
|
-
if (this.delayedFlush >= 0)
|
|
5034
|
-
return;
|
|
5035
|
-
this.lastFlush = Date.now();
|
|
5254
|
+
processRecords() {
|
|
5036
5255
|
let records = this.queue;
|
|
5037
5256
|
for (let mut of this.observer.takeRecords())
|
|
5038
5257
|
records.push(mut);
|
|
5039
5258
|
if (records.length)
|
|
5040
5259
|
this.queue = [];
|
|
5041
|
-
let selection = this.selectionRange;
|
|
5042
|
-
let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
|
|
5043
|
-
if (records.length == 0 && !newSel)
|
|
5044
|
-
return;
|
|
5045
5260
|
let from = -1, to = -1, typeOver = false;
|
|
5046
5261
|
for (let record of records) {
|
|
5047
5262
|
let range = this.readMutation(record);
|
|
@@ -5057,17 +5272,26 @@ class DOMObserver {
|
|
|
5057
5272
|
to = Math.max(range.to, to);
|
|
5058
5273
|
}
|
|
5059
5274
|
}
|
|
5275
|
+
return { from, to, typeOver };
|
|
5276
|
+
}
|
|
5277
|
+
// Apply pending changes, if any
|
|
5278
|
+
flush() {
|
|
5279
|
+
// Completely hold off flushing when pending keys are set—the code
|
|
5280
|
+
// managing those will make sure processRecords is called and the
|
|
5281
|
+
// view is resynchronized after
|
|
5282
|
+
if (this.delayedFlush >= 0 || this.view.inputState.pendingKey)
|
|
5283
|
+
return;
|
|
5284
|
+
this.lastFlush = Date.now();
|
|
5285
|
+
let { from, to, typeOver } = this.processRecords();
|
|
5286
|
+
let selection = this.selectionRange;
|
|
5287
|
+
let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
|
|
5288
|
+
if (from < 0 && !newSel)
|
|
5289
|
+
return;
|
|
5060
5290
|
let startState = this.view.state;
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
if (this.view.state == startState)
|
|
5064
|
-
|
|
5065
|
-
this.ignore(() => this.view.docView.sync());
|
|
5066
|
-
this.view.docView.dirty = 0 /* Not */;
|
|
5067
|
-
}
|
|
5068
|
-
if (newSel)
|
|
5069
|
-
this.view.docView.updateSelection();
|
|
5070
|
-
}
|
|
5291
|
+
this.onChange(from, to, typeOver);
|
|
5292
|
+
// The view wasn't updated
|
|
5293
|
+
if (this.view.state == startState)
|
|
5294
|
+
this.view.docView.reset(newSel);
|
|
5071
5295
|
this.clearSelection();
|
|
5072
5296
|
}
|
|
5073
5297
|
readMutation(rec) {
|
|
@@ -5094,6 +5318,8 @@ class DOMObserver {
|
|
|
5094
5318
|
this.stop();
|
|
5095
5319
|
if (this.intersection)
|
|
5096
5320
|
this.intersection.disconnect();
|
|
5321
|
+
if (this.gapIntersection)
|
|
5322
|
+
this.gapIntersection.disconnect();
|
|
5097
5323
|
for (let dom of this.scrollTargets)
|
|
5098
5324
|
dom.removeEventListener("scroll", this.onScroll);
|
|
5099
5325
|
window.removeEventListener("scroll", this.onScroll);
|
|
@@ -5202,9 +5428,9 @@ function applyDOMChange(view, start, end, typeOver) {
|
|
|
5202
5428
|
(change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
|
|
5203
5429
|
dispatchKey(view.contentDOM, "Backspace", 8)) ||
|
|
5204
5430
|
(change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
|
|
5205
|
-
dispatchKey(view.contentDOM, "Delete", 46)))
|
|
5206
|
-
browser.ios && view.inputState.flushIOSKey(view))
|
|
5431
|
+
dispatchKey(view.contentDOM, "Delete", 46)))) {
|
|
5207
5432
|
return;
|
|
5433
|
+
}
|
|
5208
5434
|
let text = change.insert.toString();
|
|
5209
5435
|
if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
|
|
5210
5436
|
return;
|
|
@@ -5425,7 +5651,7 @@ class EditorView {
|
|
|
5425
5651
|
this.dom.appendChild(this.scrollDOM);
|
|
5426
5652
|
this._dispatch = config.dispatch || ((tr) => this.update([tr]));
|
|
5427
5653
|
this.dispatch = this.dispatch.bind(this);
|
|
5428
|
-
this.root = (config.root || document);
|
|
5654
|
+
this.root = (config.root || getRoot(config.parent) || document);
|
|
5429
5655
|
this.viewState = new ViewState(config.state || state.EditorState.create());
|
|
5430
5656
|
this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec).update(this));
|
|
5431
5657
|
this.observer = new DOMObserver(this, (from, to, typeOver) => {
|
|
@@ -5506,21 +5732,24 @@ class EditorView {
|
|
|
5506
5732
|
if (state$1.facet(state.EditorState.phrases) != this.state.facet(state.EditorState.phrases))
|
|
5507
5733
|
return this.setState(state$1);
|
|
5508
5734
|
update = new ViewUpdate(this, state$1, transactions);
|
|
5509
|
-
let
|
|
5735
|
+
let scrollTarget = null;
|
|
5510
5736
|
try {
|
|
5511
5737
|
this.updateState = 2 /* Updating */;
|
|
5512
5738
|
for (let tr of transactions) {
|
|
5513
|
-
if (
|
|
5514
|
-
|
|
5739
|
+
if (scrollTarget)
|
|
5740
|
+
scrollTarget = scrollTarget.map(tr.changes);
|
|
5515
5741
|
if (tr.scrollIntoView) {
|
|
5516
5742
|
let { main } = tr.state.selection;
|
|
5517
|
-
|
|
5743
|
+
scrollTarget = new ScrollTarget(main.empty ? main : state.EditorSelection.cursor(main.head, main.head > main.anchor ? -1 : 1));
|
|
5518
5744
|
}
|
|
5519
|
-
for (let e of tr.effects)
|
|
5745
|
+
for (let e of tr.effects) {
|
|
5520
5746
|
if (e.is(scrollTo))
|
|
5521
|
-
|
|
5747
|
+
scrollTarget = new ScrollTarget(e.value);
|
|
5748
|
+
else if (e.is(centerOn))
|
|
5749
|
+
scrollTarget = new ScrollTarget(e.value, true);
|
|
5750
|
+
}
|
|
5522
5751
|
}
|
|
5523
|
-
this.viewState.update(update,
|
|
5752
|
+
this.viewState.update(update, scrollTarget);
|
|
5524
5753
|
this.bidiCache = CachedOrder.update(this.bidiCache, update.changes);
|
|
5525
5754
|
if (!update.empty) {
|
|
5526
5755
|
this.updatePlugins(update);
|
|
@@ -5535,7 +5764,7 @@ class EditorView {
|
|
|
5535
5764
|
finally {
|
|
5536
5765
|
this.updateState = 0 /* Idle */;
|
|
5537
5766
|
}
|
|
5538
|
-
if (redrawn ||
|
|
5767
|
+
if (redrawn || scrollTarget || this.viewState.mustEnforceCursorAssoc)
|
|
5539
5768
|
this.requestMeasure();
|
|
5540
5769
|
if (!update.empty)
|
|
5541
5770
|
for (let listener of this.state.facet(updateListener))
|
|
@@ -5617,14 +5846,16 @@ class EditorView {
|
|
|
5617
5846
|
this.updateState = 1 /* Measuring */;
|
|
5618
5847
|
let oldViewport = this.viewport;
|
|
5619
5848
|
let changed = this.viewState.measure(this.docView, i > 0);
|
|
5620
|
-
|
|
5621
|
-
if (!changed && !measuring.length && this.viewState.scrollTo == null)
|
|
5849
|
+
if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
|
|
5622
5850
|
break;
|
|
5623
|
-
this.measureRequests = [];
|
|
5624
5851
|
if (i > 5) {
|
|
5625
5852
|
console.warn("Viewport failed to stabilize");
|
|
5626
5853
|
break;
|
|
5627
5854
|
}
|
|
5855
|
+
let measuring = [];
|
|
5856
|
+
// Only run measure requests in this cycle when the viewport didn't change
|
|
5857
|
+
if (!(changed & 4 /* Viewport */))
|
|
5858
|
+
[this.measureRequests, measuring] = [measuring, this.measureRequests];
|
|
5628
5859
|
let measured = measuring.map(m => {
|
|
5629
5860
|
try {
|
|
5630
5861
|
return m.read(this);
|
|
@@ -5657,9 +5888,9 @@ class EditorView {
|
|
|
5657
5888
|
logException(this.state, e);
|
|
5658
5889
|
}
|
|
5659
5890
|
}
|
|
5660
|
-
if (this.viewState.
|
|
5661
|
-
this.docView.
|
|
5662
|
-
this.viewState.
|
|
5891
|
+
if (this.viewState.scrollTarget) {
|
|
5892
|
+
this.docView.scrollIntoView(this.viewState.scrollTarget);
|
|
5893
|
+
this.viewState.scrollTarget = null;
|
|
5663
5894
|
}
|
|
5664
5895
|
if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
|
|
5665
5896
|
break;
|
|
@@ -5691,6 +5922,7 @@ class EditorView {
|
|
|
5691
5922
|
spellcheck: "false",
|
|
5692
5923
|
autocorrect: "off",
|
|
5693
5924
|
autocapitalize: "off",
|
|
5925
|
+
translate: "no",
|
|
5694
5926
|
contenteditable: !this.state.facet(editable) ? "false" : contentEditablePlainTextSupported() ? "plaintext-only" : "true",
|
|
5695
5927
|
class: "cm-content",
|
|
5696
5928
|
style: `${browser.tabSize}: ${this.state.tabSize}`,
|
|
@@ -5881,12 +6113,9 @@ class EditorView {
|
|
|
5881
6113
|
moveVertically(start, forward, distance) {
|
|
5882
6114
|
return skipAtoms(this, start, moveVertically(this, start, forward, distance));
|
|
5883
6115
|
}
|
|
5884
|
-
|
|
5885
|
-
Scroll the given document position into view.
|
|
5886
|
-
*/
|
|
6116
|
+
// FIXME remove on next major version
|
|
5887
6117
|
scrollPosIntoView(pos) {
|
|
5888
|
-
this.
|
|
5889
|
-
this.requestMeasure();
|
|
6118
|
+
this.dispatch({ effects: scrollTo.of(state.EditorSelection.cursor(pos)) });
|
|
5890
6119
|
}
|
|
5891
6120
|
/**
|
|
5892
6121
|
Find the DOM parent node and offset (child offset if `node` is
|
|
@@ -6053,7 +6282,7 @@ class EditorView {
|
|
|
6053
6282
|
target editors with a dark or light theme.
|
|
6054
6283
|
*/
|
|
6055
6284
|
static baseTheme(spec) {
|
|
6056
|
-
return state.Prec.
|
|
6285
|
+
return state.Prec.lowest(styleModule.of(buildTheme("." + baseThemeID, spec, lightDarkIDs)));
|
|
6057
6286
|
}
|
|
6058
6287
|
}
|
|
6059
6288
|
/**
|
|
@@ -6062,6 +6291,11 @@ transaction to make it scroll the given range into view.
|
|
|
6062
6291
|
*/
|
|
6063
6292
|
EditorView.scrollTo = scrollTo;
|
|
6064
6293
|
/**
|
|
6294
|
+
Effect that makes the editor scroll the given range to the
|
|
6295
|
+
center of the visible view.
|
|
6296
|
+
*/
|
|
6297
|
+
EditorView.centerOn = centerOn;
|
|
6298
|
+
/**
|
|
6065
6299
|
Facet to add a [style
|
|
6066
6300
|
module](https://github.com/marijnh/style-mod#documentation) to
|
|
6067
6301
|
an editor view. The view will ensure that the module is
|
|
@@ -6191,11 +6425,7 @@ class CachedOrder {
|
|
|
6191
6425
|
}
|
|
6192
6426
|
}
|
|
6193
6427
|
|
|
6194
|
-
const currentPlatform =
|
|
6195
|
-
: /Mac/.test(navigator.platform) ? "mac"
|
|
6196
|
-
: /Win/.test(navigator.platform) ? "win"
|
|
6197
|
-
: /Linux|X11/.test(navigator.platform) ? "linux"
|
|
6198
|
-
: "key";
|
|
6428
|
+
const currentPlatform = browser.mac ? "mac" : browser.windows ? "win" : browser.linux ? "linux" : "key";
|
|
6199
6429
|
function normalizeKeyName(name, platform) {
|
|
6200
6430
|
const parts = name.split(/-(?!$)/);
|
|
6201
6431
|
let result = parts[parts.length - 1];
|
|
@@ -6494,7 +6724,7 @@ const themeSpec = {
|
|
|
6494
6724
|
};
|
|
6495
6725
|
if (CanHidePrimary)
|
|
6496
6726
|
themeSpec[".cm-line"].caretColor = "transparent !important";
|
|
6497
|
-
const hideNativeSelection = state.Prec.
|
|
6727
|
+
const hideNativeSelection = state.Prec.highest(EditorView.theme(themeSpec));
|
|
6498
6728
|
function getBase(view) {
|
|
6499
6729
|
let rect = view.scrollDOM.getBoundingClientRect();
|
|
6500
6730
|
let left = view.textDirection == exports.Direction.LTR ? rect.left : rect.right - view.scrollDOM.clientWidth;
|
|
@@ -6701,7 +6931,7 @@ class MatchDecorator {
|
|
|
6701
6931
|
}
|
|
6702
6932
|
|
|
6703
6933
|
const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g";
|
|
6704
|
-
const Specials = new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
|
|
6934
|
+
const Specials = new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
|
|
6705
6935
|
const Names = {
|
|
6706
6936
|
0: "null",
|
|
6707
6937
|
7: "bell",
|
|
@@ -6716,6 +6946,8 @@ const Names = {
|
|
|
6716
6946
|
8206: "left-to-right mark",
|
|
6717
6947
|
8207: "right-to-left mark",
|
|
6718
6948
|
8232: "line separator",
|
|
6949
|
+
8237: "left-to-right override",
|
|
6950
|
+
8238: "right-to-left override",
|
|
6719
6951
|
8233: "paragraph separator",
|
|
6720
6952
|
65279: "zero width no-break space",
|
|
6721
6953
|
65532: "object replacement"
|
|
@@ -6882,7 +7114,7 @@ DOM class.
|
|
|
6882
7114
|
function highlightActiveLine() {
|
|
6883
7115
|
return activeLineHighlighter;
|
|
6884
7116
|
}
|
|
6885
|
-
const lineDeco = Decoration.line({
|
|
7117
|
+
const lineDeco = Decoration.line({ class: "cm-activeLine" });
|
|
6886
7118
|
const activeLineHighlighter = ViewPlugin.fromClass(class {
|
|
6887
7119
|
constructor(view) {
|
|
6888
7120
|
this.decorations = this.getDeco(view);
|